mondrian-3.4.1/0000755000175000017500000000000011772400412013220 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/0000755000175000017500000000000011735330606014715 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/0000755000175000017500000000000011735330606015641 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/0000755000175000017500000000000011735330606017450 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/rolap/0000755000175000017500000000000011735330606020565 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/rolap/VirtualCubeTest.java0000644000175000017500000017032511735330606024525 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.spi.Dialect; import mondrian.test.SqlPattern; import mondrian.test.TestContext; import java.util.List; /** * Unit tests for virtual cubes. * * @author remberson * @since Feb 14, 2003 */ public class VirtualCubeTest extends BatchTestCase { /** * Creates an anonymous VirtualCubeTest. */ public VirtualCubeTest() { } /** * Creates a VirtualCubeTest. * @param name Test case name */ public VirtualCubeTest(String name) { super(name); } /** * Test case for bug * MONDRIAN-163, "VirtualCube SegmentArrayQuerySpec.addMeasure assert". */ public void testNoTimeDimension() { TestContext testContext = TestContext.instance().create( null, null, "\n" + "\n" + "\n" + "\n" + "", null, null, null); checkXxx(testContext); } public void testCalculatedMeasureAsDefaultMeasureInVC() { TestContext testContext = TestContext.instance().create( null, null, "\n" + "\n" + "\n" + "\n" + "\n" + "", null, null, null); String query1 = "select from [Sales vs Warehouse]"; String query2 = "select from [Sales vs Warehouse] where measures.profit"; assertQueriesReturnSimilarResults(query1, query2, testContext); } public void testDefaultMeasureInVCForIncorrectMeasureName() { TestContext testContext = TestContext.instance().create( null, null, "\n" + "\n" + "\n" + "\n" + "\n" + "", null, null, null); String query1 = "select from [Sales vs Warehouse]"; String query2 = "select from [Sales vs Warehouse] " + "where measures.[Warehouse Sales]"; assertQueriesReturnSimilarResults(query1, query2, testContext); } public void testVirtualCubeMeasureInvalidCubeName() { TestContext testContext = TestContext.instance().create( null, null, "\n" + "\n" + "\n" + "\n" + "", null, null, null); testContext.assertQueryThrows( "select from [Sales vs Warehouse]", "Cube 'Bad cube' not found"); } public void testDefaultMeasureInVCForCaseSensitivity() { TestContext testContext = TestContext.instance().create( null, null, "\n" + "\n" + "\n" + "\n" + "\n" + "", null, null, null); String queryWithoutFilter = "select from [Sales vs Warehouse]"; String queryWithFirstMeasure = "select from [Sales vs Warehouse] " + "where measures.[Warehouse Sales]"; String queryWithDefaultMeasureFilter = "select from [Sales vs Warehouse] " + "where measures.[Profit]"; if (MondrianProperties.instance().CaseSensitive.get()) { assertQueriesReturnSimilarResults( queryWithoutFilter, queryWithFirstMeasure, testContext); } else { assertQueriesReturnSimilarResults( queryWithoutFilter, queryWithDefaultMeasureFilter, testContext); } } public void testWithTimeDimension() { TestContext testContext = TestContext.instance().create( null, null, "\n" + "\n" + "\n" + "\n" + "\n" + "", null, null, null); checkXxx(testContext); } private void checkXxx(TestContext testContext) { // I do not know/believe that the return values are correct. testContext.assertQueryReturns( "select\n" + "{ [Measures].[Warehouse Sales], [Measures].[Unit Sales] }\n" + "ON COLUMNS,\n" + "{[Product].[All Products]}\n" + "ON ROWS\n" + "from [Sales vs Warehouse]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Warehouse Sales]}\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[All Products]}\n" + "Row #0: 196,770.888\n" + "Row #0: 266,773\n"); } /** * Query a virtual cube that contains a non-conforming dimension that * does not have ALL as its default member. */ public void testNonDefaultAllMember() { // Create a virtual cube with a non-conforming dimension (Warehouse) // that does not have ALL as its default member. TestContext testContext = createContextWithNonDefaultAllMember(); testContext.assertQueryReturns( "select {[Warehouse].defaultMember} on columns, " + "{[Measures].[Warehouse Cost]} on rows from [Warehouse (Default USA)]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Warehouse].[USA]}\n" + "Axis #2:\n" + "{[Measures].[Warehouse Cost]}\n" + "Row #0: 89,043.253\n"); // There is a value for [USA] -- because it is the default member and // the hierarchy has no all member -- but not for [USA].[CA]. testContext.assertQueryReturns( "select {[Warehouse].defaultMember, [Warehouse].[USA].[CA]} on columns, " + "{[Measures].[Warehouse Cost], [Measures].[Sales Count]} on rows " + "from [Warehouse (Default USA) and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Warehouse].[USA]}\n" + "{[Warehouse].[USA].[CA]}\n" + "Axis #2:\n" + "{[Measures].[Warehouse Cost]}\n" + "{[Measures].[Sales Count]}\n" + "Row #0: 89,043.253\n" + "Row #0: 25,789.086\n" + "Row #1: 86,837\n" + "Row #1: \n"); } public void testNonDefaultAllMember2() { TestContext testContext = createContextWithNonDefaultAllMember(); testContext.assertQueryReturns( "select { measures.[unit sales] } on 0 \n" + "from [warehouse (Default USA) and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 266,773\n"); } /** * Creates a TestContext containing a cube * "Warehouse (Default USA) and Sales". * * @return test context with a cube where the default member in the * Warehouse dimension is USA */ private TestContext createContextWithNonDefaultAllMember() { return TestContext.instance().create( null, // Warehouse cube where the default member in the Warehouse // dimension is USA. "\n" + " \n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", // Virtual cube based on [Warehouse (Default USA)] "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null); } public void testMemberVisibility() { TestContext testContext = TestContext.instance().create( null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " [Measures].[Store Sales] - [Measures].[Store Cost]\n" + " \n" + "", null, null, null); Result result = testContext.executeQuery( "select {[Measures].[Sales Count],\n" + " [Measures].[Store Cost],\n" + " [Measures].[Store Sales],\n" + " [Measures].[Units Shipped],\n" + " [Measures].[Profit]} on columns\n" + "from [Warehouse and Sales Member Visibility]"); assertVisibility(result, 0, "Sales Count", true); // explicitly visible assertVisibility( result, 1, "Store Cost", false); // explicitly invisible assertVisibility(result, 2, "Store Sales", true); // visible by default assertVisibility( result, 3, "Units Shipped", false); // explicitly invisible assertVisibility(result, 4, "Profit", false); // explicitly invisible } private void assertVisibility( Result result, int ordinal, String expectedName, boolean expectedVisibility) { List columnPositions = result.getAxes()[0].getPositions(); Member measure = columnPositions.get(ordinal).get(0); assertEquals(expectedName, measure.getName()); assertEquals( expectedVisibility, measure.getPropertyValue(Property.VISIBLE.name)); } /** * Test an expression for the format_string of a calculated member that * evaluates calculated members based on a virtual cube. One cube has cache * turned on, the other cache turned off. * *

Since evaluation of the format_string used to happen after the * aggregate cache was cleared, this used to fail, this should be solved * with the caching of the format string. * *

Without caching of format string, the query returns green for all * styles. */ public void testFormatStringExpressionCubeNoCache() { TestContext testContext = TestContext.instance().create( null, null, "\n" + "

\n" + "\n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " [Measures].[Store Sales] - [Measures].[Store Cost]\n" + " \n" + " \n" + " [Measures].[Profit] / [Measures].[Units Shipped]\n" + " 2.0), '|0.#|style=green', '|0.#|style=red')\"/>\n" + " \n" + "", null, null, null); testContext.assertQueryReturns( "select {[Measures].[Profit Per Unit Shipped]} ON COLUMNS, " + "{[Store].[All Stores].[USA].[CA], [Store].[All Stores].[USA].[OR], [Store].[All Stores].[USA].[WA]} ON ROWS " + "from [Warehouse and Sales Format Expression Cube No Cache] " + "where [Time].[1997]", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Profit Per Unit Shipped]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: |1.6|style=red\n" + "Row #1: |2.1|style=green\n" + "Row #2: |1.5|style=red\n"); } public void testCalculatedMeasure() { // calculated measures reference measures defined in the base cube assertQueryReturns( "select\n" + "{[Measures].[Profit Growth], " + "[Measures].[Profit], " + "[Measures].[Average Warehouse Sale] }\n" + "ON COLUMNS\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Profit Growth]}\n" + "{[Measures].[Profit]}\n" + "{[Measures].[Average Warehouse Sale]}\n" + "Row #0: 0.0%\n" + "Row #0: $339,610.90\n" + "Row #0: $2.21\n"); } public void testLostData() { assertQueryReturns( "select {[Time].[Time].Members} on columns,\n" + " {[Product].Children} on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q1].[1]}\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[5]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q3].[7]}\n" + "{[Time].[1997].[Q3].[8]}\n" + "{[Time].[1997].[Q3].[9]}\n" + "{[Time].[1997].[Q4]}\n" + "{[Time].[1997].[Q4].[10]}\n" + "{[Time].[1997].[Q4].[11]}\n" + "{[Time].[1997].[Q4].[12]}\n" + "{[Time].[1998]}\n" + "{[Time].[1998].[Q1]}\n" + "{[Time].[1998].[Q1].[1]}\n" + "{[Time].[1998].[Q1].[2]}\n" + "{[Time].[1998].[Q1].[3]}\n" + "{[Time].[1998].[Q2]}\n" + "{[Time].[1998].[Q2].[4]}\n" + "{[Time].[1998].[Q2].[5]}\n" + "{[Time].[1998].[Q2].[6]}\n" + "{[Time].[1998].[Q3]}\n" + "{[Time].[1998].[Q3].[7]}\n" + "{[Time].[1998].[Q3].[8]}\n" + "{[Time].[1998].[Q3].[9]}\n" + "{[Time].[1998].[Q4]}\n" + "{[Time].[1998].[Q4].[10]}\n" + "{[Time].[1998].[Q4].[11]}\n" + "{[Time].[1998].[Q4].[12]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 24,597\n" + "Row #0: 5,976\n" + "Row #0: 1,910\n" + "Row #0: 1,951\n" + "Row #0: 2,115\n" + "Row #0: 5,895\n" + "Row #0: 1,948\n" + "Row #0: 2,039\n" + "Row #0: 1,908\n" + "Row #0: 6,065\n" + "Row #0: 2,205\n" + "Row #0: 1,921\n" + "Row #0: 1,939\n" + "Row #0: 6,661\n" + "Row #0: 1,898\n" + "Row #0: 2,344\n" + "Row #0: 2,419\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #1: 191,940\n" + "Row #1: 47,809\n" + "Row #1: 15,604\n" + "Row #1: 15,142\n" + "Row #1: 17,063\n" + "Row #1: 44,825\n" + "Row #1: 14,393\n" + "Row #1: 15,055\n" + "Row #1: 15,377\n" + "Row #1: 47,440\n" + "Row #1: 17,036\n" + "Row #1: 15,741\n" + "Row #1: 14,663\n" + "Row #1: 51,866\n" + "Row #1: 14,232\n" + "Row #1: 18,278\n" + "Row #1: 19,356\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #2: 50,236\n" + "Row #2: 12,506\n" + "Row #2: 4,114\n" + "Row #2: 3,864\n" + "Row #2: 4,528\n" + "Row #2: 11,890\n" + "Row #2: 3,838\n" + "Row #2: 3,987\n" + "Row #2: 4,065\n" + "Row #2: 12,343\n" + "Row #2: 4,522\n" + "Row #2: 4,035\n" + "Row #2: 3,786\n" + "Row #2: 13,497\n" + "Row #2: 3,828\n" + "Row #2: 4,648\n" + "Row #2: 5,021\n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n"); assertQueryReturns( "select\n" + " {[Measures].[Unit Sales]} on 0,\n" + " {[Product].Children} on 1\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 24,597\n" + "Row #1: 191,940\n" + "Row #2: 50,236\n"); } /** * Tests a calc measure which combines a measures from the Sales cube with a * measures from the Warehouse cube. */ public void testCalculatedMeasureAcrossCubes() { assertQueryReturns( "with member [Measures].[Shipped per Ordered] as ' [Measures].[Units Shipped] / [Measures].[Unit Sales] ', format_string='#.00%'\n" + " member [Measures].[Profit per Unit Shipped] as ' [Measures].[Profit] / [Measures].[Units Shipped] '\n" + "select\n" + " {[Measures].[Unit Sales], \n" + " [Measures].[Units Shipped],\n" + " [Measures].[Shipped per Ordered],\n" + " [Measures].[Profit per Unit Shipped]} on 0,\n" + " NON EMPTY Crossjoin([Product].Children, [Time].[1997].Children) on 1\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Units Shipped]}\n" + "{[Measures].[Shipped per Ordered]}\n" + "{[Measures].[Profit per Unit Shipped]}\n" + "Axis #2:\n" + "{[Product].[Drink], [Time].[1997].[Q1]}\n" + "{[Product].[Drink], [Time].[1997].[Q2]}\n" + "{[Product].[Drink], [Time].[1997].[Q3]}\n" + "{[Product].[Drink], [Time].[1997].[Q4]}\n" + "{[Product].[Food], [Time].[1997].[Q1]}\n" + "{[Product].[Food], [Time].[1997].[Q2]}\n" + "{[Product].[Food], [Time].[1997].[Q3]}\n" + "{[Product].[Food], [Time].[1997].[Q4]}\n" + "{[Product].[Non-Consumable], [Time].[1997].[Q1]}\n" + "{[Product].[Non-Consumable], [Time].[1997].[Q2]}\n" + "{[Product].[Non-Consumable], [Time].[1997].[Q3]}\n" + "{[Product].[Non-Consumable], [Time].[1997].[Q4]}\n" + "Row #0: 5,976\n" + "Row #0: 4637.0\n" + "Row #0: 77.59%\n" + "Row #0: $1.50\n" + "Row #1: 5,895\n" + "Row #1: 4501.0\n" + "Row #1: 76.35%\n" + "Row #1: $1.60\n" + "Row #2: 6,065\n" + "Row #2: 6258.0\n" + "Row #2: 103.18%\n" + "Row #2: $1.15\n" + "Row #3: 6,661\n" + "Row #3: 5802.0\n" + "Row #3: 87.10%\n" + "Row #3: $1.38\n" + "Row #4: 47,809\n" + "Row #4: 37153.0\n" + "Row #4: 77.71%\n" + "Row #4: $1.64\n" + "Row #5: 44,825\n" + "Row #5: 35459.0\n" + "Row #5: 79.11%\n" + "Row #5: $1.62\n" + "Row #6: 47,440\n" + "Row #6: 41545.0\n" + "Row #6: 87.57%\n" + "Row #6: $1.47\n" + "Row #7: 51,866\n" + "Row #7: 34706.0\n" + "Row #7: 66.91%\n" + "Row #7: $1.91\n" + "Row #8: 12,506\n" + "Row #8: 9161.0\n" + "Row #8: 73.25%\n" + "Row #8: $1.76\n" + "Row #9: 11,890\n" + "Row #9: 9227.0\n" + "Row #9: 77.60%\n" + "Row #9: $1.65\n" + "Row #10: 12,343\n" + "Row #10: 9986.0\n" + "Row #10: 80.90%\n" + "Row #10: $1.59\n" + "Row #11: 13,497\n" + "Row #11: 9291.0\n" + "Row #11: 68.84%\n" + "Row #11: $1.86\n"); } /** * Tests a calc member defined in the cube. */ public void testCalculatedMemberInSchema() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Warehouse and Sales", null, " \n" + " [Measures].[Units Shipped] / [Measures].[Unit Sales]\n" + " \n" + " \n"); testContext.assertQueryReturns( "select\n" + " {[Measures].[Unit Sales], \n" + " [Measures].[Shipped per Ordered]} on 0,\n" + " NON EMPTY Crossjoin([Product].Children, [Time].[1997].Children) on 1\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Shipped per Ordered]}\n" + "Axis #2:\n" + "{[Product].[Drink], [Time].[1997].[Q1]}\n" + "{[Product].[Drink], [Time].[1997].[Q2]}\n" + "{[Product].[Drink], [Time].[1997].[Q3]}\n" + "{[Product].[Drink], [Time].[1997].[Q4]}\n" + "{[Product].[Food], [Time].[1997].[Q1]}\n" + "{[Product].[Food], [Time].[1997].[Q2]}\n" + "{[Product].[Food], [Time].[1997].[Q3]}\n" + "{[Product].[Food], [Time].[1997].[Q4]}\n" + "{[Product].[Non-Consumable], [Time].[1997].[Q1]}\n" + "{[Product].[Non-Consumable], [Time].[1997].[Q2]}\n" + "{[Product].[Non-Consumable], [Time].[1997].[Q3]}\n" + "{[Product].[Non-Consumable], [Time].[1997].[Q4]}\n" + "Row #0: 5,976\n" + "Row #0: 77.6%\n" + "Row #1: 5,895\n" + "Row #1: 76.4%\n" + "Row #2: 6,065\n" + "Row #2: 103.2%\n" + "Row #3: 6,661\n" + "Row #3: 87.1%\n" + "Row #4: 47,809\n" + "Row #4: 77.7%\n" + "Row #5: 44,825\n" + "Row #5: 79.1%\n" + "Row #6: 47,440\n" + "Row #6: 87.6%\n" + "Row #7: 51,866\n" + "Row #7: 66.9%\n" + "Row #8: 12,506\n" + "Row #8: 73.3%\n" + "Row #9: 11,890\n" + "Row #9: 77.6%\n" + "Row #10: 12,343\n" + "Row #10: 80.9%\n" + "Row #11: 13,497\n" + "Row #11: 68.8%\n"); } public void testAllMeasureMembers() { // result should exclude measures that are not explicitly defined // in the virtual cube (e.g., [Profit last Period]) assertQueryReturns( "select\n" + "{[Measures].allMembers} on columns\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Sales Count]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Invoice]}\n" + "{[Measures].[Supply Time]}\n" + "{[Measures].[Units Ordered]}\n" + "{[Measures].[Units Shipped]}\n" + "{[Measures].[Warehouse Cost]}\n" + "{[Measures].[Warehouse Profit]}\n" + "{[Measures].[Warehouse Sales]}\n" + "{[Measures].[Profit]}\n" + "{[Measures].[Profit Growth]}\n" + "{[Measures].[Average Warehouse Sale]}\n" + "{[Measures].[Profit Per Unit Shipped]}\n" + "Row #0: 86,837\n" + "Row #0: 225,627.23\n" + "Row #0: 565,238.13\n" + "Row #0: 266,773\n" + "Row #0: 102,278.409\n" + "Row #0: 10,425\n" + "Row #0: 227238.0\n" + "Row #0: 207726.0\n" + "Row #0: 89,043.253\n" + "Row #0: 107,727.635\n" + "Row #0: 196,770.888\n" + "Row #0: $339,610.90\n" + "Row #0: 0.0%\n" + "Row #0: $2.21\n" + "Row #0: $1.63\n"); } /** * Test a virtual cube where one of the dimensions contains an * ordinalColumn property */ public void testOrdinalColumn() { TestContext testContext = TestContext.instance().create( null, null, "\n" + "\n" + "\n" + "\n" + "", null, null, null); testContext.assertQueryReturns( "select {[Measures].[Org Salary]} on columns, " + "non empty " + "crossjoin([Store].[Store Country].members, [Position].[Store Management].children) " + "on rows from [Sales vs HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Org Salary]}\n" + "Axis #2:\n" + "{[Store].[Canada], [Position].[Store Management].[Store Manager]}\n" + "{[Store].[Canada], [Position].[Store Management].[Store Assistant Manager]}\n" + "{[Store].[Canada], [Position].[Store Management].[Store Shift Supervisor]}\n" + "{[Store].[Mexico], [Position].[Store Management].[Store Manager]}\n" + "{[Store].[Mexico], [Position].[Store Management].[Store Assistant Manager]}\n" + "{[Store].[Mexico], [Position].[Store Management].[Store Shift Supervisor]}\n" + "{[Store].[USA], [Position].[Store Management].[Store Manager]}\n" + "{[Store].[USA], [Position].[Store Management].[Store Assistant Manager]}\n" + "{[Store].[USA], [Position].[Store Management].[Store Shift Supervisor]}\n" + "Row #0: $462.86\n" + "Row #1: $394.29\n" + "Row #2: $565.71\n" + "Row #3: $13,254.55\n" + "Row #4: $11,443.64\n" + "Row #5: $17,705.46\n" + "Row #6: $4,069.80\n" + "Row #7: $3,417.72\n" + "Row #8: $5,145.96\n"); } public void testDefaultMeasureProperty() { TestContext testContext = TestContext.instance().create( null, null, "\n" + "\n" + "\n" + "\n" + "\n" + "", null, null, null); String queryWithoutFilter = "select" + " from [Sales vs Warehouse]"; String queryWithDeflaultMeasureFilter = "select " + "from [Sales vs Warehouse] where measures.[Unit Sales]"; assertQueriesReturnSimilarResults( queryWithoutFilter, queryWithDeflaultMeasureFilter, testContext); } /** * Checks that native set caching considers base cubes in the cache key. * Native sets referencing different base cubes do not share the cached * result. */ public void testNativeSetCaching() { // Only need to run this against one db to verify caching // behavior is correct. final Dialect dialect = getTestContext().getDialect(); if (dialect.getDatabaseProduct() != Dialect.DatabaseProduct.DERBY) { return; } if (!MondrianProperties.instance().EnableNativeCrossJoin.get() && !MondrianProperties.instance().EnableNativeNonEmpty.get()) { // Only run the tests if either native CrossJoin or native NonEmpty // is enabled. return; } String query1 = "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([Product].[Product Family].Members, [Store].[Store Country].Members)' " + "Select " + "{[Store Sales]} on columns, " + "Non Empty Generate([*NATIVE_CJ_SET], {([Product].CurrentMember,[Store].CurrentMember)}) on rows " + "From [Warehouse and Sales]"; String query2 = "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([Product].[Product Family].Members, [Store].[Store Country].Members)' " + "Select " + "{[Warehouse Sales]} on columns, " + "Non Empty Generate([*NATIVE_CJ_SET], {([Product].CurrentMember,[Store].CurrentMember)}) on rows " + "From [Warehouse and Sales]"; String derbyNecjSql1, derbyNecjSql2; if (MondrianProperties.instance().EnableNativeCrossJoin.get()) { derbyNecjSql1 = "select " + "\"product_class\".\"product_family\", " + "\"store\".\"store_country\" " + "from " + "\"product\" as \"product\", " + "\"product_class\" as \"product_class\", " + "\"sales_fact_1997\" as \"sales_fact_1997\", " + "\"store\" as \"store\" " + "where " + "\"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" " + "and \"sales_fact_1997\".\"product_id\" = \"product\".\"product_id\" " + "and \"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" " + "group by \"product_class\".\"product_family\", \"store\".\"store_country\" " + "order by 1 ASC, 2 ASC"; derbyNecjSql2 = "select " + "\"product_class\".\"product_family\", " + "\"store\".\"store_country\" " + "from " + "\"product\" as \"product\", " + "\"product_class\" as \"product_class\", " + "\"inventory_fact_1997\" as \"inventory_fact_1997\", " + "\"store\" as \"store\" " + "where " + "\"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" " + "and \"inventory_fact_1997\".\"product_id\" = \"product\".\"product_id\" " + "and \"inventory_fact_1997\".\"store_id\" = \"store\".\"store_id\" " + "group by \"product_class\".\"product_family\", \"store\".\"store_country\" " + "order by 1 ASC, 2 ASC"; } else { // NECJ is truend off so native NECJ SQL will not be generated; // however, because the NECJ set should not find match in the cache, // each NECJ input will still be joined with the correct // fact table if NonEmpty condition is natively evaluated. derbyNecjSql1 = "select " + "\"store\".\"store_country\" " + "from " + "\"store\" as \"store\", " + "\"sales_fact_1997\" as \"sales_fact_1997\" " + "where " + "\"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" " + "group by \"store\".\"store_country\" " + "order by 1 ASC"; derbyNecjSql2 = "select " + "\"store\".\"store_country\" " + "from " + "\"store\" as \"store\", " + "\"inventory_fact_1997\" as \"inventory_fact_1997\" " + "where " + "\"inventory_fact_1997\".\"store_id\" = \"store\".\"store_id\" " + "group by \"store\".\"store_country\" " + "order by 1 ASC"; } SqlPattern[] patterns1 = { new SqlPattern( Dialect.DatabaseProduct.DERBY, derbyNecjSql1, derbyNecjSql1) }; SqlPattern[] patterns2 = { new SqlPattern( Dialect.DatabaseProduct.DERBY, derbyNecjSql2, derbyNecjSql2) }; // Run query 1 with cleared cache; // Make sure NECJ 1 is evaluated natively. assertQuerySql(query1, patterns1, true); // Now run query 2 with warm cache; // Make sure NECJ 2 does not reuse the cache result from NECJ 1, and // NECJ 2 is evaluated natively. assertQuerySql(query2, patterns2, false); } /** * Test case for bug * MONDRIAN-322, "cube.getStar() throws NullPointerException". * Happens when you aggregate distinct-count measures in a virtual cube. */ public void testBugMondrian322() { final TestContext testContext = TestContext.instance().create( null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null); /* * This test case does not actually reject the dimension constraint from * an unrelated base cube. The reason is that the constraint contains an * AllLevel member. Even though semantically constraining Cells using an * non-existent dimension perhaps does not make sense; however, in the * case where the constraint contains AllLevel member, the constraint * can be considered "always true". * * See the next test case for a constraint that does not contain * AllLevel member and hence cannot be satisfied. The cell should be * empty. */ testContext.assertQueryReturns( "with member [Warehouse].[x] as 'Aggregate([Warehouse].members)'\n" + "member [Measures].[foo] AS '([Warehouse].[x],[Measures].[Customer Count])'\n" + "select {[Measures].[foo]} on 0 from [Warehouse And Sales2]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[foo]}\n" + "Row #0: 5,581\n"); } public void testBugMondrian322a() { final TestContext testContext = TestContext.instance().create( null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null); testContext.assertQueryReturns( "with member [Warehouse].[x] as 'Aggregate({[Warehouse].[Canada], [Warehouse].[USA]})'\n" + "member [Measures].[foo] AS '([Warehouse].[x],[Measures].[Customer Count])'\n" + "select {[Measures].[foo]} on 0 from [Warehouse And Sales2]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[foo]}\n" + "Row #0: \n"); } /** * Test case for bug * MONDRIAN-352, "Caption is not set on RolapVirtualCubeMesure". */ public void testVirtualCubeMeasureCaption() { TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n", "\n" + " \n" + " \n" + "", null, null, null); Result result = testContext.executeQuery( "select {[Measures].[Store Sqft]} ON COLUMNS," + "{[HCB]} ON ROWS " + "from [VirtualTestStore]"); Axis[] axes = result.getAxes(); List positions = axes[0].getPositions(); Member m0 = positions.get(0).get(0); String caption = m0.getCaption(); assertEquals("Store Sqft Caption", caption); } /** * Test that RolapCubeLevel is used correctly in the context of virtual * cube. */ public void testRolapCubeLevelInVirtualCube() { String query1 = "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Warehouse],[*BASE_MEMBERS_Time])' " + "Set [*NATIVE_MEMBERS_Warehouse] as 'Generate([*NATIVE_CJ_SET], {[Warehouse].CurrentMember})' " + "Set [*BASE_MEMBERS_Warehouse] as '[Warehouse].[Country].Members' " + "Set [*NATIVE_MEMBERS_Time] as 'Generate([*NATIVE_CJ_SET], {[Time].[Time].CurrentMember})' " + "Set [*BASE_MEMBERS_Time] as '[Time].[Month].Members' " + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}' Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Warehouse Sales]', FORMAT_STRING = '#,##0', SOLVE_ORDER=400 " + "Select [*BASE_MEMBERS_Measures] on columns, Non Empty Generate([*NATIVE_CJ_SET], {([Warehouse].currentMember,[Time].[Time].currentMember)}) on rows From [Warehouse and Sales] "; String query2 = "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Warehouse],[*BASE_MEMBERS_Time])' " + "Set [*NATIVE_MEMBERS_Warehouse] as 'Generate([*NATIVE_CJ_SET], {[Warehouse].CurrentMember})' " + "Set [*BASE_MEMBERS_Warehouse] as '[Warehouse].[Country].Members' " + "Set [*NATIVE_MEMBERS_Time] as 'Generate([*NATIVE_CJ_SET], {[Time].[Time].CurrentMember})' " + "Set [*BASE_MEMBERS_Time] as 'Filter([Time].[Month].Members,[Time].[Time].CurrentMember Not In {[Time].[1997].[Q1].[2]})' " + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}' Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Warehouse Sales]', FORMAT_STRING = '#,##0', SOLVE_ORDER=400 " + "Select [*BASE_MEMBERS_Measures] on columns, Non Empty Generate([*NATIVE_CJ_SET], {([Warehouse].currentMember,[Time].[Time].currentMember)}) on rows From [Warehouse and Sales]"; executeQuery(query1); /* The query with the filter should now succeed without NPE */ String result = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*FORMATTED_MEASURE_0]}\n" + "Axis #2:\n" + "{[Warehouse].[USA], [Time].[1997].[Q1].[1]}\n" + "{[Warehouse].[USA], [Time].[1997].[Q1].[3]}\n" + "{[Warehouse].[USA], [Time].[1997].[Q2].[4]}\n" + "{[Warehouse].[USA], [Time].[1997].[Q2].[5]}\n" + "{[Warehouse].[USA], [Time].[1997].[Q2].[6]}\n" + "{[Warehouse].[USA], [Time].[1997].[Q3].[7]}\n" + "{[Warehouse].[USA], [Time].[1997].[Q3].[8]}\n" + "{[Warehouse].[USA], [Time].[1997].[Q3].[9]}\n" + "{[Warehouse].[USA], [Time].[1997].[Q4].[10]}\n" + "{[Warehouse].[USA], [Time].[1997].[Q4].[11]}\n" + "{[Warehouse].[USA], [Time].[1997].[Q4].[12]}\n" + "Row #0: 21,762\n" + "Row #1: 13,775\n" + "Row #2: 15,938\n" + "Row #3: 15,649\n" + "Row #4: 14,629\n" + "Row #5: 18,626\n" + "Row #6: 15,833\n" + "Row #7: 21,393\n" + "Row #8: 17,100\n" + "Row #9: 15,356\n" + "Row #10: 13,948\n"; assertQueryReturns(query2, result); } /** * Tests that the logic to apply non empty context constraint in virtual * cube is correct. The joins shouldn't be cartesian product. */ public void testNonEmptyCJConstraintOnVirtualCube() { if (!MondrianProperties.instance().EnableNativeCrossJoin.get()) { // Generated SQL is different if NonEmptyCrossJoin is evaluated in // memory. return; } String query = "with " + "set [foo] as [Time].[Month].members " + "set [bar] as {[Store].[USA]} " + "Select {[Measures].[Warehouse Sales],[Measures].[Store Sales]} on columns, " + "nonemptycrossjoin([foo],[bar]) on rows " + "From [Warehouse and Sales] " + "Where ([Product].[All Products].[Food])"; // Note that for MySQL (because MySQL sorts NULLs first), because there // is a UNION (which prevents us from sorting on column names or // expressions) the ORDER BY clause should be something like // ORDER BY ISNULL(1), 1 ASC, ISNULL(2), 2 ASC, ISNULL(3), 3 ASC, // ISNULL(4), 4 ASC // but ISNULL(1) isn't valid SQL, so we forego correct ordering of NULL // values. String mysqlSQL = "select " + "`time_by_day`.`the_year` as `c0`, `time_by_day`.`quarter` as `c1`, `time_by_day`.`month_of_year` as `c2`, `store`.`store_country` as `c3` " + "from " + "`time_by_day` as `time_by_day`, `sales_fact_1997` as `sales_fact_1997`, " + "`store` as `store`, `product_class` as `product_class`, `product` as `product` " + "where " + "`sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and `sales_fact_1997`.`store_id` = `store`.`store_id` and " + "`sales_fact_1997`.`product_id` = `product`.`product_id` and `product`.`product_class_id` = `product_class`.`product_class_id` and " + "`product_class`.`product_family` = 'Food' and (`store`.`store_country` = 'USA') " + "group by " + "`time_by_day`.`the_year`, `time_by_day`.`quarter`, `time_by_day`.`month_of_year`, `store`.`store_country` " + "union " + "select " + "`time_by_day`.`the_year` as `c0`, `time_by_day`.`quarter` as `c1`, `time_by_day`.`month_of_year` as `c2`, `store`.`store_country` as `c3` " + "from " + "`time_by_day` as `time_by_day`, `inventory_fact_1997` as `inventory_fact_1997`, `store` as `store`, " + "`product_class` as `product_class`, `product` as `product` " + "where " + "`inventory_fact_1997`.`time_id` = `time_by_day`.`time_id` and `inventory_fact_1997`.`store_id` = `store`.`store_id` and " + "`inventory_fact_1997`.`product_id` = `product`.`product_id` and `product`.`product_class_id` = `product_class`.`product_class_id` and " + "`product_class`.`product_family` = 'Food' and (`store`.`store_country` = 'USA') " + "group by " + "`time_by_day`.`the_year`, `time_by_day`.`quarter`, `time_by_day`.`month_of_year`, `store`.`store_country` " + "order by " + "1 ASC, 2 ASC, 3 ASC, 4 ASC"; String derbySQL = "select " + "\"time_by_day\".\"the_year\", \"time_by_day\".\"quarter\", \"time_by_day\".\"month_of_year\", \"store\".\"store_country\" " + "from \"time_by_day\" as \"time_by_day\", \"sales_fact_1997\" as \"sales_fact_1997\", \"store\" as \"store\", " + "\"product_class\" as \"product_class\", \"product\" as \"product\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" and " + "\"sales_fact_1997\".\"product_id\" = \"product\".\"product_id\" and \"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" and " + "\"product_class\".\"product_family\" = 'Food' and (\"store\".\"store_country\" = 'USA') " + "group by \"time_by_day\".\"the_year\", \"time_by_day\".\"quarter\", \"time_by_day\".\"month_of_year\", \"store\".\"store_country\" " + "union " + "select " + "\"time_by_day\".\"the_year\", \"time_by_day\".\"quarter\", \"time_by_day\".\"month_of_year\", \"store\".\"store_country\" " + "from \"time_by_day\" as \"time_by_day\", \"inventory_fact_1997\" as \"inventory_fact_1997\", \"store\" as \"store\", " + "\"product_class\" as \"product_class\", \"product\" as \"product\" " + "where \"inventory_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"inventory_fact_1997\".\"store_id\" = \"store\".\"store_id\" and " + "\"inventory_fact_1997\".\"product_id\" = \"product\".\"product_id\" and \"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" and " + "\"product_class\".\"product_family\" = 'Food' and (\"store\".\"store_country\" = 'USA') " + "group by \"time_by_day\".\"the_year\", \"time_by_day\".\"quarter\", \"time_by_day\".\"month_of_year\", \"store\".\"store_country\" " + "order by 1 ASC, 2 ASC, 3 ASC, 4 ASC"; SqlPattern[] mysqlPattern = { new SqlPattern(Dialect.DatabaseProduct.MYSQL, mysqlSQL, mysqlSQL), new SqlPattern(Dialect.DatabaseProduct.DERBY, derbySQL, derbySQL) }; String result = "Axis #0:\n" + "{[Product].[Food]}\n" + "Axis #1:\n" + "{[Measures].[Warehouse Sales]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1].[1], [Store].[USA]}\n" + "{[Time].[1997].[Q1].[2], [Store].[USA]}\n" + "{[Time].[1997].[Q1].[3], [Store].[USA]}\n" + "{[Time].[1997].[Q2].[4], [Store].[USA]}\n" + "{[Time].[1997].[Q2].[5], [Store].[USA]}\n" + "{[Time].[1997].[Q2].[6], [Store].[USA]}\n" + "{[Time].[1997].[Q3].[7], [Store].[USA]}\n" + "{[Time].[1997].[Q3].[8], [Store].[USA]}\n" + "{[Time].[1997].[Q3].[9], [Store].[USA]}\n" + "{[Time].[1997].[Q4].[10], [Store].[USA]}\n" + "{[Time].[1997].[Q4].[11], [Store].[USA]}\n" + "{[Time].[1997].[Q4].[12], [Store].[USA]}\n" + "Row #0: 16,083.015\n" + "Row #0: 32,993.12\n" + "Row #1: 9,298.379\n" + "Row #1: 32,139.91\n" + "Row #2: 10,129.659\n" + "Row #2: 36,128.29\n" + "Row #3: 11,415.462\n" + "Row #3: 30,747.21\n" + "Row #4: 11,358.086\n" + "Row #4: 31,896.24\n" + "Row #5: 10,425.768\n" + "Row #5: 32,792.55\n" + "Row #6: 13,684.193\n" + "Row #6: 36,324.76\n" + "Row #7: 11,332.797\n" + "Row #7: 33,842.75\n" + "Row #8: 15,667.978\n" + "Row #8: 31,640.09\n" + "Row #9: 11,902.18\n" + "Row #9: 30,337.12\n" + "Row #10: 10,144.841\n" + "Row #10: 38,709.15\n" + "Row #11: 9,705.561\n" + "Row #11: 41,484.40\n"; assertQuerySql(query, mysqlPattern, true); assertQueryReturns(query, result); } /** * Tests that the logic to apply non empty context constraint in virtual * cube is correct. The joins shouldn't be cartesian product. */ public void testNonEmptyConstraintOnVirtualCubeWithCalcMeasure() { if (!MondrianProperties.instance().EnableNativeNonEmpty.get()) { // Generated SQL is different if NON EMPTY is evaluated in memory. return; } String query = "with " + "set [bar] as {[Store].[USA]} " + "member [Measures].[CalcMeasure] as '[Measures].[Warehouse Sales] / [Measures].[Store Sales]' " + "Select " + "{[Measures].[CalcMeasure]} on columns, " + "non empty([Product].[Product Family].Members) on rows " + "From [Warehouse and Sales] " + "where [bar]"; // Comments as for testNonEmptyCJConstraintOnVirtualCube. The ORDER BY // clause should be "order by ISNULL(1), 1 ASC" but we will settle for // "order by 1 ASC" and forego correct sorting of NULL values. String mysqlSQL = "select `product_class`.`product_family` as `c0` " + "from `product` as `product`, `product_class` as `product_class`, `sales_fact_1997` as `sales_fact_1997`, `store` as `store` " + "where `product`.`product_class_id` = `product_class`.`product_class_id` and `sales_fact_1997`.`product_id` = `product`.`product_id` and " + "`sales_fact_1997`.`store_id` = `store`.`store_id` and `store`.`store_country` = 'USA' " + "group by `product_class`.`product_family` " + "union " + "select `product_class`.`product_family` as `c0` " + "from `product` as `product`, `product_class` as `product_class`, `inventory_fact_1997` as `inventory_fact_1997`, `store` as `store` " + "where `product`.`product_class_id` = `product_class`.`product_class_id` and `inventory_fact_1997`.`product_id` = `product`.`product_id` and " + "`inventory_fact_1997`.`store_id` = `store`.`store_id` and `store`.`store_country` = 'USA' " + "group by `product_class`.`product_family` " + "order by 1 ASC"; String derbySQL = "select \"product_class\".\"product_family\" " + "from \"product\" as \"product\", \"product_class\" as \"product_class\", \"sales_fact_1997\" as \"sales_fact_1997\", \"store\" as \"store\" " + "where \"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" and \"sales_fact_1997\".\"product_id\" = \"product\".\"product_id\"" + " and \"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" and \"store\".\"store_country\" = 'USA' " + "group by \"product_class\".\"product_family\" " + "union " + "select \"product_class\".\"product_family\" " + "from \"product\" as \"product\", \"product_class\" as \"product_class\", \"inventory_fact_1997\" as \"inventory_fact_1997\", \"store\" as \"store\" " + "where \"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" and \"inventory_fact_1997\".\"product_id\" = \"product\".\"product_id\"" + " and \"inventory_fact_1997\".\"store_id\" = \"store\".\"store_id\" and \"store\".\"store_country\" = 'USA' " + "group by \"product_class\".\"product_family\" " + "order by 1 ASC"; String result = "Axis #0:\n" + "{[Store].[USA]}\n" + "Axis #1:\n" + "{[Measures].[CalcMeasure]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 0.369\n" + "Row #1: 0.345\n" + "Row #2: 0.35\n"; SqlPattern[] mysqlPattern = { new SqlPattern( Dialect.DatabaseProduct.MYSQL, mysqlSQL, mysqlSQL), new SqlPattern( Dialect.DatabaseProduct.DERBY, derbySQL, derbySQL) }; assertQuerySql(query, mysqlPattern, true); assertQueryReturns(query, result); } /** * Test case for bug * MONDRIAN-902, "mondrian populating the same members on both axes". */ public void testBugMondrian902() { Result result = executeQuery( "SELECT\n" + "NON EMPTY CrossJoin(\n" + " [Education Level].[Education Level].Members,\n" + " CrossJoin(\n" + " [Product].[Product Family].Members,\n" + " [Store].[Store State].Members)) ON COLUMNS,\n" + "NON EMPTY CrossJoin(\n" + " [Promotions].[Promotion Name].Members,\n" + " [Marital Status].[Marital Status].Members) ON ROWS\n" + "FROM [Warehouse and Sales]"); assertEquals( "[[Education Level].[Bachelors Degree], [Product].[Drink], [Store].[USA].[CA]]", result.getAxes()[0].getPositions().get(0).toString()); assertEquals(45, result.getAxes()[0].getPositions().size()); // With bug MONDRIAN-902, this gave the same result as for axis #0: assertEquals( "[[Promotions].[Bag Stuffers], [Marital Status].[M]]", result.getAxes()[1].getPositions().get(0).toString()); assertEquals(96, result.getAxes()[1].getPositions().size()); } } // End VirtualCubeTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/RolapConnectionTest.java0000644000175000017500000004332711735330606025376 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. // // jng, 16 April, 2004 */ package mondrian.rolap; import mondrian.olap.*; import mondrian.spi.Dialect; import mondrian.test.TestContext; import mondrian.util.Pair; import junit.framework.TestCase; import java.sql.Connection; import java.sql.SQLException; import java.util.*; import javax.naming.*; import javax.naming.spi.*; import javax.sql.DataSource; /** * Unit test for {@link RolapConnection}. * * @author jng * @since 16 April, 2004 */ public class RolapConnectionTest extends TestCase { private static final ThreadLocal THREAD_INITIAL_CONTEXT = new ThreadLocal(); public RolapConnectionTest(String name) { super(name); } protected void setUp() throws Exception { super.setUp(); if (!NamingManager.hasInitialContextFactoryBuilder()) { NamingManager.setInitialContextFactoryBuilder( new InitialContextFactoryBuilder() { public InitialContextFactory createInitialContextFactory( Hashtable environment) throws NamingException { return new InitialContextFactory() { public Context getInitialContext( Hashtable environment) throws NamingException { return THREAD_INITIAL_CONTEXT.get(); } }; } } ); } } public void testPooledConnectionWithProperties() throws SQLException { Util.PropertyList properties = TestContext.instance().getConnectionProperties().clone(); // Only the JDBC-ODBC bridge gives the error necessary for this // test to succeed. So trivially succeed for all other JDBC // drivers. final String jdbc = properties.get("Jdbc"); if (jdbc != null && !jdbc.startsWith("jdbc:odbc:")) { return; } // JDBC-ODBC driver does not support UTF-16, so this test succeeds // because creating the connection from the DataSource will fail. properties.put("jdbc.charSet", "UTF-16"); final StringBuilder buf = new StringBuilder(); DataSource dataSource = RolapConnection.createDataSource(null, properties, buf); final String desc = buf.toString(); assertTrue(desc.startsWith("Jdbc=")); Connection connection; try { connection = dataSource.getConnection(); connection.close(); fail("Expected exception"); } catch (SQLException e) { if (e.getClass().getName().equals( "org.apache.commons.dbcp.DbcpException")) { // This is expected. (We use string-comparison so that the // compiler doesn't warn about using a deprecated class.) } else if (e.getClass() == SQLException.class && e.getCause() == null && e.getMessage() != null && e.getMessage().equals("")) { // This is expected, from a later version of Dbcp. } else { fail("Expected exception, but got a different one: " + e); } } catch (IllegalArgumentException e) { handleIllegalArgumentException(properties, e); } finally { RolapConnectionPool.instance().clearPool(); } } public void testNonPooledConnectionWithProperties() { Util.PropertyList properties = TestContext.instance().getConnectionProperties().clone(); // Only the JDBC-ODBC bridge gives the error necessary for this // test to succeed. So trivially succeed for all other JDBC // drivers. final String jdbc = properties.get("Jdbc"); if (jdbc != null && !jdbc.startsWith("jdbc:odbc:")) { return; } // This test is just like the test testPooledConnectionWithProperties // except with non-pooled connections. properties.put("jdbc.charSet", "UTF-16"); properties.put(RolapConnectionProperties.PoolNeeded.name(), "false"); final StringBuilder buf = new StringBuilder(); DataSource dataSource = RolapConnection.createDataSource(null, properties, buf); final String desc = buf.toString(); assertTrue(desc.startsWith("Jdbc=")); Connection connection = null; try { connection = dataSource.getConnection(); fail("Expected exception"); } catch (SQLException se) { // this is expected } catch (IllegalArgumentException e) { handleIllegalArgumentException(properties, e); } finally { if (connection != null) { try { connection.close(); } catch (SQLException e) { // ignore } } } } /** * Handle an {@link IllegalArgumentException} which occurs when have * tried to create a connection with an illegal charset. */ private void handleIllegalArgumentException( Util.PropertyList properties, IllegalArgumentException e) { // Workaround Java bug #6504538 (see http://bugs.sun.com) with synopsis // "DriverManager.getConnection throws IllegalArgumentException". if (System.getProperties().getProperty("java.version") .startsWith("1.6.")) { properties.remove("jdbc.charSet"); final StringBuilder buf = new StringBuilder(); DataSource dataSource = RolapConnection.createDataSource(null, properties, buf); final String desc = buf.toString(); assertTrue(desc.startsWith("Jdbc=")); try { Connection connection1 = dataSource.getConnection(); connection1.close(); } catch (SQLException e1) { // ignore } } else { fail("Expect IllegalArgumentException only in JDK 1.6, got " + e); } } /** * Tests that the FORMAT function uses the connection's locale. */ public void testFormatLocale() { String expr = "FORMAT(1234.56, \"#,##.#\")"; checkLocale("es_ES", expr, "1.234,6", false); checkLocale("es_MX", expr, "1,234.6", false); checkLocale("en_US", expr, "1,234.6", false); } /** * Tests that measures are formatted using the connection's locale. */ public void testFormatStringLocale() { checkLocale("es_ES", "1234.56", "1.234,6", true); checkLocale("es_MX", "1234.56", "1,234.6", true); checkLocale("en_US", "1234.56", "1,234.6", true); } private static void checkLocale( final String localeName, String expr, String expected, boolean isQuery) { TestContext testContextSpain = new TestContext() { public mondrian.olap.Connection getConnection() { Util.PropertyList properties = Util.parseConnectString(getConnectString()); properties.put( RolapConnectionProperties.Locale.name(), localeName); return DriverManager.getConnection(properties, null); } }; if (isQuery) { String query = "WITH MEMBER [Measures].[Foo] AS '" + expr + "',\n" + " FORMAT_STRING = '#,##.#' \n" + "SELECT {[MEasures].[Foo]} ON COLUMNS FROM [Sales]"; String expected2 = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: " + expected + "\n"; testContextSpain.assertQueryReturns(query, expected2); } else { testContextSpain.assertExprReturns(expr, expected); } } public void testConnectSansCatalogFails() { Util.PropertyList properties = TestContext.instance().getConnectionProperties().clone(); properties.remove(RolapConnectionProperties.Catalog.name()); properties.remove(RolapConnectionProperties.CatalogContent.name()); if (RolapUtil.SQL_LOGGER.isDebugEnabled()) { RolapUtil.SQL_LOGGER.debug( this.getName() + "\n [Connection Properties | " + properties + "]\n"); } else { System.out.println(properties); } try { DriverManager.getConnection( properties, null); fail("expected exception"); } catch (MondrianException e) { assertTrue( e.getMessage().indexOf( "Connect string must contain property 'Catalog' or " + "property 'CatalogContent'") >= 0); } } public void testJndiConnection() throws NamingException { // Cannot guarantee that this test will work if they have chosen to // resolve data sources other than by JNDI. if (MondrianProperties.instance().DataSourceResolverClass.isSet()) { return; } // get a regular connection Util.PropertyList properties = TestContext.instance().getConnectionProperties().clone(); final StringBuilder buf = new StringBuilder(); final DataSource dataSource = RolapConnection.createDataSource(null, properties, buf); // Don't know what the connect string is - it differs with database // and with the user's set up - but we know that it contains a JDBC // connect string. Best we can do is check that createDataSource is // setting it to something. final String desc = buf.toString(); assertTrue(desc, desc.startsWith("Jdbc=")); final List lookupCalls = new ArrayList(); // mock the JNDI naming manager to provide that datasource THREAD_INITIAL_CONTEXT.set( // Use lazy initialization. Otherwise during initialization of this // initial context JNDI tries to create a default initial context // and bumps into itself coming the other way. new InitialContext(true) { public Object lookup(String str) { lookupCalls.add("Called"); return dataSource; } } ); // Use the datasource property to connect to the database. // Remove user and password, because some data sources (those using // pools) don't allow you to override user. Util.PropertyList properties2 = TestContext.instance().getConnectionProperties().clone(); properties2.remove(RolapConnectionProperties.Jdbc.name()); properties2.remove(RolapConnectionProperties.JdbcUser.name()); properties2.remove(RolapConnectionProperties.JdbcPassword.name()); properties2.put( RolapConnectionProperties.DataSource.name(), "jnditest"); DriverManager.getConnection(properties2, null); // if we've made it here with lookupCalls, // we've successfully used JNDI assertTrue(lookupCalls.size() > 0); } public void testDataSourceOverrideUserPass() throws SQLException, NamingException { // use the datasource property to connect to the database Util.PropertyList properties = TestContext.instance().getConnectionProperties().clone(); final Dialect dialect = TestContext.instance().getDialect(); if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.ACCESS) { // Access doesn't accept user/password, so this test is pointless. return; } final String jdbcUser = properties.get(RolapConnectionProperties.JdbcUser.name()); final String jdbcPassword = properties.get(RolapConnectionProperties.JdbcPassword.name()); if (jdbcUser == null || jdbcPassword == null) { // Can only run this test if username and password are explicit. return; } // Define a data source with bogus user and password. properties.put( RolapConnectionProperties.JdbcUser.name(), "bogususer"); properties.put( RolapConnectionProperties.JdbcPassword.name(), "boguspassword"); properties.put( RolapConnectionProperties.PoolNeeded.name(), "false"); final StringBuilder buf = new StringBuilder(); final DataSource dataSource = RolapConnection.createDataSource(null, properties, buf); final String desc = buf.toString(); assertTrue(desc, desc.startsWith("Jdbc=")); assertTrue( desc, desc.indexOf("JdbcUser=bogususer; JdbcPassword=boguspassword") >= 0); final String jndiName = "jndiDataSource"; THREAD_INITIAL_CONTEXT.set( new InitialContext() { public Object lookup(String str) { return str.equals(jndiName) ? dataSource : null; } } ); // Create a property list that we will use for the actual mondrian // connection. Replace the original JDBC info with the data source we // just created. final Util.PropertyList properties2 = new Util.PropertyList(); for (Pair entry : properties) { properties2.put(entry.getKey(), entry.getValue()); } properties2.remove(RolapConnectionProperties.Jdbc.name()); properties2.put( RolapConnectionProperties.DataSource.name(), jndiName); // With JdbcUser and JdbcPassword credentials in the mondrian connect // string, the data source's "user" and "password" properties are // overridden and the connection succeeds. properties2.put( RolapConnectionProperties.JdbcUser.name(), jdbcUser); properties2.put( RolapConnectionProperties.JdbcPassword.name(), jdbcPassword); mondrian.olap.Connection connection = null; try { connection = DriverManager.getConnection(properties2, null); Query query = connection.parseQuery("select from [Sales]"); final Result result = connection.execute(query); assertNotNull(result); } finally { if (connection != null) { connection.close(); connection = null; } } // If we don't specify JdbcUser and JdbcPassword in the mondrian // connection properties, mondrian uses the data source's // bogus credentials, and the connection fails. properties2.remove(RolapConnectionProperties.JdbcUser.name()); properties2.remove(RolapConnectionProperties.JdbcPassword.name()); for (String poolNeeded : Arrays.asList("false", "true")) { // Important to test with & without pooling. Connection pools // typically do not let you change user, so it's important that // mondrian handles these right. properties2.put( RolapConnectionProperties.PoolNeeded.name(), poolNeeded); try { connection = DriverManager.getConnection(properties2, null); fail("Expected exception"); } catch (MondrianException e) { final String s = TestContext.getStackTrace(e); assertTrue( s, s.indexOf( "Error while creating SQL connection: " + "DataSource=jndiDataSource") >= 0); switch (dialect.getDatabaseProduct()) { case DERBY: assertTrue( s, s.indexOf( "Caused by: java.sql.SQLException: " + "Schema 'BOGUSUSER' does not exist") >= 0); break; case ORACLE: assertTrue( s, s.indexOf( "Caused by: java.sql.SQLException: ORA-01017: " + "invalid username/password; logon denied") >= 0); break; case MYSQL: assertTrue( s, s.indexOf( "Caused by: java.sql.SQLException: Access denied " + "for user 'bogususer'@'localhost' (using " + "password: YES)") >= 0); break; case POSTGRESQL: assertTrue( s, s.indexOf( "Caused by: org.postgresql.util.PSQLException: " + "FATAL: password authentication failed for " + "user \"bogususer\"") >= 0); break; } } finally { if (connection != null) { connection.close(); connection = null; } } } } } // End RolapConnectionTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/SharedDimensionTest.java0000644000175000017500000004146611735330606025357 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.test.FoodMartTestCase; import mondrian.test.TestContext; /** * SharedDimensionTest tests shared dimensions. * * @author Rushan Chen */ public class SharedDimensionTest extends FoodMartTestCase { public static final String sharedDimension = "\n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + ""; // Base Cube A: use product_id as foreign key for Employee diemnsion // because there exist rows satidfying the join condition // "employee.employee_id = inventory_fact_1997.product_id" public static final String cubeA = "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + ""; // Base Cube B: use time_id as foreign key for Employee diemnsion // because there exist rows satidfying the join condition // "employee.employee_id = inventory_fact_1997.time_id" public static final String cubeB = "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + ""; public static final String virtualCube = "\n" + " \n" + " \n" + " \n" + " \n" + ""; public static final String queryCubeA = "with\n" + " set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Employee], [*BASE_MEMBERS_Store Type])'\n" + " set [*BASE_MEMBERS_Measures] as '{[Measures].[Employee Store Sales], [Measures].[Employee Store Cost]}'\n" + " set [*BASE_MEMBERS_Employee] as '[Employee].[Role].Members'\n" + " set [*NATIVE_MEMBERS_Employee] as 'Generate([*NATIVE_CJ_SET], {[Employee].CurrentMember})'\n" + " set [*BASE_MEMBERS_Store Type] as '[Store Type].[Store Type].Members'\n" + " set [*NATIVE_MEMBERS_Store Type] as 'Generate([*NATIVE_CJ_SET], {[Store Type].CurrentMember})'\n" + "select\n" + " [*BASE_MEMBERS_Measures] ON COLUMNS,\n" + " NON EMPTY Generate([*NATIVE_CJ_SET], {([Employee].CurrentMember, [Store Type].CurrentMember)}) ON ROWS\n" + "from\n" + " [Employee Store Analysis A]"; public static final String queryCubeB = "with\n" + " set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Employee], [*BASE_MEMBERS_Store Type])'\n" + " set [*BASE_MEMBERS_Measures] as '{[Measures].[Employee Store Sales], [Measures].[Employee Store Cost]}'\n" + " set [*BASE_MEMBERS_Employee] as '[Employee].[Role].Members'\n" + " set [*NATIVE_MEMBERS_Employee] as 'Generate([*NATIVE_CJ_SET], {[Employee].CurrentMember})'\n" + " set [*BASE_MEMBERS_Store Type] as '[Store Type].[Store Type].Members'\n" + " set [*NATIVE_MEMBERS_Store Type] as 'Generate([*NATIVE_CJ_SET], {[Store Type].CurrentMember})'\n" + "select\n" + " [*BASE_MEMBERS_Measures] ON COLUMNS,\n" + " NON EMPTY Generate([*NATIVE_CJ_SET], {([Employee].CurrentMember, [Store Type].CurrentMember)}) ON ROWS\n" + "from\n" + " [Employee Store Analysis B]"; public static final String queryVirtualCube = "with\n" + " set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Employee], [*BASE_MEMBERS_Store Type])'\n" + " set [*BASE_MEMBERS_Measures] as '{[Measures].[Employee Store Sales], [Measures].[Employee Store Cost]}'\n" + " set [*BASE_MEMBERS_Employee] as '[Employee].[Role].Members'\n" + " set [*NATIVE_MEMBERS_Employee] as 'Generate([*NATIVE_CJ_SET], {[Employee].CurrentMember})'\n" + " set [*BASE_MEMBERS_Store Type] as '[Store Type].[Store Type].Members'\n" + " set [*NATIVE_MEMBERS_Store Type] as 'Generate([*NATIVE_CJ_SET], {[Store Type].CurrentMember})'\n" + "select\n" + " [*BASE_MEMBERS_Measures] ON COLUMNS,\n" + " NON EMPTY Generate([*NATIVE_CJ_SET], {([Employee].CurrentMember, [Store Type].CurrentMember)}) ON ROWS\n" + "from\n" + " [Employee Store Analysis]"; public static final String queryStoreCube = "with set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Store Type], [*BASE_MEMBERS_Store])'\n" + "set [*BASE_MEMBERS_Measures] as '{[Measures].[Store Sqft]}'\n" + "set [*BASE_MEMBERS_Store Type] as '[Store Type].[Store Type].Members'\n" + "set [*BASE_MEMBERS_Store] as '[Store].[Store State].Members'\n" + "select [*BASE_MEMBERS_Measures] ON COLUMNS,\n" + "Non Empty Generate([*NATIVE_CJ_SET], {[Store Type].CurrentMember}) on rows from [Store]"; public static final String queryNECJMemberList = "select {[Measures].[Employee Store Sales]} on columns,\n" + "NonEmptyCrossJoin([Store Type].[Store Type].Members,\n" + "{[Employee].[All Employees].[Middle Management],\n" + " [Employee].[All Employees].[Store Management]})\n" + "on rows from [Employee Store Analysis B]"; public static final String queryNECJMultiLevelMemberList = "select {[Employee Store Sales]} on columns, " + "NonEmptyCrossJoin([Store Type].[Store Type].Members, " + "{[Employee].[Store Management].[Store Manager], " + " [Employee].[Senior Management].[President]}) " + "on rows from [Employee Store Analysis B]"; public static final String querySF1711865 = "select NON EMPTY {[Product].[Product Family].Members} ON COLUMNS from [Sales 2]"; public static final String resultCubeA = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Employee Store Sales]}\n" + "{[Measures].[Employee Store Cost]}\n" + "Axis #2:\n" + "{[Employee].[Middle Management], [Store Type].[Deluxe Supermarket]}\n" + "{[Employee].[Middle Management], [Store Type].[Supermarket]}\n" + "{[Employee].[Senior Management], [Store Type].[Deluxe Supermarket]}\n" + "{[Employee].[Senior Management], [Store Type].[Gourmet Supermarket]}\n" + "{[Employee].[Senior Management], [Store Type].[Mid-Size Grocery]}\n" + "{[Employee].[Senior Management], [Store Type].[Small Grocery]}\n" + "{[Employee].[Senior Management], [Store Type].[Supermarket]}\n" + "{[Employee].[Store Management], [Store Type].[Deluxe Supermarket]}\n" + "{[Employee].[Store Management], [Store Type].[Gourmet Supermarket]}\n" + "{[Employee].[Store Management], [Store Type].[Mid-Size Grocery]}\n" + "{[Employee].[Store Management], [Store Type].[Small Grocery]}\n" + "{[Employee].[Store Management], [Store Type].[Supermarket]}\n" + "Row #0: $200\n" + "Row #0: $87\n" + "Row #1: $161\n" + "Row #1: $68\n" + "Row #2: $1,721\n" + "Row #2: $739\n" + "Row #3: $261\n" + "Row #3: $114\n" + "Row #4: $257\n" + "Row #4: $111\n" + "Row #5: $196\n" + "Row #5: $101\n" + "Row #6: $3,993\n" + "Row #6: $1,858\n" + "Row #7: $45,014\n" + "Row #7: $20,604\n" + "Row #8: $7,231\n" + "Row #8: $3,211\n" + "Row #9: $8,171\n" + "Row #9: $3,635\n" + "Row #10: $4,471\n" + "Row #10: $2,045\n" + "Row #11: $77,236\n" + "Row #11: $34,842\n"; public static final String resultCubeB = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Employee Store Sales]}\n" + "{[Measures].[Employee Store Cost]}\n" + "Axis #2:\n" + "{[Employee].[Store Management], [Store Type].[Deluxe Supermarket]}\n" + "{[Employee].[Store Management], [Store Type].[Gourmet Supermarket]}\n" + "{[Employee].[Store Management], [Store Type].[Mid-Size Grocery]}\n" + "{[Employee].[Store Management], [Store Type].[Small Grocery]}\n" + "{[Employee].[Store Management], [Store Type].[Supermarket]}\n" + "Row #0: $61,860\n" + "Row #0: $28,093\n" + "Row #1: $10,156\n" + "Row #1: $4,482\n" + "Row #2: $10,212\n" + "Row #2: $4,576\n" + "Row #3: $5,932\n" + "Row #3: $2,714\n" + "Row #4: $108,610\n" + "Row #4: $49,178\n"; public static final String resultVirtualCube = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Employee Store Sales]}\n" + "{[Measures].[Employee Store Cost]}\n" + "Axis #2:\n" + "{[Employee].[Middle Management], [Store Type].[Deluxe Supermarket]}\n" + "{[Employee].[Middle Management], [Store Type].[Supermarket]}\n" + "{[Employee].[Senior Management], [Store Type].[Deluxe Supermarket]}\n" + "{[Employee].[Senior Management], [Store Type].[Gourmet Supermarket]}\n" + "{[Employee].[Senior Management], [Store Type].[Mid-Size Grocery]}\n" + "{[Employee].[Senior Management], [Store Type].[Small Grocery]}\n" + "{[Employee].[Senior Management], [Store Type].[Supermarket]}\n" + "{[Employee].[Store Management], [Store Type].[Deluxe Supermarket]}\n" + "{[Employee].[Store Management], [Store Type].[Gourmet Supermarket]}\n" + "{[Employee].[Store Management], [Store Type].[Mid-Size Grocery]}\n" + "{[Employee].[Store Management], [Store Type].[Small Grocery]}\n" + "{[Employee].[Store Management], [Store Type].[Supermarket]}\n" + "Row #0: $200\n" + "Row #0: \n" + "Row #1: $161\n" + "Row #1: \n" + "Row #2: $1,721\n" + "Row #2: \n" + "Row #3: $261\n" + "Row #3: \n" + "Row #4: $257\n" + "Row #4: \n" + "Row #5: $196\n" + "Row #5: \n" + "Row #6: $3,993\n" + "Row #6: \n" + "Row #7: $45,014\n" + "Row #7: $28,093\n" + "Row #8: $7,231\n" + "Row #8: $4,482\n" + "Row #9: $8,171\n" + "Row #9: $4,576\n" + "Row #10: $4,471\n" + "Row #10: $2,714\n" + "Row #11: $77,236\n" + "Row #11: $49,178\n"; // This result is actually incorrect for native evaluation. // Keep the test case here to test the SQL generation. public static final String resultStoreCube = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sqft]}\n" + "Axis #2:\n" + "{[Store Type].[Deluxe Supermarket]}\n" + "{[Store Type].[Gourmet Supermarket]}\n" + "{[Store Type].[Mid-Size Grocery]}\n" + "{[Store Type].[Small Grocery]}\n" + "{[Store Type].[Supermarket]}\n" + "Row #0: 146,045\n" + "Row #1: 47,447\n" + "Row #2: 109,343\n" + "Row #3: 75,281\n" + "Row #4: 193,480\n"; public static final String resultNECJMemberList = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Employee Store Sales]}\n" + "Axis #2:\n" + "{[Store Type].[Deluxe Supermarket], [Employee].[Store Management]}\n" + "{[Store Type].[Gourmet Supermarket], [Employee].[Store Management]}\n" + "{[Store Type].[Mid-Size Grocery], [Employee].[Store Management]}\n" + "{[Store Type].[Small Grocery], [Employee].[Store Management]}\n" + "{[Store Type].[Supermarket], [Employee].[Store Management]}\n" + "Row #0: $61,860\n" + "Row #1: $10,156\n" + "Row #2: $10,212\n" + "Row #3: $5,932\n" + "Row #4: $108,610\n"; public static final String resultNECJMultiLevelMemberList = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Employee Store Sales]}\n" + "Axis #2:\n" + "{[Store Type].[Deluxe Supermarket], [Employee].[Store Management].[Store Manager]}\n" + "{[Store Type].[Gourmet Supermarket], [Employee].[Store Management].[Store Manager]}\n" + "{[Store Type].[Supermarket], [Employee].[Store Management].[Store Manager]}\n" + "Row #0: $1,783\n" + "Row #1: $286\n" + "Row #2: $1,020\n"; public static final String resultSF1711865 = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 7,978\n" + "Row #0: 62,445\n" + "Row #0: 16,414\n"; public SharedDimensionTest() { } public SharedDimensionTest(String name) { super(name); } public void testA() { // Schema has two cubes sharing a dimension. // Query from the first cube. TestContext testContext = getTestContextForSharedDimCubeACubeB(); testContext.assertQueryReturns(queryCubeA, resultCubeA); } public void testB() { // Schema has two cubes sharing a dimension. // Query from the second cube. TestContext testContext = getTestContextForSharedDimCubeACubeB(); testContext.assertQueryReturns(queryCubeB, resultCubeB); } public void testVirtualCube() { // Schema has two cubes sharing a dimension, and a virtual cube built // over these two cubes. // Query from the virtual cube. TestContext testContext = TestContext.instance().create( sharedDimension, cubeA + "\n" + cubeB, virtualCube, null, null, null); testContext.assertQueryReturns(queryVirtualCube, resultVirtualCube); } public void testNECJMemberList() { // Schema has two cubes sharing a dimension. // Query from the second cube. TestContext testContext = getTestContextForSharedDimCubeACubeB(); testContext.assertQueryReturns( queryNECJMemberList, resultNECJMemberList); } public void testNECJMultiLevelMemberList() { // Schema has two cubes sharing a dimension. // Query from the first cube. // This is a case where not using alias not only affects performance, // but also produces incorrect result. TestContext testContext = getTestContextForSharedDimCubeACubeB(); testContext.assertQueryReturns( queryNECJMultiLevelMemberList, resultNECJMultiLevelMemberList); } public void testSF1711865() { // Test for sourceforge.net bug 1711865 // Use the default FoodMart schema getTestContext().assertQueryReturns(querySF1711865, resultSF1711865); } public void testStoreCube() { // Use the default FoodMart schema getTestContext().assertQueryReturns(queryStoreCube, resultStoreCube); } private TestContext getTestContextForSharedDimCubeACubeB() { return TestContext.instance().create( sharedDimension, cubeA + "\n" + cubeB, null, null, null, null); } } // End SharedDimensionTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/agg/0000755000175000017500000000000011735330606021323 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/rolap/agg/SegmentLoaderTest.java0000644000175000017500000011310411735330606025557 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.olap.*; import mondrian.rolap.*; import mondrian.server.*; import mondrian.server.Statement; import mondrian.spi.Dialect; import mondrian.test.SqlPattern; import mondrian.util.DelegatingInvocationHandler; import java.lang.reflect.Proxy; import java.sql.*; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; /** *

Test for SegmentLoader

* * @author Thiyagu * @since 06-Jun-2007 */ public class SegmentLoaderTest extends BatchTestCase { private Execution execution; private Locus locus; private SegmentCacheManager cacheMgr; protected void setUp() throws Exception { super.setUp(); final Statement statement = ((RolapConnection) getConnection()).getInternalStatement(); execution = new Execution(statement, 1000); locus = new Locus(execution, null, null); cacheMgr = execution.getMondrianStatement().getMondrianConnection() .getServer().getAggregationManager().cacheMgr; Locus.push(locus); } @Override protected void tearDown() throws Exception { Locus.pop(locus); super.tearDown(); } public void testRollup() throws Exception { propSaver.set( MondrianProperties.instance().DisableCaching, true); propSaver.set( MondrianProperties.instance().SegmentCache, MockSegmentCache.class.getName()); final String queryOracle = "select \"store\".\"store_country\" as \"c0\", \"time_by_day\".\"the_year\" as \"c1\", sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\" from \"store\" \"store\", \"sales_fact_1997\" \"sales_fact_1997\", \"time_by_day\" \"time_by_day\" where \"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" and \"store\".\"store_country\" = 'USA' and \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"time_by_day\".\"the_year\" = 1997 group by \"store\".\"store_country\", \"time_by_day\".\"the_year\""; executeQuery( "select {[Store].[Store Country].Members} on rows, {[Measures].[Unit Sales]} on columns from [Sales]"); getTestContext().flushSchemaCache(); assertQuerySqlOrNot( getTestContext(), "select {[Store].[Store Country].[USA]} on rows, {[Measures].[Unit Sales]} on columns from [Sales]", new SqlPattern[] { new SqlPattern( Dialect.DatabaseProduct.ORACLE, queryOracle, queryOracle.length()) }, true, true, true); } public void testLoadWithMockResultsForLoadingSummaryAndDetailedSegments() throws ExecutionException, InterruptedException { GroupingSet groupableSetsInfo = getGroupingSetRollupOnGender(); GroupingSet groupingSetsInfo = getDefaultGroupingSet(); ArrayList groupingSets = new ArrayList(); groupingSets.add(groupingSetsInfo); groupingSets.add(groupableSetsInfo); SegmentLoader loader = new SegmentLoader(cacheMgr) { SqlStatement createExecuteSql( int cellRequestCount, final GroupingSetsList groupingSetsList, List compoundPredicateList) { return new MockSqlStatement( cellRequestCount, groupingSetsList, getData(true)); } }; final List>> segmentFutures = new ArrayList>>(); loader.load(0, groupingSets, null, segmentFutures); SegmentAxis[] axes = groupingSetsInfo.getAxes(); verifyYearAxis(axes[0]); verifyProductFamilyAxis(axes[1]); verifyProductDepartmentAxis(axes[2]); verifyGenderAxis(axes[3]); verifyUnitSalesDetailed( getFor( segmentFutures, groupingSets.get(0).getSegments().get(0))); axes = groupingSets.get(0).getAxes(); verifyYearAxis(axes[0]); verifyProductFamilyAxis(axes[1]); verifyProductDepartmentAxis(axes[2]); verifyUnitSalesAggregate( getFor( segmentFutures, groupingSets.get(1).getSegments().get(0))); } private ResultSet toResultSet(final List list) { final MyDelegatingInvocationHandler handler = new MyDelegatingInvocationHandler(list); Object o = Proxy.newProxyInstance( null, new Class[] {ResultSet.class, ResultSetMetaData.class}, handler); handler.resultSetMetaData = (ResultSetMetaData) o; return (ResultSet) o; } private SegmentLoader.RowList toRowList2(List list) { final SegmentLoader.RowList rowList = new SegmentLoader.RowList( Collections.nCopies( list.get(0).length, SqlStatement.Type.OBJECT)); for (Object[] objects : list) { rowList.createRow(); for (int i = 0; i < objects.length; i++) { Object object = objects[i]; rowList.setObject(i, object); } } return rowList; } /** * Tests load with mock results for loading summary and detailed * segments with null in rollup column. */ public void testLoadWithWithNullInRollupColumn() throws ExecutionException, InterruptedException { GroupingSet groupableSetsInfo = getGroupingSetRollupOnGender(); GroupingSet groupingSetsInfo = getDefaultGroupingSet(); ArrayList groupingSets = new ArrayList(); groupingSets.add(groupingSetsInfo); groupingSets.add(groupableSetsInfo); SegmentLoader loader = new SegmentLoader(cacheMgr) { SqlStatement createExecuteSql( int cellRequestCount, GroupingSetsList groupingSetsList, List compoundPredicateList) { return new MockSqlStatement( cellRequestCount, groupingSetsList, getDataWithNullInRollupColumn(true)); } }; final List>> segmentFutures = new ArrayList>>(); loader.load(0, groupingSets, null, segmentFutures); SegmentWithData detailedSegment = getFor( segmentFutures, groupingSets.get(0).getSegments().get(0)); assertEquals(3, detailedSegment.getCellCount()); } public void testLoadWithMockResultsForLoadingSummaryAndDetailedSegmentsUsingSparse() throws ExecutionException, InterruptedException { GroupingSet groupableSetsInfo = getGroupingSetRollupOnGender(); GroupingSet groupingSetsInfo = getDefaultGroupingSet(); ArrayList groupingSets = new ArrayList(); groupingSets.add(groupingSetsInfo); groupingSets.add(groupableSetsInfo); SegmentLoader loader = new SegmentLoader(cacheMgr) { SqlStatement createExecuteSql( int cellRequestCount, GroupingSetsList groupingSetsList, List compoundPredicateList) { return new MockSqlStatement( cellRequestCount, groupingSetsList, getData(true)); } boolean useSparse(boolean sparse, int n, RowList rows) { return true; } }; final List>> segmentFutures = new ArrayList>>(); loader.load(0, groupingSets, null, segmentFutures); SegmentAxis[] axes = groupingSetsInfo.getAxes(); verifyYearAxis(axes[0]); verifyProductFamilyAxis(axes[1]); verifyProductDepartmentAxis(axes[2]); verifyGenderAxis(axes[3]); verifyUnitSalesDetailedForSparse( getFor( segmentFutures, groupingSets.get(0).getSegments().get(0))); axes = groupingSets.get(0).getAxes(); verifyYearAxis(axes[0]); verifyProductFamilyAxis(axes[1]); verifyProductDepartmentAxis(axes[2]); verifyUnitSalesAggregateForSparse( getFor( segmentFutures, groupingSets.get(1).getSegments().get(0))); } private SegmentWithData getFor( List>> mapFutures, Segment segment) throws ExecutionException, InterruptedException { for (Future> mapFuture : mapFutures) { final Map map = mapFuture.get(); if (map.containsKey(segment)) { return map.get(segment); } } return null; } private List trim(final int length, final List data) { return new AbstractList() { @Override public Object[] get(int index) { return Util.copyOf(data.get(index), length); } @Override public int size() { return data.size(); } }; } public void testLoadWithMockResultsForLoadingOnlyDetailedSegments() throws ExecutionException, InterruptedException { GroupingSet groupingSetsInfo = getDefaultGroupingSet(); ArrayList groupingSets = new ArrayList(); groupingSets.add(groupingSetsInfo); SegmentLoader loader = new SegmentLoader(cacheMgr) { SqlStatement createExecuteSql( int cellRequestCount, GroupingSetsList groupingSetsList, List compoundPredicateList) { return new MockSqlStatement( cellRequestCount, groupingSetsList, trim(5, getData(false))); } }; final List>> segmentFutures = new ArrayList>>(); loader.load(0, groupingSets, null, segmentFutures); SegmentAxis[] axes = groupingSetsInfo.getAxes(); verifyYearAxis(axes[0]); verifyProductFamilyAxis(axes[1]); verifyProductDepartmentAxis(axes[2]); verifyGenderAxis(axes[3]); verifyUnitSalesDetailed( getFor( segmentFutures, groupingSetsInfo.getSegments().get(0))); } public void testProcessDataForGettingGroupingSetsBitKeysAndLoadingAxisValueSet() throws SQLException { GroupingSet groupableSetsInfo = getGroupingSetRollupOnGender(); GroupingSet groupingSetsInfo = getDefaultGroupingSet(); List groupingSets = new ArrayList(); groupingSets.add(groupingSetsInfo); groupingSets.add(groupableSetsInfo); final SqlStatement stmt = new MockSqlStatement( 0, new GroupingSetsList(groupingSets), getData(true)); SegmentLoader loader = new SegmentLoader(cacheMgr) { @Override SqlStatement createExecuteSql( int cellRequestCount, GroupingSetsList groupingSetsList, List compoundPredicateList) { return stmt; } }; int axisCount = 4; SortedSet[] axisValueSet = loader.getDistinctValueWorkspace(axisCount); boolean[] axisContainsNull = new boolean[axisCount]; SegmentLoader.RowList list = loader.processData( stmt, axisContainsNull, axisValueSet, new GroupingSetsList(groupingSets)); int totalNoOfRows = 12; int lengthOfRowWithBitKey = 6; assertEquals(totalNoOfRows, list.size()); assertEquals(lengthOfRowWithBitKey, list.getTypes().size()); list.first(); list.next(); assertEquals(BitKey.Factory.makeBitKey(0), list.getObject(5)); BitKey bitKeyForSummaryRow = BitKey.Factory.makeBitKey(0); bitKeyForSummaryRow.set(0); list.next(); list.next(); assertEquals(bitKeyForSummaryRow, list.getObject(5)); SortedSet yearAxis = axisValueSet[0]; assertEquals(1, yearAxis.size()); SortedSet productFamilyAxis = axisValueSet[1]; assertEquals(3, productFamilyAxis.size()); SortedSet productDepartmentAxis = axisValueSet[2]; assertEquals(4, productDepartmentAxis.size()); SortedSet genderAxis = axisValueSet[3]; assertEquals(2, genderAxis.size()); assertFalse(axisContainsNull[0]); assertFalse(axisContainsNull[1]); assertFalse(axisContainsNull[2]); assertFalse(axisContainsNull[3]); } private GroupingSet getGroupingSetRollupOnGender() { return getGroupingSet( execution, new String[]{tableTime, tableProductClass, tableProductClass}, new String[]{ fieldYear, fieldProductFamily, fieldProductDepartment}, new String[][]{ fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment}, cubeNameSales, measureUnitSales); } public void testProcessDataForSettingNullAxis() throws SQLException { GroupingSet groupingSetsInfo = getDefaultGroupingSet(); final SqlStatement stmt = new MockSqlStatement( 0, new GroupingSetsList( Collections.singletonList(groupingSetsInfo)), trim(5, getDataWithNullInAxisColumn(false))); SegmentLoader loader = new SegmentLoader(cacheMgr) { @Override SqlStatement createExecuteSql( int cellRequestCount, GroupingSetsList groupingSetsList, List compoundPredicateList) { return stmt; } }; int axisCount = 4; SortedSet[] axisValueSet = loader.getDistinctValueWorkspace(axisCount); boolean[] axisContainsNull = new boolean[axisCount]; List groupingSets = new ArrayList(); groupingSets.add(groupingSetsInfo); loader.processData( stmt, axisContainsNull, axisValueSet, new GroupingSetsList(groupingSets)); assertFalse(axisContainsNull[0]); assertFalse(axisContainsNull[1]); assertTrue(axisContainsNull[2]); assertFalse(axisContainsNull[3]); } public void testProcessDataForNonGroupingSetsScenario() throws SQLException { GroupingSet groupingSetsInfo = getDefaultGroupingSet(); final List data = new ArrayList(); data.add(new Object[]{"1997", "Food", "Deli", "F", "5990"}); data.add(new Object[]{"1997", "Food", "Deli", "M", "6047"}); data.add(new Object[]{"1997", "Food", "Canned_Products", "F", "867"}); final SqlStatement stmt = new MockSqlStatement( 0, new GroupingSetsList( Collections.singletonList(groupingSetsInfo)), data); SegmentLoader loader = new SegmentLoader(cacheMgr) { @Override SqlStatement createExecuteSql( int cellRequestCount, GroupingSetsList groupingSetsList, List compoundPredicateList) { return stmt; } }; List groupingSets = new ArrayList(); groupingSets.add(groupingSetsInfo); SortedSet[] axisValueSet = loader.getDistinctValueWorkspace(4); SegmentLoader.RowList list = loader.processData( stmt, new boolean[4], axisValueSet, new GroupingSetsList(groupingSets)); int totalNoOfRows = 3; assertEquals(totalNoOfRows, list.size()); int lengthOfRowWithoutBitKey = 5; assertEquals(lengthOfRowWithoutBitKey, list.getTypes().size()); SortedSet yearAxis = axisValueSet[0]; assertEquals(1, yearAxis.size()); SortedSet productFamilyAxis = axisValueSet[1]; assertEquals(1, productFamilyAxis.size()); SortedSet productDepartmentAxis = axisValueSet[2]; assertEquals(2, productDepartmentAxis.size()); SortedSet genderAxis = axisValueSet[3]; assertEquals(2, genderAxis.size()); } private void verifyUnitSalesDetailed(SegmentWithData segment) { Double[] unitSalesValues = { null, null, null, null, 1987.0, 2199.0, null, null, 867.0, 945.0, null, null, null, null, 5990.0, 6047.0, null, null, 368.0, 473.0, null, null, null, null }; int index = 0; for (Map.Entry x : segment.getData()) { assertEquals(unitSalesValues[index++], x.getValue()); } } private void verifyUnitSalesDetailedForSparse(SegmentWithData segment) { List cellKeys = new ArrayList(); cellKeys.add(CellKey.Generator.newCellKey(new int[]{0, 2, 1, 0})); cellKeys.add(CellKey.Generator.newCellKey(new int[]{0, 0, 2, 0})); cellKeys.add(CellKey.Generator.newCellKey(new int[]{0, 1, 0, 0})); cellKeys.add(CellKey.Generator.newCellKey(new int[]{0, 2, 1, 1})); cellKeys.add(CellKey.Generator.newCellKey(new int[]{0, 1, 0, 1})); cellKeys.add(CellKey.Generator.newCellKey(new int[]{0, 1, 3, 0})); cellKeys.add(CellKey.Generator.newCellKey(new int[]{0, 0, 2, 1})); cellKeys.add(CellKey.Generator.newCellKey(new int[]{0, 1, 3, 1})); Double[] unitSalesValues = { 368.0, 1987.0, 867.0, 473.0, 945.0, 5990.0, 2199.0, 6047.0 }; int index = 0; for (Map.Entry x : segment.getData()) { assertEquals(cellKeys.get(index), x.getKey()); assertEquals(unitSalesValues[index], x.getValue()); index++; } } private void verifyUnitSalesAggregateForSparse(SegmentWithData segment) { List cellKeys = new ArrayList(); cellKeys.add(CellKey.Generator.newCellKey(new int[]{0, 2, 1})); cellKeys.add(CellKey.Generator.newCellKey(new int[]{0, 1, 0})); cellKeys.add(CellKey.Generator.newCellKey(new int[]{0, 1, 3})); cellKeys.add(CellKey.Generator.newCellKey(new int[]{0, 0, 2})); Double[] unitSalesValues = {841.0, 1812.0, 12037.0, 4186.0,}; int index = 0; for (Map.Entry x : segment.getData()) { assertEquals(cellKeys.get(index), x.getKey()); assertEquals(unitSalesValues[index], x.getValue()); index++; } } private void verifyUnitSalesAggregate(SegmentWithData segment) { Double[] unitSalesValues = { null, null, 4186.0, null, 1812.0, null, null, 12037.0, null, 841.0, null, null }; int index = 0; for (Map.Entry x : segment.getData()) { assertEquals(unitSalesValues[index++], x.getValue()); } } public void testGetGroupingBitKey() throws SQLException { Object[] data = { "1997", "Food", "Deli", "M", "6047", 0, 0, 0, 0 }; ResultSet rowList = toResultSet(Collections.singletonList(data)); assertTrue(rowList.next()); assertEquals( BitKey.Factory.makeBitKey(4), new SegmentLoader(cacheMgr).getRollupBitKey(4, rowList, 5)); data = new Object[]{ "1997", "Food", "Deli", null, "12037", 0, 0, 0, 1 }; rowList = toResultSet(Collections.singletonList(data)); BitKey key = BitKey.Factory.makeBitKey(4); key.set(3); assertEquals( key, new SegmentLoader(cacheMgr).getRollupBitKey(4, rowList, 5)); data = new Object[] { "1997", null, "Deli", null, "12037", 0, 1, 0, 1 }; rowList = toResultSet(Collections.singletonList(data)); key = BitKey.Factory.makeBitKey(4); key.set(1); key.set(3); assertEquals( key, new SegmentLoader(cacheMgr).getRollupBitKey(4, rowList, 5)); } public void testGroupingSetsUtilForMissingGroupingBitKeys() { List groupingSets = new ArrayList(); groupingSets.add(getDefaultGroupingSet()); groupingSets.add(getGroupingSetRollupOnGender()); GroupingSetsList detail = new GroupingSetsList(groupingSets); List bitKeysList = detail.getRollupColumnsBitKeyList(); int columnsCount = 4; assertEquals( BitKey.Factory.makeBitKey(columnsCount), bitKeysList.get(0)); BitKey key = BitKey.Factory.makeBitKey(columnsCount); key.set(0); assertEquals(key, bitKeysList.get(1)); groupingSets = new ArrayList(); groupingSets.add(getDefaultGroupingSet()); groupingSets.add(getGroupingSetRollupOnGenderAndProductFamily()); bitKeysList = new GroupingSetsList(groupingSets) .getRollupColumnsBitKeyList(); assertEquals( BitKey.Factory.makeBitKey(columnsCount), bitKeysList.get(0)); key = BitKey.Factory.makeBitKey(columnsCount); key.set(0); key.set(1); assertEquals(key, bitKeysList.get(1)); assertTrue( new GroupingSetsList( new ArrayList()) .getRollupColumnsBitKeyList().isEmpty()); } private GroupingSet getGroupingSetRollupOnGenderAndProductFamily() { return getGroupingSet( execution, new String[]{tableTime, tableProductClass}, new String[]{fieldYear, fieldProductDepartment}, new String[][]{fieldValuesYear, fieldValueProductDepartment}, cubeNameSales, measureUnitSales); } public void testGroupingSetsUtilSetsDetailForRollupColumns() { RolapStar.Measure measure = getMeasure(cubeNameSales, measureUnitSales); RolapStar star = measure.getStar(); RolapStar.Column year = star.lookupColumn(tableTime, fieldYear); RolapStar.Column productFamily = star.lookupColumn(tableProductClass, fieldProductFamily); RolapStar.Column productDepartment = star.lookupColumn(tableProductClass, fieldProductDepartment); RolapStar.Column gender = star.lookupColumn(tableCustomer, fieldGender); List groupingSets = new ArrayList(); groupingSets.add(getDefaultGroupingSet()); groupingSets.add(getGroupingSetRollupOnProductDepartment()); groupingSets.add(getGroupingSetRollupOnGenderAndProductDepartment()); GroupingSetsList detail = new GroupingSetsList(groupingSets); List rollupColumnsList = detail.getRollupColumns(); assertEquals(2, rollupColumnsList.size()); assertEquals(gender, rollupColumnsList.get(0)); assertEquals(productDepartment, rollupColumnsList.get(1)); groupingSets .add(getGroupingSetRollupOnGenderAndProductDepartmentAndYear()); detail = new GroupingSetsList(groupingSets); rollupColumnsList = detail.getRollupColumns(); assertEquals(3, rollupColumnsList.size()); assertEquals(gender, rollupColumnsList.get(0)); assertEquals(productDepartment, rollupColumnsList.get(1)); assertEquals(year, rollupColumnsList.get(2)); groupingSets .add(getGroupingSetRollupOnProductFamilyAndProductDepartment()); detail = new GroupingSetsList(groupingSets); rollupColumnsList = detail.getRollupColumns(); assertEquals(4, rollupColumnsList.size()); assertEquals(gender, rollupColumnsList.get(0)); assertEquals(productDepartment, rollupColumnsList.get(1)); assertEquals(productFamily, rollupColumnsList.get(2)); assertEquals(year, rollupColumnsList.get(3)); assertTrue( new GroupingSetsList(new ArrayList()) .getRollupColumns().isEmpty()); } private GroupingSet getGroupingSetRollupOnGenderAndProductDepartment() { return getGroupingSet( execution, new String[]{tableProductClass, tableTime}, new String[]{fieldProductFamily, fieldYear}, new String[][]{fieldValuesProductFamily, fieldValuesYear}, cubeNameSales, measureUnitSales); } private GroupingSet getGroupingSetRollupOnProductFamilyAndProductDepartment() { return getGroupingSet( execution, new String[]{tableCustomer, tableTime}, new String[]{fieldGender, fieldYear}, new String[][]{fieldValuesGender, fieldValuesYear}, cubeNameSales, measureUnitSales); } private GroupingSet getGroupingSetRollupOnGenderAndProductDepartmentAndYear() { return getGroupingSet( execution, new String[]{tableProductClass}, new String[]{fieldProductFamily}, new String[][]{fieldValuesProductFamily}, cubeNameSales, measureUnitSales); } private GroupingSet getGroupingSetRollupOnProductDepartment() { return getGroupingSet( execution, new String[]{tableCustomer, tableProductClass, tableTime}, new String[]{fieldGender, fieldProductFamily, fieldYear}, new String[][]{ fieldValuesGender, fieldValuesProductFamily, fieldValuesYear}, cubeNameSales, measureUnitSales); } public void testGroupingSetsUtilSetsForDetailForRollupColumns() { RolapStar.Measure measure = getMeasure(cubeNameSales, measureUnitSales); RolapStar star = measure.getStar(); RolapStar.Column year = star.lookupColumn(tableTime, fieldYear); RolapStar.Column productFamily = star.lookupColumn(tableProductClass, fieldProductFamily); RolapStar.Column productDepartment = star.lookupColumn(tableProductClass, fieldProductDepartment); RolapStar.Column gender = star.lookupColumn(tableCustomer, fieldGender); List groupingSets = new ArrayList(); groupingSets.add(getDefaultGroupingSet()); groupingSets.add(getGroupingSetRollupOnProductDepartment()); groupingSets.add(getGroupingSetRollupOnGenderAndProductDepartment()); GroupingSetsList detail = new GroupingSetsList(groupingSets); List rollupColumnsList = detail.getRollupColumns(); assertEquals(2, rollupColumnsList.size()); assertEquals(gender, rollupColumnsList.get(0)); assertEquals(productDepartment, rollupColumnsList.get(1)); groupingSets .add(getGroupingSetRollupOnGenderAndProductDepartmentAndYear()); detail = new GroupingSetsList(groupingSets); rollupColumnsList = detail.getRollupColumns(); assertEquals(3, rollupColumnsList.size()); assertEquals(gender, rollupColumnsList.get(0)); assertEquals(productDepartment, rollupColumnsList.get(1)); assertEquals(year, rollupColumnsList.get(2)); groupingSets .add(getGroupingSetRollupOnProductFamilyAndProductDepartment()); detail = new GroupingSetsList(groupingSets); rollupColumnsList = detail.getRollupColumns(); assertEquals(4, rollupColumnsList.size()); assertEquals(gender, rollupColumnsList.get(0)); assertEquals(productDepartment, rollupColumnsList.get(1)); assertEquals(productFamily, rollupColumnsList.get(2)); assertEquals(year, rollupColumnsList.get(3)); assertTrue( new GroupingSetsList(new ArrayList()) .getRollupColumns().isEmpty()); } public void testGroupingSetsUtilSetsForGroupingFunctionIndex() { List groupingSets = new ArrayList(); groupingSets.add(getDefaultGroupingSet()); groupingSets.add(getGroupingSetRollupOnProductDepartment()); groupingSets.add(getGroupingSetRollupOnGenderAndProductDepartment()); GroupingSetsList detail = new GroupingSetsList(groupingSets); assertEquals(0, detail.findGroupingFunctionIndex(3)); assertEquals(1, detail.findGroupingFunctionIndex(2)); groupingSets .add(getGroupingSetRollupOnGenderAndProductDepartmentAndYear()); detail = new GroupingSetsList(groupingSets); assertEquals(0, detail.findGroupingFunctionIndex(3)); assertEquals(1, detail.findGroupingFunctionIndex(2)); assertEquals(2, detail.findGroupingFunctionIndex(0)); groupingSets .add(getGroupingSetRollupOnProductFamilyAndProductDepartment()); detail = new GroupingSetsList(groupingSets); assertEquals(0, detail.findGroupingFunctionIndex(3)); assertEquals(1, detail.findGroupingFunctionIndex(2)); assertEquals(2, detail.findGroupingFunctionIndex(1)); assertEquals(3, detail.findGroupingFunctionIndex(0)); } public void testGetGroupingColumnsList() { GroupingSet groupingSetsInfo = getDefaultGroupingSet(); GroupingSet groupableSetsInfo = getGroupingSetRollupOnGender(); RolapStar.Column[] detailedColumns = groupingSetsInfo.getSegments().get(0).getColumns(); RolapStar.Column[] summaryColumns = groupableSetsInfo.getSegments().get(0).getColumns(); List groupingSets = new ArrayList(); groupingSets.add(groupingSetsInfo); groupingSets.add(groupableSetsInfo); List groupingColumns = new GroupingSetsList(groupingSets).getGroupingSetsColumns(); assertEquals(2, groupingColumns.size()); assertEquals(detailedColumns, groupingColumns.get(0)); assertEquals(summaryColumns, groupingColumns.get(1)); groupingColumns = new GroupingSetsList( new ArrayList()).getGroupingSetsColumns(); assertEquals(0, groupingColumns.size()); } private GroupingSet getDefaultGroupingSet() { return getGroupingSet( execution, new String[]{tableCustomer, tableProductClass, tableProductClass, tableTime}, new String[]{fieldGender, fieldProductDepartment, fieldProductFamily, fieldYear}, new String[][]{fieldValuesGender, fieldValueProductDepartment, fieldValuesProductFamily, fieldValuesYear}, cubeNameSales, measureUnitSales); } private void verifyYearAxis(SegmentAxis axis) { Comparable[] keys = axis.getKeys(); assertEquals(1, keys.length); assertEquals("1997", keys[0].toString()); } private void verifyProductFamilyAxis(SegmentAxis axis) { Comparable[] keys = axis.getKeys(); assertEquals(3, keys.length); assertEquals("Drink", keys[0].toString()); assertEquals("Food", keys[1].toString()); assertEquals("Non-Consumable", keys[2].toString()); } private void verifyProductDepartmentAxis(SegmentAxis axis) { Comparable[] keys = axis.getKeys(); assertEquals(4, keys.length); assertEquals("Canned_Products", keys[0].toString()); } private void verifyGenderAxis(SegmentAxis axis) { Comparable[] keys = axis.getKeys(); assertEquals(2, keys.length); assertEquals("F", keys[0].toString()); assertEquals("M", keys[1].toString()); } private List getData(boolean incSummaryData) { List data = new ArrayList(); data.add(new Object[]{"1997", "Food", "Deli", "F", "5990", 0}); data.add(new Object[]{"1997", "Food", "Deli", "M", "6047", 0}); if (incSummaryData) { data.add(new Object[]{"1997", "Food", "Deli", null, "12037", 1}); } data.add( new Object[]{"1997", "Food", "Canned_Products", "F", "867", 0}); data.add( new Object[]{"1997", "Food", "Canned_Products", "M", "945", 0}); if (incSummaryData) { data.add( new Object[]{ "1997", "Food", "Canned_Products", null, "1812", 1}); } data.add(new Object[]{"1997", "Drink", "Dairy", "F", "1987", 0}); data.add(new Object[]{"1997", "Drink", "Dairy", "M", "2199", 0}); if (incSummaryData) { data.add(new Object[]{"1997", "Drink", "Dairy", null, "4186", 1}); } data.add( new Object[]{ "1997", "Non-Consumable", "Carousel", "F", "368", 0}); data.add( new Object[]{ "1997", "Non-Consumable", "Carousel", "M", "473", 0}); if (incSummaryData) { data.add( new Object[]{ "1997", "Non-Consumable", "Carousel", null, "841", 1}); } return data; } private List getDataWithNullInRollupColumn( boolean incSummaryData) { List data = new ArrayList(); data.add(new Object[]{"1997", "Food", "Deli", "F", "5990", 0}); data.add(new Object[]{"1997", "Food", "Deli", "M", "6047", 0}); data.add(new Object[]{"1997", "Food", "Deli", null, "867", 0}); if (incSummaryData) { data.add(new Object[]{"1997", "Food", "Deli", null, "12037", 1}); } return data; } private List getDataWithNullInAxisColumn( boolean incSummaryData) { List data = new ArrayList(); data.add(new Object[]{"1997", "Food", "Deli", "F", "5990", 0}); data.add(new Object[]{"1997", "Food", "Deli", "M", "6047", 0}); if (incSummaryData) { data.add(new Object[]{"1997", "Food", "Deli", null, "12037", 1}); } data.add( new Object[]{"1997", "Food", null, "F", "867", 0}); return data; } public static class MyDelegatingInvocationHandler extends DelegatingInvocationHandler { int row; public boolean wasNull; ResultSetMetaData resultSetMetaData; private final List list; public MyDelegatingInvocationHandler(List list) { this.list = list; row = -1; } protected Object getTarget() { return null; } public ResultSetMetaData getMetaData() { return resultSetMetaData; } // implement ResultSetMetaData public int getColumnCount() { return list.get(0).length; } // implement ResultSetMetaData public int getColumnType(int column) { return Types.VARCHAR; } public boolean next() { if (row < list.size() - 1) { ++row; return true; } return false; } public Object getObject(int column) { return list.get(row)[column - 1]; } public int getInt(int column) { final Object o = list.get(row)[column - 1]; if (o == null) { wasNull = true; return 0; } else { wasNull = false; return ((Number) o).intValue(); } } public double getDouble(int column) { final Object o = list.get(row)[column - 1]; if (o == null) { wasNull = true; return 0D; } else { wasNull = false; return ((Number) o).doubleValue(); } } public boolean wasNull() { return wasNull; } } private class MockSqlStatement extends SqlStatement { private final List data; public MockSqlStatement( int cellRequestCount, GroupingSetsList groupingSetsList, List data) { super( groupingSetsList.getStar().getDataSource(), "", null, cellRequestCount, 0, SegmentLoaderTest.this.locus, 0, 0); this.data = data; } @Override public List guessTypes() throws SQLException { return Collections.nCopies( getResultSet().getMetaData().getColumnCount(), Type.OBJECT); } @Override public ResultSet getResultSet() { return toResultSet(data); } } } // End SegmentLoaderTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/agg/SegmentCacheTest.java0000644000175000017500000001321511735330606025356 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.olap.CacheControl; import mondrian.olap.Cube; import mondrian.olap.MondrianProperties; import mondrian.olap.MondrianServer; import mondrian.spi.SegmentCache; import mondrian.spi.SegmentHeader; import mondrian.test.BasicQueryTest; import java.util.ArrayList; import java.util.List; /** * Test suite that runs the {@link BasicQueryTest} but with the * {@link MockSegmentCache} active. * * @author LBoudreau */ public class SegmentCacheTest extends BasicQueryTest { private SegmentCache mockCache; private SegmentCacheWorker testWorker; @Override protected void setUp() throws Exception { super.setUp(); propSaver.set( MondrianProperties.instance().DisableCaching, true); this.mockCache = new MockSegmentCache(); this.testWorker = new SegmentCacheWorker(mockCache, null); MondrianServer.forConnection(getTestContext().getConnection()) .getAggregationManager().cacheMgr.segmentCacheWorkers .add(testWorker); getTestContext().getConnection().getCacheControl(null) .flushSchemaCache(); } @Override protected void tearDown() throws Exception { propSaver.reset(); MondrianServer.forConnection(getTestContext().getConnection()) .getAggregationManager().cacheMgr.segmentCacheWorkers .remove(testWorker); getTestContext().getConnection().getCacheControl(null) .flushSchemaCache(); this.mockCache = null; this.testWorker = null; super.tearDown(); } public void testCompoundPredicatesCollision() { String query = "SELECT [Gender].[All Gender] ON 0, [MEASURES].[CUSTOMER COUNT] ON 1 FROM SALES"; String query2 = "WITH MEMBER GENDER.X AS 'AGGREGATE({[GENDER].[GENDER].members} * " + "{[STORE].[ALL STORES].[USA].[CA]})', solve_order=100 " + "SELECT GENDER.X ON 0, [MEASURES].[CUSTOMER COUNT] ON 1 FROM SALES"; String result = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[All Gender]}\n" + "Axis #2:\n" + "{[Measures].[Customer Count]}\n" + "Row #0: 5,581\n"; String result2 = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[X]}\n" + "Axis #2:\n" + "{[Measures].[Customer Count]}\n" + "Row #0: 2,716\n"; assertQueryReturns(query, result); assertQueryReturns(query2, result2); } public void testSegmentCacheEvents() throws Exception { // Flush the cache before we start. Wait a second for the cache // flush to propagate. final CacheControl cc = getTestContext().getConnection().getCacheControl(null); Cube salesCube = getCube("Sales"); cc.flush(cc.createMeasuresRegion(salesCube)); Thread.sleep(1000); final List createdHeaders = new ArrayList(); final List deletedHeaders = new ArrayList(); final SegmentCache.SegmentCacheListener listener = new SegmentCache.SegmentCacheListener() { public void handle(SegmentCacheEvent e) { switch (e.getEventType()) { case ENTRY_CREATED: createdHeaders.add(e.getSource()); break; case ENTRY_DELETED: deletedHeaders.add(e.getSource()); break; default: throw new UnsupportedOperationException(); } } }; try { // Register our custom listener. mockCache.addListener(listener); // Now execute a query and check the events executeQuery( "select {[Measures].[Unit Sales]} on columns from [Sales]"); // Wait for propagation. Thread.sleep(2000); assertEquals(1, createdHeaders.size()); assertEquals(0, deletedHeaders.size()); assertEquals("Sales", createdHeaders.get(0).cubeName); assertEquals("FoodMart", createdHeaders.get(0).schemaName); assertEquals("Unit Sales", createdHeaders.get(0).measureName); createdHeaders.clear(); deletedHeaders.clear(); // Now flush the segment and check the events. cc.flush(cc.createMeasuresRegion(salesCube)); // Wait for propagation. Thread.sleep(2000); assertEquals(0, createdHeaders.size()); assertEquals(1, deletedHeaders.size()); assertEquals("Sales", deletedHeaders.get(0).cubeName); assertEquals("FoodMart", deletedHeaders.get(0).schemaName); assertEquals("Unit Sales", deletedHeaders.get(0).measureName); } finally { mockCache.removeListener(listener); } } private Cube getCube(String cubeName) { for (Cube cube : getConnection().getSchemaReader().withLocus().getCubes()) { if (cube.getName().equals(cubeName)) { return cube; } } return null; } } // End SegmentCacheTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/agg/MockSegmentCache.java0000644000175000017500000001365411735330606025337 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.olap.Util; import mondrian.spi.*; import java.io.*; import java.util.*; import java.util.concurrent.*; /** * Mock implementation of {@link SegmentCache} that is used for automated * testing. * *

It tries to marshall / unmarshall all {@link SegmentHeader} and * {@link SegmentBody} objects that are sent to it. * * @author LBoudreau */ public class MockSegmentCache implements SegmentCache { private static final Map cache = new ConcurrentHashMap(); private final List listeners = new CopyOnWriteArrayList(); private Random rnd; private static final int maxElements = 100; public boolean contains(SegmentHeader header) { return cache.containsKey(header); } public SegmentBody get(SegmentHeader header) { return cache.get(header); } public boolean put( final SegmentHeader header, final SegmentBody body) { // Try to serialize back and forth. if the tests fail because of this, // then the objects could not be serialized properly. // First try with the header try { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(out); oos.writeObject(header); oos.close(); // deserialize byte[] pickled = out.toByteArray(); InputStream in = new ByteArrayInputStream(pickled); ObjectInputStream ois = new ObjectInputStream(in); SegmentHeader o = (SegmentHeader) ois.readObject(); Util.discard(o); } catch (Exception e) { throw new RuntimeException(e); } // Now try it with the body. try { ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(out); oos.writeObject(body); oos.close(); // deserialize byte[] pickled = out.toByteArray(); InputStream in = new ByteArrayInputStream(pickled); ObjectInputStream ois = new ObjectInputStream(in); SegmentBody o = (SegmentBody) ois.readObject(); Util.discard(o); } catch (NotSerializableException e) { throw new RuntimeException( "while serializing " + body, e); } catch (Exception e) { throw new RuntimeException(e); } cache.put(header, body); fireSegmentCacheEvent( new SegmentCache.SegmentCacheListener.SegmentCacheEvent() { public boolean isLocal() { return true; } public SegmentHeader getSource() { return header; } public EventType getEventType() { return SegmentCacheListener.SegmentCacheEvent .EventType.ENTRY_CREATED; } }); if (cache.size() > maxElements) { // Cache is full. pop one out at random. if (rnd == null) { rnd = new Random(); } int index = rnd.nextInt(maxElements); for (Iterator iterator = cache.keySet().iterator(); iterator.hasNext();) { Util.discard(iterator.next()); if (index-- == 0) { iterator.remove(); break; } } fireSegmentCacheEvent( new SegmentCache.SegmentCacheListener.SegmentCacheEvent() { public boolean isLocal() { return true; } public SegmentHeader getSource() { return header; } public EventType getEventType() { return SegmentCacheListener.SegmentCacheEvent .EventType.ENTRY_DELETED; } }); } return true; } public List getSegmentHeaders() { return new ArrayList(cache.keySet()); } public boolean remove(final SegmentHeader header) { cache.remove(header); fireSegmentCacheEvent( new SegmentCache.SegmentCacheListener.SegmentCacheEvent() { public boolean isLocal() { return true; } public SegmentHeader getSource() { return header; } public EventType getEventType() { return SegmentCacheListener.SegmentCacheEvent .EventType.ENTRY_DELETED; } }); return true; } public void tearDown() { listeners.clear(); cache.clear(); } public void addListener(SegmentCacheListener listener) { listeners.add(listener); } public void removeListener(SegmentCacheListener listener) { listeners.remove(listener); } public boolean supportsRichIndex() { return true; } public void fireSegmentCacheEvent( SegmentCache.SegmentCacheListener.SegmentCacheEvent event) { for (SegmentCacheListener listener : listeners) { listener.handle(event); } } } // End MockSegmentCache.java mondrian-3.4.1/testsrc/main/mondrian/rolap/agg/AggregationOnDistinctCountMeasuresTest.java0000644000175000017500000017714711735330606032013 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2011 Pentaho and others // All Rights Reserved. // // ajogleka, 19 December, 2007 */ package mondrian.rolap.agg; import mondrian.calc.TupleList; import mondrian.calc.impl.UnaryTupleList; import mondrian.olap.*; import mondrian.olap.fun.AggregateFunDef; import mondrian.olap.fun.CrossJoinFunDef; import mondrian.rolap.BatchTestCase; import mondrian.rolap.RolapCube; import mondrian.server.Execution; import mondrian.server.Locus; import mondrian.spi.Dialect; import mondrian.test.SqlPattern; import mondrian.test.TestContext; import java.util.*; /** * AggregationOnDistinctCountMeasureTest tests the * Distinct Count functionality with tuples and members. * * @author ajogleka * @since 19 December, 2007 */ public class AggregationOnDistinctCountMeasuresTest extends BatchTestCase { private final MondrianProperties props = MondrianProperties.instance(); private SchemaReader salesCubeSchemaReader = null; private SchemaReader schemaReader = null; private RolapCube salesCube; protected void setUp() throws Exception { schemaReader = getTestContext().getConnection().getSchemaReader().withLocus(); salesCube = (RolapCube) cubeByName( getTestContext().getConnection(), cubeNameSales); salesCubeSchemaReader = salesCube.getSchemaReader( getTestContext().getConnection().getRole()).withLocus(); } public TestContext getTestContext() { return TestContext.instance().create( null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "" + "\n" + " \n" + " " + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null); } public void testTupleWithAllLevelMembersOnly() { assertQueryReturns( "WITH MEMBER GENDER.X AS 'AGGREGATE({([GENDER].DEFAULTMEMBER,\n" + "[STORE].DEFAULTMEMBER)})'\n" + "SELECT GENDER.X ON 0, [MEASURES].[CUSTOMER COUNT] ON 1 FROM SALES", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[X]}\n" + "Axis #2:\n" + "{[Measures].[Customer Count]}\n" + "Row #0: 5,581\n"); } public void testCrossJoinOfAllMembers() { assertQueryReturns( "WITH MEMBER GENDER.X AS 'AGGREGATE({CROSSJOIN({[GENDER].DEFAULTMEMBER},\n" + "{[STORE].DEFAULTMEMBER})})'\n" + "SELECT GENDER.X ON 0, [MEASURES].[CUSTOMER COUNT] ON 1 FROM SALES", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[X]}\n" + "Axis #2:\n" + "{[Measures].[Customer Count]}\n" + "Row #0: 5,581\n"); } public void testCrossJoinMembersWithASingleMember() { String query = "WITH MEMBER GENDER.X AS 'AGGREGATE({[GENDER].[GENDER].members} * " + "{[STORE].[ALL STORES].[USA].[CA]})', solve_order=100 " + "SELECT GENDER.X ON 0, [MEASURES].[CUSTOMER COUNT] ON 1 FROM SALES"; String result = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[X]}\n" + "Axis #2:\n" + "{[Measures].[Customer Count]}\n" + "Row #0: 2,716\n"; assertQueryReturns(query, result); // Check aggregate loading sql pattern String derbySql = "select \"time_by_day\".\"the_year\" as \"c0\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from \"time_by_day\" as \"time_by_day\", \"sales_fact_1997\" as \"sales_fact_1997\", \"store\" as \"store\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" " + "and \"time_by_day\".\"the_year\" = 1997 " + "and \"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" and \"store\".\"store_state\" = 'CA' " + "group by \"time_by_day\".\"the_year\""; String mysqlSql = "select `time_by_day`.`the_year` as `c0`, " + "count(distinct `sales_fact_1997`.`customer_id`) as `m0` " + "from `time_by_day` as `time_by_day`, `sales_fact_1997` as `sales_fact_1997`, `store` as `store` " + "where `sales_fact_1997`.`time_id` = `time_by_day`.`time_id` " + "and `time_by_day`.`the_year` = 1997 " + "and `sales_fact_1997`.`store_id` = `store`.`store_id` and `store`.`store_state` = 'CA' " + "group by `time_by_day`.`the_year`"; String oraTeraSql = "select \"time_by_day\".\"the_year\" as \"c0\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from \"time_by_day\" =as= \"time_by_day\", \"sales_fact_1997\" =as= \"sales_fact_1997\", \"store\" =as= \"store\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"time_by_day\".\"the_year\" = 1997 " + "and \"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" and \"store\".\"store_state\" = 'CA' " + "group by \"time_by_day\".\"the_year\""; SqlPattern[] patterns = { new SqlPattern(Dialect.DatabaseProduct.DERBY, derbySql, derbySql), new SqlPattern(Dialect.DatabaseProduct.MYSQL, mysqlSql, mysqlSql), new SqlPattern( Dialect.DatabaseProduct.ORACLE, oraTeraSql, oraTeraSql), new SqlPattern( Dialect.DatabaseProduct.TERADATA, oraTeraSql, oraTeraSql), }; assertQuerySql(query, patterns); } public void testCrossJoinMembersWithSetOfMembers() { String query = "WITH MEMBER GENDER.X AS 'AGGREGATE({[GENDER].[GENDER].members} * " + "{[STORE].[ALL STORES].[USA].[CA], [Store].[All Stores].[Canada]})', solve_order=100 " + "SELECT GENDER.X ON 0, [MEASURES].[CUSTOMER COUNT] ON 1 FROM SALES"; String result = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[X]}\n" + "Axis #2:\n" + "{[Measures].[Customer Count]}\n" + "Row #0: 2,716\n"; assertQueryReturns(query, result); // Check aggregate loading sql pattern. Note Derby does not support // multicolumn IN list, so the predicates remain in AND/OR form. String derbySql = "select \"time_by_day\".\"the_year\" as \"c0\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from \"time_by_day\" as \"time_by_day\", \"sales_fact_1997\" as \"sales_fact_1997\", \"store\" as \"store\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" " + "and \"time_by_day\".\"the_year\" = 1997 " + "and \"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" " + "and (\"store\".\"store_state\" = 'CA' or \"store\".\"store_country\" = 'Canada') " + "group by \"time_by_day\".\"the_year\""; String mysqlSql = "select `time_by_day`.`the_year` as `c0`, " + "count(distinct `sales_fact_1997`.`customer_id`) as `m0` " + "from `time_by_day` as `time_by_day`, `sales_fact_1997` as `sales_fact_1997`, `store` as `store` " + "where `sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and `time_by_day`.`the_year` = 1997 " + "and `sales_fact_1997`.`store_id` = `store`.`store_id` " + "and (`store`.`store_state` = 'CA' or `store`.`store_country` = 'Canada') " + "group by `time_by_day`.`the_year`"; String oraTeraSql = "select \"time_by_day\".\"the_year\" as \"c0\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from \"time_by_day\" =as= \"time_by_day\", \"sales_fact_1997\" =as= \"sales_fact_1997\", \"store\" =as= \"store\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"time_by_day\".\"the_year\" = 1997 " + "and \"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" " + "and (\"store\".\"store_state\" = 'CA' or \"store\".\"store_country\" = 'Canada') " + "group by \"time_by_day\".\"the_year\""; SqlPattern[] patterns = { new SqlPattern(Dialect.DatabaseProduct.DERBY, derbySql, derbySql), new SqlPattern(Dialect.DatabaseProduct.MYSQL, mysqlSql, mysqlSql), new SqlPattern( Dialect.DatabaseProduct.ORACLE, oraTeraSql, oraTeraSql), new SqlPattern( Dialect.DatabaseProduct.TERADATA, oraTeraSql, oraTeraSql), }; assertQuerySql(query, patterns); } public void testCrossJoinParticularMembersFromTwoDimensions() { assertQueryReturns( "WITH MEMBER GENDER.X AS 'AGGREGATE({[GENDER].M} * " + "{[STORE].[ALL STORES].[USA].[CA]})', solve_order=100 " + "SELECT GENDER.X ON 0, [MEASURES].[CUSTOMER COUNT] ON 1 FROM SALES", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[X]}\n" + "Axis #2:\n" + "{[Measures].[Customer Count]}\n" + "Row #0: 1,389\n"); } public void testDistinctCountOnSetOfMembersFromOneDimension() { assertQueryReturns( "WITH MEMBER GENDER.X AS 'AGGREGATE({[GENDER].[GENDER].members})'" + "SELECT GENDER.X ON 0, [MEASURES].[CUSTOMER COUNT] ON 1 FROM SALES", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[X]}\n" + "Axis #2:\n" + "{[Measures].[Customer Count]}\n" + "Row #0: 5,581\n"); } public void testDistinctCountWithAMeasureAsPartOfTuple() { assertQueryReturns( "SELECT [STORE].[ALL STORES].[USA].[CA] ON 0, " + "([MEASURES].[CUSTOMER COUNT], [Gender].[m]) ON 1 FROM SALES", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA]}\n" + "Axis #2:\n" + "{[Measures].[Customer Count], [Gender].[M]}\n" + "Row #0: 1,389\n"); } public void testDistinctCountOnSetOfMembers() { assertQueryReturns( "WITH MEMBER STORE.X as 'Aggregate({[STORE].[ALL STORES].[USA].[CA]," + "[STORE].[ALL STORES].[USA].[WA]})'" + "SELECT STORE.X ON ROWS, " + "{[MEASURES].[CUSTOMER COUNT]} ON COLUMNS\n" + "FROM [SALES]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Store].[X]}\n" + "Row #0: 4,544\n"); } public void testDistinctCountOnTuplesWithSomeNonJoiningDimensions() { propSaver.set( props.IgnoreMeasureForNonJoiningDimension, false); String mdx = "WITH MEMBER WAREHOUSE.X as 'Aggregate({WAREHOUSE.[STATE PROVINCE].MEMBERS}*" + "{[Gender].Members})'" + "SELECT WAREHOUSE.X ON ROWS, " + "{[MEASURES].[CUSTOMER COUNT]} ON COLUMNS\n" + "FROM [WAREHOUSE AND SALES2]"; String expectedResult = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Warehouse].[X]}\n" + "Row #0: \n"; assertQueryReturns(mdx, expectedResult); propSaver.set( props.IgnoreMeasureForNonJoiningDimension, true); assertQueryReturns(mdx, expectedResult); } public void testAggregationListOptimizationForChildren() { assertQueryReturns( "WITH MEMBER GENDER.X AS 'AGGREGATE({[GENDER].[GENDER].members} * " + "{[STORE].[ALL STORES].[USA].[CA], [STORE].[ALL STORES].[USA].[OR], " + "[STORE].[ALL STORES].[USA].[WA], [Store].[All Stores].[Canada]})' " + "SELECT GENDER.X ON 0, [MEASURES].[CUSTOMER COUNT] ON 1 FROM SALES", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[X]}\n" + "Axis #2:\n" + "{[Measures].[Customer Count]}\n" + "Row #0: 5,581\n"); } public void testDistinctCountOnMembersWithNonJoiningDimensionNotAtAllLevel() { assertQueryReturns( "WITH MEMBER WAREHOUSE.X as " + "'Aggregate({WAREHOUSE.[STATE PROVINCE].MEMBERS})'" + "SELECT WAREHOUSE.X ON ROWS, " + "{[MEASURES].[CUSTOMER COUNT]} ON COLUMNS\n" + "FROM [WAREHOUSE AND SALES2]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Warehouse].[X]}\n" + "Row #0: \n"); } public void testNonJoiningDimensionWithAllMember() { assertQueryReturns( "WITH MEMBER WAREHOUSE.X as 'Aggregate({WAREHOUSE.MEMBERS})'" + "SELECT WAREHOUSE.X ON ROWS, " + "{[MEASURES].[CUSTOMER COUNT]} ON COLUMNS\n" + "FROM [WAREHOUSE AND SALES2]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Warehouse].[X]}\n" + "Row #0: 5,581\n"); } public void testCrossJoinOfJoiningAndNonJoiningDimensionWithAllMember() { assertQueryReturns( "WITH MEMBER WAREHOUSE.X AS " + "'AGGREGATE({GENDER.GENDER.MEMBERS} * {WAREHOUSE.MEMBERS})'" + "SELECT WAREHOUSE.X ON ROWS, " + "{[MEASURES].[CUSTOMER COUNT]} ON COLUMNS\n" + "FROM [WAREHOUSE AND SALES2]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Warehouse].[X]}\n" + "Row #0: 5,581\n"); assertQueryReturns( "WITH MEMBER WAREHOUSE.X AS " + "'AGGREGATE({GENDER.GENDER.MEMBERS} * {WAREHOUSE.MEMBERS})'" + "SELECT WAREHOUSE.X ON ROWS, " + "{[MEASURES].[CUSTOMER COUNT]} ON COLUMNS\n" + "FROM [WAREHOUSE AND SALES3]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Warehouse].[X]}\n" + "Row #0: 5,581\n"); } public void testCrossJoinOfJoiningAndNonJoiningDimension() { assertQueryReturns( "WITH MEMBER WAREHOUSE.X AS " + "'AGGREGATE({GENDER.GENDER.MEMBERS} * {WAREHOUSE.[STATE PROVINCE].MEMBERS})'" + "SELECT WAREHOUSE.X ON ROWS, " + "{[MEASURES].[CUSTOMER COUNT]} ON COLUMNS\n" + "FROM [WAREHOUSE AND SALES2]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Warehouse].[X]}\n" + "Row #0: \n"); assertQueryReturns( "WITH MEMBER WAREHOUSE.X AS " + "'AGGREGATE({GENDER.GENDER.MEMBERS} * {WAREHOUSE.[STATE PROVINCE].MEMBERS})'" + "SELECT WAREHOUSE.X ON ROWS, " + "{[MEASURES].[CUSTOMER COUNT]} ON COLUMNS\n" + "FROM [WAREHOUSE AND SALES3]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Warehouse].[X]}\n" + "Row #0: 5,581\n"); } public void testAggregationOverLargeListGeneratesError() { int origMaxConstraint = props.MaxConstraints.get(); props.MaxConstraints.set(7); String result; final Dialect dialect = getTestContext().getDialect(); if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.LUCIDDB) { // LucidDB has no limit on the size of IN list result = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Product].[X]}\n" + "Row #0: 1,360\n"; } else { result = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Product].[X]}\n" + "Row #0: #ERR: mondrian.olap.fun.MondrianEvaluationException: " + "Aggregation is not supported over a list with more than 7 predicates (see property mondrian.rolap.maxConstraints)\n"; } assertQueryReturns( "WITH MEMBER PRODUCT.X as 'Aggregate({" + "[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good],\n" + "[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth],\n" + "[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Top Measure],\n" + "[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Walrus],\n" + "[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Pearl],\n" + "[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Portsmouth],\n" + "[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Top Measure],\n" + "[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Walrus]})' " + "SELECT PRODUCT.X ON ROWS, " + "{[MEASURES].[CUSTOMER COUNT]} ON COLUMNS\n" + "FROM [WAREHOUSE AND SALES2]", result); props.MaxConstraints.set(origMaxConstraint); } public void testMultiLevelMembersNullParents() { if (!isDefaultNullMemberRepresentation()) { return; } String dimension = "\n" + " \n" + "

\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; String cube = "\n" + "
\n" + " \n" + " \n" + " \n" + ""; String query = "with set [Filtered Warehouse Set] as " + "{[Warehouse2].[#null].[#null].[5617 Saclan Terrace].[Arnold and Sons]," + " [Warehouse2].[#null].[#null].[3377 Coachman Place].[Jones International]} " + "member [Warehouse2].[TwoMembers] as 'AGGREGATE([Filtered Warehouse Set])' " + "select {[Measures].[Cost Count]} on columns, {[Warehouse2].[TwoMembers]} on rows " + "from [Warehouse2]"; String necjSqlDerby = "select count(distinct \"inventory_fact_1997\".\"warehouse_cost\") as \"m0\" " + "from \"warehouse\" as \"warehouse\", " + "\"inventory_fact_1997\" as \"inventory_fact_1997\" " + "where \"inventory_fact_1997\".\"warehouse_id\" = \"warehouse\".\"warehouse_id\" " + "and ((\"warehouse\".\"warehouse_name\" = 'Arnold and Sons' " + "and \"warehouse\".\"wa_address1\" = '5617 Saclan Terrace' " + "and \"warehouse\".\"wa_address2\" is null) " + "or (\"warehouse\".\"warehouse_name\" = 'Jones International' " + "and \"warehouse\".\"wa_address1\" = '3377 Coachman Place' " + "and \"warehouse\".\"wa_address2\" is null))"; String necjSqlMySql = "select count(distinct `inventory_fact_1997`.`warehouse_cost`) as `m0` " + "from `warehouse` as `warehouse`, `inventory_fact_1997` as `inventory_fact_1997` " + "where `inventory_fact_1997`.`warehouse_id` = `warehouse`.`warehouse_id` " + "and ((`warehouse`.`wa_address2` is null " + "and (`warehouse`.`wa_address1`, `warehouse`.`warehouse_name`) " + "in (('5617 Saclan Terrace', 'Arnold and Sons'), " + "('3377 Coachman Place', 'Jones International'))))"; TestContext testContext = TestContext.instance().create( dimension, cube, null, null, null, null); SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.DERBY, necjSqlDerby, necjSqlDerby), new SqlPattern( Dialect.DatabaseProduct.MYSQL, necjSqlMySql, necjSqlMySql) }; assertQuerySql(testContext, query, patterns); } public void testMultiLevelMembersMixedNullNonNullParent() { if (!isDefaultNullMemberRepresentation()) { return; } String dimension = "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "\n"; String cube = "\n" + "
\n" + " \n" + " \n" + " \n" + ""; String query = "with\n" + "set [Filtered Warehouse Set] as " + "{[Warehouse2].[#null].[234 West Covina Pkwy].[Freeman And Co]," + " [Warehouse2].[971-555-6213].[3377 Coachman Place].[Jones International]} " + "member [Warehouse2].[TwoMembers] as 'AGGREGATE([Filtered Warehouse Set])' " + "select {[Measures].[Cost Count]} on columns, {[Warehouse2].[TwoMembers]} on rows " + "from [Warehouse2]"; String necjSqlMySql2 = "select count(distinct `inventory_fact_1997`.`warehouse_cost`) as `m0` from `warehouse` as `warehouse`, `inventory_fact_1997` as `inventory_fact_1997` where `inventory_fact_1997`.`warehouse_id` = `warehouse`.`warehouse_id` and ((`warehouse`.`warehouse_name` = 'Freeman And Co' and `warehouse`.`wa_address1` = '234 West Covina Pkwy' and `warehouse`.`warehouse_fax` is null) or (`warehouse`.`warehouse_name` = 'Jones International' and `warehouse`.`wa_address1` = '3377 Coachman Place' and `warehouse`.`warehouse_fax` = '971-555-6213'))"; TestContext testContext = TestContext.instance().create( dimension, cube, null, null, null, null); String result = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Cost Count]}\n" + "Axis #2:\n" + "{[Warehouse2].[TwoMembers]}\n" + "Row #0: 220\n"; testContext.assertQueryReturns(query, result); } public void testMultiLevelsMixedNullNonNullChild() { if (!isDefaultNullMemberRepresentation()) { return; } String dimension = "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "\n"; String cube = "\n" + "
\n" + " \n" + " \n" + " \n" + ""; String query = "with\n" + "set [Filtered Warehouse Set] as " + "{[Warehouse2].[#null].[#null].[#null]," + " [Warehouse2].[#null].[#null].[971-555-6213]} " + "member [Warehouse2].[TwoMembers] as 'AGGREGATE([Filtered Warehouse Set])' " + "select {[Measures].[Cost Count]} on columns, {[Warehouse2].[TwoMembers]} on rows " + "from [Warehouse2]"; String necjSqlMySql2 = "select count(distinct `inventory_fact_1997`.`warehouse_cost`) as `m0` from `warehouse` as `warehouse`, `inventory_fact_1997` as `inventory_fact_1997` where `inventory_fact_1997`.`warehouse_id` = `warehouse`.`warehouse_id` and ((`warehouse`.`warehouse_fax` is null and `warehouse`.`wa_address2` is null and `warehouse`.`wa_address3` is null) or (`warehouse`.`warehouse_fax` = '971-555-6213' and `warehouse`.`wa_address2` is null and `warehouse`.`wa_address3` is null))"; TestContext testContext = TestContext.instance().create( dimension, cube, null, null, null, null); String result = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Cost Count]}\n" + "Axis #2:\n" + "{[Warehouse2].[TwoMembers]}\n" + "Row #0: 220\n"; testContext.assertQueryReturns(query, result); } public void testAggregationOnCJofMembersGeneratesOptimalQuery() { // Mondrian does not use GROUPING SETS for distinct-count measures. // So, this test should not use GROUPING SETS, even if they are enabled. // See change 12310, bug MONDRIAN-470 (aka SF.net 2207515). Util.discard(props.EnableGroupingSets); String oraTeraSql = "select \"store\".\"store_state\" as \"c0\"," + " \"time_by_day\".\"the_year\" as \"c1\"," + " count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from \"store\" =as= \"store\"," + " \"sales_fact_1997\" =as= \"sales_fact_1997\"," + " \"time_by_day\" =as= \"time_by_day\" " + "where \"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\" = 1997 " + "group by \"store\".\"store_state\", \"time_by_day\".\"the_year\""; SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.ORACLE, oraTeraSql, oraTeraSql), new SqlPattern( Dialect.DatabaseProduct.TERADATA, oraTeraSql, oraTeraSql), }; assertQuerySql( "WITH \n" + "SET [COG_OQP_INT_s2] AS 'CROSSJOIN({[Store].[Store].MEMBERS}, " + "{{[Gender].[Gender].MEMBERS}, " + "{([Gender].[COG_OQP_USR_Aggregate(Gender)])}})' \n" + "SET [COG_OQP_INT_s1] AS 'CROSSJOIN({[Store].[Store].MEMBERS}, " + "{[Gender].[Gender].MEMBERS})' \n" + "\n" + "MEMBER [Store].[COG_OQP_USR_Aggregate(Store)] AS '\n" + "AGGREGATE({COG_OQP_INT_s1})', SOLVE_ORDER = 4 \n" + "\n" + "MEMBER [Gender].[COG_OQP_USR_Aggregate(Gender)] AS '\n" + "AGGREGATE({[Gender].DEFAULTMEMBER})', SOLVE_ORDER = 8 \n" + "\n" + "\n" + "SELECT {[Measures].[Customer Count]} ON AXIS(0), \n" + "{[COG_OQP_INT_s2], HEAD({([Store].[COG_OQP_USR_Aggregate(Store)], " + "[Gender].DEFAULTMEMBER)}, " + "IIF(COUNT([COG_OQP_INT_s1], INCLUDEEMPTY) > 0, 1, 0))} ON AXIS(1) \n" + "FROM [sales]", patterns); } public void testCanNotBatchForDifferentCompoundPredicate() { boolean originalGroupingSetsPropertyValue = props.EnableGroupingSets.get(); props.EnableGroupingSets.set(true); String mdxQueryWithFewMembers = "WITH " + "MEMBER [Store].[COG_OQP_USR_Aggregate(Store)] AS " + "'AGGREGATE({[Store].[All Stores].[USA].[CA], [Store].[All Stores].[USA].[OR],[Store].[All Stores].[USA].[WA]})', SOLVE_ORDER = 8" + "SELECT {[Measures].[Customer Count]} ON AXIS(0), " + "{[Store].[All Stores].[USA].[CA], [Store].[All Stores].[USA].[OR], [Store].[COG_OQP_USR_Aggregate(Store)]} " + "ON AXIS(1) " + "FROM [Sales]"; String desiredResult = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[COG_OQP_USR_Aggregate(Store)]}\n" + "Row #0: 2,716\n" + "Row #1: 1,037\n" + "Row #2: 5,581\n"; String oraTeraSqlForAgg = "select \"time_by_day\".\"the_year\" as \"c0\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from \"time_by_day\" =as= \"time_by_day\", " + "\"sales_fact_1997\" =as= \"sales_fact_1997\", \"store\" =as= \"store\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" " + "and \"time_by_day\".\"the_year\" = 1997 and " + "\"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" " + "and \"store\".\"store_country\" = 'USA' " + "group by \"time_by_day\".\"the_year\""; String oraTeraSqlForDetail = "select \"store\".\"store_state\" as \"c0\", " + "\"time_by_day\".\"the_year\" as \"c1\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from \"store\" =as= \"store\", " + "\"sales_fact_1997\" =as= \"sales_fact_1997\", " + "\"time_by_day\" =as= \"time_by_day\" " + "where \"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" " + "and \"store\".\"store_state\" in ('CA', 'OR') " + "and \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" " + "and \"time_by_day\".\"the_year\" = 1997 " + "group by \"store\".\"store_state\", \"time_by_day\".\"the_year\""; SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.ORACLE, oraTeraSqlForAgg, oraTeraSqlForAgg), new SqlPattern( Dialect.DatabaseProduct.TERADATA, oraTeraSqlForAgg, oraTeraSqlForAgg), new SqlPattern( Dialect.DatabaseProduct.ORACLE, oraTeraSqlForDetail, oraTeraSqlForDetail), new SqlPattern( Dialect.DatabaseProduct.TERADATA, oraTeraSqlForDetail, oraTeraSqlForDetail), }; assertQueryReturns(mdxQueryWithFewMembers, desiredResult); assertQuerySql(mdxQueryWithFewMembers, patterns); props.EnableGroupingSets.set(originalGroupingSetsPropertyValue); } /** * Test distinct count agg happens in non gs query for subset of members * with mixed measures. */ public void testDistinctCountInNonGroupingSetsQuery() { boolean originalGroupingSetsPropertyValue = props.EnableGroupingSets.get(); props.EnableGroupingSets.set(true); String mdxQueryWithFewMembers = "WITH " + "MEMBER [Store].[COG_OQP_USR_Aggregate(Store)] AS " + "'AGGREGATE({[Store].[All Stores].[USA].[CA], [Store].[All Stores].[USA].[OR]})', SOLVE_ORDER = 8" + "SELECT {[Measures].[Customer Count],[Measures].[Unit Sales]} ON AXIS(0), " + "{[Store].[All Stores].[USA].[CA], [Store].[All Stores].[USA].[OR], [Store].[COG_OQP_USR_Aggregate(Store)]} " + "ON AXIS(1) " + "FROM [Sales]"; String desiredResult = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[COG_OQP_USR_Aggregate(Store)]}\n" + "Row #0: 2,716\n" + "Row #0: 74,748\n" + "Row #1: 1,037\n" + "Row #1: 67,659\n" + "Row #2: 3,753\n" + "Row #2: 142,407\n"; String oraTeraSqlForDetail = "select \"store\".\"store_state\" as \"c0\", " + "\"time_by_day\".\"the_year\" as \"c1\", " + "sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m1\" " + "from \"store\" =as= \"store\", \"sales_fact_1997\" =as= \"sales_fact_1997\", " + "\"time_by_day\" =as= \"time_by_day\" " + "where \"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" " + "and \"store\".\"store_state\" in ('CA', 'OR') " + "and \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" " + "and \"time_by_day\".\"the_year\" = 1997 " + "group by \"store\".\"store_state\", \"time_by_day\".\"the_year\""; String oraTeraSqlForDistinctCountAgg = "select \"time_by_day\".\"the_year\" as \"c0\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from \"time_by_day\" =as= \"time_by_day\", " + "\"sales_fact_1997\" =as= \"sales_fact_1997\", \"store\" =as= \"store\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" " + "and \"time_by_day\".\"the_year\" = 1997 " + "and \"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" " + "and \"store\".\"store_state\" in ('CA', 'OR') " + "group by \"time_by_day\".\"the_year\""; SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.ORACLE, oraTeraSqlForDetail, oraTeraSqlForDetail), new SqlPattern( Dialect.DatabaseProduct.TERADATA, oraTeraSqlForDetail, oraTeraSqlForDetail), new SqlPattern( Dialect.DatabaseProduct.ORACLE, oraTeraSqlForDistinctCountAgg, oraTeraSqlForDistinctCountAgg), new SqlPattern( Dialect.DatabaseProduct.TERADATA, oraTeraSqlForDistinctCountAgg, oraTeraSqlForDistinctCountAgg), }; assertQueryReturns(mdxQueryWithFewMembers, desiredResult); assertQuerySql(mdxQueryWithFewMembers, patterns); props.EnableGroupingSets.set(originalGroupingSetsPropertyValue); } public void testAggregationOfMembersAndDefaultMemberWithoutGroupingSets() { boolean originalGroupingSetsPropertyValue = props.EnableGroupingSets.get(); props.EnableGroupingSets.set(false); String mdxQueryWithMembers = "WITH " + "MEMBER [Gender].[COG_OQP_USR_Aggregate(Gender)] AS " + "'AGGREGATE({[Gender].MEMBERS})', SOLVE_ORDER = 8" + "SELECT {[Measures].[Customer Count]} ON AXIS(0), " + "{[Gender].MEMBERS, [Gender].[COG_OQP_USR_Aggregate(Gender)]} " + "ON AXIS(1) " + "FROM [Sales]"; String mdxQueryWithDefaultMember = "WITH " + "MEMBER [Gender].[COG_OQP_USR_Aggregate(Gender)] AS " + "'AGGREGATE({[Gender].DEFAULTMEMBER})', SOLVE_ORDER = 8" + "SELECT {[Measures].[Customer Count]} ON AXIS(0), \n" + "{[Gender].MEMBERS, [Gender].[COG_OQP_USR_Aggregate(Gender)]} " + "ON AXIS(1) \n" + "FROM [sales]"; String desiredResult = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "{[Gender].[COG_OQP_USR_Aggregate(Gender)]}\n" + "Row #0: 5,581\n" + "Row #1: 2,755\n" + "Row #2: 2,826\n" + "Row #3: 5,581\n"; String oraTeraSql = "select \"time_by_day\".\"the_year\" as \"c0\", " + "\"customer\".\"gender\" as \"c1\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from \"time_by_day\" =as= \"time_by_day\", " + "\"sales_fact_1997\" =as= \"sales_fact_1997\", \"customer\" =as= \"customer\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" " + "and \"time_by_day\".\"the_year\" = 1997 " + "and \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "group by \"time_by_day\".\"the_year\", \"customer\".\"gender\""; SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.ORACLE, oraTeraSql, oraTeraSql), new SqlPattern( Dialect.DatabaseProduct.TERADATA, oraTeraSql, oraTeraSql), }; assertQueryReturns(mdxQueryWithMembers, desiredResult); assertQuerySql(mdxQueryWithMembers, patterns); assertQueryReturns(mdxQueryWithDefaultMember, desiredResult); assertQuerySql(mdxQueryWithDefaultMember, patterns); props.EnableGroupingSets.set(originalGroupingSetsPropertyValue); } public void testOptimizeChildren() { String query = "with member gender.x as " + "'aggregate(" + "{gender.gender.members * Store.[all stores].[usa].children})' " + "select {gender.x} on 0, measures.[customer count] on 1 from sales"; String expected = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[x]}\n" + "Axis #2:\n" + "{[Measures].[Customer Count]}\n" + "Row #0: 5,581\n"; assertQueryReturns(query, expected); String derbySql = "select \"time_by_day\".\"the_year\" as \"c0\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from \"time_by_day\" as \"time_by_day\", \"sales_fact_1997\" as \"sales_fact_1997\", \"store\" as \"store\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"time_by_day\".\"the_year\" = 1997 " + "and \"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" " + "and \"store\".\"store_country\" = 'USA' group by \"time_by_day\".\"the_year\""; String accessSql = "select `d0` as `c0`, count(`m0`) as `c1` " + "from (select distinct `time_by_day`.`the_year` as `d0`, `sales_fact_1997`.`customer_id` as `m0` " + "from `time_by_day` as `time_by_day`, `sales_fact_1997` as `sales_fact_1997`, `store` as `store` " + "where `sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and `time_by_day`.`the_year` = 1997 " + "and `sales_fact_1997`.`store_id` = `store`.`store_id` " + "and `store`.`store_country` = 'USA') as `dummyname` group by `d0`"; // For LucidDB, we don't optimize since it supports // unlimited IN list. String luciddbSql = "select \"time_by_day\".\"the_year\" as \"c0\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from \"time_by_day\" as \"time_by_day\", \"sales_fact_1997\" as \"sales_fact_1997\", \"customer\" as \"customer\", \"store\" as \"store\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"time_by_day\".\"the_year\" = 1997 and \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "and \"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" " + "and (((\"store\".\"store_state\", \"customer\".\"gender\") in (('CA', 'F'), ('OR', 'F'), ('WA', 'F'), ('CA', 'M'), ('OR', 'M'), ('WA', 'M')))) group by \"time_by_day\".\"the_year\""; SqlPattern[] patterns = { new SqlPattern(Dialect.DatabaseProduct.DERBY, derbySql, derbySql), new SqlPattern( Dialect.DatabaseProduct.ACCESS, accessSql, accessSql), new SqlPattern( Dialect.DatabaseProduct.LUCIDDB, luciddbSql, luciddbSql), }; assertQuerySql(query, patterns); } public void testOptimizeListWhenTuplesAreFormedWithDifferentLevels() { String query = "WITH\n" + "MEMBER Product.Agg AS \n" + "'Aggregate({[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Cormorant],\n" + "[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Denny],\n" + "[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[High Quality],\n" + "[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Red Wing],\n" + "[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Cormorant],\n" + "[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Denny],\n" + "[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[High Quality],\n" + "[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Red Wing],\n" + "[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Sunset]} *\n" + "{[Gender].[Gender].Members})'\n" + "SELECT {Product.Agg} on 0, {[Measures].[Customer Count]} on 1\n" + "from Sales\n" + "where [Time.Weekly].[1997]"; String expected = "Axis #0:\n" + "{[Time].[Weekly].[1997]}\n" + "Axis #1:\n" + "{[Product].[Agg]}\n" + "Axis #2:\n" + "{[Measures].[Customer Count]}\n" + "Row #0: 421\n"; assertQueryReturns(query, expected); String derbySql = "select \"time_by_day\".\"the_year\" as \"c0\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from \"time_by_day\" as \"time_by_day\", \"sales_fact_1997\" as \"sales_fact_1997\", " + "\"product\" as \"product\", \"product_class\" as \"product_class\" " + "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\".\"brand_name\" = 'Red Wing' and \"product_class\".\"product_subcategory\" = 'Pot Scrubbers' " + "and \"product_class\".\"product_category\" = 'Kitchen Products' " + "and \"product_class\".\"product_department\" = 'Household' " + "and \"product_class\".\"product_family\" = 'Non-Consumable') " + "or (\"product\".\"brand_name\" = 'Cormorant' and \"product_class\".\"product_subcategory\" = 'Pot Scrubbers' " + "and \"product_class\".\"product_category\" = 'Kitchen Products' " + "and \"product_class\".\"product_department\" = 'Household' " + "and \"product_class\".\"product_family\" = 'Non-Consumable') " + "or (\"product\".\"brand_name\" = 'Denny' and \"product_class\".\"product_subcategory\" = 'Pot Scrubbers' " + "and \"product_class\".\"product_category\" = 'Kitchen Products' " + "and \"product_class\".\"product_department\" = 'Household' " + "and \"product_class\".\"product_family\" = 'Non-Consumable') or (\"product\".\"brand_name\" = 'High Quality' " + "and \"product_class\".\"product_subcategory\" = 'Pot Scrubbers' " + "and \"product_class\".\"product_category\" = 'Kitchen Products' " + "and \"product_class\".\"product_department\" = 'Household' and \"product_class\".\"product_family\" = 'Non-Consumable')) " + "or (\"product_class\".\"product_subcategory\" = 'Pots and Pans' " + "and \"product_class\".\"product_category\" = 'Kitchen Products' and \"product_class\".\"product_department\" = 'Household' " + "and \"product_class\".\"product_family\" = 'Non-Consumable')) " + "group by \"time_by_day\".\"the_year\""; String accessSql = "select `d0` as `c0`, count(`m0`) as `c1` from (select distinct `time_by_day`.`the_year` as `d0`, `sales_fact_1997`.`customer_id` as `m0` " + "from `time_by_day` as `time_by_day`, `sales_fact_1997` as `sales_fact_1997`, `product` as `product`, `product_class` as `product_class` " + "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`.`brand_name` = 'High Quality' and `product_class`.`product_subcategory` = 'Pot Scrubbers' " + "and `product_class`.`product_category` = 'Kitchen Products' and `product_class`.`product_department` = 'Household' " + "and `product_class`.`product_family` = 'Non-Consumable') or (`product`.`brand_name` = 'Denny' " + "and `product_class`.`product_subcategory` = 'Pot Scrubbers' and `product_class`.`product_category` = 'Kitchen Products' " + "and `product_class`.`product_department` = 'Household' " + "and `product_class`.`product_family` = 'Non-Consumable') or (`product`.`brand_name` = 'Red Wing' " + "and `product_class`.`product_subcategory` = 'Pot Scrubbers' and `product_class`.`product_category` = 'Kitchen Products' " + "and `product_class`.`product_department` = 'Household' " + "and `product_class`.`product_family` = 'Non-Consumable') or (`product`.`brand_name` = 'Cormorant' " + "and `product_class`.`product_subcategory` = 'Pot Scrubbers' and `product_class`.`product_category` = 'Kitchen Products' " + "and `product_class`.`product_department` = 'Household' " + "and `product_class`.`product_family` = 'Non-Consumable')) or (`product_class`.`product_subcategory` = 'Pots and Pans' " + "and `product_class`.`product_category` = 'Kitchen Products' and `product_class`.`product_department` = 'Household' " + "and `product_class`.`product_family` = 'Non-Consumable'))) as `dummyname` group by `d0`"; // FIXME jvs 20-Sept-2008: The Derby pattern fails, probably due to // usage of non-order-deterministic Hash data structures in // AggregateFunDef. (Access may be failing too; I haven't tried it.) // So it is disabled for now. Perhaps this test should be calling // directly into optimizeChildren like some of the tests below rather // than using SQL pattern verification. SqlPattern[] patterns = { /* new SqlPattern(SqlPattern.Dialect.DERBY, derbySql, derbySql), */ new SqlPattern( Dialect.DatabaseProduct.ACCESS, accessSql, accessSql)}; assertQuerySql(query, patterns); } public void testOptimizeListWithTuplesOfLength3() { String query = "WITH\n" + "MEMBER Product.Agg AS \n" + "'Aggregate" + "({[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Cormorant],\n" + "[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Denny],\n" + "[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[High Quality],\n" + "[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Red Wing],\n" + "[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Cormorant],\n" + "[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Denny],\n" + "[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[High Quality],\n" + "[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Red Wing],\n" + "[Product].[All Products].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Sunset]} *\n" + "{[Gender].[Gender].Members}*" + "{[Store].[All Stores].[USA].[CA].[Alameda],\n" + "[Store].[All Stores].[USA].[CA].[Alameda].[HQ],\n" + "[Store].[All Stores].[USA].[CA].[Beverly Hills],\n" + "[Store].[All Stores].[USA].[CA].[Beverly Hills].[Store 6],\n" + "[Store].[All Stores].[USA].[CA].[Los Angeles],\n" + "[Store].[All Stores].[USA].[OR].[Portland],\n" + "[Store].[All Stores].[USA].[OR].[Portland].[Store 11],\n" + "[Store].[All Stores].[USA].[OR].[Salem],\n" + "[Store].[All Stores].[USA].[OR].[Salem].[Store 13]})'\n" + "SELECT {Product.Agg} on 0, {[Measures].[Customer Count]} on 1 from Sales"; String expected = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Agg]}\n" + "Axis #2:\n" + "{[Measures].[Customer Count]}\n" + "Row #0: 189\n"; assertQueryReturns(query, expected); } public void testOptimizeChildrenForTuplesWithLength1() { TupleList memberList = productMembersPotScrubbersPotsAndPans( salesCubeSchemaReader); TupleList tuples = optimizeChildren(memberList); assertTrue( tuppleListContains( tuples, member( Id.Segment.toList( "Product", "All Products", "Non-Consumable", "Household", "Kitchen Products", "Pot Scrubbers", "Cormorant"), salesCubeSchemaReader))); assertFalse( tuppleListContains( tuples, member( Id.Segment.toList( "Product", "All Products", "Non-Consumable", "Household", "Kitchen Products", "Pot Scrubbers"), salesCubeSchemaReader))); assertFalse( tuppleListContains( tuples, member( Id.Segment.toList( "Product", "All Products", "Non-Consumable", "Household", "Kitchen Products", "Pots and Pans", "Cormorant"), salesCubeSchemaReader))); assertTrue( tuppleListContains( tuples, member( Id.Segment.toList( "Product", "All Products", "Non-Consumable", "Household", "Kitchen Products", "Pots and Pans"), salesCubeSchemaReader))); assertEquals(4, tuples.size()); } public void testOptimizeChildrenForTuplesWithLength3() { TupleList memberList = CrossJoinFunDef.mutableCrossJoin( genderMembersIncludingAll( false, salesCubeSchemaReader, salesCube), productMembersPotScrubbersPotsAndPans(salesCubeSchemaReader)); memberList = CrossJoinFunDef.mutableCrossJoin( memberList, storeMembersCAAndOR(salesCubeSchemaReader)); TupleList tuples = optimizeChildren(memberList); assertFalse( tuppleListContains( tuples, member( Id.Segment.toList( "Store", "All Stores", "USA", "OR", "Portland"), salesCubeSchemaReader))); assertTrue( tuppleListContains( tuples, member( Id.Segment.toList("Store", "All Stores", "USA", "OR"), salesCubeSchemaReader))); assertEquals(16, tuples.size()); } public void testOptimizeChildrenWhenTuplesAreFormedWithDifferentLevels() { TupleList memberList = CrossJoinFunDef.mutableCrossJoin( genderMembersIncludingAll( false, salesCubeSchemaReader, salesCube), productMembersPotScrubbersPotsAndPans(salesCubeSchemaReader)); TupleList tuples = optimizeChildren(memberList); assertEquals(4, tuples.size()); assertFalse( tuppleListContains( tuples, member( Id.Segment.toList( "Product", "All Products", "Non-Consumable", "Household", "Kitchen Products", "Pots and Pans", "Cormorant"), salesCubeSchemaReader))); assertTrue( tuppleListContains( tuples, member( Id.Segment.toList( "Product", "All Products", "Non-Consumable", "Household", "Kitchen Products", "Pots and Pans"), salesCubeSchemaReader))); assertTrue( tuppleListContains( tuples, member( Id.Segment.toList( "Product", "All Products", "Non-Consumable", "Household", "Kitchen Products", "Pot Scrubbers", "Cormorant"), salesCubeSchemaReader))); } public void testWhetherCJOfChildren() { TupleList memberList = CrossJoinFunDef.mutableCrossJoin( genderMembersIncludingAll( false, salesCubeSchemaReader, salesCube), storeMembersUsaAndCanada( false, salesCubeSchemaReader, salesCube)); List tuples = optimizeChildren(memberList); assertEquals(2, tuples.size()); } public void testShouldNotRemoveDuplicateTuples() { Member maleChildMember = member( Id.Segment.toList("Gender", "All Gender", "M"), salesCubeSchemaReader); Member femaleChildMember = member( Id.Segment.toList("Gender", "All Gender", "F"), salesCubeSchemaReader); List memberList = new ArrayList(); memberList.add(maleChildMember); memberList.add(maleChildMember); memberList.add(femaleChildMember); TupleList tuples = new UnaryTupleList(memberList); tuples = optimizeChildren(tuples); assertEquals(3, tuples.size()); } public void testMemberCountIsSameForAllMembersInTuple() { TupleList memberList = CrossJoinFunDef.mutableCrossJoin( genderMembersIncludingAll( false, salesCubeSchemaReader, salesCube), storeMembersUsaAndCanada( false, salesCubeSchemaReader, salesCube)); Map[] memberCounterMap = AggregateFunDef.AggregateCalc.membersVersusOccurencesInTuple( memberList); assertTrue( Util.areOccurencesEqual( memberCounterMap[0].values())); assertTrue( Util.areOccurencesEqual( memberCounterMap[1].values())); } public void testMemberCountIsNotSameForAllMembersInTuple() { Member maleChild = member( Id.Segment.toList("Gender", "All Gender", "M"), salesCubeSchemaReader); Member femaleChild = member( Id.Segment.toList("Gender", "All Gender", "F"), salesCubeSchemaReader); Member mexicoMember = member( Id.Segment.toList("Store", "All Stores", "Mexico"), salesCubeSchemaReader); TupleList memberList = new UnaryTupleList( Collections.singletonList(maleChild)); memberList = CrossJoinFunDef.mutableCrossJoin( memberList, storeMembersUsaAndCanada( false, salesCubeSchemaReader, salesCube)); memberList.addTuple(femaleChild, mexicoMember); Map[] memberCounterMap = AggregateFunDef.AggregateCalc.membersVersusOccurencesInTuple( memberList); assertFalse( Util.areOccurencesEqual( memberCounterMap[0].values())); assertTrue( Util.areOccurencesEqual( memberCounterMap[1].values())); } public void testAggregatesAtTheSameLevelForNormalAndDistinctCountMeasure() { boolean useGroupingSets = props.EnableGroupingSets.get(); props.EnableGroupingSets.set(true); try { assertQueryReturns( "WITH " + "MEMBER GENDER.AGG AS 'AGGREGATE({ GENDER.[F] })' " + "MEMBER GENDER.AGG2 AS 'AGGREGATE({ GENDER.[M] })' " + "SELECT " + "{ MEASURES.[CUSTOMER COUNT], MEASURES.[UNIT SALES] } ON 0, " + "{ GENDER.AGG, GENDER.AGG2 } ON 1 \n" + "FROM SALES", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[AGG]}\n" + "{[Gender].[AGG2]}\n" + "Row #0: 2,755\n" + "Row #0: 131,558\n" + "Row #1: 2,826\n" + "Row #1: 135,215\n"); } finally { props.EnableGroupingSets.set(useGroupingSets); } } public void testDistinctCountForAggregatesAtTheSameLevel() { boolean useGroupingSets = props.EnableGroupingSets.get(); props.EnableGroupingSets.set(true); try { assertQueryReturns( "WITH " + "MEMBER GENDER.AGG AS 'AGGREGATE({ GENDER.[F], GENDER.[M] })' " + "SELECT " + "{MEASURES.[CUSTOMER COUNT]} ON 0, " + "{GENDER.AGG } ON 1 " + "FROM SALES", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Gender].[AGG]}\n" + "Row #0: 5,581\n"); } finally { props.EnableGroupingSets.set(useGroupingSets); } } /** * This test makes sure that the AggregateFunDef will not optimize a tuples * list when the rollup policy is set to something else than FULL, as it * results in wrong data for a distinct count operation when using roles to * narrow down the members access. */ public void testMondrian906() { final TestContext context = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); final String mdx = "select {[Customers].[USA], [Customers].[USA].[OR], [Customers].[USA].[WA]} on columns, {[Measures].[Customer Count]} on rows from [Sales]"; context .withRole("Role1") .assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA]}\n" + "{[Customers].[USA].[OR]}\n" + "{[Customers].[USA].[WA]}\n" + "Axis #2:\n" + "{[Measures].[Customer Count]}\n" + "Row #0: 2,865\n" + "Row #0: 1,037\n" + "Row #0: 1,828\n"); } private boolean tuppleListContains( TupleList tuples, Member memberByUniqueName) { if (tuples.getArity() == 1) { return tuples.contains( Collections.singletonList(memberByUniqueName)); } for (List tuple : tuples) { if (tuple.contains(memberByUniqueName)) { return true; } } return false; } private TupleList optimizeChildren(final TupleList memberList) { return Locus.execute( Execution.NONE, "AggregationOnDistinctCountMeasuresTest", new Locus.Action() { public TupleList execute() { return AggregateFunDef.AggregateCalc.optimizeChildren( memberList, schemaReader, salesCube); } } ); } } // End AggregationOnDistinctCountMeasuresTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/CacheControlTest.java0000644000175000017500000013365011735330606024644 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2012 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.olap.CacheControl.CellRegion; import mondrian.test.*; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; /** * Unit-test for cache-flushing functionality. * * @author jhyde * @since Sep 27, 2006 */ public class CacheControlTest extends FoodMartTestCase { /** * Creates a CacheControlTest. */ public CacheControlTest() { } /** * Creates a CacheControlTest with the given name. */ public CacheControlTest(String name) { super(name); } /** * Returns the repository of result strings. * @return repository of result strings */ DiffRepository getDiffRepos() { return DiffRepository.lookup(CacheControlTest.class); } /** * Flushes the entire contents of the cache. Utility method used to ensure * that cache control tests are starting with a blank page. * * @param testContext Test context */ public static void flushCache(TestContext testContext) { final CacheControl cacheControl = testContext.getConnection().getCacheControl(null); // Flush the entire cache. CacheControl.CellRegion measuresRegion = null; for (Cube cube : testContext.getConnection().getSchema().getCubes()) { measuresRegion = cacheControl.createMeasuresRegion(cube); cacheControl.flush(measuresRegion); } // Check the cache is empty. StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); cacheControl.printCacheState(pw, measuresRegion); pw.flush(); assertEquals("", sw.toString()); } /** * Asserts that a cache state string is equal to an expected cache state, * after segment ids have been masked out. * * @param tag Tag of resource in diff repository * @param expected Expected state * @param actual Actual state */ private void assertCacheStateEquals( String tag, String expected, String actual) { String expected2 = expected.replaceAll("Segment #[0-9]+", "Segment ##"); String actual2 = actual.replaceAll("Segment #[0-9]+", "Segment ##"); getDiffRepos().assertEquals(tag, expected2, actual2); } /** * Runs a simple query an asserts that the results are as expected. * * @param testContext Test context */ private void standardQuery(TestContext testContext) { testContext.assertQueryReturns( "select {[Time].[Time].Members} on columns,\n" + " {[Product].Children} on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q1].[1]}\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[5]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q3].[7]}\n" + "{[Time].[1997].[Q3].[8]}\n" + "{[Time].[1997].[Q3].[9]}\n" + "{[Time].[1997].[Q4]}\n" + "{[Time].[1997].[Q4].[10]}\n" + "{[Time].[1997].[Q4].[11]}\n" + "{[Time].[1997].[Q4].[12]}\n" + "{[Time].[1998]}\n" + "{[Time].[1998].[Q1]}\n" + "{[Time].[1998].[Q1].[1]}\n" + "{[Time].[1998].[Q1].[2]}\n" + "{[Time].[1998].[Q1].[3]}\n" + "{[Time].[1998].[Q2]}\n" + "{[Time].[1998].[Q2].[4]}\n" + "{[Time].[1998].[Q2].[5]}\n" + "{[Time].[1998].[Q2].[6]}\n" + "{[Time].[1998].[Q3]}\n" + "{[Time].[1998].[Q3].[7]}\n" + "{[Time].[1998].[Q3].[8]}\n" + "{[Time].[1998].[Q3].[9]}\n" + "{[Time].[1998].[Q4]}\n" + "{[Time].[1998].[Q4].[10]}\n" + "{[Time].[1998].[Q4].[11]}\n" + "{[Time].[1998].[Q4].[12]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 24,597\n" + "Row #0: 5,976\n" + "Row #0: 1,910\n" + "Row #0: 1,951\n" + "Row #0: 2,115\n" + "Row #0: 5,895\n" + "Row #0: 1,948\n" + "Row #0: 2,039\n" + "Row #0: 1,908\n" + "Row #0: 6,065\n" + "Row #0: 2,205\n" + "Row #0: 1,921\n" + "Row #0: 1,939\n" + "Row #0: 6,661\n" + "Row #0: 1,898\n" + "Row #0: 2,344\n" + "Row #0: 2,419\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #1: 191,940\n" + "Row #1: 47,809\n" + "Row #1: 15,604\n" + "Row #1: 15,142\n" + "Row #1: 17,063\n" + "Row #1: 44,825\n" + "Row #1: 14,393\n" + "Row #1: 15,055\n" + "Row #1: 15,377\n" + "Row #1: 47,440\n" + "Row #1: 17,036\n" + "Row #1: 15,741\n" + "Row #1: 14,663\n" + "Row #1: 51,866\n" + "Row #1: 14,232\n" + "Row #1: 18,278\n" + "Row #1: 19,356\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #2: 50,236\n" + "Row #2: 12,506\n" + "Row #2: 4,114\n" + "Row #2: 3,864\n" + "Row #2: 4,528\n" + "Row #2: 11,890\n" + "Row #2: 3,838\n" + "Row #2: 3,987\n" + "Row #2: 4,065\n" + "Row #2: 12,343\n" + "Row #2: 4,522\n" + "Row #2: 4,035\n" + "Row #2: 3,786\n" + "Row #2: 13,497\n" + "Row #2: 3,828\n" + "Row #2: 4,648\n" + "Row #2: 5,021\n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n"); } // --------------------- // Tests /** * Tests creation of a cell region against an abstract implementation of * {@link CacheControl}. */ public void testCreateCellRegion() { // Execute a query. final TestContext testContext = getTestContext(); final RolapConnection connection = ((RolapConnection) testContext.getConnection()); final CacheControl cacheControl = new CacheControlImpl(connection); final CacheControl.CellRegion region = createCellRegion(testContext, cacheControl); assertNotNull(region); } /** * Creates a cell region, runs a query, then flushes the cache. */ public void testNormalize2() { // Execute a query. final TestContext testContext = getTestContext(); final CacheControl cacheControl = testContext.getConnection().getCacheControl(null); final CacheControl.CellRegion region = createCellRegion(testContext, cacheControl); CacheControlImpl.CellRegion normalizedRegion = ((CacheControlImpl) cacheControl).normalize( (CacheControlImpl.CellRegionImpl) region); assertEquals( "Union(" + "Crossjoin(" + "Member([Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer]), " + "Member([Time].[1997].[Q1]), " + "Member([Measures].[Unit Sales], [Measures].[Store Cost], [Measures].[Store Sales], [Measures].[Sales Count], [Measures].[Customer Count], [Measures].[Promotion Sales])), " + "Crossjoin(" + "Member([Product].[Drink].[Dairy]), " + "Member([Time].[1997].[Q1]), " + "Member([Measures].[Unit Sales], [Measures].[Store Cost], [Measures].[Store Sales], [Measures].[Sales Count], [Measures].[Customer Count], [Measures].[Promotion Sales])))", normalizedRegion.toString()); } /** * Creates a cell region, runs a query, then flushes the cache. */ public void testFlush() { assertQueryReturns( "SELECT {[Product].[Product Department].MEMBERS} ON AXIS(0),\n" + "{{[Gender].[Gender].MEMBERS}, {[Gender].[All Gender]}} ON AXIS(1)\n" + "FROM [Sales 2] WHERE {[Measures].[Unit Sales]}", "Axis #0:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #1:\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Drink].[Dairy]}\n" + "{[Product].[Food].[Baked Goods]}\n" + "{[Product].[Food].[Baking Goods]}\n" + "{[Product].[Food].[Breakfast Foods]}\n" + "{[Product].[Food].[Canned Foods]}\n" + "{[Product].[Food].[Canned Products]}\n" + "{[Product].[Food].[Dairy]}\n" + "{[Product].[Food].[Deli]}\n" + "{[Product].[Food].[Eggs]}\n" + "{[Product].[Food].[Frozen Foods]}\n" + "{[Product].[Food].[Meat]}\n" + "{[Product].[Food].[Produce]}\n" + "{[Product].[Food].[Seafood]}\n" + "{[Product].[Food].[Snack Foods]}\n" + "{[Product].[Food].[Snacks]}\n" + "{[Product].[Food].[Starchy Foods]}\n" + "{[Product].[Non-Consumable].[Carousel]}\n" + "{[Product].[Non-Consumable].[Checkout]}\n" + "{[Product].[Non-Consumable].[Health and Hygiene]}\n" + "{[Product].[Non-Consumable].[Household]}\n" + "{[Product].[Non-Consumable].[Periodicals]}\n" + "Axis #2:\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "{[Gender].[All Gender]}\n" + "Row #0: 3,439\n" + "Row #0: 6,776\n" + "Row #0: 1,987\n" + "Row #0: 3,771\n" + "Row #0: 9,841\n" + "Row #0: 1,821\n" + "Row #0: 9,407\n" + "Row #0: 867\n" + "Row #0: 6,513\n" + "Row #0: 5,990\n" + "Row #0: 2,001\n" + "Row #0: 13,011\n" + "Row #0: 841\n" + "Row #0: 18,713\n" + "Row #0: 947\n" + "Row #0: 14,936\n" + "Row #0: 3,459\n" + "Row #0: 2,696\n" + "Row #0: 368\n" + "Row #0: 887\n" + "Row #0: 7,841\n" + "Row #0: 13,278\n" + "Row #0: 2,168\n" + "Row #1: 3,399\n" + "Row #1: 6,797\n" + "Row #1: 2,199\n" + "Row #1: 4,099\n" + "Row #1: 10,404\n" + "Row #1: 1,496\n" + "Row #1: 9,619\n" + "Row #1: 945\n" + "Row #1: 6,372\n" + "Row #1: 6,047\n" + "Row #1: 2,131\n" + "Row #1: 13,644\n" + "Row #1: 873\n" + "Row #1: 19,079\n" + "Row #1: 817\n" + "Row #1: 15,609\n" + "Row #1: 3,425\n" + "Row #1: 2,566\n" + "Row #1: 473\n" + "Row #1: 892\n" + "Row #1: 8,443\n" + "Row #1: 13,760\n" + "Row #1: 2,126\n" + "Row #2: 6,838\n" + "Row #2: 13,573\n" + "Row #2: 4,186\n" + "Row #2: 7,870\n" + "Row #2: 20,245\n" + "Row #2: 3,317\n" + "Row #2: 19,026\n" + "Row #2: 1,812\n" + "Row #2: 12,885\n" + "Row #2: 12,037\n" + "Row #2: 4,132\n" + "Row #2: 26,655\n" + "Row #2: 1,714\n" + "Row #2: 37,792\n" + "Row #2: 1,764\n" + "Row #2: 30,545\n" + "Row #2: 6,884\n" + "Row #2: 5,262\n" + "Row #2: 841\n" + "Row #2: 1,779\n" + "Row #2: 16,284\n" + "Row #2: 27,038\n" + "Row #2: 4,294\n"); if (MondrianProperties.instance().DisableCaching.get()) { return; } final TestContext testContext = getTestContext(); flushCache(testContext); // Make sure MaxConstraint is high enough int minConstraints = 3; if (MondrianProperties.instance().MaxConstraints.get() < minConstraints) { propSaver.set( MondrianProperties.instance().MaxConstraints, minConstraints); } // Execute a query, to bring data into the cache. standardQuery(testContext); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); final CacheControl cacheControl = testContext.getConnection().getCacheControl(pw); // Flush the cache. This time, flush is successful. final CacheControl.CellRegion region = createCellRegion(testContext, cacheControl); cacheControl.flush(region); pw.flush(); String tag = "output"; String expected = "${output}"; String actual = sw.toString(); assertCacheStateEquals(tag, expected, actual); // Run query again, then inspect the contents of the cache. standardQuery(testContext); sw.getBuffer().setLength(0); cacheControl.printCacheState(pw, region); pw.flush(); assertCacheStateEquals("output2", "${output2}", sw.toString()); } /** * Creates a partial cell region, runs a query, then flushes the cache. */ public void testPartialFlush() { if (MondrianProperties.instance().DisableCaching.get()) { return; } final TestContext testContext = getTestContext(); flushCache(testContext); // Execute a query. StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); final CacheControl cacheControl = testContext.getConnection().getCacheControl(pw); // Create a region ([Measures].[Unit Sales], [Time].[1997].[Q1]) final CacheControl.CellRegion region = createCellRegion1997_Q1_UnitSales(testContext, cacheControl); // Execute a query, to bring data into the cache. standardQuery(testContext); // This time, flush is successful. // The segment on "year" is entirely flushed. // The segment on "year", "quarter" has "Q1" masked out. // The segment on "year", "quarter", "month" has "Q1" masked out. cacheControl.flush(region); pw.flush(); assertCacheStateEquals("output", "${output}", sw.toString()); // Flush the same region again. Should be the same result. sw.getBuffer().setLength(0); cacheControl.flush(region); pw.flush(); assertCacheStateEquals("output2", "${output2}", sw.toString()); // Create the region ([Time].[1997]) final CacheControl.CellRegion region2 = createCellRegion1997(testContext, cacheControl); // Flush a different region. Everything is in 1997, so the entire cache // is emptied. sw.getBuffer().setLength(0); cacheControl.flush(region2); pw.flush(); assertCacheStateEquals("output3", "${output3}", sw.toString()); // Create the region ([Gender].[F], [Product].[Drink] : // [Product].[Food]) final CacheControl.CellRegion region3 = createCellRegionFemaleFoodDrink(testContext, cacheControl); // Flush a different region. sw.getBuffer().setLength(0); cacheControl.flush(region3); pw.flush(); assertCacheStateEquals("output4", "${output4}", sw.toString()); // Run query again, just to make sure. standardQuery(testContext); } /** * Creates a partial cell region over a range, runs a query, then flushes * the cache. */ public void testPartialFlushRange() { if (MondrianProperties.instance().DisableCaching.get()) { return; } final TestContext testContext = getTestContext(); flushCache(testContext); // Execute a query. StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); final CacheControl cacheControl = testContext.getConnection().getCacheControl(pw); // Create a region // ([Measures].[Unit Sales], // [Time].[1997].[Q2].[4] .. infinity) final CacheControl.CellRegion region = createCellRegionAprilOnwards(testContext, cacheControl); // Execute a query, to bring data into the cache. standardQuery(testContext); // This time, flush is successful. // The segment on "year" is entirely flushed. // The segment on "year", "quarter" has "Q2", "Q3", "Q4" masked out. // The segment on "year", "quarter", "month" has "Q3", "Q4" masked out, // and "Q2" masked out if month > 6. cacheControl.flush(region); pw.flush(); assertCacheStateEquals("output", "${output}", sw.toString()); // Flush the same region again. Should be the same result. sw.getBuffer().setLength(0); cacheControl.flush(region); pw.flush(); assertCacheStateEquals("output2", "${output2}", sw.toString()); // Run query again, then inspect the contents of the cache. standardQuery(testContext); sw.getBuffer().setLength(0); cacheControl.printCacheState(pw, region); pw.flush(); assertCacheStateEquals("output3", "${output3}", sw.toString()); } /** * Creates a cell region using a given {@link CacheControl}, and runs some * sanity checks. * * @param testContext Test context * @param cacheControl Cache control */ private CacheControl.CellRegion createCellRegion( TestContext testContext, CacheControl cacheControl) { // Flush a region of the cache. final Connection connection = testContext.getConnection(); final Cube salesCube = connection.getSchema().lookupCube("Sales", true); // Region consists of [Time].[1997].[Q1] and its children, and products // [Beer] and [Dairy]. final SchemaReader schemaReader = salesCube.getSchemaReader(null).withLocus(); final Member memberQ1 = schemaReader.getMemberByUniqueName( Id.Segment.toList("Time", "1997", "Q1"), true); final Member memberBeer = schemaReader.getMemberByUniqueName( Id.Segment.toList( "Product", "Drink", "Alcoholic Beverages", "Beer and Wine", "Beer"), true); final Member memberDairy = schemaReader.getMemberByUniqueName( Id.Segment.toList("Product", "Drink", "Dairy"), true); final CacheControl.CellRegion regionTimeQ1 = cacheControl.createMemberRegion(memberQ1, true); assertEquals("Member([Time].[1997].[Q1])", regionTimeQ1.toString()); final CacheControl.CellRegion regionProductBeer = cacheControl.createMemberRegion(memberBeer, false); assertEquals( "Member([Product].[Drink]." + "[Alcoholic Beverages].[Beer and Wine].[Beer])", regionProductBeer.toString()); final CacheControl.CellRegion regionProductDairy = cacheControl.createMemberRegion(memberDairy, true); final CacheControl.CellRegion regionProductUnion = cacheControl.createUnionRegion( regionProductBeer, regionProductDairy); assertEquals( "Union(Member([Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer]), Member([Product].[Drink].[Dairy]))", regionProductUnion.toString()); final CacheControl.CellRegion regionProductXTime = cacheControl.createCrossjoinRegion( regionProductUnion, regionTimeQ1); assertEquals( "Crossjoin(Union(Member([Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer]), Member([Product].[Drink].[Dairy])), Member([Time].[1997].[Q1]))", regionProductXTime.toString()); try { cacheControl.flush(regionProductXTime); fail("expceted error"); } catch (RuntimeException e) { assertContains( "Region of cells to be flushed must contain measures.", e.getMessage()); } final CacheControl.CellRegion measuresRegion = cacheControl.createMeasuresRegion(salesCube); return cacheControl.createCrossjoinRegion( regionProductXTime, measuresRegion); } /** * Creates a cell region using a given {@link CacheControl}, containing * only [Time].[1997].[Q1] * {Measures}. * * @param testContext Test context * @param cacheControl Cache control */ private CacheControl.CellRegion createCellRegion1997_Q1_UnitSales( TestContext testContext, CacheControl cacheControl) { final Connection connection = testContext.getConnection(); final Cube salesCube = connection.getSchema().lookupCube("Sales", true); // Region consists of [Time].[1997].[Q1] and its children. final SchemaReader schemaReader = salesCube.getSchemaReader(null).withLocus(); final Member memberQ1 = schemaReader.getMemberByUniqueName( Id.Segment.toList("Time", "1997", "Q1"), true); final CacheControl.CellRegion regionTimeQ1 = cacheControl.createMemberRegion(memberQ1, true); assertEquals("Member([Time].[1997].[Q1])", regionTimeQ1.toString()); final CacheControl.CellRegion measuresRegion = cacheControl.createMeasuresRegion(salesCube); return cacheControl.createCrossjoinRegion( regionTimeQ1, measuresRegion); } /** * Creates a cell region using a given {@link CacheControl}, containing * only [Time].[1997].[Q1] * {Measures}. * * @param testContext Test context * @param cacheControl Cache control */ private CacheControl.CellRegion createCellRegionAprilOnwards( TestContext testContext, CacheControl cacheControl) { final Connection connection = testContext.getConnection(); final Cube salesCube = connection.getSchema().lookupCube("Sales", true); // Region consists of [Time].[1997].[Q2].[4] and its children. final SchemaReader schemaReader = salesCube.getSchemaReader(null).withLocus(); final Member memberApril = schemaReader.getMemberByUniqueName( Id.Segment.toList("Time", "1997", "Q2", "4"), true); final CacheControl.CellRegion regionTimeApril = cacheControl.createMemberRegion( true, memberApril, false, null, true); assertEquals( "Range([Time].[1997].[Q2].[4] inclusive to null)", regionTimeApril.toString()); final CacheControl.CellRegion measuresRegion = cacheControl.createMeasuresRegion(salesCube); return cacheControl.createCrossjoinRegion( regionTimeApril, measuresRegion); } /** * Creates a cell region using a given {@link CacheControl}, containing * only [Time].[1997] * {Measures}. * * @param testContext Test context * @param cacheControl Cache control */ private CacheControl.CellRegion createCellRegion1997( TestContext testContext, CacheControl cacheControl) { final Connection connection = testContext.getConnection(); final Cube salesCube = connection.getSchema().lookupCube("Sales", true); // Region consists of [Time].[1997] and its children. final SchemaReader schemaReader = salesCube.getSchemaReader(null); final Member member1997 = schemaReader.getMemberByUniqueName( Id.Segment.toList("Time", "1997"), true); final CacheControl.CellRegion region1997 = cacheControl.createMemberRegion(member1997, true); assertEquals( "Member([Time].[1997])", region1997.toString()); final CacheControl.CellRegion measuresRegion = cacheControl.createMeasuresRegion(salesCube); return cacheControl.createCrossjoinRegion( region1997, measuresRegion); } /** * Creates a cell region using a given {@link CacheControl}, containing * only [Gender].[F] * {[Product].[Food], [Product].[Drink]} * {Measures}. * * @param testContext Test context * @param cacheControl Cache control */ private CacheControl.CellRegion createCellRegionFemaleFoodDrink( TestContext testContext, CacheControl cacheControl) { final Connection connection = testContext.getConnection(); final Cube salesCube = connection.getSchema().lookupCube("Sales", true); // Region consists of [Product].[Food], [Product].[Drink] and their // children. final SchemaReader schemaReader = salesCube.getSchemaReader(null).withLocus(); final Member memberFood = schemaReader.getMemberByUniqueName( Id.Segment.toList("Product", "Food"), true); final Member memberDrink = schemaReader.getMemberByUniqueName( Id.Segment.toList("Product", "Drink"), true); final Member memberFemale = schemaReader.getMemberByUniqueName( Id.Segment.toList("Gender", "F"), true); final CacheControl.CellRegion regionProductFoodDrink = cacheControl.createMemberRegion( true, memberDrink, true, memberFood, true); assertEquals( "Range([Product].[Drink] inclusive to [Product].[Food] inclusive)", regionProductFoodDrink.toString()); final CacheControl.CellRegion regionFemale = cacheControl.createMemberRegion(memberFemale, true); final CacheControl.CellRegion measuresRegion = cacheControl.createMeasuresRegion(salesCube); return cacheControl.createCrossjoinRegion( regionProductFoodDrink, measuresRegion, regionFemale); } /** * Asserts that a given string contains a given pattern. * * @param pattern Pattern to find * @param message String * @throws junit.framework.AssertionFailedError if pattern is not found */ static void assertContains(String pattern, String message) { assertTrue(message, message.indexOf(pattern) > -1); } /** * A number of negative tests, trying to do invalid things with cache * flushing and getting errors. */ public void testNegative() { final TestContext testContext = getTestContext(); final Connection connection = testContext.getConnection(); final Cube salesCube = connection.getSchema().lookupCube("Sales", true); final SchemaReader schemaReader = salesCube.getSchemaReader(null); final CacheControl cacheControl = new CacheControlImpl((RolapConnection) connection); final Member memberQ1 = schemaReader.withLocus().getMemberByUniqueName( Id.Segment.toList("Time", "1997", "Q1"), true); final Member memberBeer = schemaReader.withLocus().getMemberByUniqueName( Id.Segment.toList( "Product", "Drink", "Alcoholic Beverages", "Beer and Wine"), true); final Member memberDairy = schemaReader.withLocus().getMemberByUniqueName( Id.Segment.toList("Product", "Drink", "Dairy"), true); final CacheControl.CellRegion regionTimeQ1 = cacheControl.createMemberRegion(memberQ1, false); final CacheControl.CellRegion regionProductBeer = cacheControl.createMemberRegion(memberBeer, false); final CacheControl.CellRegion regionProductDairy = cacheControl.createMemberRegion(memberDairy, true); // Try to combine [Time] region with [Product] region. // Cannot union regions with different dimensionality. try { final CacheControl.CellRegion cellRegion = cacheControl.createUnionRegion( regionTimeQ1, regionProductBeer); fail("expected exception, got " + cellRegion); } catch (RuntimeException e) { assertContains( "Cannot union cell regions of different dimensionalities. " + "(Dimensionalities are '[[Time]]', '[[Product]]'.)", e.getMessage()); } final CacheControl.CellRegion regionTimeXProduct = cacheControl.createCrossjoinRegion( regionTimeQ1, regionProductBeer); assertNotNull(regionTimeXProduct); assertEquals(2, regionTimeXProduct.getDimensionality().size()); assertEquals( "Crossjoin(Member([Time].[1997].[Q1]), " + "Member([Product].[Drink].[Alcoholic Beverages]." + "[Beer and Wine]))", regionTimeXProduct.toString()); // Try to combine ([Time], [Product]) region with ([Time]) region. try { final CacheControl.CellRegion cellRegion = cacheControl.createUnionRegion( regionTimeXProduct, regionTimeQ1); fail("expected exception, got " + cellRegion); } catch (RuntimeException e) { assertContains( "Cannot union cell regions of different dimensionalities. " + "(Dimensionalities are '[[Time], [Product]]', '[[Time]]'.)", e.getMessage()); } // Try to combine ([Time], [Product]) region with ([Product]) region. try { final CacheControl.CellRegion cellRegion = cacheControl.createUnionRegion( regionTimeXProduct, regionProductBeer); fail("expected exception, got " + cellRegion); } catch (RuntimeException e) { assertContains( "Cannot union cell regions of different dimensionalities. " + "(Dimensionalities are '[[Time], [Product]]', " + "'[[Product]]'.)", e.getMessage()); } // Try to combine ([Time]) region with ([Time], [Product]) region. try { final CacheControl.CellRegion cellRegion = cacheControl.createUnionRegion( regionTimeQ1, regionTimeXProduct); fail("expected exception, got " + cellRegion); } catch (RuntimeException e) { assertContains( "Cannot union cell regions of different dimensionalities. " + "(Dimensionalities are '[[Time]]', '[[Time], [Product]]'.)", e.getMessage()); } // Union [Time] region with itself -- OK. final CacheControl.CellRegion regionTimeUnionTime = cacheControl.createUnionRegion( regionTimeQ1, regionTimeQ1); assertNotNull(regionTimeUnionTime); assertEquals(1, regionTimeUnionTime.getDimensionality().size()); // Union [Time] region with itself -- OK. final CacheControl.CellRegion regionTimeXProductUnionTimeXProduct = cacheControl.createUnionRegion( regionTimeXProduct, regionTimeXProduct); assertNotNull(regionTimeXProductUnionTimeXProduct); assertEquals( 2, regionTimeXProductUnionTimeXProduct.getDimensionality().size()); // Cartesian product two [Product] regions - not OK. try { final CacheControl.CellRegion cellRegion = cacheControl.createCrossjoinRegion( regionProductBeer, regionProductDairy); fail("expected exception, got " + cellRegion); } catch (RuntimeException e) { assertContains( "Cannot crossjoin cell regions which have dimensions in common." + " (Dimensionalities are '[[Product]]', '[[Product]]'.)", e.getMessage()); } // Cartesian product [Product] and [Time] x [Product] regions - not OK. try { final CacheControl.CellRegion cellRegion = cacheControl.createCrossjoinRegion( regionProductBeer, regionTimeXProduct); fail("expected exception, got " + cellRegion); } catch (RuntimeException e) { assertContains( "Cannot crossjoin cell regions which have dimensions in common." + " (Dimensionalities are " + "'[[Product]]', '[[Time], [Product]]'.)", e.getMessage()); } } /** * Tests crossjoin of regions, {@link CacheControl#createCrossjoinRegion}. */ public void testCrossjoin() { final TestContext testContext = getTestContext(); final Connection connection = testContext.getConnection(); final Cube salesCube = connection.getSchema().lookupCube("Sales", true); final CacheControl cacheControl = new CacheControlImpl((RolapConnection) connection); // Region consists of [Time].[1997].[Q1] and its children, and products // [Beer] and [Dairy]. final SchemaReader schemaReader = salesCube.getSchemaReader(null).withLocus(); final Member memberQ1 = schemaReader.getMemberByUniqueName( Id.Segment.toList("Time", "1997", "Q1"), true); final Member memberBeer = schemaReader.getMemberByUniqueName( Id.Segment.toList( "Product", "Drink", "Alcoholic Beverages", "Beer and Wine", "Beer"), true); final CacheControl.CellRegion regionProductBeer = cacheControl.createMemberRegion(memberBeer, false); final Member memberFemale = schemaReader.getMemberByUniqueName( Id.Segment.toList("Gender", "F"), true); final CacheControl.CellRegion regionGenderFemale = cacheControl.createMemberRegion(memberFemale, false); final CacheControl.CellRegion regionTimeQ1 = cacheControl.createMemberRegion(memberQ1, true); final CacheControl.CellRegion regionTimeXProduct = cacheControl.createCrossjoinRegion( regionTimeQ1, regionProductBeer); // Compose a crossjoin with a non crossjoin final CacheControl.CellRegion regionTimeXProductXGender = cacheControl.createCrossjoinRegion( regionTimeXProduct, regionGenderFemale); assertEquals( "Crossjoin(" + "Member([Time].[1997].[Q1]), " + "Member([Product].[Drink].[Alcoholic Beverages]." + "[Beer and Wine].[Beer]), " + "Member([Gender].[F]))", regionTimeXProductXGender.toString()); assertEquals( "[[Time], [Product], [Gender]]", regionTimeXProductXGender.getDimensionality().toString()); // Three-way crossjoin, should be same as previous final CacheControl.CellRegion regionTimeXProductXGender2 = cacheControl.createCrossjoinRegion( regionTimeQ1, regionProductBeer, regionGenderFemale); assertEquals( "Crossjoin(" + "Member([Time].[1997].[Q1]), " + "Member([Product].[Drink].[Alcoholic Beverages]" + ".[Beer and Wine].[Beer]), " + "Member([Gender].[F]))", regionTimeXProductXGender2.toString()); assertEquals( "[[Time], [Product], [Gender]]", regionTimeXProductXGender2.getDimensionality().toString()); // Compose a non crossjoin with a crossjoin final CacheControl.CellRegion regionGenderXTimeXProduct = cacheControl.createCrossjoinRegion( regionGenderFemale, regionTimeXProduct); assertEquals( "Crossjoin(" + "Member([Gender].[F]), " + "Member([Time].[1997].[Q1]), " + "Member([Product].[Drink].[Alcoholic Beverages]" + ".[Beer and Wine].[Beer]))", regionGenderXTimeXProduct.toString()); assertEquals( "[[Gender], [Time], [Product]]", regionGenderXTimeXProduct.getDimensionality().toString()); } /** * Helper method, creates a region consisting of a single member, given its * unique name (e.g. "[Gender].[F]"). */ CacheControl.CellRegion memberRegion(String uniqueName) { final String[] names = uniqueName.split("\\."); final List ids = new ArrayList(names.length); for (int i = 0; i < names.length; i++) { String name = names[i]; assert name.startsWith("[") && name.endsWith("]"); names[i] = name.substring(1, name.length() - 1); ids.add(new Id.Segment(names[i], Id.Quoting.QUOTED)); } final TestContext testContext = getTestContext(); final Connection connection = testContext.getConnection(); final Cube salesCube = connection.getSchema().lookupCube("Sales", true); final CacheControl cacheControl = new CacheControlImpl((RolapConnection) connection); final SchemaReader schemaReader = salesCube.getSchemaReader(null).withLocus(); final Member member = schemaReader.getMemberByUniqueName(ids, true); return cacheControl.createMemberRegion(member, false); } /** * Tests the algorithm which converts a cache region specification into * normal form. */ public void testNormalize() { // Create // Union( // Crossjoin( // [Marital Status].[S], // Union( // Crossjoin( // [Gender].[F] // [Time].[1997].[Q1]) // Crossjoin( // [Gender].[M] // [Time].[1997].[Q2])) // Crossjoin( // Crossjoin( // [Marital Status].[S], // [Gender].[F]) // [Time].[1997].[Q1]) // final CacheControl cacheControl = new CacheControlImpl( (RolapConnection) getTestContext().getConnection()); final CacheControl.CellRegion region = cacheControl.createUnionRegion( cacheControl.createCrossjoinRegion( memberRegion("[Marital Status].[S]"), cacheControl.createUnionRegion( cacheControl.createCrossjoinRegion( memberRegion("[Gender].[F]"), memberRegion("[Time].[1997].[Q1]")), cacheControl.createCrossjoinRegion( memberRegion("[Gender].[M]"), memberRegion("[Time].[1997].[Q2]")))), cacheControl.createCrossjoinRegion( cacheControl.createCrossjoinRegion( memberRegion("[Marital Status].[S]"), memberRegion("[Gender].[F]")), memberRegion("[Time].[1997].[Q1]"))); assertEquals( "Union(" + "Crossjoin(" + "Member([Marital Status].[S]), " + "Union(" + "Crossjoin(" + "Member([Gender].[F]), " + "Member([Time].[1997].[Q1])), " + "Crossjoin(Member([Gender].[M]), " + "Member([Time].[1997].[Q2])))), " + "Crossjoin(" + "Member([Marital Status].[S]), " + "Member([Gender].[F]), " + "Member([Time].[1997].[Q1])))", region.toString()); final CacheControl.CellRegion normalizedRegion = ((CacheControlImpl) cacheControl).normalize( (CacheControlImpl.CellRegionImpl) region); assertEquals( "Union(" + "Crossjoin(Member([Marital Status].[S]), Member([Gender].[F]), Member([Time].[1997].[Q1])), " + "Crossjoin(Member([Marital Status].[S]), Member([Gender].[M]), Member([Time].[1997].[Q2])), " + "Crossjoin(Member([Marital Status].[S]), Member([Gender].[F]), Member([Time].[1997].[Q1])))", normalizedRegion.toString()); } /** * Test case for bug * MONDRIAN-1077, * "Cache flush for region that is not necessarily populated results in * NullPointerException". */ public void testFlushNonPrimedContent() throws Exception { final TestContext testContext = getTestContext(); flushCache(testContext); final CacheControl cacheControl = testContext.getConnection().getCacheControl(null); final Cube cube = testContext.getConnection() .getSchema().lookupCube("Sales", true); Hierarchy hier = cube.getDimensions()[2].getHierarchies()[0]; Member hierMember = hier.getAllMember(); CellRegion measuresRegion = cacheControl.createMeasuresRegion(cube); CellRegion hierRegion = cacheControl.createMemberRegion(hierMember, true); CellRegion flushRegion = cacheControl.createCrossjoinRegion(measuresRegion, hierRegion); cacheControl.flush(flushRegion); } public void testMondrian1094() throws Exception { final String query = "select NON EMPTY {[Measures].[Unit Sales]} ON COLUMNS, \n" + "NON EMPTY {[Store].[All Stores].Children} ON ROWS \n" + "from [Sales] \n"; flushCache(getTestContext()); assertQueryReturns( query, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA]}\n" + "Row #0: 266,773\n"); if (MondrianProperties.instance().DisableCaching.get()) { return; } // Make sure MaxConstraint is high enough int minConstraints = 3; if (MondrianProperties.instance().MaxConstraints.get() < minConstraints) { propSaver.set( MondrianProperties.instance().MaxConstraints, minConstraints); } StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); final CacheControl cacheControl = getConnection().getCacheControl(pw); // Flush the cache. final Cube salesCube = getConnection().getSchema().lookupCube("Sales", true); final Hierarchy storeHierarchy = salesCube.lookupHierarchy( new Id.Segment( "Store", Id.Quoting.UNQUOTED), false); final CacheControl.CellRegion measuresRegion = cacheControl.createMeasuresRegion(salesCube); final CacheControl.CellRegion hierarchyRegion = cacheControl.createMemberRegion( storeHierarchy.getAllMember(), true); final CacheControl.CellRegion region = cacheControl.createCrossjoinRegion( measuresRegion, hierarchyRegion); cacheControl.flush(region); pw.flush(); String tag = "output"; String expected = "${output}"; String actual = sw.toString(); assertCacheStateEquals(tag, expected, actual); } // todo: Test flushing a segment which is unconstrained // todo: Test flushing a segment where 2 or more axes are reduced. E.g. // Given segment // (state={CA, OR}, quarter={Q1, Q2, Q3}, year=1997) // flush // (state=OR, quarter=Q2) // which leaves // (state={CA, OR}, quarter={Q1, Q3}, year=1997) // (state=CA, quarter=Q2, year=1997) // For now, we kill the slice of the segment with the fewest values, which // is // (quarter=Q2) // leaving // (state={CA, OR}, quarter={Q1, Q3}, year=1997) // todo: Test flushing values which are not present in a segment. Need to // reduce the scope of the segment. // todo: Solve the fragmentation problem. Continually ask for later and // later times. Two cases: the segment's specification contains the time // asked for (and therefore the segment will later need to be pared back), // and it doesn't. Either way, end up with a lot of segments which could // be merged. Solve by triggering a coalesce or flush, but what should // trigger that? // todo: test which tries to constraint on calc member. Should get error. // todo: test which tries to constrain on member of parent-child hierarchy } // End CacheControlTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/NonEmptyPropertyForAllAxisTest.java0000644000175000017500000002120311735330606027531 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2010 Pentaho and others // Copyright (C) 2005-2006 Thomson Medstat, Inc, Ann Arbor, MI. // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.MondrianProperties; import mondrian.olap.Query; import mondrian.test.FoodMartTestCase; import mondrian.test.TestContext; /** * Tests the {@link MondrianProperties#EnableNonEmptyOnAllAxis} property. */ public class NonEmptyPropertyForAllAxisTest extends FoodMartTestCase { public void testNonEmptyForAllAxesWithPropertySet() { propSaver.set( MondrianProperties.instance().EnableNonEmptyOnAllAxis, true); final String MDX_QUERY = "select {[Country].[USA].[OR].Children} on 0," + " {[Promotions].Members} on 1 " + "from [Sales] " + "where [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Light Beer]"; final String EXPECTED_RESULT = "Axis #0:\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Light Beer]}\n" + "Axis #1:\n" + "{[Customers].[USA].[OR].[Albany]}\n" + "{[Customers].[USA].[OR].[Corvallis]}\n" + "{[Customers].[USA].[OR].[Lake Oswego]}\n" + "{[Customers].[USA].[OR].[Lebanon]}\n" + "{[Customers].[USA].[OR].[Portland]}\n" + "{[Customers].[USA].[OR].[Woodburn]}\n" + "Axis #2:\n" + "{[Promotions].[All Promotions]}\n" + "{[Promotions].[Cash Register Lottery]}\n" + "{[Promotions].[No Promotion]}\n" + "{[Promotions].[Saving Days]}\n" + "Row #0: 4\n" + "Row #0: 6\n" + "Row #0: 5\n" + "Row #0: 10\n" + "Row #0: 6\n" + "Row #0: 3\n" + "Row #1: \n" + "Row #1: 2\n" + "Row #1: \n" + "Row #1: 2\n" + "Row #1: \n" + "Row #1: \n" + "Row #2: 4\n" + "Row #2: 4\n" + "Row #2: 3\n" + "Row #2: 8\n" + "Row #2: 6\n" + "Row #2: 3\n" + "Row #3: \n" + "Row #3: \n" + "Row #3: 2\n" + "Row #3: \n" + "Row #3: \n" + "Row #3: \n"; assertQueryReturns(MDX_QUERY, EXPECTED_RESULT); } public void testNonEmptyForAllAxesWithOutPropertySet() { final String MDX_QUERY = "SELECT {customers.USA.CA.[Santa Cruz].[Brian Merlo]} on 0, " + "[product].[product category].members on 1 FROM [sales]"; final String EXPECTED_RESULT = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[CA].[Santa Cruz].[Brian Merlo]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine]}\n" + "{[Product].[Drink].[Beverages].[Carbonated Beverages]}\n" + "{[Product].[Drink].[Beverages].[Drinks]}\n" + "{[Product].[Drink].[Beverages].[Hot Beverages]}\n" + "{[Product].[Drink].[Beverages].[Pure Juice Beverages]}\n" + "{[Product].[Drink].[Dairy].[Dairy]}\n" + "{[Product].[Food].[Baked Goods].[Bread]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods]}\n" + "{[Product].[Food].[Baking Goods].[Jams and Jellies]}\n" + "{[Product].[Food].[Breakfast Foods].[Breakfast Foods]}\n" + "{[Product].[Food].[Canned Foods].[Canned Anchovies]}\n" + "{[Product].[Food].[Canned Foods].[Canned Clams]}\n" + "{[Product].[Food].[Canned Foods].[Canned Oysters]}\n" + "{[Product].[Food].[Canned Foods].[Canned Sardines]}\n" + "{[Product].[Food].[Canned Foods].[Canned Shrimp]}\n" + "{[Product].[Food].[Canned Foods].[Canned Soup]}\n" + "{[Product].[Food].[Canned Foods].[Canned Tuna]}\n" + "{[Product].[Food].[Canned Foods].[Vegetables]}\n" + "{[Product].[Food].[Canned Products].[Fruit]}\n" + "{[Product].[Food].[Dairy].[Dairy]}\n" + "{[Product].[Food].[Deli].[Meat]}\n" + "{[Product].[Food].[Deli].[Side Dishes]}\n" + "{[Product].[Food].[Eggs].[Eggs]}\n" + "{[Product].[Food].[Frozen Foods].[Breakfast Foods]}\n" + "{[Product].[Food].[Frozen Foods].[Frozen Desserts]}\n" + "{[Product].[Food].[Frozen Foods].[Frozen Entrees]}\n" + "{[Product].[Food].[Frozen Foods].[Meat]}\n" + "{[Product].[Food].[Frozen Foods].[Pizza]}\n" + "{[Product].[Food].[Frozen Foods].[Vegetables]}\n" + "{[Product].[Food].[Meat].[Meat]}\n" + "{[Product].[Food].[Produce].[Fruit]}\n" + "{[Product].[Food].[Produce].[Packaged Vegetables]}\n" + "{[Product].[Food].[Produce].[Specialty]}\n" + "{[Product].[Food].[Produce].[Vegetables]}\n" + "{[Product].[Food].[Seafood].[Seafood]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods]}\n" + "{[Product].[Food].[Snacks].[Candy]}\n" + "{[Product].[Food].[Starchy Foods].[Starchy Foods]}\n" + "{[Product].[Non-Consumable].[Carousel].[Specialty]}\n" + "{[Product].[Non-Consumable].[Checkout].[Hardware]}\n" + "{[Product].[Non-Consumable].[Checkout].[Miscellaneous]}\n" + "{[Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products]}\n" + "{[Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies]}\n" + "{[Product].[Non-Consumable].[Health and Hygiene].[Decongestants]}\n" + "{[Product].[Non-Consumable].[Health and Hygiene].[Hygiene]}\n" + "{[Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers]}\n" + "{[Product].[Non-Consumable].[Household].[Bathroom Products]}\n" + "{[Product].[Non-Consumable].[Household].[Candles]}\n" + "{[Product].[Non-Consumable].[Household].[Cleaning Supplies]}\n" + "{[Product].[Non-Consumable].[Household].[Electrical]}\n" + "{[Product].[Non-Consumable].[Household].[Hardware]}\n" + "{[Product].[Non-Consumable].[Household].[Kitchen Products]}\n" + "{[Product].[Non-Consumable].[Household].[Paper Products]}\n" + "{[Product].[Non-Consumable].[Household].[Plastic Products]}\n" + "{[Product].[Non-Consumable].[Periodicals].[Magazines]}\n" + "Row #0: 2\n" + "Row #1: 2\n" + "Row #2: \n" + "Row #3: \n" + "Row #4: \n" + "Row #5: \n" + "Row #6: \n" + "Row #7: \n" + "Row #8: \n" + "Row #9: \n" + "Row #10: \n" + "Row #11: \n" + "Row #12: \n" + "Row #13: \n" + "Row #14: \n" + "Row #15: \n" + "Row #16: \n" + "Row #17: \n" + "Row #18: \n" + "Row #19: \n" + "Row #20: \n" + "Row #21: \n" + "Row #22: 1\n" + "Row #23: \n" + "Row #24: \n" + "Row #25: \n" + "Row #26: \n" + "Row #27: \n" + "Row #28: 1\n" + "Row #29: \n" + "Row #30: \n" + "Row #31: \n" + "Row #32: \n" + "Row #33: \n" + "Row #34: \n" + "Row #35: \n" + "Row #36: \n" + "Row #37: \n" + "Row #38: \n" + "Row #39: \n" + "Row #40: \n" + "Row #41: \n" + "Row #42: \n" + "Row #43: \n" + "Row #44: \n" + "Row #45: \n" + "Row #46: \n" + "Row #47: \n" + "Row #48: \n" + "Row #49: \n" + "Row #50: 1\n" + "Row #51: \n" + "Row #52: \n" + "Row #53: \n" + "Row #54: 2\n"; assertQueryReturns(MDX_QUERY, EXPECTED_RESULT); } public void testSlicerAxisDoesNotGetNonEmptyApplied() { propSaver.set( MondrianProperties.instance().EnableNonEmptyOnAllAxis, true); String mdxQuery = "select from [Sales]\n" + "where [Time].[1997]\n"; Query query = getConnection().parseQuery(mdxQuery); TestContext.assertEqualsVerbose(mdxQuery, query.toString()); } } // End NonEmptyPropertyForAllAxisTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/TestAggregationManager.java0000644000175000017500000027231311735330606026022 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. // // jhyde, 28 September, 2002 */ package mondrian.rolap; import mondrian.olap.*; import mondrian.rolap.agg.*; import mondrian.server.*; import mondrian.spi.Dialect; import mondrian.test.SqlPattern; import mondrian.test.TestContext; import org.olap4j.impl.Olap4jUtil; import java.util.*; /** * Unit test for {@link AggregationManager}. * * @author jhyde * @since 21 March, 2002 */ public class TestAggregationManager extends BatchTestCase { private static final Set ACCESS_MYSQL = Olap4jUtil.enumSetOf( Dialect.DatabaseProduct.ACCESS, Dialect.DatabaseProduct.MYSQL); private Locus locus; private Execution execution; private AggregationManager aggMgr; @Override protected void setUp() throws Exception { super.setUp(); final Statement statement = ((RolapConnection) getTestContext().getConnection()) .getInternalStatement(); execution = new Execution(statement, 0); aggMgr = execution.getMondrianStatement() .getMondrianConnection() .getServer().getAggregationManager(); locus = new Locus(execution, "TestAggregationManager", null); Locus.push(locus); } @Override protected void tearDown() throws Exception { Locus.pop(locus); // allow gc locus = null; execution = null; aggMgr = null; super.tearDown(); } public TestAggregationManager(String name) { super(name); } public TestAggregationManager() { super(); } public void testFemaleUnitSales() { final FastBatchingCellReader fbcr = new FastBatchingCellReader(execution, getCube("Sales"), aggMgr); CellRequest request = createRequest( "Sales", "[Measures].[Unit Sales]", "customer", "gender", "F"); Object value = aggMgr.getCellFromCache(request); assertNull(value); // before load, the cell is not found fbcr.recordCellRequest(request); fbcr.loadAggregations(); value = aggMgr.getCellFromCache(request); // after load, cell found assertTrue(value instanceof Number); assertEquals(131558, ((Number) value).intValue()); } public void testFemaleCustomerCount() { final FastBatchingCellReader fbcr = new FastBatchingCellReader(execution, getCube("Sales"), aggMgr); CellRequest request = createRequest( "Sales", "[Measures].[Customer Count]", "customer", "gender", "F"); Object value = aggMgr.getCellFromCache(request); assertNull(value); // before load, the cell is not found fbcr.recordCellRequest(request); fbcr.loadAggregations(); value = aggMgr.getCellFromCache(request); // after load, cell found assertTrue(value instanceof Number); assertEquals(2755, ((Number) value).intValue()); } public void testFemaleCustomerCountWithConstraints() { List Q1M1 = new ArrayList (); Q1M1.add(new String[] {"1997", "Q1", "1"}); List Q2M5 = new ArrayList (); Q2M5.add(new String[] {"1997", "Q2", "5"}); List Q1M1Q2M5 = new ArrayList (); Q1M1Q2M5.add(new String[] {"1997", "Q1", "1"}); Q1M1Q2M5.add(new String[] {"1997", "Q2", "5"}); CellRequest request1 = createRequest( "Sales", "[Measures].[Customer Count]", "customer", "gender", "F", makeConstraintYearQuarterMonth(Q1M1)); CellRequest request2 = createRequest( "Sales", "[Measures].[Customer Count]", "customer", "gender", "F", makeConstraintYearQuarterMonth(Q2M5)); CellRequest request3 = createRequest( "Sales", "[Measures].[Customer Count]", "customer", "gender", "F", makeConstraintYearQuarterMonth(Q1M1Q2M5)); FastBatchingCellReader fbcr = new FastBatchingCellReader(execution, getCube("Sales"), aggMgr); Object value = aggMgr.getCellFromCache(request1); assertNull(value); // before load, the cell is not found fbcr.recordCellRequest(request1); fbcr.recordCellRequest(request2); fbcr.recordCellRequest(request3); fbcr.loadAggregations(); value = aggMgr.getCellFromCache(request1); // after load, found assertTrue(value instanceof Number); assertEquals(694, ((Number) value).intValue()); value = aggMgr.getCellFromCache(request2); // after load, found assertTrue(value instanceof Number); assertEquals(672, ((Number) value).intValue()); value = aggMgr.getCellFromCache(request3); // after load, found assertTrue(value instanceof Number); assertEquals(1122, ((Number) value).intValue()); // Note: 1122 != (694 + 672) } /** * Tests that a request for ([Measures].[Unit Sales], [Gender].[F]) * generates the correct SQL. */ public void testFemaleUnitSalesSql() { if (!(MondrianProperties.instance().UseAggregates.get() && MondrianProperties.instance().ReadAggregates.get())) { return; } CellRequest request = createRequest( "Sales", "[Measures].[Unit Sales]", "customer", "gender", "F"); SqlPattern[] patterns = { new SqlPattern( ACCESS_MYSQL, "select `agg_g_ms_pcat_sales_fact_1997`.`gender` as `c0`," + " sum(`agg_g_ms_pcat_sales_fact_1997`.`unit_sales`) as `m0` " + "from `agg_g_ms_pcat_sales_fact_1997` as `agg_g_ms_pcat_sales_fact_1997` " + "where `agg_g_ms_pcat_sales_fact_1997`.`gender` = 'F' " + "group by `agg_g_ms_pcat_sales_fact_1997`.`gender`", 26) }; assertRequestSql(new CellRequest[]{request}, patterns); } /** * As {@link #testFemaleUnitSalesSql()}, but with aggregate tables switched * on. * * TODO: Enable this test. */ private void _testFemaleUnitSalesSql_withAggs() { CellRequest request = createRequest( "Sales", "[Measures].[Unit Sales]", "customer", "gender", "F"); SqlPattern[] patterns = { new SqlPattern( ACCESS_MYSQL, "select `customer`.`gender` as `c0`," + " sum(`agg_l_03_sales_fact_1997`.`unit_sales`) as `m0` " + "from `customer` as `customer`," + " `agg_l_03_sales_fact_1997` as `agg_l_03_sales_fact_1997` " + "where `agg_l_03_sales_fact_1997`.`customer_id` = `customer`.`customer_id` " + "and `customer`.`gender` = 'F' " + "group by `customer`.`gender`", 26) }; assertRequestSql(new CellRequest[]{request}, patterns); } /** * Test a batch containing multiple measures: * (store_state=CA, gender=F, measure=[Unit Sales]) * (store_state=CA, gender=M, measure=[Store Sales]) * (store_state=OR, gender=M, measure=[Unit Sales]) */ public void testMultipleMeasures() { if (!(MondrianProperties.instance().UseAggregates.get() && MondrianProperties.instance().ReadAggregates.get())) { return; } CellRequest[] requests = new CellRequest[] { createRequest( "Sales", "[Measures].[Unit Sales]", new String[] {"customer", "store"}, new String[] {"gender", "store_state"}, new String[] {"F", "CA"}), createRequest( "Sales", "[Measures].[Store Sales]", new String[] {"customer", "store"}, new String[] {"gender", "store_state"}, new String[] {"M", "CA"}), createRequest( "Sales", "[Measures].[Unit Sales]", new String[] {"customer", "store"}, new String[] {"gender", "store_state"}, new String[] {"F", "OR"})}; SqlPattern[] patterns = { new SqlPattern( ACCESS_MYSQL, "select `store`.`store_state` as `c0`," + " `customer`.`gender` as `c1`," + " sum(`agg_l_05_sales_fact_1997`.`unit_sales`) as `m0`," + " sum(`agg_l_05_sales_fact_1997`.`store_sales`) as `m1` " + "from `store` as `store`," + " `agg_l_05_sales_fact_1997` as `agg_l_05_sales_fact_1997`," + " `customer` as `customer` " + "where `agg_l_05_sales_fact_1997`.`store_id` = `store`.`store_id` " + "and `store`.`store_state` in ('CA', 'OR') " + "and `agg_l_05_sales_fact_1997`.`customer_id` = `customer`.`customer_id` " + "group by `store`.`store_state`, " + "`customer`.`gender`", 29) }; assertRequestSql(requests, patterns); } /** * As {@link #testMultipleMeasures()}, but with aggregate tables switched * on. * * TODO: Enable this test. */ private void _testMultipleMeasures_withAgg() { CellRequest[] requests = new CellRequest[] { createRequest( "Sales", "[Measures].[Unit Sales]", new String[] {"customer", "store"}, new String[] {"gender", "store_state"}, new String[] {"F", "CA"}), createRequest( "Sales", "[Measures].[Store Sales]", new String[] {"customer", "store"}, new String[] {"gender", "store_state"}, new String[] {"M", "CA"}), createRequest( "Sales", "[Measures].[Unit Sales]", new String[] {"customer", "store"}, new String[] {"gender", "store_state"}, new String[] {"F", "OR"})}; SqlPattern[] patterns = { new SqlPattern( ACCESS_MYSQL, "select `customer`.`gender` as `c0`," + " `store`.`store_state` as `c1`," + " sum(`agg_l_05_sales_fact_1997`.`unit_sales`) as `m0`," + " sum(`agg_l_05_sales_fact_1997`.`store_sales`) as `m1` " + "from `customer` as `customer`," + " `agg_l_05_sales_fact_1997` as `agg_l_05_sales_fact_1997`," + " `store` as `store` " + "where `agg_l_05_sales_fact_1997`.`customer_id` = `customer`.`customer_id`" + " and `agg_l_05_sales_fact_1997`.`store_id` = `store`.`store_id`" + " and `store`.`store_state` in ('CA', 'OR') " + "group by `customer`.`gender`, `store`.`store_state`", 26) }; assertRequestSql(requests, patterns); } /** */ private CellRequest createMultipleMeasureCellRequest() { String cube = "Sales"; String measure = "[Measures].[Unit Sales]"; String table = "store"; String column = "store_state"; String value = "CA"; final Connection connection = TestContext.instance().getConnection(); final boolean fail = true; Cube salesCube = connection.getSchema().lookupCube(cube, fail); Member storeSqftMeasure = salesCube.getSchemaReader(null).getMemberByUniqueName( Util.parseIdentifier(measure), fail); RolapStar.Measure starMeasure = RolapStar.getStarMeasure(storeSqftMeasure); CellRequest request = new CellRequest(starMeasure, false, false); final RolapStar star = starMeasure.getStar(); final RolapStar.Column storeTypeColumn = star.lookupColumn(table, column); request.addConstrainedColumn( storeTypeColumn, new ValueColumnPredicate(storeTypeColumn, value)); return request; } // todo: test unrestricted column, (Unit Sales, Gender=*) // todo: test one unrestricted, one restricted, (UNit Sales, Gender=*, // State={CA, OR}) // todo: test with 2 dimension columns on the same table, e.g. // (Unit Sales, Gender={F}, MaritalStatus={S}) and make sure that the // table only appears once in the from clause. /** * Tests that if a level is marked 'unique members', then its parent * is not constrained. */ public void testUniqueMembers() { // [Store].[Store State] is unique, so we don't expect to see any // references to country. final String mdxQuery = "select {[Measures].[Unit Sales]} on columns," + " {[Store].[USA].[CA], [Store].[USA].[OR]} on rows " + "from [Sales]"; SqlPattern[] patterns; String accessMysqlSql, derbySql; /* * Note: the following aggregate loading sqls contain no * references to the parent level column "store_country". */ if (MondrianProperties.instance().UseAggregates.get() && MondrianProperties.instance().ReadAggregates.get()) { accessMysqlSql = "select `store`.`store_state` as `c0`," + " `agg_c_14_sales_fact_1997`.`the_year` as `c1`," + " sum(`agg_c_14_sales_fact_1997`.`unit_sales`) as `m0` " + "from `store` as `store`," + " `agg_c_14_sales_fact_1997` as `agg_c_14_sales_fact_1997` " + "where `agg_c_14_sales_fact_1997`.`store_id` = `store`.`store_id`" + " and `store`.`store_state` in ('CA', 'OR')" + " and `agg_c_14_sales_fact_1997`.`the_year` = 1997 " + "group by `store`.`store_state`," + " `agg_c_14_sales_fact_1997`.`the_year`"; derbySql = "select " + "\"store\".\"store_state\" as \"c0\", \"agg_c_14_sales_fact_1997\".\"the_year\" as \"c1\", " + "sum(\"agg_c_14_sales_fact_1997\".\"unit_sales\") as \"m0\" " + "from " + "\"store\" as \"store\", \"agg_c_14_sales_fact_1997\" as \"agg_c_14_sales_fact_1997\" " + "where " + "\"agg_c_14_sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" and " + "\"store\".\"store_state\" in ('CA', 'OR') and " + "\"agg_c_14_sales_fact_1997\".\"the_year\" = 1997 " + "group by " + "\"store\".\"store_state\", \"agg_c_14_sales_fact_1997\".\"the_year\""; patterns = new SqlPattern[] { new SqlPattern( ACCESS_MYSQL, accessMysqlSql, 50), new SqlPattern( Dialect.DatabaseProduct.DERBY, derbySql, derbySql) }; } else { accessMysqlSql = "select `store`.`store_state` as `c0`," + " `time_by_day`.`the_year` as `c1`," + " sum(`sales_fact_1997`.`unit_sales`) as `m0` from `store` as `store`," + " `sales_fact_1997` as `sales_fact_1997`," + " `time_by_day` as `time_by_day` " + "where `sales_fact_1997`.`store_id` = `store`.`store_id`" + " and `store`.`store_state` in ('CA', 'OR')" + " and `sales_fact_1997`.`time_id` = `time_by_day`.`time_id`" + " and `time_by_day`.`the_year` = 1997 " + "group by `store`.`store_state`, `time_by_day`.`the_year`"; derbySql = "select \"store\".\"store_state\" as \"c0\", \"time_by_day\".\"the_year\" as \"c1\", " + "sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\" " + "from " + "\"store\" as \"store\", \"sales_fact_1997\" as \"sales_fact_1997\", " + "\"time_by_day\" as \"time_by_day\" " + "where " + "\"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" and " + "\"store\".\"store_state\" in ('CA', 'OR') and " + "\"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and " + "\"time_by_day\".\"the_year\" = 1997 " + "group by " + "\"store\".\"store_state\", \"time_by_day\".\"the_year\""; patterns = new SqlPattern[] { new SqlPattern( ACCESS_MYSQL, accessMysqlSql, 50), new SqlPattern( Dialect.DatabaseProduct.DERBY, derbySql, derbySql) }; } assertQuerySql(mdxQuery, patterns); } /** * Tests that a NonEmptyCrossJoin uses the measure referenced by the query * (Store Sales) instead of the default measure (Unit Sales) in the case * where the query only has one result axis. The setup here is necessarily * elaborate because the original bug was quite arbitrary. */ public void testNonEmptyCrossJoinLoneAxis() { // Not sure what this test is checking. // For now, only run it for derby. final Dialect dialect = getTestContext().getDialect(); if (dialect.getDatabaseProduct() != Dialect.DatabaseProduct.DERBY) { return; } String mdxQuery = "With " + "Set [*NATIVE_CJ_SET] as " + "'NonEmptyCrossJoin([*BASE_MEMBERS_Store],[*BASE_MEMBERS_Product])' " + "Set [*BASE_MEMBERS_Store] as '{[Store].[All Stores].[USA]}' " + "Set [*GENERATED_MEMBERS_Store] as " + "'Generate([*NATIVE_CJ_SET], {[Store].CurrentMember})' " + "Set [*BASE_MEMBERS_Product] as " + "'{[Product].[All Products].[Food],[Product].[All Products].[Drink]}' " + "Set [*GENERATED_MEMBERS_Product] as " + "'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' " + "Member [Store].[*FILTER_MEMBER] as 'Aggregate ([*GENERATED_MEMBERS_Store])' " + "Member [Product].[*FILTER_MEMBER] as 'Aggregate ([*GENERATED_MEMBERS_Product])' " + "Select {[Measures].[Store Sales]} on columns " + "From [Sales] " + "Where ([Store].[*FILTER_MEMBER], [Product].[*FILTER_MEMBER])"; String derbySql = "select " + "\"store\".\"store_country\" as \"c0\", " + "\"time_by_day\".\"the_year\" as \"c1\", " + "\"product_class\".\"product_family\" as \"c2\", " + "sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\" " + "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\" " + "where " + "\"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" and " + "\"store\".\"store_country\" = 'USA' and " + "\"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\" " + "group by " + "\"store\".\"store_country\", \"time_by_day\".\"the_year\", " + "\"product_class\".\"product_family\""; SqlPattern[] patterns = { new SqlPattern(Dialect.DatabaseProduct.DERBY, derbySql, derbySql)}; // For derby, the TestAggregationManager.testNonEmptyCrossJoinLoneAxis // test fails if the non-empty crossjoin optimizer is used. // With it on one gets a recursive call coming through the // RolapEvaluator.getCachedResult. assertNoQuerySql(mdxQuery, patterns); } /** * If a hierarchy lives in the fact table, we should not generate a join. */ public void testHierarchyInFactTable() { CellRequest request = createRequest( "Store", "[Measures].[Store Sqft]", "store", "store_type", "Supermarket"); String accessMysqlSql = "select `store`.`store_type` as `c0`," + " sum(`store`.`store_sqft`) as `m0` " + "from `store` as `store` " + "where `store`.`store_type` = 'Supermarket' " + "group by `store`.`store_type`"; String derbySql = "select " + "\"store\".\"store_type\" as \"c0\", " + "sum(\"store\".\"store_sqft\") as \"m0\" " + "from " + "\"store\" as \"store\" " + "where " + "\"store\".\"store_type\" = 'Supermarket' " + "group by \"store\".\"store_type\""; SqlPattern[] patterns = { new SqlPattern( ACCESS_MYSQL, accessMysqlSql, 26), new SqlPattern(Dialect.DatabaseProduct.DERBY, derbySql, derbySql) }; assertRequestSql(new CellRequest[]{request}, patterns); } public void testCountDistinctAggMiss() { CellRequest request = createRequest( "Sales", "[Measures].[Customer Count]", new String[]{"time_by_day", "time_by_day"}, new String[]{"the_year", "quarter"}, new String[]{"1997", "Q1"}); String accessSql = "select" + " `d0` as `c0`," + " `d1` as `c1`," + " count(`m0`) as `c2` " + "from (" + "select distinct `time_by_day`.`the_year` as `d0`, " + "`time_by_day`.`quarter` as `d1`, " + "`sales_fact_1997`.`customer_id` as `m0` " + "from " + "`time_by_day` as `time_by_day`, " + "`sales_fact_1997` as `sales_fact_1997` " + "where " + "`sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and " + "`time_by_day`.`the_year` = 1997 and " + "`time_by_day`.`quarter` = 'Q1'" + ") as `dummyname` " + "group by `d0`, `d1`"; String mysqlSql = "select" + " `time_by_day`.`the_year` as `c0`," + " `time_by_day`.`quarter` as `c1`," + " count(distinct `sales_fact_1997`.`customer_id`) as `m0` " + "from `time_by_day` as `time_by_day`," + " `sales_fact_1997` as `sales_fact_1997` " + "where `sales_fact_1997`.`time_id` = `time_by_day`.`time_id`" + " and `time_by_day`.`the_year` = 1997" + " and `time_by_day`.`quarter` = 'Q1' " + "group by `time_by_day`.`the_year`," + " `time_by_day`.`quarter`"; String derbySql = "select " + "\"time_by_day\".\"the_year\" as \"c0\", " + "\"time_by_day\".\"quarter\" as \"c1\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from " + "\"time_by_day\" as \"time_by_day\", " + "\"sales_fact_1997\" as \"sales_fact_1997\" " + "where " + "\"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and " + "\"time_by_day\".\"the_year\" = 1997 and " + "\"time_by_day\".\"quarter\" = 'Q1' " + "group by \"time_by_day\".\"the_year\", \"time_by_day\".\"quarter\""; SqlPattern[] patterns = { new SqlPattern(Dialect.DatabaseProduct.ACCESS, accessSql, 26), new SqlPattern(Dialect.DatabaseProduct.MYSQL, mysqlSql, 26), new SqlPattern(Dialect.DatabaseProduct.DERBY, derbySql, derbySql) }; assertRequestSql(new CellRequest[]{request}, patterns); } public void testCountDistinctAggMatch() { if (!(MondrianProperties.instance().UseAggregates.get() && MondrianProperties.instance().ReadAggregates.get())) { return; } CellRequest request = createRequest( "Sales", "[Measures].[Customer Count]", new String[] { "time_by_day", "time_by_day", "time_by_day" }, new String[] { "the_year", "quarter", "month_of_year" }, new String[] { "1997", "Q1", "1" }); String accessSql = "select " + "`agg_c_10_sales_fact_1997`.`the_year` as `c0`, " + "`agg_c_10_sales_fact_1997`.`quarter` as `c1`, " + "`agg_c_10_sales_fact_1997`.`month_of_year` as `c2`, " + "`agg_c_10_sales_fact_1997`.`customer_count` as `m0` " + "from " + "`agg_c_10_sales_fact_1997` as `agg_c_10_sales_fact_1997` " + "where " + "`agg_c_10_sales_fact_1997`.`the_year` = 1997 and " + "`agg_c_10_sales_fact_1997`.`quarter` = 'Q1' and " + "`agg_c_10_sales_fact_1997`.`month_of_year` = 1"; SqlPattern[] patterns = { new SqlPattern(Dialect.DatabaseProduct.ACCESS, accessSql, 26)}; assertRequestSql(new CellRequest[]{request}, patterns); } public void testCountDistinctCannotRollup() { // Summary "agg_g_ms_pcat_sales_fact_1997" doesn't match, // because we'd need to roll-up the distinct-count measure over // "month_of_year". CellRequest request = createRequest( "Sales", "[Measures].[Customer Count]", new String[] { "time_by_day", "time_by_day", "product_class" }, new String[] { "the_year", "quarter", "product_family" }, new String[] { "1997", "Q1", "Food" }); SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.MYSQL, "select" + " `time_by_day`.`the_year` as `c0`," + " `time_by_day`.`quarter` as `c1`," + " `product_class`.`product_family` as `c2`," + " count(distinct `sales_fact_1997`.`customer_id`) as `m0` " + "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 `time_by_day`.`quarter` = `Q1`" + " and `sales_fact_1997`.`product_id` = `product`.`product_id`" + " and `product`.`product_class_id` = `product_class`.`product_class_id`" + " and `product_class`.`product_family` = `Food` " + "group by `time_by_day`.`the_year`," + " `time_by_day`.`quarter`," + " `product_class`.`product_family`", 23), new SqlPattern( Dialect.DatabaseProduct.ACCESS, "select" + " `d0` as `c0`," + " `d1` as `c1`," + " `d2` as `c2`," + " count(`m0`) as `c3` " + "from (" + "select distinct `time_by_day`.`the_year` as `d0`," + " `time_by_day`.`quarter` as `d1`," + " `product_class`.`product_family` as `d2`," + " `sales_fact_1997`.`customer_id` as `m0` " + "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 `time_by_day`.`quarter` = 'Q1'" + " and `sales_fact_1997`.`product_id` = `product`.`product_id`" + " and `product`.`product_class_id` = `product_class`.`product_class_id`" + " and `product_class`.`product_family` = 'Food') as `dummyname` " + "group by `d0`, `d1`, `d2`", 23), new SqlPattern( Dialect.DatabaseProduct.DERBY, "select " + "\"time_by_day\".\"the_year\" as \"c0\", \"time_by_day\".\"quarter\" as \"c1\", " + "\"product_class\".\"product_family\" as \"c2\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "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 " + "\"time_by_day\".\"quarter\" = 'Q1' and " + "\"sales_fact_1997\".\"product_id\" = \"product\".\"product_id\" and " + "\"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" and " + "\"product_class\".\"product_family\" = 'Food' " + "group by \"time_by_day\".\"the_year\", \"time_by_day\".\"quarter\", " + "\"product_class\".\"product_family\"", 23) }; assertRequestSql(new CellRequest[]{request}, patterns); } /** * Now, here's a funny thing. Usually you can't roll up a distinct-count * aggregate. But if you're rolling up along the dimension which the * count is counting, it's OK. In this case, you know that every member * can only belong to one group. */ public void testCountDistinctRollupAlongDim() { if (!(MondrianProperties.instance().UseAggregates.get() && MondrianProperties.instance().ReadAggregates.get())) { return; } // Request has granularity // [Time].[Month] // [Product].[Category] // // whereas agg table "agg_g_ms_pcat_sales_fact_1997" has // granularity // // [Time].[Month] // [Product].[Category] // [Gender].[Gender] // [Marital Status].[Marital Status] // // Because [Gender] and [Marital Status] come from the [Customer] // table (the same as the distinct-count measure), we can roll up. CellRequest request = createRequest( "Sales", "[Measures].[Customer Count]", new String[] { "time_by_day", "time_by_day", "time_by_day", "product_class", "product_class", "product_class" }, new String[] { "the_year", "quarter", "month_of_year", "product_family", "product_department", "product_category" }, new String[] { "1997", "Q1", "1", "Food", "Deli", "Meat" }); SqlPattern[] patterns = { new SqlPattern( ACCESS_MYSQL, "select `agg_g_ms_pcat_sales_fact_1997`.`the_year` as `c0`," + " `agg_g_ms_pcat_sales_fact_1997`.`quarter` as `c1`," + " `agg_g_ms_pcat_sales_fact_1997`.`month_of_year` as `c2`," + " `agg_g_ms_pcat_sales_fact_1997`.`product_family` as `c3`," + " `agg_g_ms_pcat_sales_fact_1997`.`product_department` as `c4`," + " `agg_g_ms_pcat_sales_fact_1997`.`product_category` as `c5`," + " sum(`agg_g_ms_pcat_sales_fact_1997`.`customer_count`) as `m0` " + "from `agg_g_ms_pcat_sales_fact_1997` as `agg_g_ms_pcat_sales_fact_1997` " + "where `agg_g_ms_pcat_sales_fact_1997`.`the_year` = 1997" + " and `agg_g_ms_pcat_sales_fact_1997`.`quarter` = 'Q1'" + " and `agg_g_ms_pcat_sales_fact_1997`.`month_of_year` = 1" + " and `agg_g_ms_pcat_sales_fact_1997`.`product_family` = 'Food'" + " and `agg_g_ms_pcat_sales_fact_1997`.`product_department` = 'Deli'" + " and `agg_g_ms_pcat_sales_fact_1997`.`product_category` = 'Meat' " + "group by `agg_g_ms_pcat_sales_fact_1997`.`the_year`," + " `agg_g_ms_pcat_sales_fact_1997`.`quarter`," + " `agg_g_ms_pcat_sales_fact_1997`.`month_of_year`," + " `agg_g_ms_pcat_sales_fact_1997`.`product_family`," + " `agg_g_ms_pcat_sales_fact_1997`.`product_department`," + " `agg_g_ms_pcat_sales_fact_1997`.`product_category`", 58) }; assertRequestSql(new CellRequest[]{request}, patterns); } /** * As above, but we rollup [Marital Status] but not [Gender]. */ public void testCountDistinctRollup2() { if (!(MondrianProperties.instance().UseAggregates.get() && MondrianProperties.instance().ReadAggregates.get())) { return; } CellRequest request = createRequest( "Sales", "[Measures].[Customer Count]", new String[] { "time_by_day", "time_by_day", "time_by_day", "product_class", "product_class", "product_class", "customer" }, new String[] { "the_year", "quarter", "month_of_year", "product_family", "product_department", "product_category", "gender" }, new String[] { "1997", "Q1", "1", "Food", "Deli", "Meat", "F" }); SqlPattern[] patterns = { new SqlPattern( ACCESS_MYSQL, "select `agg_g_ms_pcat_sales_fact_1997`.`the_year` as `c0`," + " `agg_g_ms_pcat_sales_fact_1997`.`quarter` as `c1`," + " `agg_g_ms_pcat_sales_fact_1997`.`month_of_year` as `c2`," + " `agg_g_ms_pcat_sales_fact_1997`.`product_family` as `c3`," + " `agg_g_ms_pcat_sales_fact_1997`.`product_department` as `c4`," + " `agg_g_ms_pcat_sales_fact_1997`.`product_category` as `c5`," + " `agg_g_ms_pcat_sales_fact_1997`.`gender` as `c6`," + " sum(`agg_g_ms_pcat_sales_fact_1997`.`customer_count`) as `m0` " + "from `agg_g_ms_pcat_sales_fact_1997` as `agg_g_ms_pcat_sales_fact_1997` " + "where `agg_g_ms_pcat_sales_fact_1997`.`the_year` = 1997" + " and `agg_g_ms_pcat_sales_fact_1997`.`quarter` = 'Q1'" + " and `agg_g_ms_pcat_sales_fact_1997`.`month_of_year` = 1" + " and `agg_g_ms_pcat_sales_fact_1997`.`product_family` = 'Food'" + " and `agg_g_ms_pcat_sales_fact_1997`.`product_department` = 'Deli'" + " and `agg_g_ms_pcat_sales_fact_1997`.`product_category` = 'Meat'" + " and `agg_g_ms_pcat_sales_fact_1997`.`gender` = 'F' " + "group by `agg_g_ms_pcat_sales_fact_1997`.`the_year`," + " `agg_g_ms_pcat_sales_fact_1997`.`quarter`," + " `agg_g_ms_pcat_sales_fact_1997`.`month_of_year`," + " `agg_g_ms_pcat_sales_fact_1997`.`product_family`," + " `agg_g_ms_pcat_sales_fact_1997`.`product_department`," + " `agg_g_ms_pcat_sales_fact_1997`.`product_category`," + " `agg_g_ms_pcat_sales_fact_1997`.`gender`", 58) }; assertRequestSql(new CellRequest[]{request}, patterns); } /* * Test that cells with the same compound member constraints are * loaded in one Sql statement. * * Cells [Food] and [Drink] have the same constraint: * * {[1997].[Q1].[1], [1997].[Q3].[7]} */ public void testCountDistinctBatchLoading() { List compoundMembers = new ArrayList(); compoundMembers.add(new String[] {"1997", "Q1", "1"}); compoundMembers.add(new String[] {"1997", "Q3", "7"}); CellRequestConstraint aggConstraint = makeConstraintYearQuarterMonth(compoundMembers); CellRequest request1 = createRequest( "Sales", "[Measures].[Customer Count]", new String[] {"product_class"}, new String[] {"product_family"}, new String[] {"Food"}, aggConstraint); CellRequest request2 = createRequest( "Sales", "[Measures].[Customer Count]", new String[] {"product_class"}, new String[] {"product_family"}, new String[] {"Drink"}, aggConstraint); String mysqlSql = "select `product_class`.`product_family` as `c0`, " + "count(distinct `sales_fact_1997`.`customer_id`) as `m0` " + "from `product_class` as `product_class`, `product` as `product`, " + "`sales_fact_1997` as `sales_fact_1997`, `time_by_day` as `time_by_day` " + "where `sales_fact_1997`.`product_id` = `product`.`product_id` and " + "`product`.`product_class_id` = `product_class`.`product_class_id` and " + "`sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and " + "(((`time_by_day`.`the_year`, `time_by_day`.`quarter`, `time_by_day`.`month_of_year`) " + "in ((1997, 'Q1', 1), (1997, 'Q3', 7)))) " + "group by `product_class`.`product_family`"; String derbySql = "select \"product_class\".\"product_family\" as \"c0\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from \"product_class\" as \"product_class\", \"product\" as \"product\", " + "\"sales_fact_1997\" as \"sales_fact_1997\", \"time_by_day\" as \"time_by_day\" " + "where \"sales_fact_1997\".\"product_id\" = \"product\".\"product_id\" and " + "\"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" and " + "\"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and " + "((\"time_by_day\".\"the_year\" = 1997 and \"time_by_day\".\"quarter\" = 'Q1' and \"time_by_day\".\"month_of_year\" = 1) or " + "(\"time_by_day\".\"the_year\" = 1997 and \"time_by_day\".\"quarter\" = 'Q3' and \"time_by_day\".\"month_of_year\" = 7)) " + "group by \"product_class\".\"product_family\""; SqlPattern[] patterns = { new SqlPattern(Dialect.DatabaseProduct.MYSQL, mysqlSql, mysqlSql), new SqlPattern(Dialect.DatabaseProduct.DERBY, derbySql, derbySql) }; assertRequestSql(new CellRequest[]{request1, request2}, patterns); } /** * Tests that an aggregate table is used to speed up a * <Member>.Children expression. */ public void testAggMembers() { if (MondrianProperties.instance().TestExpDependencies.get() > 0) { return; } if (!(MondrianProperties.instance().UseAggregates.get() && MondrianProperties.instance().ReadAggregates.get())) { return; } if (!(MondrianProperties.instance().EnableNativeCrossJoin.get())) { return; } SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.ACCESS, "select `store`.`store_country` as `c0` " + "from `agg_c_14_sales_fact_1997` as `agg_c_14_sales_fact_1997`," + " `store` as `store` " + "where `agg_c_14_sales_fact_1997`.`the_year` = 1998 " + "and `agg_c_14_sales_fact_1997`.`store_id` = `store`.`store_id` " + "group by `store`.`store_country` " + "order by Iif(`store`.`store_country` IS NULL, 1, 0)," + " `store`.`store_country` ASC", 26), new SqlPattern( Dialect.DatabaseProduct.MYSQL, "select `store`.`store_country` as `c0` " + "from `agg_c_14_sales_fact_1997` as `agg_c_14_sales_fact_1997`," + " `store` as `store` " + "where `agg_c_14_sales_fact_1997`.`the_year` = 1998 " + "and `agg_c_14_sales_fact_1997`.`store_id` = `store`.`store_id` " + "group by `store`.`store_country` " + "order by ISNULL(`store`.`store_country`) ASC, `store`.`store_country` ASC", 26)}; assertQuerySql( "select NON EMPTY {[Customers].[USA]} ON COLUMNS,\n" + " NON EMPTY Crossjoin(Hierarchize(Union({[Store].[All Stores]},\n" + " [Store].[All Stores].Children)), {[Product].[All Products]}) \n" + " ON ROWS\n" + " from [Sales]\n" + " where ([Measures].[Unit Sales], [Time].[1998])", patterns); } /** * As {@link #testAggMembers()}, but asks for children of a leaf level. * Rewrite using an aggregate table is not possible, so just check that it * gets the right result. */ public void testAggChildMembersOfLeaf() { assertQueryReturns( "select NON EMPTY {[Time].[1997]} ON COLUMNS,\n" + " NON EMPTY Crossjoin(Hierarchize(Union({[Store].[All Stores]},\n" + " [Store].[USA].[CA].[San Francisco].[Store 14].Children)), {[Product].[All Products]}) \n" + " ON ROWS\n" + " from [Sales]\n" + " where [Measures].[Unit Sales]", "Axis #0:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "Axis #2:\n" + "{[Store].[All Stores], [Product].[All Products]}\n" + "Row #0: 266,773\n"); } /** * This test case tests for a null pointer that was being thrown * inside of CellRequest. */ public void testNoNullPtrInCellRequest() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " " + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""); testContext.assertQueryReturns( "select {[Measures].[Unit Sales]} on columns, " + "Filter ({ " + "[Store2].[All Stores].[USA].[CA].[Beverly Hills], " + "[Store2].[All Stores].[USA].[CA].[Beverly Hills].[Gourmet Supermarket] " + "},[Measures].[Unit Sales] > 0) on rows " + "from [Sales] " + "where [Store Type].[Store Type].[Small Grocery]", "Axis #0:\n" + "{[Store Type].[Small Grocery]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n"); } /** * Test that once fetched, column cardinality can be shared between * different queries using the same connection. * *

Test also that expressions with only table alias difference do not * share cardinality result. */ public void testColumnCadinalityCache() { String query1 = "select " + "NonEmptyCrossJoin(" + "[Product].[Product Family].Members, " + "[Gender].[Gender].Members) on columns " + "from [Sales]"; String query2 = "select " + "NonEmptyCrossJoin(" + "[Store].[Store Country].Members, " + "[Product].[Product Family].Members) on columns " + "from [Warehouse]"; String cardinalitySqlDerby = "select " + "count(distinct \"product_class\".\"product_family\") " + "from \"product_class\" as \"product_class\""; String cardinalitySqlMySql = "select " + "count(distinct `product_class`.`product_family`) as `c0` " + "from `product_class` as `product_class`"; SqlPattern[] patterns = new SqlPattern[] { new SqlPattern( Dialect.DatabaseProduct.DERBY, cardinalitySqlDerby, cardinalitySqlDerby), new SqlPattern( Dialect.DatabaseProduct.MYSQL, cardinalitySqlMySql, cardinalitySqlMySql) }; final TestContext context = getTestContext().withFreshConnection(); // This MDX gets the [Product].[Product Family] cardinality from the DB. context.executeQuery(query1); // This MDX should be able to reuse the cardinality for // [Product].[Product Family]; and should not issue a SQL to fetch // that from DB again. assertQuerySqlOrNot(context, query2, patterns, true, false, false); } public void testKeyExpressionCardinalityCache() { String storeDim1 = "\n" + " \n" + "

\n" + " \n" + " \n" + " \n" + "\"store_country\"\n" + " \n" + " \n" + "\"store_country\"\n" + " \n" + " \n" + "\"store_country\"\n" + " \n" + " \n" + "\"store_country\"\n" + " \n" + " \n" + "`store_country`\n" + " \n" + " \n" + "\"store_country\"\n" + " \n" + " \n" + "\"store_country\"\n" + " \n" + " \n" + "store_country\n" + " \n" + " \n" + " \n" + " \n" + "\n"; String storeDim2 = "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + "\"store_country\"\n" + " \n" + " \n" + "\"store_country\"\n" + " \n" + " \n" + "\"store_country\"\n" + " \n" + " \n" + "`store_country`\n" + " \n" + " \n" + "store_country\n" + " \n" + " \n" + " \n" + " \n" + "\n"; String salesCube1 = "\n" + "
\n" + " \n" + " \n" + " \n" + "\n"; String salesCube2 = "\n" + "
\n" + " \n" + " \n" + "\n"; String query = "select {[Measures].[Unit Sales]} ON COLUMNS, {[Store1].members} ON ROWS FROM [Sales1]"; String query1 = "select {[Measures].[Store Sales]} ON COLUMNS, {[Store1].members} ON ROWS FROM [Sales1]"; String query2 = "select {[Measures].[Unit Sales]} ON COLUMNS, {[Store2].members} ON ROWS FROM [Sales2]"; String cardinalitySqlDerby1 = "select count(distinct \"store_country\") from \"store\" as \"store\""; String cardinalitySqlMySql1 = "select count(distinct `store_country`) as `c0` from `store` as `store`"; String cardinalitySqlDerby2 = "select count(distinct \"store_country\") from \"store_ragged\" as \"store_ragged\""; String cardinalitySqlMySql2 = "select count(distinct `store_country`) as `c0` from `store_ragged` as `store_ragged`"; SqlPattern[] patterns1 = new SqlPattern[] { new SqlPattern( Dialect.DatabaseProduct.DERBY, cardinalitySqlDerby1, cardinalitySqlDerby1), new SqlPattern( Dialect.DatabaseProduct.MYSQL, cardinalitySqlMySql1, cardinalitySqlMySql1) }; SqlPattern[] patterns2 = new SqlPattern[] { new SqlPattern( Dialect.DatabaseProduct.DERBY, cardinalitySqlDerby2, cardinalitySqlDerby2), new SqlPattern( Dialect.DatabaseProduct.MYSQL, cardinalitySqlMySql2, cardinalitySqlMySql2) }; TestContext testContext = TestContext.instance().create( storeDim1 + storeDim2, salesCube1 + salesCube2, null, null, null, null); // This query causes "store"."store_country" cardinality to be // retrieved. testContext.executeQuery(query); // Query1 will find the "store"."store_country" cardinality in cache. assertQuerySqlOrNot(testContext, query1, patterns1, true, false, false); // Query2 again will not find the "store_ragged"."store_country" // cardinality in cache. assertQuerySqlOrNot( testContext, query2, patterns2, false, false, false); } /* * Test that using compound member constrant disables using AggregateTable */ public void testCountDistinctWithConstraintAggMiss() { if (!(MondrianProperties.instance().UseAggregates.get() && MondrianProperties.instance().ReadAggregates.get())) { return; } // Request has granularity // [Product].[Category] // and the compound constraint on // [Time].[Quarter] // // whereas agg table "agg_g_ms_pcat_sales_fact_1997" has // granularity // // [Time].[Quarter] // [Product].[Category] // [Gender].[Gender] // [Marital Status].[Marital Status] // // The presence of compound constraint causes agg table not used. // // Note ideally we should also test that non distinct measures could be // loaded from Aggregate table; however, the testing framework here uses // CellRequest directly which causes any compound constraint to be kept // separately. This will cause Aggregate tables not to be used. // // CellRequest generated by the code form MDX will in this case not // separate out the compound constraint from the "regular" constraints // and Aggregate tables can still be used. List compoundMembers = new ArrayList (); compoundMembers.add(new String[] {"1997", "Q1", "1"}); CellRequest request = createRequest( "Sales", "[Measures].[Customer Count]", new String[] { "product_class", "product_class", "product_class" }, new String[] { "product_family", "product_department", "product_category" }, new String[] { "Food", "Deli", "Meat" }, makeConstraintYearQuarterMonth(compoundMembers)); SqlPattern[] patterns = { new SqlPattern( ACCESS_MYSQL, "select " + "`product_class`.`product_family` as `c0`, " + "`product_class`.`product_department` as `c1`, " + "`product_class`.`product_category` as `c2`, " + "count(distinct `sales_fact_1997`.`customer_id`) as `m0` " + "from " + "`product_class` as `product_class`, `product` as `product`, " + "`sales_fact_1997` as `sales_fact_1997`, `time_by_day` as `time_by_day` " + "where " + "`sales_fact_1997`.`product_id` = `product`.`product_id` and " + "`product`.`product_class_id` = `product_class`.`product_class_id` and " + "`product_class`.`product_family` = 'Food' and " + "`product_class`.`product_department` = 'Deli' and " + "`product_class`.`product_category` = 'Meat' and " + "`sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and " + "(`time_by_day`.`the_year` = 1997 and `time_by_day`.`quarter` = 'Q1' and " + "`time_by_day`.`month_of_year` = 1) " + "group by " + "`product_class`.`product_family`, `product_class`.`product_department`, " + "`product_class`.`product_category`", 58) }; assertRequestSql(new CellRequest[]{request}, patterns); } public void testOrdinalExprAggTuplesAndChildren() { // this verifies that we can load properties, ordinals, etc out of // agg tables in member lookups (tuples and children) if (!(MondrianProperties.instance().UseAggregates.get() && MondrianProperties.instance().ReadAggregates.get())) { return; } if (!(MondrianProperties.instance().EnableNativeCrossJoin.get())) { return; } TestContext.instance().flushSchemaCache(); String cube = "\n" + "
\n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " " + " \n" + " \n" + ""; TestContext testContext = TestContext.instance().create( null, cube, null, null, null, null); String query = "select {[Measures].[Unit Sales]} on columns, " + "non empty CrossJoin({[Product].[Food].[Deli].[Meat]},{[Gender].[M]}) on rows " + "from [Sales_Prod_Ord] "; // first check that the sql is generated correctly SqlPattern[] patterns = { new SqlPattern( ACCESS_MYSQL, "select `agg_g_ms_pcat_sales_fact_1997`.`product_family` as `c0`, `agg_g_ms_pcat_sales_fact_1997`.`product_department` as `c1`, `product_class`.`product_category` as `c2`, `product_class`.`product_family` as `c3`, `agg_g_ms_pcat_sales_fact_1997`.`gender` as `c4` from `agg_g_ms_pcat_sales_fact_1997` as `agg_g_ms_pcat_sales_fact_1997`, `product_class` as `product_class` where `product_class`.`product_category` = `agg_g_ms_pcat_sales_fact_1997`.`product_category` and (`agg_g_ms_pcat_sales_fact_1997`.`product_category` = 'Meat' and `agg_g_ms_pcat_sales_fact_1997`.`product_department` = 'Deli' and `agg_g_ms_pcat_sales_fact_1997`.`product_family` = 'Food') and (`agg_g_ms_pcat_sales_fact_1997`.`gender` = 'M') group by `agg_g_ms_pcat_sales_fact_1997`.`product_family`, `agg_g_ms_pcat_sales_fact_1997`.`product_department`, `product_class`.`product_category`, `product_class`.`product_family`, `agg_g_ms_pcat_sales_fact_1997`.`gender` order by ISNULL(`agg_g_ms_pcat_sales_fact_1997`.`product_family`) ASC, `agg_g_ms_pcat_sales_fact_1997`.`product_family` ASC, ISNULL(`agg_g_ms_pcat_sales_fact_1997`.`product_department`) ASC, `agg_g_ms_pcat_sales_fact_1997`.`product_department` ASC, ISNULL(`product_class`.`product_category`) ASC, `product_class`.`product_category` ASC, ISNULL(`agg_g_ms_pcat_sales_fact_1997`.`gender`) ASC, `agg_g_ms_pcat_sales_fact_1997`.`gender` ASC", null) }; assertQuerySqlOrNot( testContext, query, patterns, false, false, false); testContext.assertQueryReturns( query, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Deli].[Meat], [Gender].[M]}\n" + "Row #0: 4,705\n"); Result result = testContext.executeQuery(query); // this verifies that the caption for meat is Food assertEquals( "Meat", result.getAxes()[1].getPositions().get(0).get(0).getName()); assertEquals( "Food", result.getAxes()[1].getPositions().get(0).get(0).getCaption()); // Test children query = "select {[Measures].[Unit Sales]} on columns, " + "non empty [Product].[Food].[Deli].Children on rows " + "from [Sales_Prod_Ord] "; testContext.assertQueryReturns( query, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Deli].[Meat]}\n" + "{[Product].[Food].[Deli].[Side Dishes]}\n" + "Row #0: 4,728\n" + "Row #1: 1,262\n"); } public void testAggregatingTuples() { if (!(MondrianProperties.instance().UseAggregates.get() && MondrianProperties.instance().ReadAggregates.get())) { return; } if (!(MondrianProperties.instance().EnableNativeCrossJoin.get())) { return; } // flush cache, to be sure sql is executed TestContext.instance().flushSchemaCache(); // This first query verifies that simple collapsed levels in aggregate // tables load as tuples correctly. The collapsed levels appear // in the aggregate table SQL below. // also note that at the time of this writing, this exercising the high // cardinality tuple reader String query = "select {[Measures].[Unit Sales]} on columns, " + "non empty CrossJoin({[Gender].[M]},{[Marital Status].[M]}) on rows " + "from [Sales] "; SqlPattern[] patterns = { new SqlPattern( ACCESS_MYSQL, "select " + "`agg_g_ms_pcat_sales_fact_1997`.`gender` as `c0`, " + "`agg_g_ms_pcat_sales_fact_1997`.`marital_status` as `c1` " + "from " + "`agg_g_ms_pcat_sales_fact_1997` as `agg_g_ms_pcat_sales_fact_1997` " + "where " + "(`agg_g_ms_pcat_sales_fact_1997`.`gender` = 'M') " + "and (`agg_g_ms_pcat_sales_fact_1997`.`marital_status` = 'M') " + "group by " + "`agg_g_ms_pcat_sales_fact_1997`.`gender`, " + "`agg_g_ms_pcat_sales_fact_1997`.`marital_status` " + "order by " + "ISNULL(`agg_g_ms_pcat_sales_fact_1997`.`gender`) ASC, " + "`agg_g_ms_pcat_sales_fact_1997`.`gender` ASC, " + "ISNULL(`agg_g_ms_pcat_sales_fact_1997`.`marital_status`) ASC, " + "`agg_g_ms_pcat_sales_fact_1997`.`marital_status` ASC", null) }; assertQuerySqlOrNot( getTestContext(), query, patterns, false, false, false); assertQueryReturns( query, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[M], [Marital Status].[M]}\n" + "Row #0: 66,460\n"); // This second query verifies that joined levels on aggregate tables // load correctly. String query2 = "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NON EMPTY {[Store].[Store State].Members} ON ROWS " + "from [Sales] where [Time].[1997].[Q1]"; SqlPattern[] patterns2 = { new SqlPattern( ACCESS_MYSQL, "select " + "`store`.`store_country` as `c0`, " + "`store`.`store_state` as `c1` " + "from " + "`store` as `store`, " + "`agg_c_14_sales_fact_1997` as `agg_c_14_sales_fact_1997` " + "where " + "`agg_c_14_sales_fact_1997`.`store_id` = `store`.`store_id` and " + "`agg_c_14_sales_fact_1997`.`the_year` = 1997 and " + "`agg_c_14_sales_fact_1997`.`quarter` = 'Q1' " + "group by " + "`store`.`store_country`, `store`.`store_state` " + "order by " + "ISNULL(`store`.`store_country`) ASC, " + "`store`.`store_country` ASC, " + "ISNULL(`store`.`store_state`) ASC, " + "`store`.`store_state` ASC", null) }; assertQuerySqlOrNot( getTestContext(), query2, patterns2, false, false, false); assertQueryReturns( query2, "Axis #0:\n" + "{[Time].[1997].[Q1]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 16,890\n" + "Row #1: 19,287\n" + "Row #2: 30,114\n"); } /** * this test verifies the collapsed children code in SqlMemberSource */ public void testCollapsedChildren() { if (!(MondrianProperties.instance().UseAggregates.get() && MondrianProperties.instance().ReadAggregates.get())) { return; } if (!(MondrianProperties.instance().EnableNativeCrossJoin.get())) { return; } // flush cache to be sure sql is executed TestContext.instance().flushSchemaCache(); SqlPattern[] patterns = { new SqlPattern( ACCESS_MYSQL, "select " + "`agg_g_ms_pcat_sales_fact_1997`.`gender` as `c0` " + "from `agg_g_ms_pcat_sales_fact_1997` " + "as `agg_g_ms_pcat_sales_fact_1997` " + "group by " + "`agg_g_ms_pcat_sales_fact_1997`.`gender`" + " order by ISNULL(`agg_g_ms_pcat_sales_fact_1997`.`gender`) ASC, `agg_g_ms_pcat_sales_fact_1997`.`gender` ASC", null) }; String query = "select non empty [Gender].Children on columns\n" + "from [Sales]"; assertQuerySqlOrNot( getTestContext(), query, patterns, false, false, false); assertQueryReturns( query, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 131,558\n" + "Row #0: 135,215\n"); } /** * Testcase for * bug MONDRIAN-812. Using a key expression for a level * element would make aggregate tables fail to be used. */ public void testLevelKeyAsSqlExpWithAgg() { propSaver.set(MondrianProperties.instance().UseAggregates, true); propSaver.set(MondrianProperties.instance().ReadAggregates, true); final String mdxQuery = "select non empty{[Promotions].[All Promotions].Children} ON rows, " + "non empty {[Store].[All Stores]} ON columns " + "from [Sales] " + "where {[Measures].[Unit Sales]}"; // Provoke an error in the key resolution to prove it uses it. final String colName = TestContext.instance().getDialect() .quoteIdentifier("promotion_name"); TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " ERROR_TEST_FUNCTION_NAME(" + colName + ")\n" + " \n" + " \n" + ""); testContext.assertQueryThrows( mdxQuery, "ERROR_TEST_FUNCTION_NAME"); // Run for real this time testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " RTRIM(" + colName + ")\n" + " \n" + " \n" + ""); testContext.assertQueryReturns( "select non empty{[Promotions].[All Promotions].Children} ON rows, " + "non empty {[Store].[All Stores]} ON columns " + "from [Sales] " + "where {[Measures].[Unit Sales]}", "Axis #0:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #1:\n" + "{[Store].[All Stores]}\n" + "Axis #2:\n" + "{[Promotions].[Bag Stuffers]}\n" + "{[Promotions].[Best Savings]}\n" + "{[Promotions].[Big Promo]}\n" + "{[Promotions].[Big Time Discounts]}\n" + "{[Promotions].[Big Time Savings]}\n" + "{[Promotions].[Bye Bye Baby]}\n" + "{[Promotions].[Cash Register Lottery]}\n" + "{[Promotions].[Dimes Off]}\n" + "{[Promotions].[Dollar Cutters]}\n" + "{[Promotions].[Dollar Days]}\n" + "{[Promotions].[Double Down Sale]}\n" + "{[Promotions].[Double Your Savings]}\n" + "{[Promotions].[Free For All]}\n" + "{[Promotions].[Go For It]}\n" + "{[Promotions].[Green Light Days]}\n" + "{[Promotions].[Green Light Special]}\n" + "{[Promotions].[High Roller Savings]}\n" + "{[Promotions].[I Cant Believe It Sale]}\n" + "{[Promotions].[Money Savers]}\n" + "{[Promotions].[Mystery Sale]}\n" + "{[Promotions].[No Promotion]}\n" + "{[Promotions].[One Day Sale]}\n" + "{[Promotions].[Pick Your Savings]}\n" + "{[Promotions].[Price Cutters]}\n" + "{[Promotions].[Price Destroyers]}\n" + "{[Promotions].[Price Savers]}\n" + "{[Promotions].[Price Slashers]}\n" + "{[Promotions].[Price Smashers]}\n" + "{[Promotions].[Price Winners]}\n" + "{[Promotions].[Sale Winners]}\n" + "{[Promotions].[Sales Days]}\n" + "{[Promotions].[Sales Galore]}\n" + "{[Promotions].[Save-It Sale]}\n" + "{[Promotions].[Saving Days]}\n" + "{[Promotions].[Savings Galore]}\n" + "{[Promotions].[Shelf Clearing Days]}\n" + "{[Promotions].[Shelf Emptiers]}\n" + "{[Promotions].[Super Duper Savers]}\n" + "{[Promotions].[Super Savers]}\n" + "{[Promotions].[Super Wallet Savers]}\n" + "{[Promotions].[Three for One]}\n" + "{[Promotions].[Tip Top Savings]}\n" + "{[Promotions].[Two Day Sale]}\n" + "{[Promotions].[Two for One]}\n" + "{[Promotions].[Unbeatable Price Savers]}\n" + "{[Promotions].[Wallet Savers]}\n" + "{[Promotions].[Weekend Markdown]}\n" + "{[Promotions].[You Save Days]}\n" + "Row #0: 901\n" + "Row #1: 2,081\n" + "Row #2: 1,789\n" + "Row #3: 932\n" + "Row #4: 700\n" + "Row #5: 921\n" + "Row #6: 4,792\n" + "Row #7: 1,219\n" + "Row #8: 781\n" + "Row #9: 1,652\n" + "Row #10: 1,959\n" + "Row #11: 843\n" + "Row #12: 1,638\n" + "Row #13: 689\n" + "Row #14: 1,607\n" + "Row #15: 436\n" + "Row #16: 2,654\n" + "Row #17: 253\n" + "Row #18: 899\n" + "Row #19: 1,021\n" + "Row #20: 195,448\n" + "Row #21: 1,973\n" + "Row #22: 323\n" + "Row #23: 1,624\n" + "Row #24: 2,173\n" + "Row #25: 4,094\n" + "Row #26: 1,148\n" + "Row #27: 504\n" + "Row #28: 1,294\n" + "Row #29: 444\n" + "Row #30: 2,055\n" + "Row #31: 2,572\n" + "Row #32: 2,203\n" + "Row #33: 1,446\n" + "Row #34: 1,382\n" + "Row #35: 754\n" + "Row #36: 2,118\n" + "Row #37: 2,628\n" + "Row #38: 2,497\n" + "Row #39: 1,183\n" + "Row #40: 1,155\n" + "Row #41: 525\n" + "Row #42: 2,053\n" + "Row #43: 335\n" + "Row #44: 2,100\n" + "Row #45: 916\n" + "Row #46: 914\n" + "Row #47: 3,145\n"); } /** * This is a test for MONDRIAN-918 and MONDRIAN-903. We have added * an attribute to AggName called approxRowCount so that the * aggregation manager can optimize the aggregation tables without * having to issue a select count() query. */ public void testAggNameApproxRowCount() { propSaver.set(MondrianProperties.instance().UseAggregates, true); propSaver.set(MondrianProperties.instance().ReadAggregates, true); final TestContext context = TestContext.instance().withSchema( "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\"fname\" || ' ' || \"lname\"\n" + " \n" + " \n" + "`customer`.`fullname`\n" + " \n" + " \n" + "\"fname\" || ' ' || \"lname\"\n" + " \n" + " \n" + "fname + ' ' + lname\n" + " \n" + " \n" + "\"fname\" || ' ' || \"lname\"\n" + " \n" + " \n" + "CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)\n" + " \n" + " \n" + "fname + ' ' + lname\n" + " \n" + " \n" + "\"customer\".\"fullname\"\n" + " \n" + " \n" + "CONCAT(CONCAT(\"customer\".\"fname\", ' '), \"customer\".\"lname\")\n" + " \n" + " \n" + "\"fname\" || ' ' || \"lname\"\n" + " \n" + " \n" + "\"customer\".\"fullname\"\n" + " \n" + " \n" + "\"fname\" || ' ' || \"lname\"\n" + " \n" + " \n" + "fullname\n" + " \n" + " \n" + " \n" + " \n" + "\"fname\" || ' ' || \"lname\"\n" + " \n" + " \n" + "\"fname\" || ' ' || \"lname\"\n" + " \n" + " \n" + "fname + ' ' + lname\n" + " \n" + " \n" + "\"fname\" || ' ' || \"lname\"\n" + " \n" + " \n" + "CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)\n" + " \n" + " \n" + "fname + ' ' + lname\n" + " \n" + " \n" + "\"customer\".\"fullname\"\n" + " \n" + " \n" + "\"customer\".\"fullname\"\n" + " \n" + " \n" + "CONCAT(CONCAT(\"customer\".\"fname\", ' '), \"customer\".\"lname\")\n" + " \n" + " \n" + "\"fname\" || ' ' || \"lname\"\n" + " \n" + " \n" + "fullname\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); final String mdxQuery = "select {[Measures].[Unit Sales]} on columns, " + "non empty CrossJoin({[Time.Weekly].[1997].[1].[15]},CrossJoin({[Customers].[USA].[CA].[Lincoln Acres].[William Smith]}, {[Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Washington].[Washington Diet Cola]})) on rows " + "from [Sales_Foo] "; final String sqlOracle = "select count(*) as \"c0\" from \"agg_pl_01_sales_fact_1997\" \"agg_pl_01_sales_fact_1997\""; final String sqlMysql = "select count(*) as `c0` from `agg_pl_01_sales_fact_1997` as `agg_pl_01_sales_fact_1997`"; // If the approxRowcount is used, there should not be // a query like : select count(*) from agg_pl_01_sales_fact_1997 assertQuerySqlOrNot( context, mdxQuery, new SqlPattern[] { new SqlPattern( Dialect.DatabaseProduct.ORACLE, sqlOracle, sqlOracle.length()), new SqlPattern( Dialect.DatabaseProduct.MYSQL, sqlMysql, sqlMysql.length()) }, true, false, false); } public void testNonCollapsedAggregate() throws Exception { propSaver.set(MondrianProperties.instance().UseAggregates, true); propSaver.set(MondrianProperties.instance().ReadAggregates, true); final String cube = "\n" + "
\n" + " " + " " + " " + " " + " " + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "\n" + "\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "\n" + "\n"; final TestContext context = TestContext.instance().create( null, cube, null, null, null, null); final String mdx = "select {[Product].[Product Family].Members} on rows, {[Measures].[Unit Sales]} on columns from [Foo]"; final String sqlOracle = "select \"product_class\".\"product_family\" as \"c0\", sum(\"agg_l_05_sales_fact_1997\".\"unit_sales\") as \"m0\" from \"product_class\" \"product_class\", \"product\" \"product\", \"agg_l_05_sales_fact_1997\" \"agg_l_05_sales_fact_1997\" where \"agg_l_05_sales_fact_1997\".\"product_id\" = \"product\".\"product_id\" and \"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" group by \"product_class\".\"product_family\""; final String sqlMysql = "select `product_class`.`product_family` as `c0`, sum(`agg_l_05_sales_fact_1997`.`unit_sales`) as `m0` from `product_class` as `product_class`, `product` as `product`, `agg_l_05_sales_fact_1997` as `agg_l_05_sales_fact_1997` where `agg_l_05_sales_fact_1997`.`product_id` = `product`.`product_id` and `product`.`product_class_id` = `product_class`.`product_class_id` group by `product_class`.`product_family`"; assertQuerySqlOrNot( context, mdx, new SqlPattern[] { new SqlPattern( Dialect.DatabaseProduct.ORACLE, sqlOracle, sqlOracle.length()), new SqlPattern( Dialect.DatabaseProduct.MYSQL, sqlMysql, sqlMysql.length()) }, false, false, true); } public void testTwoNonCollapsedAggregate() throws Exception { propSaver.set(MondrianProperties.instance().UseAggregates, true); propSaver.set(MondrianProperties.instance().ReadAggregates, true); final String cube = "\n" + "
\n" + " " + " " + " " + " " + " " + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "\n" + "\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n"; final TestContext context = TestContext.instance().create( null, cube, null, null, null, null); final String mdx = "select {Crossjoin([Product].[Product Family].Members, [Store].[Store Id].Members)} on rows, {[Measures].[Unit Sales]} on columns from [Foo]"; final String sqlOracle = "select \"product_class\".\"product_family\" as \"c0\", \"store\".\"store_id\" as \"c1\", sum(\"agg_l_05_sales_fact_1997\".\"unit_sales\") as \"m0\" from \"product_class\" \"product_class\", \"product\" \"product\", \"agg_l_05_sales_fact_1997\" \"agg_l_05_sales_fact_1997\", \"store\" \"store\" where \"agg_l_05_sales_fact_1997\".\"product_id\" = \"product\".\"product_id\" and \"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" and \"agg_l_05_sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" group by \"product_class\".\"product_family\", \"store\".\"store_id\""; final String sqlMysql = "select `product_class`.`product_family` as `c0`, `store`.`store_id` as `c1`, sum(`agg_l_05_sales_fact_1997`.`unit_sales`) as `m0` from `product_class` as `product_class`, `product` as `product`, `agg_l_05_sales_fact_1997` as `agg_l_05_sales_fact_1997`, `store` as `store` where `agg_l_05_sales_fact_1997`.`product_id` = `product`.`product_id` and `product`.`product_class_id` = `product_class`.`product_class_id` and `agg_l_05_sales_fact_1997`.`store_id` = `store`.`store_id` group by `product_class`.`product_family`, `store`.`store_id`"; assertQuerySqlOrNot( context, mdx, new SqlPattern[] { new SqlPattern( Dialect.DatabaseProduct.ORACLE, sqlOracle, sqlOracle.length()), new SqlPattern( Dialect.DatabaseProduct.MYSQL, sqlMysql, sqlMysql.length()) }, false, false, true); } } // End TestAggregationManager.java mondrian-3.4.1/testsrc/main/mondrian/rolap/FilterTest.java0000644000175000017500000016417211735330606023530 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.Connection; import mondrian.olap.MondrianProperties; import mondrian.rolap.sql.MemberListCrossJoinArg; import mondrian.spi.Dialect; import mondrian.test.SqlPattern; import mondrian.test.TestContext; /** * Tests for Filter and native Filters. * * @author Rushan Chen * @since April 28, 2009 */ public class FilterTest extends BatchTestCase { public FilterTest() { super(); } public FilterTest(String name) { super(name); } @Override protected void setUp() throws Exception { super.setUp(); propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, true); } public void testInFilterSimple() throws Exception { propSaver.set(MondrianProperties.instance().ExpandNonNative, false); propSaver.set(MondrianProperties.instance().EnableNativeFilter, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; String query = "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Customers],[*BASE_MEMBERS_Product])' " + "Set [*BASE_MEMBERS_Customers] as 'Filter([Customers].[City].Members,Ancestor([Customers].CurrentMember, [Customers].[State Province]) In {[Customers].[All Customers].[USA].[CA]})' " + "Set [*BASE_MEMBERS_Product] as 'Filter([Product].[Product Family].Members,[Product].CurrentMember In {[Product].[All Products].[Drink]})' " + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Customers].currentMember,[Product].currentMember)})' " + "Select " + "{[Measures].[Customer Count]} on columns, " + "Non Empty [*CJ_ROW_AXIS] on rows " + "From [Sales]"; checkNative(100, 45, query, null, requestFreshConnection); } public void testNotInFilterSimple() throws Exception { propSaver.set(MondrianProperties.instance().ExpandNonNative, false); propSaver.set(MondrianProperties.instance().EnableNativeFilter, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; String query = "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Customers],[*BASE_MEMBERS_Product])' " + "Set [*BASE_MEMBERS_Customers] as 'Filter([Customers].[City].Members,Ancestor([Customers].CurrentMember, [Customers].[State Province]) Not In {[Customers].[All Customers].[USA].[CA]})' " + "Set [*BASE_MEMBERS_Product] as 'Filter([Product].[Product Family].Members,[Product].CurrentMember Not In {[Product].[All Products].[Drink]})' " + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Customers].currentMember,[Product].currentMember)})' " + "Select " + "{[Measures].[Customer Count]} on columns, " + "Non Empty [*CJ_ROW_AXIS] on rows " + "From [Sales]"; checkNative(100, 66, query, null, requestFreshConnection); } public void testInFilterAND() throws Exception { propSaver.set(MondrianProperties.instance().ExpandNonNative, false); propSaver.set(MondrianProperties.instance().EnableNativeFilter, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; String query = "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Customers],[*BASE_MEMBERS_Product])' " + "Set [*BASE_MEMBERS_Customers] as 'Filter([Customers].[City].Members," + "((Ancestor([Customers].CurrentMember, [Customers].[State Province]) In {[Customers].[All Customers].[USA].[CA]}) " + "AND ([Customers].CurrentMember Not In {[Customers].[All Customers].[USA].[CA].[Altadena]})))' " + "Set [*BASE_MEMBERS_Product] as 'Filter([Product].[Product Family].Members,[Product].CurrentMember Not In {[Product].[All Products].[Drink]})' " + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Customers].currentMember,[Product].currentMember)})' " + "Select " + "{[Measures].[Customer Count]} on columns, " + "Non Empty [*CJ_ROW_AXIS] on rows " + "From [Sales]"; checkNative(100, 88, query, null, requestFreshConnection); } public void testIsFilterSimple() throws Exception { propSaver.set(MondrianProperties.instance().ExpandNonNative, false); propSaver.set(MondrianProperties.instance().EnableNativeFilter, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; String query = "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Customers],[*BASE_MEMBERS_Product])' " + "Set [*BASE_MEMBERS_Customers] as 'Filter([Customers].[City].Members,Ancestor([Customers].CurrentMember, [Customers].[State Province]) Is [Customers].[All Customers].[USA].[CA])' " + "Set [*BASE_MEMBERS_Product] as 'Filter([Product].[Product Family].Members,[Product].CurrentMember Is [Product].[All Products].[Drink])' " + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Customers].currentMember,[Product].currentMember)})' " + "Select " + "{[Measures].[Customer Count]} on columns, " + "Non Empty [*CJ_ROW_AXIS] on rows " + "From [Sales]"; checkNative(100, 45, query, null, requestFreshConnection); } public void testNotIsFilterSimple() throws Exception { propSaver.set(MondrianProperties.instance().ExpandNonNative, false); propSaver.set(MondrianProperties.instance().EnableNativeFilter, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; String query = "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Customers],[*BASE_MEMBERS_Product])' " + "Set [*BASE_MEMBERS_Customers] as 'Filter([Customers].[City].Members, not (Ancestor([Customers].CurrentMember, [Customers].[State Province]) Is [Customers].[All Customers].[USA].[CA]))' " + "Set [*BASE_MEMBERS_Product] as 'Filter([Product].[Product Family].Members,not ([Product].CurrentMember Is [Product].[All Products].[Drink]))' " + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Customers].currentMember,[Product].currentMember)})' " + "Select " + "{[Measures].[Customer Count]} on columns, " + "Non Empty [*CJ_ROW_AXIS] on rows " + "From [Sales]"; checkNative(100, 66, query, null, requestFreshConnection); } public void testMixedInIsFilters() throws Exception { propSaver.set(MondrianProperties.instance().ExpandNonNative, false); propSaver.set(MondrianProperties.instance().EnableNativeFilter, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; String query = "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Customers],[*BASE_MEMBERS_Product])' " + "Set [*BASE_MEMBERS_Customers] as 'Filter([Customers].[City].Members," + "((Ancestor([Customers].CurrentMember, [Customers].[State Province]) Is [Customers].[All Customers].[USA].[CA]) " + "AND ([Customers].CurrentMember Not In {[Customers].[All Customers].[USA].[CA].[Altadena]})))' " + "Set [*BASE_MEMBERS_Product] as 'Filter([Product].[Product Family].Members, not ([Product].CurrentMember Is [Product].[All Products].[Drink]))' " + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Customers].currentMember,[Product].currentMember)})' " + "Select " + "{[Measures].[Customer Count]} on columns, " + "Non Empty [*CJ_ROW_AXIS] on rows " + "From [Sales]"; checkNative(100, 88, query, null, requestFreshConnection); } /** * Here the filter is above (rather than as inputs to) the NECJ. These * types of filters are currently not natively evaluated. * *

To expand on this case, RolapNativeFilter needs to be improved so it * knows how to represent the dimension filter constraint. Currently the * FilterConstraint is only used for filters on measures. * * @throws Exception */ public void testInFilterNonNative() throws Exception { propSaver.set(MondrianProperties.instance().ExpandNonNative, false); propSaver.set(MondrianProperties.instance().EnableNativeFilter, true); String query = "With " + "Set [*BASE_CJ_SET] as 'CrossJoin([Customers].[City].Members,[Product].[Product Family].Members)' " + "Set [*NATIVE_CJ_SET] as 'Filter([*BASE_CJ_SET], " + "(Ancestor([Customers].CurrentMember,[Customers].[State Province]) In {[Customers].[All Customers].[USA].[CA]}) AND ([Product].CurrentMember In {[Product].[All Products].[Drink]}))' " + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Customers].currentMember,[Product].currentMember)})' " + "Select " + "{[Measures].[Customer Count]} on columns, " + "Non Empty [*CJ_ROW_AXIS] on rows " + "From [Sales]"; checkNotNative(45, query); } public void testTopCountOverInFilter() throws Exception { propSaver.set(MondrianProperties.instance().ExpandNonNative, false); propSaver.set(MondrianProperties.instance().EnableNativeFilter, true); propSaver.set(MondrianProperties.instance().EnableNativeTopCount, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; String query = "With " + "Set [*NATIVE_TOP_SET] as 'TopCount([*BASE_MEMBERS_Customers], 3, [Measures].[Customer Count])' " + "Set [*BASE_MEMBERS_Customers] as 'Filter([Customers].[City].Members,Ancestor([Customers].CurrentMember, [Customers].[State Province]) In {[Customers].[All Customers].[USA].[CA]})' " + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_TOP_SET], {([Customers].currentMember,[Product].currentMember)})' " + "Select " + "{[Measures].[Customer Count]} on columns, " + "Non Empty [*CJ_ROW_AXIS] on rows " + "From [Sales]"; checkNative(100, 3, query, null, requestFreshConnection); } /** * Test that if Null member is not explicitly excluded, then the native * filter SQL should not filter out null members. * * @throws Exception */ public void testNotInFilterKeepNullMember() throws Exception { propSaver.set(MondrianProperties.instance().ExpandNonNative, false); propSaver.set(MondrianProperties.instance().EnableNativeFilter, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; String query = "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Customers],[*BASE_MEMBERS_SQFT])' " + "Set [*BASE_MEMBERS_Customers] as 'Filter([Customers].[Country].Members, [Customers].CurrentMember In {[Customers].[All Customers].[USA]})' " + "Set [*BASE_MEMBERS_SQFT] as 'Filter([Store Size in SQFT].[Store Sqft].Members, [Store Size in SQFT].currentMember not in {[Store Size in SQFT].[All Store Size in SQFTs].[39696]})' " + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Customers].currentMember,[Store Size in SQFT].currentMember)})' " + "Set [*ORDERED_CJ_ROW_AXIS] as 'Order([*CJ_ROW_AXIS], [Store Size in SQFT].currentmember.OrderKey, BASC)' " + "Select " + "{[Measures].[Customer Count]} on columns, " + "Non Empty [*ORDERED_CJ_ROW_AXIS] on rows " + "From [Sales]"; String result = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Customers].[USA], [Store Size in SQFT].[#null]}\n" + "{[Customers].[USA], [Store Size in SQFT].[20319]}\n" + "{[Customers].[USA], [Store Size in SQFT].[21215]}\n" + "{[Customers].[USA], [Store Size in SQFT].[22478]}\n" + "{[Customers].[USA], [Store Size in SQFT].[23598]}\n" + "{[Customers].[USA], [Store Size in SQFT].[23688]}\n" + "{[Customers].[USA], [Store Size in SQFT].[27694]}\n" + "{[Customers].[USA], [Store Size in SQFT].[28206]}\n" + "{[Customers].[USA], [Store Size in SQFT].[30268]}\n" + "{[Customers].[USA], [Store Size in SQFT].[33858]}\n" + "Row #0: 1,153\n" + "Row #1: 563\n" + "Row #2: 906\n" + "Row #3: 296\n" + "Row #4: 1,147\n" + "Row #5: 1,059\n" + "Row #6: 474\n" + "Row #7: 190\n" + "Row #8: 84\n" + "Row #9: 278\n"; checkNative(0, 10, query, result, requestFreshConnection); } /** * Test that if Null member is explicitly excluded, then the native filter * SQL should filter out null members. * * @throws Exception */ public void testNotInFilterExcludeNullMember() throws Exception { propSaver.set(MondrianProperties.instance().ExpandNonNative, false); propSaver.set(MondrianProperties.instance().EnableNativeFilter, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; String query = "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Customers],[*BASE_MEMBERS_SQFT])' " + "Set [*BASE_MEMBERS_Customers] as 'Filter([Customers].[Country].Members, [Customers].CurrentMember In {[Customers].[All Customers].[USA]})' " + "Set [*BASE_MEMBERS_SQFT] as 'Filter([Store Size in SQFT].[Store Sqft].Members, " + "[Store Size in SQFT].currentMember not in {[Store Size in SQFT].[All Store Size in SQFTs].[#null], [Store Size in SQFT].[All Store Size in SQFTs].[39696]})' " + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Customers].currentMember,[Store Size in SQFT].currentMember)})' " + "Set [*ORDERED_CJ_ROW_AXIS] as 'Order([*CJ_ROW_AXIS], [Store Size in SQFT].currentmember.OrderKey, BASC)' " + "Select " + "{[Measures].[Customer Count]} on columns, " + "Non Empty [*ORDERED_CJ_ROW_AXIS] on rows " + "From [Sales]"; String result = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Customers].[USA], [Store Size in SQFT].[20319]}\n" + "{[Customers].[USA], [Store Size in SQFT].[21215]}\n" + "{[Customers].[USA], [Store Size in SQFT].[22478]}\n" + "{[Customers].[USA], [Store Size in SQFT].[23598]}\n" + "{[Customers].[USA], [Store Size in SQFT].[23688]}\n" + "{[Customers].[USA], [Store Size in SQFT].[27694]}\n" + "{[Customers].[USA], [Store Size in SQFT].[28206]}\n" + "{[Customers].[USA], [Store Size in SQFT].[30268]}\n" + "{[Customers].[USA], [Store Size in SQFT].[33858]}\n" + "Row #0: 563\n" + "Row #1: 906\n" + "Row #2: 296\n" + "Row #3: 1,147\n" + "Row #4: 1,059\n" + "Row #5: 474\n" + "Row #6: 190\n" + "Row #7: 84\n" + "Row #8: 278\n"; checkNative(0, 9, query, result, requestFreshConnection); } /** * Test that null members are included when the filter excludes members * that contain multiple levels, but none being null. */ public void testNotInMultiLevelMemberConstraintNonNullParent() { if (MondrianProperties.instance().ReadAggregates.get()) { // If aggregate tables are enabled, generates similar SQL involving // agg tables. return; } String query = "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Customers],[*BASE_MEMBERS_Quarters])' " + "Set [*BASE_MEMBERS_Customers] as 'Filter([Customers].[Country].Members, [Customers].CurrentMember In {[Customers].[All Customers].[USA]})' " + "Set [*BASE_MEMBERS_Quarters] as 'Filter([Time].[Quarter].Members, " + "[Time].currentMember not in {[Time].[1997].[Q1], [Time].[1998].[Q3]})' " + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Customers].currentMember,[Time].currentMember)})' " + "Set [*ORDERED_CJ_ROW_AXIS] as 'Order([*CJ_ROW_AXIS], [Time].currentmember.OrderKey, BASC)' " + "Select " + "{[Measures].[Customer Count]} on columns, " + "Non Empty [*ORDERED_CJ_ROW_AXIS] on rows " + "From [Sales]"; String necjSqlDerby = "select \"customer\".\"country\", \"time_by_day\".\"the_year\", " + "\"time_by_day\".\"quarter\" from \"customer\" as \"customer\", " + "\"sales_fact_1997\" as \"sales_fact_1997\", \"time_by_day\" as " + "\"time_by_day\" where \"sales_fact_1997\".\"customer_id\" = " + "\"customer\".\"customer_id\" and \"sales_fact_1997\".\"time_id\" = " + "\"time_by_day\".\"time_id\" and (\"customer\".\"country\" = 'USA') and " + "(not ((\"time_by_day\".\"the_year\" = 1997 and \"time_by_day\".\"quarter\" " + "= 'Q1') or (\"time_by_day\".\"the_year\" = 1998 and " + "\"time_by_day\".\"quarter\" = 'Q3')) or ((\"time_by_day\".\"quarter\" is " + "null or \"time_by_day\".\"the_year\" is null) and " + "not((\"time_by_day\".\"the_year\" = 1997 and \"time_by_day\".\"quarter\" " + "= 'Q1') or (\"time_by_day\".\"the_year\" = 1998 and " + "\"time_by_day\".\"quarter\" = 'Q3')))) group by \"customer\".\"country\", " + "\"time_by_day\".\"the_year\", \"time_by_day\".\"quarter\" " + "order by CASE WHEN \"customer\".\"country\" IS NULL THEN 1 ELSE 0 END, \"customer\".\"country\" ASC, CASE WHEN \"time_by_day\".\"the_year\" IS NULL THEN 1 ELSE 0 END, \"time_by_day\".\"the_year\" ASC, CASE WHEN \"time_by_day\".\"quarter\" IS NULL THEN 1 ELSE 0 END, \"time_by_day\".\"quarter\" ASC"; String necjSqlMySql = "select `customer`.`country` as `c0`, `time_by_day`.`the_year` as `c1`, " + "`time_by_day`.`quarter` as `c2` from `customer` as `customer`, " + "`sales_fact_1997` as `sales_fact_1997`, `time_by_day` as `time_by_day` " + "where `sales_fact_1997`.`customer_id` = `customer`.`customer_id` " + "and `sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and " + "(`customer`.`country` = 'USA') and " + "(not ((`time_by_day`.`quarter`, `time_by_day`.`the_year`) in " + "(('Q1', 1997), ('Q3', 1998))) or (`time_by_day`.`quarter` is null or " + "`time_by_day`.`the_year` is null)) " + "group by `customer`.`country`, `time_by_day`.`the_year`, " + "`time_by_day`.`quarter` order by ISNULL(`customer`.`country`) ASC, " + "`customer`.`country` ASC, ISNULL(`time_by_day`.`the_year`) ASC, " + "`time_by_day`.`the_year` ASC, ISNULL(`time_by_day`.`quarter`) ASC, " + "`time_by_day`.`quarter` ASC"; SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.DERBY, necjSqlDerby, necjSqlDerby), new SqlPattern( Dialect.DatabaseProduct.MYSQL, necjSqlMySql, necjSqlMySql) }; assertQuerySql(query, patterns); } /** * Test that null members are included when the filter excludes members * that contain multiple levels, but none being null. The members have * the same parent. */ public void testNotInMultiLevelMemberConstraintNonNullSameParent() { if (MondrianProperties.instance().ReadAggregates.get()) { // If aggregate tables are enabled, generates similar SQL involving // agg tables. return; } String query = "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Customers],[*BASE_MEMBERS_Quarters])' " + "Set [*BASE_MEMBERS_Customers] as 'Filter([Customers].[Country].Members, [Customers].CurrentMember In {[Customers].[All Customers].[USA]})' " + "Set [*BASE_MEMBERS_Quarters] as 'Filter([Time].[Quarter].Members, " + "[Time].currentMember not in {[Time].[1997].[Q1], [Time].[1997].[Q3]})' " + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Customers].currentMember,[Time].currentMember)})' " + "Set [*ORDERED_CJ_ROW_AXIS] as 'Order([*CJ_ROW_AXIS], [Time].currentmember.OrderKey, BASC)' " + "Select " + "{[Measures].[Customer Count]} on columns, " + "Non Empty [*ORDERED_CJ_ROW_AXIS] on rows " + "From [Sales]"; String necjSqlDerby = "select \"customer\".\"country\", \"time_by_day\".\"the_year\", " + "\"time_by_day\".\"quarter\" from \"customer\" as \"customer\", " + "\"sales_fact_1997\" as \"sales_fact_1997\", \"time_by_day\" as " + "\"time_by_day\" where \"sales_fact_1997\".\"customer_id\" = " + "\"customer\".\"customer_id\" and \"sales_fact_1997\".\"time_id\" = " + "\"time_by_day\".\"time_id\" and (\"customer\".\"country\" = 'USA') and " + "((not (\"time_by_day\".\"quarter\" in ('Q1', 'Q3')) or " + "(\"time_by_day\".\"quarter\" is null)) or (not " + "(\"time_by_day\".\"the_year\" = 1997) or (\"time_by_day\".\"the_year\" is " + "null))) group by \"customer\".\"country\", \"time_by_day\".\"the_year\", " + "\"time_by_day\".\"quarter\" " + "order by CASE WHEN \"customer\".\"country\" IS NULL THEN 1 ELSE 0 END, \"customer\".\"country\" ASC, CASE WHEN \"time_by_day\".\"the_year\" IS NULL THEN 1 ELSE 0 END, \"time_by_day\".\"the_year\" ASC, CASE WHEN \"time_by_day\".\"quarter\" IS NULL THEN 1 ELSE 0 END, \"time_by_day\".\"quarter\" ASC"; String necjSqlMySql = "select `customer`.`country` as `c0`, `time_by_day`.`the_year` as " + "`c1`, `time_by_day`.`quarter` as `c2` from `customer` as " + "`customer`, `sales_fact_1997` as `sales_fact_1997`, `time_by_day` " + "as `time_by_day` where `sales_fact_1997`.`customer_id` = " + "`customer`.`customer_id` and `sales_fact_1997`.`time_id` = " + "`time_by_day`.`time_id` and (`customer`.`country` = 'USA') and " + "((not (`time_by_day`.`quarter` in ('Q1', 'Q3')) or " + "(`time_by_day`.`quarter` is null)) or (not " + "(`time_by_day`.`the_year` = 1997) or (`time_by_day`.`the_year` " + "is null))) group by `customer`.`country`, `time_by_day`.`the_year`," + " `time_by_day`.`quarter` order by ISNULL(`customer`.`country`) ASC, " + "`customer`.`country` ASC, ISNULL(`time_by_day`.`the_year`) ASC, " + "`time_by_day`.`the_year` ASC, ISNULL(`time_by_day`.`quarter`) ASC, " + "`time_by_day`.`quarter` ASC"; SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.DERBY, necjSqlDerby, necjSqlDerby), new SqlPattern( Dialect.DatabaseProduct.MYSQL, necjSqlMySql, necjSqlMySql) }; assertQuerySql(query, patterns); } /** * Test that null members are included when the filter explicitly excludes * certain members that contain nulls. The members span multiple levels. */ public void testNotInMultiLevelMemberConstraintMixedNullNonNullParent() { if (!isDefaultNullMemberRepresentation()) { return; } if (MondrianProperties.instance().FilterChildlessSnowflakeMembers.get()) { return; } String dimension = "\n" + " \n" + "

\n" + " \n" + " \n" + " \n" + " \n" + "\n"; String cube = "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + ""; String query = "with\n" + "set [Filtered Warehouse Set] as 'Filter([Warehouse2].[name].Members, [Warehouse2].CurrentMember Not In" + "{[Warehouse2].[#null].[234 West Covina Pkwy].[Freeman And Co]," + " [Warehouse2].[971-555-6213].[3377 Coachman Place].[Jones International]})' " + "set [NECJ] as NonEmptyCrossJoin([Filtered Warehouse Set], {[Product].[Product Family].Food}) " + "select [NECJ] on 0 from [Warehouse2]"; String necjSqlDerby = "select \"warehouse\".\"warehouse_fax\", \"warehouse\".\"wa_address1\", " + "\"warehouse\".\"warehouse_name\", \"product_class\".\"product_family\" " + "from \"warehouse\" as \"warehouse\", \"inventory_fact_1997\" as " + "\"inventory_fact_1997\", \"product\" as \"product\", \"product_class\" as " + "\"product_class\" where \"inventory_fact_1997\".\"warehouse_id\" = " + "\"warehouse\".\"warehouse_id\" and \"product\".\"product_class_id\" = " + "\"product_class\".\"product_class_id\" and " + "\"inventory_fact_1997\".\"product_id\" = \"product\".\"product_id\" and " + "(\"product_class\".\"product_family\" = 'Food') and " + "(not ((\"warehouse\".\"wa_address1\" = '234 West Covina Pkwy' and " + "\"warehouse\".\"warehouse_fax\" is null and " + "\"warehouse\".\"warehouse_name\" = 'Freeman And Co') or " + "(\"warehouse\".\"wa_address1\" = '3377 Coachman Place' and " + "\"warehouse\".\"warehouse_fax\" = '971-555-6213' and " + "\"warehouse\".\"warehouse_name\" = 'Jones International')) or " + "((\"warehouse\".\"warehouse_name\" is null or " + "\"warehouse\".\"wa_address1\" is null or \"warehouse\".\"warehouse_fax\" " + "is null) and not((\"warehouse\".\"wa_address1\" = " + "'234 West Covina Pkwy' and \"warehouse\".\"warehouse_fax\" is null " + "and \"warehouse\".\"warehouse_name\" = 'Freeman And Co') or " + "(\"warehouse\".\"wa_address1\" = '3377 Coachman Place' and " + "\"warehouse\".\"warehouse_fax\" = '971-555-6213' and " + "\"warehouse\".\"warehouse_name\" = 'Jones International')))) " + "group by \"warehouse\".\"warehouse_fax\", \"warehouse\".\"wa_address1\", " + "\"warehouse\".\"warehouse_name\", \"product_class\".\"product_family\" " + "order by \"warehouse\".\"warehouse_fax\" ASC, " + "\"warehouse\".\"wa_address1\" ASC, \"warehouse\".\"warehouse_name\" ASC, " + "\"product_class\".\"product_family\" ASC"; String necjSqlMySql = "select `warehouse`.`warehouse_fax` as `c0`, `warehouse`.`wa_address1` as `c1`, " + "`warehouse`.`warehouse_name` as `c2`, `product_class`.`product_family` as `c3` " + "from `warehouse` as `warehouse`, `inventory_fact_1997` as `inventory_fact_1997`, " + "`product` as `product`, `product_class` as `product_class` where " + "`inventory_fact_1997`.`warehouse_id` = `warehouse`.`warehouse_id` " + "and `product`.`product_class_id` = `product_class`.`product_class_id` " + "and `inventory_fact_1997`.`product_id` = `product`.`product_id` " + "and (`product_class`.`product_family` = 'Food') and " + "(not ((`warehouse`.`warehouse_name`, `warehouse`.`wa_address1`, `warehouse`.`warehouse_fax`) " + "in (('Jones International', '3377 Coachman Place', '971-555-6213')) " + "or (`warehouse`.`warehouse_fax` is null and (`warehouse`.`warehouse_name`, `warehouse`.`wa_address1`) " + "in (('Freeman And Co', '234 West Covina Pkwy')))) or " + "((`warehouse`.`warehouse_name` is null or `warehouse`.`wa_address1` is null " + "or `warehouse`.`warehouse_fax` is null) and not((`warehouse`.`warehouse_fax` is null " + "and (`warehouse`.`warehouse_name`, `warehouse`.`wa_address1`) in " + "(('Freeman And Co', '234 West Covina Pkwy')))))) " + "group by `warehouse`.`warehouse_fax`, `warehouse`.`wa_address1`, " + "`warehouse`.`warehouse_name`, `product_class`.`product_family` " + "order by ISNULL(`warehouse`.`warehouse_fax`), `warehouse`.`warehouse_fax` ASC, " + "ISNULL(`warehouse`.`wa_address1`), `warehouse`.`wa_address1` ASC, " + "ISNULL(`warehouse`.`warehouse_name`), `warehouse`.`warehouse_name` ASC, " + "ISNULL(`product_class`.`product_family`), `product_class`.`product_family` ASC"; SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.DERBY, necjSqlDerby, necjSqlDerby), new SqlPattern( Dialect.DatabaseProduct.MYSQL, necjSqlMySql, necjSqlMySql) }; TestContext testContext = TestContext.instance().create( dimension, cube, null, null, null, null); assertQuerySql(testContext, query, patterns); } /** * Test that null members are included when the filter explicitly excludes * a single member that has a null. The members span multiple levels. */ public void testNotInMultiLevelMemberConstraintSingleNullParent() { if (!isDefaultNullMemberRepresentation()) { return; } if (MondrianProperties.instance().FilterChildlessSnowflakeMembers.get()) { return; } String dimension = "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "\n"; String cube = "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + ""; String query = "with\n" + "set [Filtered Warehouse Set] as 'Filter([Warehouse2].[name].Members, [Warehouse2].CurrentMember Not In" + "{[Warehouse2].[#null].[234 West Covina Pkwy].[Freeman And Co]})' " + "set [NECJ] as NonEmptyCrossJoin([Filtered Warehouse Set], {[Product].[Product Family].Food}) " + "select [NECJ] on 0 from [Warehouse2]"; String necjSqlDerby = "select \"warehouse\".\"warehouse_fax\", \"warehouse\".\"wa_address1\", " + "\"warehouse\".\"warehouse_name\", \"product_class\".\"product_family\" " + "from \"warehouse\" as \"warehouse\", \"inventory_fact_1997\" as " + "\"inventory_fact_1997\", \"product\" as \"product\", \"product_class\" " + "as \"product_class\" where \"inventory_fact_1997\".\"warehouse_id\" = " + "\"warehouse\".\"warehouse_id\" and \"product\".\"product_class_id\" = " + "\"product_class\".\"product_class_id\" and " + "\"inventory_fact_1997\".\"product_id\" = \"product\".\"product_id\" and " + "(\"product_class\".\"product_family\" = 'Food') and ((not " + "(\"warehouse\".\"warehouse_name\" = 'Freeman And Co') or " + "(\"warehouse\".\"warehouse_name\" is null)) or (not " + "(\"warehouse\".\"wa_address1\" = '234 West Covina Pkwy') or " + "(\"warehouse\".\"wa_address1\" is null)) or not " + "(\"warehouse\".\"warehouse_fax\" is null)) group by " + "\"warehouse\".\"warehouse_fax\", \"warehouse\".\"wa_address1\", " + "\"warehouse\".\"warehouse_name\", \"product_class\".\"product_family\" " + "order by \"warehouse\".\"warehouse_fax\" ASC, " + "\"warehouse\".\"wa_address1\" ASC, \"warehouse\".\"warehouse_name\" ASC, " + "\"product_class\".\"product_family\" ASC"; String necjSqlMySql = "select `warehouse`.`warehouse_fax` as `c0`, " + "`warehouse`.`wa_address1` as `c1`, `warehouse`.`warehouse_name` " + "as `c2`, `product_class`.`product_family` as `c3` from " + "`warehouse` as `warehouse`, `inventory_fact_1997` as " + "`inventory_fact_1997`, `product` as `product`, `product_class` " + "as `product_class` where `inventory_fact_1997`.`warehouse_id` = " + "`warehouse`.`warehouse_id` and `product`.`product_class_id` = " + "`product_class`.`product_class_id` and " + "`inventory_fact_1997`.`product_id` = `product`.`product_id` and " + "(`product_class`.`product_family` = 'Food') and " + "((not (`warehouse`.`warehouse_name` = 'Freeman And Co') or " + "(`warehouse`.`warehouse_name` is null)) or (not " + "(`warehouse`.`wa_address1` = '234 West Covina Pkwy') or " + "(`warehouse`.`wa_address1` is null)) or not " + "(`warehouse`.`warehouse_fax` is null)) group by " + "`warehouse`.`warehouse_fax`, `warehouse`.`wa_address1`, " + "`warehouse`.`warehouse_name`, `product_class`.`product_family` " + "order by ISNULL(`warehouse`.`warehouse_fax`), " + "`warehouse`.`warehouse_fax` ASC, " + "ISNULL(`warehouse`.`wa_address1`), `warehouse`.`wa_address1` ASC, " + "ISNULL(`warehouse`.`warehouse_name`), " + "`warehouse`.`warehouse_name` ASC, " + "ISNULL(`product_class`.`product_family`), " + "`product_class`.`product_family` ASC"; SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.DERBY, necjSqlDerby, necjSqlDerby), new SqlPattern( Dialect.DatabaseProduct.MYSQL, necjSqlMySql, necjSqlMySql) }; TestContext testContext = TestContext.instance().create( dimension, cube, null, null, null, null); assertQuerySql(testContext, query, patterns); } public void testCachedNativeSetUsingFilters() throws Exception { propSaver.set(MondrianProperties.instance().ExpandNonNative, false); propSaver.set(MondrianProperties.instance().EnableNativeFilter, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; String query1 = "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Customers],[*BASE_MEMBERS_Product])' " + "Set [*BASE_MEMBERS_Customers] as 'Filter([Customers].[City].Members,Ancestor([Customers].CurrentMember, [Customers].[State Province]) In {[Customers].[All Customers].[USA].[CA]})' " + "Set [*BASE_MEMBERS_Product] as 'Filter([Product].[Product Family].Members,[Product].CurrentMember In {[Product].[All Products].[Drink]})' " + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Customers].currentMember,[Product].currentMember)})' " + "Select " + "{[Measures].[Customer Count]} on columns, " + "Non Empty [*CJ_ROW_AXIS] on rows " + "From [Sales]"; checkNative(100, 45, query1, null, requestFreshConnection); // query2 has different filters; it should not reuse the result from // query1. String query2 = "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Customers],[*BASE_MEMBERS_Product])' " + "Set [*BASE_MEMBERS_Customers] as 'Filter([Customers].[City].Members,Ancestor([Customers].CurrentMember, [Customers].[State Province]) In {[Customers].[All Customers].[USA].[OR]})' " + "Set [*BASE_MEMBERS_Product] as 'Filter([Product].[Product Family].Members,[Product].CurrentMember In {[Product].[All Products].[Drink]})' " + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Customers].currentMember,[Product].currentMember)})' " + "Select " + "{[Measures].[Customer Count]} on columns, " + "Non Empty [*CJ_ROW_AXIS] on rows " + "From [Sales]"; checkNative(100, 11, query2, null, requestFreshConnection); } public void testNativeFilter() { propSaver.set(MondrianProperties.instance().ExpandNonNative, false); propSaver.set(MondrianProperties.instance().EnableNativeFilter, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; checkNative( 32, 18, "select {[Measures].[Store Sales]} ON COLUMNS, " + "Order(Filter(Descendants([Customers].[All Customers].[USA].[CA], [Customers].[Name]), ([Measures].[Store Sales] > 200.0)), [Measures].[Store Sales], DESC) ON ROWS " + "from [Sales] " + "where ([Time].[1997])", null, requestFreshConnection); } /** * Executes a Filter() whose condition contains a calculated member. */ public void testCmNativeFilter() { propSaver.set(MondrianProperties.instance().ExpandNonNative, false); propSaver.set(MondrianProperties.instance().EnableNativeFilter, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; checkNative( 0, 8, "with member [Measures].[Rendite] as '([Measures].[Store Sales] - [Measures].[Store Cost]) / [Measures].[Store Cost]' " + "select NON EMPTY {[Measures].[Unit Sales], [Measures].[Store Cost], [Measures].[Rendite], [Measures].[Store Sales]} ON COLUMNS, " + "NON EMPTY Order(Filter([Product].[Product Name].Members, ([Measures].[Rendite] > 1.8)), [Measures].[Rendite], BDESC) ON ROWS " + "from [Sales] " + "where ([Store].[All Stores].[USA].[CA], [Time].[1997])", "Axis #0:\n" + "{[Store].[USA].[CA], [Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Rendite]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[Plato].[Plato Extra Chunky Peanut Butter]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Horatio].[Horatio Buttered Popcorn]}\n" + "{[Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Better].[Better Canned Tuna in Oil]}\n" + "{[Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top].[High Top Cantelope]}\n" + "{[Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Denny].[Denny 75 Watt Lightbulb]}\n" + "{[Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Johnson].[Johnson Oatmeal]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Portsmouth].[Portsmouth Light Wine]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Squash]}\n" + "Row #0: 42\n" + "Row #0: 24.06\n" + "Row #0: 1.93\n" + "Row #0: 70.56\n" + "Row #1: 36\n" + "Row #1: 29.02\n" + "Row #1: 1.91\n" + "Row #1: 84.60\n" + "Row #2: 39\n" + "Row #2: 20.55\n" + "Row #2: 1.85\n" + "Row #2: 58.50\n" + "Row #3: 25\n" + "Row #3: 21.76\n" + "Row #3: 1.84\n" + "Row #3: 61.75\n" + "Row #4: 43\n" + "Row #4: 59.62\n" + "Row #4: 1.83\n" + "Row #4: 168.99\n" + "Row #5: 34\n" + "Row #5: 7.20\n" + "Row #5: 1.83\n" + "Row #5: 20.40\n" + "Row #6: 36\n" + "Row #6: 33.10\n" + "Row #6: 1.83\n" + "Row #6: 93.60\n" + "Row #7: 46\n" + "Row #7: 28.34\n" + "Row #7: 1.81\n" + "Row #7: 79.58\n", requestFreshConnection); } public void testNonNativeFilterWithNullMeasure() { propSaver.set(MondrianProperties.instance().ExpandNonNative, false); propSaver.set(MondrianProperties.instance().EnableNativeFilter, false); checkNotNative( 9, "select Filter([Store].[Store Name].members, " + " Not ([Measures].[Store Sqft] - [Measures].[Grocery Sqft] < 10000)) on rows, " + "{[Measures].[Store Sqft], [Measures].[Grocery Sqft]} on columns " + "from [Store]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sqft]}\n" + "{[Measures].[Grocery Sqft]}\n" + "Axis #2:\n" + "{[Store].[Mexico].[DF].[Mexico City].[Store 9]}\n" + "{[Store].[Mexico].[DF].[San Andres].[Store 21]}\n" + "{[Store].[Mexico].[Yucatan].[Merida].[Store 8]}\n" + "{[Store].[USA].[CA].[Alameda].[HQ]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "{[Store].[USA].[WA].[Walla Walla].[Store 22]}\n" + "{[Store].[USA].[WA].[Yakima].[Store 23]}\n" + "Row #0: 36,509\n" + "Row #0: 22,450\n" + "Row #1: \n" + "Row #1: \n" + "Row #2: 30,797\n" + "Row #2: 20,141\n" + "Row #3: \n" + "Row #3: \n" + "Row #4: \n" + "Row #4: \n" + "Row #5: 39,696\n" + "Row #5: 24,390\n" + "Row #6: 33,858\n" + "Row #6: 22,123\n" + "Row #7: \n" + "Row #7: \n" + "Row #8: \n" + "Row #8: \n"); } public void testNativeFilterWithNullMeasure() { // Currently this behaves differently from the non-native evaluation. propSaver.set(MondrianProperties.instance().EnableNativeFilter, true); propSaver.set(MondrianProperties.instance().ExpandNonNative, false); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. final TestContext context = getTestContext().withFreshConnection(); context.assertQueryReturns( "select Filter([Store].[Store Name].members, " + " Not ([Measures].[Store Sqft] - [Measures].[Grocery Sqft] < 10000)) on rows, " + "{[Measures].[Store Sqft], [Measures].[Grocery Sqft]} on columns " + "from [Store]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sqft]}\n" + "{[Measures].[Grocery Sqft]}\n" + "Axis #2:\n" + "{[Store].[Mexico].[DF].[Mexico City].[Store 9]}\n" + "{[Store].[Mexico].[Yucatan].[Merida].[Store 8]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "Row #0: 36,509\n" + "Row #0: 22,450\n" + "Row #1: 30,797\n" + "Row #1: 20,141\n" + "Row #2: 39,696\n" + "Row #2: 24,390\n" + "Row #3: 33,858\n" + "Row #3: 22,123\n"); } public void testNonNativeFilterWithCalcMember() { // Currently this query cannot run natively propSaver.set(MondrianProperties.instance().EnableNativeFilter, false); propSaver.set(MondrianProperties.instance().ExpandNonNative, false); checkNotNative( 3, "with\n" + "member [Time].[Time].[Date Range] as 'Aggregate({[Time].[1997].[Q1]:[Time].[1997].[Q4]})'\n" + "select\n" + "{[Measures].[Unit Sales]} ON columns,\n" + "Filter ([Store].[Store State].members, [Measures].[Store Cost] > 100) ON rows\n" + "from [Sales]\n" + "where [Time].[Date Range]\n", "Axis #0:\n" + "{[Time].[Date Range]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 74,748\n" + "Row #1: 67,659\n" + "Row #2: 124,366\n"); } /** * Verify that filter with Not IsEmpty(storedMeasure) can be natively * evaluated. */ public void testNativeFilterNonEmpty() { propSaver.set(MondrianProperties.instance().ExpandNonNative, false); propSaver.set(MondrianProperties.instance().EnableNativeFilter, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; checkNative( 0, 20, "select Filter(CrossJoin([Store].[Store Name].members, " + " " + TestContext.hierarchyName("Store Type", "Store Type") + ".[Store Type].members), " + " Not IsEmpty([Measures].[Store Sqft])) on rows, " + "{[Measures].[Store Sqft]} on columns " + "from [Store]", null, requestFreshConnection); } /** * Testcase for * bug MONDRIAN-706, * "SQL using hierarchy attribute 'Column Name' instead of 'Column' in the * filter". */ public void testBugMondrian706() { propSaver.set( MondrianProperties.instance().UseAggregates, false); propSaver.set( MondrianProperties.instance().ReadAggregates, false); propSaver.set( MondrianProperties.instance().DisableCaching, false); propSaver.set( MondrianProperties.instance().EnableNativeNonEmpty, true); propSaver.set( MondrianProperties.instance().CompareSiblingsByOrderKey, true); propSaver.set( MondrianProperties.instance().NullDenominatorProducesNull, true); propSaver.set( MondrianProperties.instance().ExpandNonNative, true); propSaver.set( MondrianProperties.instance().EnableNativeFilter, true); // With bug MONDRIAN-706, would generate // // ((`store`.`store_name`, `store`.`store_city`, `store`.`store_state`) // in (('11', 'Portland', 'OR'), ('14', 'San Francisco', 'CA')) // // Notice that the '11' and '14' store ID is applied on the store_name // instead of the store_id. So it would return no rows. final String badMysqlSQL = "select `store`.`store_country` as `c0`, `store`.`store_state` as `c1`, `store`.`store_city` as `c2`, `store`.`store_id` as `c3`, `store`.`store_name` as `c4`, `store`.`store_type` as `c5`, `store`.`store_manager` as `c6`, `store`.`store_sqft` as `c7`, `store`.`grocery_sqft` as `c8`, `store`.`frozen_sqft` as `c9`, `store`.`meat_sqft` as `c10`, `store`.`coffee_bar` as `c11`, `store`.`store_street_address` as `c12` from `FOODMART`.`store` as `store` where (`store`.`store_state` in ('CA', 'OR')) and ((`store`.`store_name`,`store`.`store_city`,`store`.`store_state`) in (('11','Portland','OR'),('14','San Francisco','CA'))) group by `store`.`store_country`, `store`.`store_state`, `store`.`store_city`, `store`.`store_id`, `store`.`store_name`, `store`.`store_type`, `store`.`store_manager`, `store`.`store_sqft`, `store`.`grocery_sqft`, `store`.`frozen_sqft`, `store`.`meat_sqft`, `store`.`coffee_bar`, `store`.`store_street_address` having NOT((sum(`store`.`store_sqft`) is null)) order by ISNULL(`store`.`store_country`) ASC, `store`.`store_country` ASC, ISNULL(`store`.`store_state`) ASC, `store`.`store_state` ASC, ISNULL(`store`.`store_city`) ASC, `store`.`store_city` ASC, ISNULL(`store`.`store_id`) ASC, `store`.`store_id` ASC"; final String goodMysqlSQL = "select `store`.`store_country` as `c0`, `store`.`store_state` as `c1`, `store`.`store_city` as `c2`, `store`.`store_id` as `c3`, `store`.`store_name` as `c4` from `store` as `store` where (`store`.`store_state` in ('CA', 'OR')) and ((`store`.`store_id`, `store`.`store_city`, `store`.`store_state`) in ((11, 'Portland', 'OR'), (14, 'San Francisco', 'CA'))) group by `store`.`store_country`, `store`.`store_state`, `store`.`store_city`, `store`.`store_id`, `store`.`store_name` having NOT((sum(`store`.`store_sqft`) is null)) order by ISNULL(`store`.`store_country`) ASC, `store`.`store_country` ASC, ISNULL(`store`.`store_state`) ASC, `store`.`store_state` ASC, ISNULL(`store`.`store_city`) ASC, `store`.`store_city` ASC, ISNULL(`store`.`store_id`) ASC, `store`.`store_id` ASC"; final String mdx = "With\n" + "Set [*NATIVE_CJ_SET] as 'Filter([*BASE_MEMBERS_Store], Not IsEmpty ([Measures].[Store Sqft]))'\n" + "Set [*SORTED_ROW_AXIS] as 'Order([*CJ_ROW_AXIS],Ancestor([Store].CurrentMember, [Store].[Store Country]).OrderKey,BASC,Ancestor([Store].CurrentMember, [Store].[Store State]).OrderKey,BASC,Ancestor([Store].CurrentMember,\n" + "[Store].[Store City]).OrderKey,BASC,[Store].CurrentMember.OrderKey,BASC)'\n" + "Set [*NATIVE_MEMBERS_Store] as 'Generate([*NATIVE_CJ_SET], {[Store].CurrentMember})'\n" + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}'\n" + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Store].currentMember)})'\n" + "Set [*BASE_MEMBERS_Store] as 'Filter([Store].[Store Name].Members,(Ancestor([Store].CurrentMember, [Store].[Store State]) In {[Store].[All Stores].[USA].[CA],[Store].[All Stores].[USA].[OR]}) AND ([Store].CurrentMember In\n" + "{[Store].[All Stores].[USA].[OR].[Portland].[Store 11],[Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]}))'\n" + "Set [*CJ_COL_AXIS] as '[*NATIVE_CJ_SET]'\n" + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Store Sqft]', FORMAT_STRING = '#,###', SOLVE_ORDER=400\n" + "Select\n" + "[*BASE_MEMBERS_Measures] on columns,\n" + "[*SORTED_ROW_AXIS] on rows\n" + "From [Store] \n"; final SqlPattern[] badPatterns = { new SqlPattern( Dialect.DatabaseProduct.MYSQL, badMysqlSQL, null) }; final SqlPattern[] goodPatterns = { new SqlPattern( Dialect.DatabaseProduct.MYSQL, goodMysqlSQL, null) }; final TestContext testContext = TestContext.instance().createSubstitutingCube( "Store", "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n"); assertQuerySqlOrNot(testContext, mdx, badPatterns, true, true, true); assertQuerySqlOrNot(testContext, mdx, goodPatterns, false, true, true); testContext.assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*FORMATTED_MEASURE_0]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "Row #0: 22,478\n" + "Row #1: 20,319\n"); } /** * Tests the bug MONDRIAN-779. The {@link MemberListCrossJoinArg} * was not considering the 'exclude' attribute in its hash code. * This resulted in two filters being chained within two different * named sets to register a cache element with the same key, even * though they were the different because of the added "NOT" keyword. */ public void testBug779() { final String query1 = "With Set [*NATIVE_CJ_SET] as 'Filter([*BASE_MEMBERS_Product], Not IsEmpty ([Measures].[Unit Sales]))' Set [*BASE_MEMBERS_Product] as 'Filter([Product].[Product Department].Members,(Ancestor([Product].CurrentMember, [Product].[Product Family]) In {[Product].[Drink],[Product].[Food]}) AND ([Product].CurrentMember In {[Product].[Drink].[Dairy]}))' Select [Measures].[Unit Sales] on columns, [*NATIVE_CJ_SET] on rows From [Sales]"; final String query2 = "With Set [*NATIVE_CJ_SET] as 'Filter([*BASE_MEMBERS_Product], Not IsEmpty ([Measures].[Unit Sales]))' Set [*BASE_MEMBERS_Product] as 'Filter([Product].[Product Department].Members,(Ancestor([Product].CurrentMember, [Product].[Product Family]) In {[Product].[Drink],[Product].[Food]}) AND ([Product].CurrentMember Not In {[Product].[Drink].[Dairy]}))' Select [Measures].[Unit Sales] on columns, [*NATIVE_CJ_SET] on rows From [Sales]"; final String expectedResult1 = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Dairy]}\n" + "Row #0: 4,186\n"; final String expectedResult2 = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Food].[Baked Goods]}\n" + "{[Product].[Food].[Baking Goods]}\n" + "{[Product].[Food].[Breakfast Foods]}\n" + "{[Product].[Food].[Canned Foods]}\n" + "{[Product].[Food].[Canned Products]}\n" + "{[Product].[Food].[Dairy]}\n" + "{[Product].[Food].[Deli]}\n" + "{[Product].[Food].[Eggs]}\n" + "{[Product].[Food].[Frozen Foods]}\n" + "{[Product].[Food].[Meat]}\n" + "{[Product].[Food].[Produce]}\n" + "{[Product].[Food].[Seafood]}\n" + "{[Product].[Food].[Snack Foods]}\n" + "{[Product].[Food].[Snacks]}\n" + "{[Product].[Food].[Starchy Foods]}\n" + "Row #0: 6,838\n" + "Row #1: 13,573\n" + "Row #2: 7,870\n" + "Row #3: 20,245\n" + "Row #4: 3,317\n" + "Row #5: 19,026\n" + "Row #6: 1,812\n" + "Row #7: 12,885\n" + "Row #8: 12,037\n" + "Row #9: 4,132\n" + "Row #10: 26,655\n" + "Row #11: 1,714\n" + "Row #12: 37,792\n" + "Row #13: 1,764\n" + "Row #14: 30,545\n" + "Row #15: 6,884\n" + "Row #16: 5,262\n"; assertQueryReturns(query1, expectedResult1); assertQueryReturns(query2, expectedResult2); } } // End FilterTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/RolapResultTest.java0000644000175000017500000002530111735330606024545 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.Result; import mondrian.rolap.aggmatcher.AggTableTestCase; import mondrian.test.TestContext; /** * Testcase for * * * @author Richard M. Emberson * @since Feb 21 2007 */ public class RolapResultTest extends AggTableTestCase { private static final String RolapResultTest = "RolapResultTest.csv"; private static final String DIRECTORY = "testsrc/main/mondrian/rolap"; private static final String RESULTS_ALL = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[D1].[a]}\n" + "{[D1].[b]}\n" + "{[D1].[c]}\n" + "Axis #2:\n" + "{[D2].[x]}\n" + "{[D2].[y]}\n" + "{[D2].[z]}\n" + "Row #0: 5\n" + "Row #0: \n" + "Row #0: \n" + "Row #1: \n" + "Row #1: 10\n" + "Row #1: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: 15\n"; private static final String RESULTS = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[D1].[a]}\n" + "{[D1].[b]}\n" + "{[D1].[c]}\n" + "Axis #2:\n" + "{[D2].[x]}\n" + "{[D2].[y]}\n" + "{[D2].[z]}\n" + "Row #0: 5\n" + "Row #0: \n" + "Row #0: \n" + "Row #1: \n" + "Row #1: 10\n" + "Row #1: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: 15\n"; //boolean useImplicitMembers; public RolapResultTest() { super(); } public RolapResultTest(String name) { super(name); } protected void setUp() throws Exception { super.setUp(); } protected void tearDown() throws Exception { super.tearDown(); } public void testAll() throws Exception { if (!isApplicable()) { return; } String mdx = "select " + " filter({[D1].[a],[D1].[b],[D1].[c]}, " + " [Measures].[Value] > 0) " + " ON COLUMNS, " + " {[D2].[x],[D2].[y],[D2].[z]} " + " ON ROWS " + "from FTAll"; getCubeTestContext().assertQueryReturns(mdx, RESULTS_ALL); /* Result result = getCubeTestContext().executeQuery(mdx); String resultString = TestContext.toString(result); //System.out.println(resultString); assertTrue(resultString.equals(RESULTS_ALL)); */ } public void testD1() throws Exception { if (!isApplicable()) { return; } String mdx = "select " + " filter({[D1].[a],[D1].[b],[D1].[c]}, " + " [Measures].[Value] > 0) " + " ON COLUMNS, " + " {[D2].[x],[D2].[y],[D2].[z]} " + " ON ROWS " + "from FT1"; //getCubeTestContext().assertQueryReturns(mdx, RESULTS); Result result = getCubeTestContext().executeQuery(mdx); String resultString = TestContext.toString(result); //System.out.println(resultString); /* This is what is produced Axis #0: {} Axis #1: Axis #2: {[D2].[x]} {[D2].[y]} {[D2].[z]} */ assertEquals(resultString, RESULTS); } public void testD2() throws Exception { if (!isApplicable()) { return; } String mdx = "select " + " NON EMPTY filter({[D1].[a],[D1].[b],[D1].[c]}, " + " [Measures].[Value] > 0) " + " ON COLUMNS, " + " {[D2].[x],[D2].[y],[D2].[z]} " + " ON ROWS " + "from FT2"; getCubeTestContext().assertQueryReturns(mdx, RESULTS); /* Result result = getCubeTestContext().executeQuery(mdx); String resultString = TestContext.toString(result); //System.out.println(resultString); assertTrue(resultString.equals(RESULTS)); */ } /** * This ought to give the same result as the above testD2() method. * In this case, the FT2Extra cube has a default measure with no * data (null) for all members. This default measure is used * in the evaluation even though there is an implicit use of the * measure [Measures].[Value]. * * @throws Exception */ public void _testNullDefaultMeasure() throws Exception { if (!isApplicable()) { return; } String mdx = "select " + " NON EMPTY filter({[D1].[a],[D1].[b],[D1].[c]}, " + " [Measures].[Value] > 0) " + " ON COLUMNS, " + " {[D2].[x],[D2].[y],[D2].[z]} " + " ON ROWS " + "from FT2Extra"; //getCubeTestContext().assertQueryReturns(mdx, RESULTS); Result result = getCubeTestContext().executeQuery(mdx); String resultString = TestContext.toString(result); assertTrue(resultString.equals(RESULTS)); } protected String getFileName() { return RolapResultTest; } protected String getDirectoryName() { return DIRECTORY; } protected String getCubeDescription() { return "\n" + "
\n" + "\n" + " \n" + "
\n" + " \n" + " \n" + "\n" + "\n" + " \n" + "
\n" + " \n" + " \n" + "\n" + "\n" + " \n" + "\n" + "
\n" + "\n" + " \n" + "
\n" + " \n" + " \n" + "\n" + "\n" + " \n" + "
\n" + " \n" + " \n" + "\n" + "\n" + " \n" + "\n" + "
\n" + "\n" + " \n" + "
\n" + " \n" + " \n" + "\n" + "\n" + " \n" + "
\n" + " \n" + " \n" + "\n" + "\n" + "\n" + "\n" + "
\n" + "\n" + " \n" + "
\n" + " \n" + " \n" + "\n" + "\n" + " \n" + "
\n" + " \n" + " \n" + "\n" + "\n" + "\n" + ""; } public void testNonAllPromotionMembers() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + ""); testContext.assertQueryReturns( "select {[Promotion2 Name].[Price Winners], [Promotion2 Name].[Sale Winners]} * {Tail([Time].[Year].Members,3)} ON COLUMNS, " + "NON EMPTY Crossjoin({[Store].CurrentMember.Children}, {[Store Type].[All Store Types].Children}) ON ROWS " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Promotions2].[Price Winners], [Time].[1997]}\n" + "{[Promotions2].[Price Winners], [Time].[1998]}\n" + "{[Promotions2].[Sale Winners], [Time].[1997]}\n" + "{[Promotions2].[Sale Winners], [Time].[1998]}\n" + "Axis #2:\n" + "{[Store].[USA], [Store Type].[Mid-Size Grocery]}\n" + "{[Store].[USA], [Store Type].[Small Grocery]}\n" + "{[Store].[USA], [Store Type].[Supermarket]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 444\n" + "Row #0: \n" + "Row #1: 23\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #2: 1,271\n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n"); } } // End RolapResultTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/NativeFilterMatchingTest.java0000644000175000017500000005612111735330606026344 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.MondrianProperties; import mondrian.olap.Result; import mondrian.spi.Dialect; import mondrian.test.SqlPattern; import mondrian.test.TestContext; /** * Test case for pushing MDX filter conditions down to SQL. */ public class NativeFilterMatchingTest extends BatchTestCase { public void testPositiveMatching() throws Exception { if (!MondrianProperties.instance().EnableNativeFilter.get()) { /* * No point testing these if the native filters * are turned off. */ return; } final String sqlOracle = MondrianProperties.instance().UseAggregates.get() ? "select " + "\"customer\".\"country\" as \"c0\", \"customer\".\"state_province\" as \"c1\", \"customer\".\"city\" as \"c2\", \"customer\".\"customer_id\" as \"c3\", \"fname\" || ' ' || \"lname\" as \"c4\", \"fname\" || ' ' || \"lname\" as \"c5\", \"customer\".\"gender\" as \"c6\", \"customer\".\"marital_status\" as \"c7\", \"customer\".\"education\" as \"c8\", \"customer\".\"yearly_income\" as \"c9\" from \"customer\" \"customer\", \"agg_l_03_sales_fact_1997\" \"agg_l_03_sales_fact_1997\", \"time_by_day\" \"time_by_day\" where \"agg_l_03_sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" and \"agg_l_03_sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"time_by_day\".\"the_year\" = 1997 group by \"customer\".\"country\", \"customer\".\"state_province\", \"customer\".\"city\", \"customer\".\"customer_id\", \"fname\" || ' ' || \"lname\", \"customer\".\"gender\", \"customer\".\"marital_status\", \"customer\".\"education\", \"customer\".\"yearly_income\" having REGEXP_LIKE(\"fname\" || ' ' || \"lname\", '.*jeanne.*', 'i') order by \"customer\".\"country\" ASC NULLS LAST, \"customer\".\"state_province\" ASC NULLS LAST, \"customer\".\"city\" ASC NULLS LAST, \"fname\" || ' ' || \"lname\" ASC NULLS LAST" : "select " + "\"customer\".\"country\" as \"c0\", \"customer\".\"state_province\" as \"c1\", \"customer\".\"city\" as \"c2\", \"customer\".\"customer_id\" as \"c3\", \"fname\" || ' ' || \"lname\" as \"c4\", \"fname\" || ' ' || \"lname\" as \"c5\", \"customer\".\"gender\" as \"c6\", \"customer\".\"marital_status\" as \"c7\", \"customer\".\"education\" as \"c8\", \"customer\".\"yearly_income\" as \"c9\" from \"customer\" \"customer\", \"sales_fact_1997\" \"sales_fact_1997\", \"time_by_day\" \"time_by_day\" where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" and \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"time_by_day\".\"the_year\" = 1997 group by \"customer\".\"country\", \"customer\".\"state_province\", \"customer\".\"city\", \"customer\".\"customer_id\", \"fname\" || ' ' || \"lname\", \"customer\".\"gender\", \"customer\".\"marital_status\", \"customer\".\"education\", \"customer\".\"yearly_income\" having REGEXP_LIKE(\"fname\" || ' ' || \"lname\", '.*jeanne.*', 'i') order by \"customer\".\"country\" ASC NULLS LAST, \"customer\".\"state_province\" ASC NULLS LAST, \"customer\".\"city\" ASC NULLS LAST, \"fname\" || ' ' || \"lname\" ASC NULLS LAST"; final String sqlPgsql = MondrianProperties.instance().UseAggregates.get() ? "select " + "\"customer\".\"country\" as \"c0\", \"customer\".\"state_province\" as \"c1\", \"customer\".\"city\" as \"c2\", \"customer\".\"customer_id\" as \"c3\", fullname as \"c4\", fullname as \"c5\", \"customer\".\"gender\" as \"c6\", \"customer\".\"marital_status\" as \"c7\", \"customer\".\"education\" as \"c8\", \"customer\".\"yearly_income\" as \"c9\" from \"customer\" as \"customer\", \"agg_l_03_sales_fact_1997\" as \"agg_l_03_sales_fact_1997\", \"time_by_day\" as \"time_by_day\" where \"agg_l_03_sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" and \"agg_l_03_sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"time_by_day\".\"the_year\" = 1997 group by \"customer\".\"country\", \"customer\".\"state_province\", \"customer\".\"city\", \"customer\".\"customer_id\", fullname, \"customer\".\"gender\", \"customer\".\"marital_status\", \"customer\".\"education\", \"customer\".\"yearly_income\" having fullname ~ '(?i).*jeanne.*' order by \"customer\".\"country\" ASC NULLS LAST, \"customer\".\"state_province\" ASC NULLS LAST, \"customer\".\"city\" ASC NULLS LAST, fullname ASC NULLS LAST" : "select " + "\"customer\".\"country\" as \"c0\", \"customer\".\"state_province\" as \"c1\", \"customer\".\"city\" as \"c2\", \"customer\".\"customer_id\" as \"c3\", fullname as \"c4\", fullname as \"c5\", \"customer\".\"gender\" as \"c6\", \"customer\".\"marital_status\" as \"c7\", \"customer\".\"education\" as \"c8\", \"customer\".\"yearly_income\" as \"c9\" from \"customer\" as \"customer\", \"sales_fact_1997\" as \"sales_fact_1997\", \"time_by_day\" as \"time_by_day\" where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" and \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"time_by_day\".\"the_year\" = 1997 group by \"customer\".\"country\", \"customer\".\"state_province\", \"customer\".\"city\", \"customer\".\"customer_id\", fullname, \"customer\".\"gender\", \"customer\".\"marital_status\", \"customer\".\"education\", \"customer\".\"yearly_income\" having fullname ~ '(?i).*jeanne.*' order by \"customer\".\"country\" ASC NULLS LAST, \"customer\".\"state_province\" ASC NULLS LAST, \"customer\".\"city\" ASC NULLS LAST, fullname ASC NULLS LAST"; final String sqlMysql = MondrianProperties.instance().UseAggregates.get() ? "select " + "`customer`.`country` as `c0`, `customer`.`state_province` as `c1`, `customer`.`city` as `c2`, `customer`.`customer_id` as `c3`, CONCAT(`customer`.`fname`, ' ', `customer`.`lname`) as `c4`, CONCAT(`customer`.`fname`, ' ', `customer`.`lname`) as `c5`, `customer`.`gender` as `c6`, `customer`.`marital_status` as `c7`, `customer`.`education` as `c8`, `customer`.`yearly_income` as `c9` from `customer` as `customer`, `agg_l_03_sales_fact_1997` as `agg_l_03_sales_fact_1997`, `time_by_day` as `time_by_day` where `agg_l_03_sales_fact_1997`.`customer_id` = `customer`.`customer_id` and `agg_l_03_sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and `time_by_day`.`the_year` = 1997 group by `customer`.`country`, `customer`.`state_province`, `customer`.`city`, `customer`.`customer_id`, CONCAT(`customer`.`fname`, ' ', `customer`.`lname`), `customer`.`gender`, `customer`.`marital_status`, `customer`.`education`, `customer`.`yearly_income` having UPPER(c5) REGEXP '.*jeanne.*' order by ISNULL(`customer`.`country`) ASC, `customer`.`country` ASC, ISNULL(`customer`.`state_province`) ASC, `customer`.`state_province` ASC, ISNULL(`customer`.`city`) ASC, `customer`.`city` ASC, ISNULL(CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)) ASC, CONCAT(`customer`.`fname`, ' ', `customer`.`lname`) ASC" : "select " + "`customer`.`country` as `c0`, `customer`.`state_province` as `c1`, `customer`.`city` as `c2`, `customer`.`customer_id` as `c3`, CONCAT(`customer`.`fname`, ' ', `customer`.`lname`) as `c4`, CONCAT(`customer`.`fname`, ' ', `customer`.`lname`) as `c5`, `customer`.`gender` as `c6`, `customer`.`marital_status` as `c7`, `customer`.`education` as `c8`, `customer`.`yearly_income` as `c9` from `customer` as `customer`, `sales_fact_1997` as `sales_fact_1997`, `time_by_day` as `time_by_day` where `sales_fact_1997`.`customer_id` = `customer`.`customer_id` and `sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and `time_by_day`.`the_year` = 1997 group by `customer`.`country`, `customer`.`state_province`, `customer`.`city`, `customer`.`customer_id`, CONCAT(`customer`.`fname`, ' ', `customer`.`lname`), `customer`.`gender`, `customer`.`marital_status`, `customer`.`education`, `customer`.`yearly_income` having UPPER(c5) REGEXP '.*jeanne.*' order by ISNULL(`customer`.`country`) ASC, `customer`.`country` ASC, ISNULL(`customer`.`state_province`) ASC, `customer`.`state_province` ASC, ISNULL(`customer`.`city`) ASC, `customer`.`city` ASC, ISNULL(CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)) ASC, CONCAT(`customer`.`fname`, ' ', `customer`.`lname`) ASC"; SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.ORACLE, sqlOracle, sqlOracle.length()), new SqlPattern( Dialect.DatabaseProduct.MYSQL, sqlMysql, sqlMysql.length()), new SqlPattern( Dialect.DatabaseProduct.POSTGRESQL, sqlPgsql, sqlPgsql.length()) }; final String queryResults = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[WA].[Issaquah].[Jeanne Derry], [Measures].[*FORMATTED_MEASURE_0]}\n" + "{[Customers].[USA].[CA].[Los Angeles].[Jeannette Eldridge], [Measures].[*FORMATTED_MEASURE_0]}\n" + "{[Customers].[USA].[CA].[Burbank].[Jeanne Bohrnstedt], [Measures].[*FORMATTED_MEASURE_0]}\n" + "{[Customers].[USA].[OR].[Portland].[Jeanne Zysko], [Measures].[*FORMATTED_MEASURE_0]}\n" + "{[Customers].[USA].[WA].[Everett].[Jeanne McDill], [Measures].[*FORMATTED_MEASURE_0]}\n" + "{[Customers].[USA].[CA].[West Covina].[Jeanne Whitaker], [Measures].[*FORMATTED_MEASURE_0]}\n" + "{[Customers].[USA].[WA].[Everett].[Jeanne Turner], [Measures].[*FORMATTED_MEASURE_0]}\n" + "{[Customers].[USA].[WA].[Puyallup].[Jeanne Wentz], [Measures].[*FORMATTED_MEASURE_0]}\n" + "{[Customers].[USA].[OR].[Albany].[Jeannette Bura], [Measures].[*FORMATTED_MEASURE_0]}\n" + "{[Customers].[USA].[WA].[Lynnwood].[Jeanne Ibarra], [Measures].[*FORMATTED_MEASURE_0]}\n" + "Row #0: 50\n" + "Row #0: 21\n" + "Row #0: 31\n" + "Row #0: 42\n" + "Row #0: 110\n" + "Row #0: 59\n" + "Row #0: 42\n" + "Row #0: 157\n" + "Row #0: 146\n" + "Row #0: 78\n"; final String query = "With\n" + "Set [*NATIVE_CJ_SET] as 'Filter([*BASE_MEMBERS_Customers], Not IsEmpty ([Measures].[Unit Sales]))'\n" + "Set [*SORTED_COL_AXIS] as 'Order([*CJ_COL_AXIS],[Customers].CurrentMember.OrderKey,BASC,Ancestor([Customers].CurrentMember,[Customers].[City]).OrderKey,BASC)'\n" + "Set [*BASE_MEMBERS_Customers] as 'Filter([Customers].[Name].Members,[Customers].CurrentMember.Caption Matches (\"(?i).*\\Qjeanne\\E.*\"))'\n" + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}'\n" + "Set [*CJ_COL_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Customers].currentMember)})'\n" + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Unit Sales]', FORMAT_STRING = 'Standard', SOLVE_ORDER=400\n" + "Select\n" + "CrossJoin([*SORTED_COL_AXIS],[*BASE_MEMBERS_Measures]) on columns\n" + "From [Sales]"; assertQuerySqlOrNot( getTestContext(), query, patterns, false, true, true); assertQueryReturns( query, queryResults); } public void testNegativeMatching() throws Exception { if (!MondrianProperties.instance().EnableNativeFilter.get()) { /* * No point testing these if the native filters * are turned off. */ return; } final String sqlOracle = MondrianProperties.instance().UseAggregates.get() ? "select " + "\"customer\".\"country\" as \"c0\", \"customer\".\"state_province\" as \"c1\", \"customer\".\"city\" as \"c2\", \"customer\".\"customer_id\" as \"c3\", \"fname\" || ' ' || \"lname\" as \"c4\", \"fname\" || ' ' || \"lname\" as \"c5\", \"customer\".\"gender\" as \"c6\", \"customer\".\"marital_status\" as \"c7\", \"customer\".\"education\" as \"c8\", \"customer\".\"yearly_income\" as \"c9\" from \"customer\" \"customer\", \"agg_l_03_sales_fact_1997\" \"agg_l_03_sales_fact_1997\", \"time_by_day\" \"time_by_day\" where \"agg_l_03_sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" and \"agg_l_03_sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"time_by_day\".\"the_year\" = 1997 group by \"customer\".\"country\", \"customer\".\"state_province\", \"customer\".\"city\", \"customer\".\"customer_id\", \"fname\" || ' ' || \"lname\", \"customer\".\"gender\", \"customer\".\"marital_status\", \"customer\".\"education\", \"customer\".\"yearly_income\" having NOT(REGEXP_LIKE(\"fname\" || ' ' || \"lname\", '.*jeanne.*', 'i')) order by \"customer\".\"country\" ASC NULLS LAST, \"customer\".\"state_province\" ASC NULLS LAST, \"customer\".\"city\" ASC NULLS LAST, \"fname\" || ' ' || \"lname\" ASC NULLS LAST" : "select " + "\"customer\".\"country\" as \"c0\", \"customer\".\"state_province\" as \"c1\", \"customer\".\"city\" as \"c2\", \"customer\".\"customer_id\" as \"c3\", \"fname\" || ' ' || \"lname\" as \"c4\", \"fname\" || ' ' || \"lname\" as \"c5\", \"customer\".\"gender\" as \"c6\", \"customer\".\"marital_status\" as \"c7\", \"customer\".\"education\" as \"c8\", \"customer\".\"yearly_income\" as \"c9\" from \"customer\" \"customer\", \"sales_fact_1997\" \"sales_fact_1997\", \"time_by_day\" \"time_by_day\" where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" and \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"time_by_day\".\"the_year\" = 1997 group by \"customer\".\"country\", \"customer\".\"state_province\", \"customer\".\"city\", \"customer\".\"customer_id\", \"fname\" || ' ' || \"lname\", \"customer\".\"gender\", \"customer\".\"marital_status\", \"customer\".\"education\", \"customer\".\"yearly_income\" having NOT(REGEXP_LIKE(\"fname\" || ' ' || \"lname\", '.*jeanne.*', 'i')) order by \"customer\".\"country\" ASC NULLS LAST, \"customer\".\"state_province\" ASC NULLS LAST, \"customer\".\"city\" ASC NULLS LAST, \"fname\" || ' ' || \"lname\" ASC NULLS LAST"; final String sqlPgsql = MondrianProperties.instance().UseAggregates.get() ? "select " + "\"customer\".\"country\" as \"c0\", \"customer\".\"state_province\" as \"c1\", \"customer\".\"city\" as \"c2\", \"customer\".\"customer_id\" as \"c3\", fullname as \"c4\", fullname as \"c5\", \"customer\".\"gender\" as \"c6\", \"customer\".\"marital_status\" as \"c7\", \"customer\".\"education\" as \"c8\", \"customer\".\"yearly_income\" as \"c9\" from \"customer\" as \"customer\", \"agg_l_03_sales_fact_1997\" as \"agg_l_03_sales_fact_1997\", \"time_by_day\" as \"time_by_day\" where \"agg_l_03_sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" and \"agg_l_03_sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"time_by_day\".\"the_year\" = 1997 group by \"customer\".\"country\", \"customer\".\"state_province\", \"customer\".\"city\", \"customer\".\"customer_id\", fullname, \"customer\".\"gender\", \"customer\".\"marital_status\", \"customer\".\"education\", \"customer\".\"yearly_income\" having NOT(fullname ~ '(?i).*jeanne.*') order by \"customer\".\"country\" ASC NULLS LAST, \"customer\".\"state_province\" ASC NULLS LAST, \"customer\".\"city\" ASC NULLS LAST, fullname ASC NULLS LAST" : "select " + "\"customer\".\"country\" as \"c0\", \"customer\".\"state_province\" as \"c1\", \"customer\".\"city\" as \"c2\", \"customer\".\"customer_id\" as \"c3\", fullname as \"c4\", fullname as \"c5\", \"customer\".\"gender\" as \"c6\", \"customer\".\"marital_status\" as \"c7\", \"customer\".\"education\" as \"c8\", \"customer\".\"yearly_income\" as \"c9\" from \"customer\" as \"customer\", \"sales_fact_1997\" as \"sales_fact_1997\", \"time_by_day\" as \"time_by_day\" where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" and \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"time_by_day\".\"the_year\" = 1997 group by \"customer\".\"country\", \"customer\".\"state_province\", \"customer\".\"city\", \"customer\".\"customer_id\", fullname, \"customer\".\"gender\", \"customer\".\"marital_status\", \"customer\".\"education\", \"customer\".\"yearly_income\" having NOT(fullname ~ '(?i).*jeanne.*') order by \"customer\".\"country\" ASC NULLS LAST, \"customer\".\"state_province\" ASC NULLS LAST, \"customer\".\"city\" ASC NULLS LAST, fullname ASC NULLS LAST"; final String sqlMysql = MondrianProperties.instance().UseAggregates.get() ? "select " + "`customer`.`country` as `c0`, `customer`.`state_province` as `c1`, `customer`.`city` as `c2`, `customer`.`customer_id` as `c3`, CONCAT(`customer`.`fname`, ' ', `customer`.`lname`) as `c4`, CONCAT(`customer`.`fname`, ' ', `customer`.`lname`) as `c5`, `customer`.`gender` as `c6`, `customer`.`marital_status` as `c7`, `customer`.`education` as `c8`, `customer`.`yearly_income` as `c9` from `customer` as `customer`, `agg_l_03_sales_fact_1997` as `agg_l_03_sales_fact_1997`, `time_by_day` as `time_by_day` where `agg_l_03_sales_fact_1997`.`customer_id` = `customer`.`customer_id` and `agg_l_03_sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and `time_by_day`.`the_year` = 1997 group by `customer`.`country`, `customer`.`state_province`, `customer`.`city`, `customer`.`customer_id`, CONCAT(`customer`.`fname`, ' ', `customer`.`lname`), `customer`.`gender`, `customer`.`marital_status`, `customer`.`education`, `customer`.`yearly_income` having NOT(UPPER(c5) REGEXP '.*jeanne.*') order by ISNULL(`customer`.`country`) ASC, `customer`.`country` ASC, ISNULL(`customer`.`state_province`) ASC, `customer`.`state_province` ASC, ISNULL(`customer`.`city`) ASC, `customer`.`city` ASC, ISNULL(CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)) ASC, CONCAT(`customer`.`fname`, ' ', `customer`.`lname`) ASC" : "select " + "`customer`.`country` as `c0`, `customer`.`state_province` as `c1`, `customer`.`city` as `c2`, `customer`.`customer_id` as `c3`, CONCAT(`customer`.`fname`, ' ', `customer`.`lname`) as `c4`, CONCAT(`customer`.`fname`, ' ', `customer`.`lname`) as `c5`, `customer`.`gender` as `c6`, `customer`.`marital_status` as `c7`, `customer`.`education` as `c8`, `customer`.`yearly_income` as `c9` from `customer` as `customer`, `sales_fact_1997` as `sales_fact_1997`, `time_by_day` as `time_by_day` where `sales_fact_1997`.`customer_id` = `customer`.`customer_id` and `sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and `time_by_day`.`the_year` = 1997 group by `customer`.`country`, `customer`.`state_province`, `customer`.`city`, `customer`.`customer_id`, CONCAT(`customer`.`fname`, ' ', `customer`.`lname`), `customer`.`gender`, `customer`.`marital_status`, `customer`.`education`, `customer`.`yearly_income` having NOT(UPPER(c5) REGEXP '.*jeanne.*') order by ISNULL(`customer`.`country`) ASC, `customer`.`country` ASC, ISNULL(`customer`.`state_province`) ASC, `customer`.`state_province` ASC, ISNULL(`customer`.`city`) ASC, `customer`.`city` ASC, ISNULL(CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)) ASC, CONCAT(`customer`.`fname`, ' ', `customer`.`lname`) ASC"; SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.ORACLE, sqlOracle, sqlOracle.length()), new SqlPattern( Dialect.DatabaseProduct.MYSQL, sqlMysql, sqlMysql.length()), new SqlPattern( Dialect.DatabaseProduct.POSTGRESQL, sqlPgsql, sqlPgsql.length()) }; final String query = "With\n" + "Set [*NATIVE_CJ_SET] as 'Filter([*BASE_MEMBERS_Customers], Not IsEmpty ([Measures].[Unit Sales]))'\n" + "Set [*SORTED_COL_AXIS] as 'Order([*CJ_COL_AXIS],[Customers].CurrentMember.OrderKey,BASC,Ancestor([Customers].CurrentMember,[Customers].[City]).OrderKey,BASC)'\n" + "Set [*BASE_MEMBERS_Customers] as 'Filter([Customers].[Name].Members,[Customers].CurrentMember.Caption Not Matches (\"(?i).*\\Qjeanne\\E.*\"))'\n" + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}'\n" + "Set [*CJ_COL_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Customers].currentMember)})'\n" + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Unit Sales]', FORMAT_STRING = 'Standard', SOLVE_ORDER=400\n" + "Select\n" + "CrossJoin([*SORTED_COL_AXIS],[*BASE_MEMBERS_Measures]) on columns\n" + "From [Sales]"; assertQuerySqlOrNot( getTestContext(), query, patterns, false, true, true); final Result result = executeQuery(query); final String resultString = TestContext.toString(result); assertFalse(resultString.contains("Jeanne")); } /** *

System test case for bug * MONDRIAN-983, * "Regression: Unable to execute MDX statement with native MATCHES". * * @see mondrian.test.DialectTest#testRegularExpressionSqlInjection() */ public void testMatchBugMondrian983() { assertQueryReturns( "With\n" + "Set [*NATIVE_CJ_SET] as 'Filter([*BASE_MEMBERS_Product], Not IsEmpty ([Measures].[Unit Sales]))' \n" + "Set [*SORTED_ROW_AXIS] as 'Order([*CJ_ROW_AXIS],[Product].CurrentMember.OrderKey,BASC,Ancestor([Product].CurrentMember,[Product].[Product Department]).OrderKey,BASC)' \n" + "Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' \n" + "Set [*BASE_MEMBERS_Product] as 'Filter([Product].[Product Category].Members,[Product].CurrentMember.Caption Matches (\"(?i).*\\Qa\"\"\\); window.alert(\"\"woot'');\\E.*\"))' \n" + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}' \n" + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Product].currentMember)})' \n" + "Set [*CJ_COL_AXIS] as '[*NATIVE_CJ_SET]' \n" + "Member [Product].[*TOTAL_MEMBER_SEL~SUM] as 'Sum([*NATIVE_MEMBERS_Product])', SOLVE_ORDER=-100 \n" + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Unit Sales]', FORMAT_STRING = 'Standard', SOLVE_ORDER=400 \n" + "Select\n" + "[*BASE_MEMBERS_Measures] on columns,\n" + "Union({[Product].[*TOTAL_MEMBER_SEL~SUM]},[*SORTED_ROW_AXIS]) on rows\n" + "From [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*FORMATTED_MEASURE_0]}\n" + "Axis #2:\n" + "{[Product].[*TOTAL_MEMBER_SEL~SUM]}\n" + "Row #0: \n"); } } // End NativeFilterMatchingTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/CacheControlTest.ref.xml0000644000175000017500000003131511735330606025271 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/rolap/BatchTestCase.java0000644000175000017500000011726011735330606024114 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.ResultStyle; import mondrian.olap.*; import mondrian.rolap.RolapNative.*; import mondrian.rolap.agg.*; import mondrian.server.Execution; import mondrian.server.Locus; import mondrian.spi.Dialect; import mondrian.test.*; import org.apache.log4j.Logger; import org.eigenbase.util.property.IntegerProperty; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.Future; /** * To support all Batch related tests. * * @author Thiyagu * @since 06-Jun-2007 */ public class BatchTestCase extends FoodMartTestCase { public BatchTestCase(String name) { super(name); } public BatchTestCase() { } protected final String tableTime = "time_by_day"; protected final String tableProductClass = "product_class"; protected final String tableCustomer = "customer"; protected final String fieldYear = "the_year"; protected final String fieldProductFamily = "product_family"; protected final String fieldProductDepartment = "product_department"; protected final String[] fieldValuesYear = {"1997"}; protected final String[] fieldValuesProductFamily = { "Food", "Non-Consumable", "Drink" }; protected final String[] fieldValueProductDepartment = { "Alcoholic Beverages", "Baked Goods", "Baking Goods", "Beverages", "Breakfast Foods", "Canned Foods", "Canned Products", "Carousel", "Checkout", "Dairy", "Deli", "Eggs", "Frozen Foods", "Health and Hygiene", "Household", "Meat", "Packaged Foods", "Periodicals", "Produce", "Seafood", "Snack Foods", "Snacks", "Starchy Foods" }; protected final String[] fieldValuesGender = {"M", "F"}; protected final String cubeNameSales = "Sales"; protected final String measureUnitSales = "[Measures].[Unit Sales]"; protected String fieldGender = "gender"; protected BatchLoader.Batch createBatch( BatchLoader fbcr, String[] tableNames, String[] fieldNames, String[][] fieldValues, String cubeName, String measure) { List values = new ArrayList(); for (int i = 0; i < tableNames.length; i++) { values.add(fieldValues[i][0]); } BatchLoader.Batch batch = fbcr.new Batch( createRequest( cubeName, measure, tableNames, fieldNames, values.toArray(new String[values.size()]))); addRequests( batch, cubeName, measure, tableNames, fieldNames, fieldValues, new ArrayList(), 0); return batch; } protected BatchLoader.Batch createBatch( BatchLoader fbcr, String[] tableNames, String[] fieldNames, String[][] fieldValues, String cubeName, String measure, CellRequestConstraint constraint) { List values = new ArrayList(); for (int i = 0; i < tableNames.length; i++) { values.add(fieldValues[i][0]); } BatchLoader.Batch batch = fbcr.new Batch( createRequest( cubeName, measure, tableNames, fieldNames, values.toArray(new String[values.size()]), constraint)); addRequests( batch, cubeName, measure, tableNames, fieldNames, fieldValues, new ArrayList(), 0, constraint); return batch; } private void addRequests( BatchLoader.Batch batch, String cubeName, String measure, String[] tableNames, String[] fieldNames, String[][] fieldValues, List selectedValues, int currPos) { if (currPos < fieldNames.length) { for (int j = 0; j < fieldValues[currPos].length; j++) { selectedValues.add(fieldValues[currPos][j]); addRequests( batch, cubeName, measure, tableNames, fieldNames, fieldValues, selectedValues, currPos + 1); selectedValues.remove(fieldValues[currPos][j]); } } else { batch.add( createRequest( cubeName, measure, tableNames, fieldNames, selectedValues.toArray(new String[selectedValues.size()]))); } } private void addRequests( BatchLoader.Batch batch, String cubeName, String measure, String[] tableNames, String[] fieldNames, String[][] fieldValues, List selectedValues, int currPos, CellRequestConstraint constraint) { if (currPos < fieldNames.length) { for (int j = 0; j < fieldValues[currPos].length; j++) { selectedValues.add(fieldValues[currPos][j]); addRequests( batch, cubeName, measure, tableNames, fieldNames, fieldValues, selectedValues, currPos + 1, constraint); selectedValues.remove(fieldValues[currPos][j]); } } else { batch.add( createRequest( cubeName, measure, tableNames, fieldNames, selectedValues.toArray( new String[selectedValues.size()]), constraint)); } } protected GroupingSet getGroupingSet( Execution execution, String[] tableNames, String[] fieldNames, String[][] fieldValues, String cubeName, String measure) { final RolapCube cube = getCube(cubeName); final BatchLoader fbcr = new BatchLoader( Locus.peek(), execution.getMondrianStatement().getMondrianConnection() .getServer().getAggregationManager().cacheMgr, cube.getStar().getSqlQueryDialect(), cube); BatchLoader.Batch batch = createBatch( fbcr, tableNames, fieldNames, fieldValues, cubeName, measure); GroupingSetsCollector collector = new GroupingSetsCollector(true); final List>> segmentFutures = new ArrayList>>(); batch.loadAggregation(collector, segmentFutures); return collector.getGroupingSets().get(0); } /** * Checks that a given sequence of cell requests results in a * particular SQL statement being generated. * *

Always clears the cache before running the requests. * *

Runs the requests once for each SQL pattern in the current * dialect. If there are multiple patterns, runs the MDX query multiple * times, and expects to see each SQL statement appear. If there are no * patterns in this dialect, the test trivially succeeds. * * @param requests Sequence of cell requests * @param patterns Set of patterns */ protected void assertRequestSql( CellRequest[] requests, SqlPattern[] patterns) { assertRequestSql(requests, patterns, false); } /** * Checks that a given sequence of cell requests results in a * particular SQL statement being generated. * *

Always clears the cache before running the requests. * *

Runs the requests once for each SQL pattern in the current * dialect. If there are multiple patterns, runs the MDX query multiple * times, and expects to see each SQL statement appear. If there are no * patterns in this dialect, the test trivially succeeds. * * @param requests Sequence of cell requests * @param patterns Set of patterns * @param negative Set to false in order to 'expect' a query or * true to 'forbid' a query. */ protected void assertRequestSql( CellRequest[] requests, SqlPattern[] patterns, boolean negative) { final RolapStar star = requests[0].getMeasure().getStar(); final String cubeName = requests[0].getMeasure().getCubeName(); final RolapCube cube = lookupCube(cubeName); final Dialect sqlDialect = star.getSqlQueryDialect(); Dialect.DatabaseProduct d = sqlDialect.getDatabaseProduct(); SqlPattern sqlPattern = SqlPattern.getPattern(d, patterns); if (d == Dialect.DatabaseProduct.UNKNOWN) { // If the dialect is not one in the pattern set, do not run the // test. We do not print any warning message. return; } boolean patternFound = false; for (SqlPattern pattern : patterns) { if (!pattern.hasDatabaseProduct(d)) { continue; } patternFound = true; clearCache(cube); String sql = sqlPattern.getSql(); String trigger = sqlPattern.getTriggerSql(); switch (d) { case ORACLE: sql = sql.replaceAll(" =as= ", " "); trigger = trigger.replaceAll(" =as= ", " "); break; case TERADATA: sql = sql.replaceAll(" =as= ", " as "); trigger = trigger.replaceAll(" =as= ", " as "); break; } // Create a dummy DataSource which will throw a 'bomb' if it is // asked to execute a particular SQL statement, but will otherwise // behave exactly the same as the current DataSource. RolapUtil.setHook(new TriggerHook(trigger)); Bomb bomb; final Execution execution = new Execution( ((RolapConnection) getConnection()).getInternalStatement(), 1000); final AggregationManager aggMgr = execution.getMondrianStatement() .getMondrianConnection() .getServer().getAggregationManager(); final Locus locus = new Locus( execution, "BatchTestCase", "BatchTestCase"); try { FastBatchingCellReader fbcr = new FastBatchingCellReader( execution, getCube(cubeName), aggMgr); for (CellRequest request : requests) { fbcr.recordCellRequest(request); } // The FBCR will presume there is a current Locus in the stack, // so let's create a mock one. Locus.push(locus); fbcr.loadAggregations(); bomb = null; } catch (Bomb e) { bomb = e; } finally { RolapUtil.setHook(null); Locus.pop(locus); } if (!negative && bomb == null) { fail("expected query [" + sql + "] did not occur"); } else if (negative && bomb != null) { fail("forbidden query [" + sql + "] detected"); } TestContext.assertEqualsVerbose( replaceQuotes(sql), replaceQuotes(bomb.sql)); } // Print warning message that no pattern was specified for the current // dialect. if (!patternFound) { String warnDialect = MondrianProperties.instance().WarnIfNoPatternForDialect.get(); if (warnDialect.equals(d.toString())) { System.out.println( "[No expected SQL statements found for dialect \"" + sqlDialect.toString() + "\" and test not run]"); } } } private RolapCube lookupCube(String cubeName) { Connection connection = TestContext.instance().getConnection(); for (Cube cube : connection.getSchema().getCubes()) { if (cube.getName().equals(cubeName)) { return (RolapCube) cube; } } return null; } /** * Checks that a given MDX query results in a particular SQL statement * being generated. * * @param mdxQuery MDX query * @param patterns Set of patterns for expected SQL statements */ protected void assertQuerySql( String mdxQuery, SqlPattern[] patterns) { assertQuerySqlOrNot( getTestContext(), mdxQuery, patterns, false, false, true); } /** * Checks that a given MDX query results in a particular SQL statement * being generated. * * @param testContext non-default test context if required * @param mdxQuery MDX query * @param patterns Set of patterns for expected SQL statements */ protected void assertQuerySql( TestContext testContext, String mdxQuery, SqlPattern[] patterns) { assertQuerySqlOrNot( testContext, mdxQuery, patterns, false, false, true); } /** * Checks that a given MDX query does not result in a particular SQL * statement being generated. * * @param mdxQuery MDX query * @param patterns Set of patterns for expected SQL statements */ protected void assertNoQuerySql( String mdxQuery, SqlPattern[] patterns) { assertQuerySqlOrNot( getTestContext(), mdxQuery, patterns, true, false, true); } /** * Checks that a given MDX query results in a particular SQL statement * being generated. * * @param mdxQuery MDX query * @param patterns Set of patterns, one for each dialect. * @param clearCache whether to clear cache before running the query */ protected void assertQuerySql( String mdxQuery, SqlPattern[] patterns, boolean clearCache) { assertQuerySqlOrNot( getTestContext(), mdxQuery, patterns, false, false, clearCache); } /** * During MDX query parse and execution, checks that the query results * (or does not result) in a particular SQL statement being generated. * *

Parses and executes the MDX query once for each SQL * pattern in the current dialect. If there are multiple patterns, runs the * MDX query multiple times, and expects to see each SQL statement appear. * If there are no patterns in this dialect, the test trivially succeeds. * * @param testContext non-default test context if required * @param mdxQuery MDX query * @param patterns Set of patterns * @param negative false to assert if SQL is generated; * true to assert if SQL is NOT generated * @param bypassSchemaCache whether to grab a new connection and bypass the * schema cache before parsing the MDX query * @param clearCache whether to clear cache before executing the MDX query */ protected void assertQuerySqlOrNot( TestContext testContext, String mdxQuery, SqlPattern[] patterns, boolean negative, boolean bypassSchemaCache, boolean clearCache) { Connection connection = testContext.getConnection(); mdxQuery = testContext.upgradeQuery(mdxQuery); // Run the test once for each pattern in this dialect. // (We could optimize and run it once, collecting multiple queries, and // comparing all queries at the end.) Dialect dialect = testContext.getDialect(); Dialect.DatabaseProduct d = dialect.getDatabaseProduct(); boolean patternFound = false; for (SqlPattern sqlPattern : patterns) { if (!sqlPattern.hasDatabaseProduct(d)) { // If the dialect is not one in the pattern set, skip the // test. If in the end no pattern is located, print a warning // message if required. continue; } patternFound = true; String sql = sqlPattern.getSql(); String trigger = sqlPattern.getTriggerSql(); sql = dialectize(d, sql); trigger = dialectize(d, trigger); // Create a dummy DataSource which will throw a 'bomb' if it is // asked to execute a particular SQL statement, but will otherwise // behave exactly the same as the current DataSource. RolapUtil.setHook(new TriggerHook(trigger)); Bomb bomb = null; try { if (bypassSchemaCache) { connection = testContext.withSchemaPool(false).getConnection(); } final Query query = connection.parseQuery(mdxQuery); if (clearCache) { clearCache((RolapCube)query.getCube()); } final Result result = connection.execute(query); Util.discard(result); bomb = null; } catch (Bomb e) { bomb = e; } catch (RuntimeException e) { // Walk up the exception tree and see if the root cause // was a SQL bomb. bomb = Util.getMatchingCause(e, Bomb.class); if (bomb == null) { throw e; } } finally { RolapUtil.setHook(null); } if (negative) { if (bomb != null) { fail("forbidden query [" + sql + "] detected"); } } else { if (bomb == null) { fail("expected query [" + sql + "] did not occur"); } assertEquals(replaceQuotes(sql), replaceQuotes(bomb.sql)); } } // Print warning message that no pattern was specified for the current // dialect. if (!patternFound) { String warnDialect = MondrianProperties.instance().WarnIfNoPatternForDialect.get(); if (warnDialect.equals(d.toString())) { System.out.println( "[No expected SQL statements found for dialect \"" + dialect.toString() + "\" and test not run]"); } } } protected String dialectize(Dialect.DatabaseProduct d, String sql) { sql = sql.replaceAll("\r\n", "\n"); switch (d) { case ORACLE: return sql.replaceAll(" =as= ", " "); case GREENPLUM: case POSTGRESQL: case TERADATA: return sql.replaceAll(" =as= ", " as "); case DERBY: return sql.replaceAll("`", "\""); case ACCESS: return sql.replaceAll( "ISNULL\\(([^)]*)\\)", "Iif($1 IS NULL, 1, 0)"); default: return sql; } } private void clearCache(RolapCube cube) { // Clear the cache for the Sales cube, so the query runs as if // for the first time. (TODO: Cleaner way to do this.) final Cube salesCube = getConnection().getSchema().lookupCube("Sales", true); RolapHierarchy hierarchy = (RolapHierarchy) salesCube.lookupHierarchy( new Id.Segment("Store", Id.Quoting.UNQUOTED), false); SmartMemberReader memberReader = (SmartMemberReader) hierarchy.getMemberReader(); MemberCacheHelper cacheHelper = memberReader.cacheHelper; cacheHelper.mapLevelToMembers.cache.clear(); cacheHelper.mapMemberToChildren.cache.clear(); // Flush the cache, to ensure that the query gets executed. cube.clearCachedAggregations(true); CacheControl cacheControl = getConnection().getCacheControl(null); final CacheControl.CellRegion measuresRegion = cacheControl.createMeasuresRegion(cube); cacheControl.flush(measuresRegion); } private static String replaceQuotes(String s) { s = s.replace('`', '\"'); s = s.replace('\'', '\"'); return s; } protected CellRequest createRequest( final String cube, final String measure, final String table, final String column, final String value) { return createRequest( cube, measure, new String[]{table}, new String[]{column}, new String[]{value}); } protected CellRequest createRequest( final String cube, final String measureName, final String[] tables, final String[] columns, final String[] values) { RolapStar.Measure starMeasure = getMeasure(cube, measureName); CellRequest request = new CellRequest(starMeasure, false, false); final RolapStar star = starMeasure.getStar(); for (int i = 0; i < tables.length; i++) { String table = tables[i]; if (table != null && table.length() > 0) { String column = columns[i]; String value = values[i]; final RolapStar.Column storeTypeColumn = star.lookupColumn(table, column); request.addConstrainedColumn( storeTypeColumn, new ValueColumnPredicate(storeTypeColumn, value)); } } return request; } protected CellRequest createRequest( final String cube, final String measure, final String table, final String column, final String value, CellRequestConstraint aggConstraint) { return createRequest( cube, measure, new String[]{table}, new String[]{column}, new String[]{value}, aggConstraint); } protected CellRequest createRequest( final String cube, final String measureName, final String[] tables, final String[] columns, final String[] values, CellRequestConstraint aggConstraint) { RolapStar.Measure starMeasure = getMeasure(cube, measureName); CellRequest request = createRequest(cube, measureName, tables, columns, values); final RolapStar star = starMeasure.getStar(); request.addAggregateList( aggConstraint.getBitKey(star), aggConstraint.toPredicate(star)); return request; } static CellRequestConstraint makeConstraintYearQuarterMonth( List values) { String[] aggConstraintTables = new String[] { "time_by_day", "time_by_day", "time_by_day" }; String[] aggConstraintColumns = new String[] { "the_year", "quarter", "month_of_year" }; List aggConstraintValues = new ArrayList(); for (String[] value : values) { assert value.length == 3; aggConstraintValues.add(value); } return new CellRequestConstraint( aggConstraintTables, aggConstraintColumns, aggConstraintValues); } static CellRequestConstraint makeConstraintCountryState( List values) { String[] aggConstraintTables = new String[] { "store", "store"}; String[] aggConstraintColumns = new String[] { "store_country", "store_state"}; List aggConstraintValues = new ArrayList(); for (String[] value : values) { assert value.length == 2; aggConstraintValues.add(value); } return new CellRequestConstraint( aggConstraintTables, aggConstraintColumns, aggConstraintValues); } static CellRequestConstraint makeConstraintProductFamilyDepartment( List values) { String[] aggConstraintTables = new String[] { "product_class", "product_class"}; String[] aggConstraintColumns = new String[] { "product_family", "product_department"}; List aggConstraintValues = new ArrayList(); for (String[] value : values) { assert value.length == 2; aggConstraintValues.add(value); } return new CellRequestConstraint( aggConstraintTables, aggConstraintColumns, aggConstraintValues); } protected RolapStar.Measure getMeasure(String cube, String measureName) { final Connection connection = getFoodMartConnection(); final boolean fail = true; Cube salesCube = connection.getSchema().lookupCube(cube, fail); Member measure = salesCube.getSchemaReader(null).getMemberByUniqueName( Util.parseIdentifier(measureName), fail); return RolapStar.getStarMeasure(measure); } protected Connection getFoodMartConnection() { return TestContext.instance().getConnection(); } protected RolapCube getCube(final String cube) { final Connection connection = getFoodMartConnection(); final boolean fail = true; return (RolapCube) connection.getSchema().lookupCube(cube, fail); } /** * Make sure the mdx runs correctly and not in native mode. * * @param rowCount number of rows returned * @param mdx query */ protected void checkNotNative(int rowCount, String mdx) { checkNotNative(rowCount, mdx, null); } /** * Makes sure the MDX runs correctly and not in native mode. * * @param rowCount Number of rows returned * @param mdx Query * @param expectedResult Expected result string */ protected void checkNotNative( int rowCount, String mdx, String expectedResult) { getConnection().getCacheControl(null).flushSchemaCache(); Connection con = getTestContext().withSchemaPool(false).getConnection(); RolapNativeRegistry reg = getRegistry(con); reg.setListener( new Listener() { public void foundEvaluator(NativeEvent e) { fail("should not be executed native"); } public void foundInCache(TupleEvent e) { } public void executingSql(TupleEvent e) { } }); TestCase c = new TestCase(con, 0, rowCount, mdx); Result result = c.run(); if (expectedResult != null) { String nonNativeResult = TestContext.toString(result); if (!nonNativeResult.equals(expectedResult)) { TestContext.assertEqualsVerbose( expectedResult, nonNativeResult, false, "Non Native implementation returned different result than " + "expected; MDX=" + mdx); } } } RolapNativeRegistry getRegistry(Connection connection) { RolapCube cube = (RolapCube) connection.getSchema().lookupCube("Sales", true); RolapSchemaReader schemaReader = (RolapSchemaReader) cube.getSchemaReader(); return schemaReader.getSchema().getNativeRegistry(); } /** * Runs a query twice, with native crossjoin optimization enabled and * disabled. If both results are equal, its considered correct. * * @param resultLimit Maximum result size of all the MDX operations in this * query. This might be hard to estimate as it is usually * larger than the rowCount of the final result. Setting * it to 0 will cause this limit to be ignored. * @param rowCount Number of rows returned * @param mdx Query */ protected void checkNative( int resultLimit, int rowCount, String mdx) { checkNative(resultLimit, rowCount, mdx, null, false); } /** * Runs a query twice, with native crossjoin optimization enabled and * disabled. If both results are equal,and both aggree with the expected * result, it is considered correct. * *

Optionally the query can be run with * fresh connection. This is useful if the test case sets its certain * mondrian properties, e.g. native properties like: * mondrian.native.filter.enable * * @param resultLimit Maximum result size of all the MDX operations in * this query. This might be hard to estimate as it * is usually larger than the rowCount of the final * result. Setting it to 0 will cause this limit to * be ignored. * @param rowCount Number of rows returned. (That is, the number * of positions on the last axis of the query.) * @param mdx Query * @param expectedResult Expected result string * @param freshConnection Whether fresh connection is required */ protected void checkNative( int resultLimit, int rowCount, String mdx, String expectedResult, boolean freshConnection) { // Don't run the test if we're testing expression dependencies. // Expression dependencies cause spurious interval calls to // 'level.getMembers()' which create false negatives in this test. if (MondrianProperties.instance().TestExpDependencies.get() > 0) { return; } getConnection().getCacheControl(null).flushSchemaCache(); try { Logger.getLogger(getClass()).debug("*** Native: " + mdx); boolean reuseConnection = !freshConnection; Connection con = getTestContext() .withSchemaPool(reuseConnection) .getConnection(); RolapNativeRegistry reg = getRegistry(con); reg.useHardCache(true); TestListener listener = new TestListener(); reg.setListener(listener); reg.setEnabled(true); TestCase c = new TestCase(con, resultLimit, rowCount, mdx); Result result = c.run(); String nativeResult = TestContext.toString(result); if (!listener.isFoundEvaluator()) { fail("expected native execution of " + mdx); } if (!listener.isExecuteSql()) { fail("cache is empty: expected SQL query to be executed"); } if (MondrianProperties.instance().EnableRolapCubeMemberCache.get()) { // run once more to make sure that the result comes from cache // now listener.setExecuteSql(false); c.run(); if (listener.isExecuteSql()) { fail("expected result from cache when query runs twice"); } } con.close(); Logger.getLogger(getClass()).debug("*** Interpreter: " + mdx); getConnection().getCacheControl(null).flushSchemaCache(); con = getTestContext().withSchemaPool(false).getConnection(); reg = getRegistry(con); listener.setFoundEvaluator(false); reg.setListener(listener); // disable RolapNativeSet reg.setEnabled(false); result = executeQuery(mdx, con); String interpretedResult = TestContext.toString(result); if (listener.isFoundEvaluator()) { fail("did not expect native executions of " + mdx); } if (expectedResult != null) { TestContext.assertEqualsVerbose( expectedResult, nativeResult, false, "Native implementation returned different result than " + "expected; MDX=" + mdx); TestContext.assertEqualsVerbose( expectedResult, interpretedResult, false, "Interpreter implementation returned different result than " + "expected; MDX=" + mdx); } if (!nativeResult.equals(interpretedResult)) { TestContext.assertEqualsVerbose( interpretedResult, nativeResult, false, "Native implementation returned different result than " + "interpreter; MDX=" + mdx); } } finally { Connection con = getConnection(); RolapNativeRegistry reg = getRegistry(con); reg.setEnabled(true); reg.useHardCache(false); } } public static void checkNotNative(String mdx, Result expectedResult) { BatchTestCase test = new BatchTestCase(); test.checkNotNative( getRowCount(expectedResult), mdx, TestContext.toString(expectedResult)); } public static void checkNative(String mdx, Result expectedResult) { BatchTestCase test = new BatchTestCase(); test.checkNative( 0, getRowCount(expectedResult), mdx, TestContext.toString(expectedResult), true); } private static int getRowCount(Result result) { return result.getAxes()[result.getAxes().length - 1] .getPositions().size(); } protected Result executeQuery(String mdx, Connection connection) { Query query = connection.parseQuery(mdx); query.setResultStyle(ResultStyle.LIST); return connection.execute(query); } /** * Convenience method for debugging; please do not delete. */ public void assertNotNative(String mdx) { new BatchTestCase().checkNotNative(0, mdx, null); } /** * Convenience method for debugging; please do not delete. */ public void assertNative(String mdx) { new BatchTestCase().checkNative(0, 0, mdx, null, true); } /** * Runs an MDX query with a predefined resultLimit and checks the number of * positions of the row axis. The reduced resultLimit ensures that the * optimization is present. */ protected class TestCase { /** * Maximum number of rows to be read from SQL. If more than this number * of rows are read, the test will fail. */ final int resultLimit; /** * MDX query to execute. */ final String query; /** * Number of positions we expect on rows axis of result. */ final int rowCount; /** * Mondrian connection. */ final Connection con; public TestCase(int resultLimit, int rowCount, String query) { this.con = getConnection(); this.resultLimit = resultLimit; this.rowCount = rowCount; this.query = query; } public TestCase( Connection con, int resultLimit, int rowCount, String query) { this.con = con; this.resultLimit = resultLimit; this.rowCount = rowCount; this.query = query; } protected Result run() { getConnection().getCacheControl(null).flushSchemaCache(); IntegerProperty monLimit = MondrianProperties.instance().ResultLimit; int oldLimit = monLimit.get(); try { monLimit.set(this.resultLimit); Result result = executeQuery(query, con); // Check the number of positions on the last axis, which is // the ROWS axis in a 2 axis query. int numAxes = result.getAxes().length; Axis a = result.getAxes()[numAxes - 1]; final int positionCount = a.getPositions().size(); assertEquals(rowCount, positionCount); return result; } finally { monLimit.set(oldLimit); } } } /** * Fake exception to interrupt the test when we see the desired query. * It is an {@link Error} because we need it to be unchecked * ({@link Exception} is checked), and we don't want handlers to handle * it. */ static class Bomb extends Error { final String sql; Bomb(final String sql) { this.sql = sql; } } private static class TriggerHook implements RolapUtil.ExecuteQueryHook { private final String trigger; public TriggerHook(String trigger) { this.trigger = trigger; } private boolean matchTrigger(String sql) { if (trigger == null) { return true; } // different versions of mysql drivers use different quoting, so // ignore quotes String s = replaceQuotes(sql); String t = replaceQuotes(trigger); return s.startsWith(t); } public void onExecuteQuery(String sql) { if (matchTrigger(sql)) { throw new Bomb(sql); } } } static class CellRequestConstraint { String[] tables; String[] columns; List valueList; CellRequestConstraint( String[] tables, String[] columns, List valueList) { this.tables = tables; this.columns = columns; this.valueList = valueList; } BitKey getBitKey(RolapStar star) { return star.getBitKey(tables, columns); } StarPredicate toPredicate(RolapStar star) { RolapStar.Column starColumn[] = new RolapStar.Column[tables.length]; for (int i = 0; i < tables.length; i++) { String table = tables[i]; String column = columns[i]; starColumn[i] = star.lookupColumn(table, column); } List orPredList = new ArrayList(); for (String[] values : valueList) { assert (values.length == tables.length); List andPredList = new ArrayList(); for (int i = 0; i < values.length; i++) { andPredList.add( new ValueColumnPredicate(starColumn[i], values[i])); } final StarPredicate predicate = andPredList.size() == 1 ? andPredList.get(0) : new AndPredicate(andPredList); orPredList.add(predicate); } return orPredList.size() == 1 ? orPredList.get(0) : new OrPredicate(orPredList); } } /** * Gets notified on various test events: *

    *
  • when a matching native evaluator was found *
  • when SQL is executed *
  • when result is found in the cache *
*/ static class TestListener implements Listener { boolean foundEvaluator; boolean foundInCache; boolean executeSql; boolean isExecuteSql() { return executeSql; } void setExecuteSql(boolean excecuteSql) { this.executeSql = excecuteSql; } boolean isFoundEvaluator() { return foundEvaluator; } void setFoundEvaluator(boolean foundEvaluator) { this.foundEvaluator = foundEvaluator; } boolean isFoundInCache() { return foundInCache; } void setFoundInCache(boolean foundInCache) { this.foundInCache = foundInCache; } public void foundEvaluator(NativeEvent e) { this.foundEvaluator = true; } public void foundInCache(TupleEvent e) { this.foundInCache = true; } public void executingSql(TupleEvent e) { this.executeSql = true; } } } // End BatchTestCase.java mondrian-3.4.1/testsrc/main/mondrian/rolap/RolapAxisTest.java0000644000175000017500000000571411735330606024201 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.TupleCollections; import mondrian.calc.TupleList; import mondrian.olap.Member; import mondrian.olap.Position; import mondrian.olap.fun.TestMember; import mondrian.test.FoodMartTestCase; import junit.framework.Assert; import java.util.Arrays; import java.util.List; /** * Unit test for lists and iterators over members and tuples. */ public class RolapAxisTest extends FoodMartTestCase { public RolapAxisTest() { super(); } public RolapAxisTest(String name) { super(name); } public void testMemberArrayList() { TupleList list = TupleCollections.createList(3); list.add( Arrays.asList( new TestMember("a"), new TestMember("b"), new TestMember("c"))); list.add( Arrays.asList( new TestMember("d"), new TestMember("e"), new TestMember("f"))); list.add( Arrays.asList( new TestMember("g"), new TestMember("h"), new TestMember("i"))); StringBuilder buf = new StringBuilder(100); RolapAxis axis = new RolapAxis(list); List positions = axis.getPositions(); boolean firstTimeInner = true; for (Position position : positions) { if (! firstTimeInner) { buf.append(','); } buf.append(toString(position)); firstTimeInner = false; } String s = buf.toString(); String e = "{a,b,c},{d,e,f},{g,h,i}"; //System.out.println("s=" +s); Assert.assertEquals(s, e); positions = axis.getPositions(); int size = positions.size(); //System.out.println("size=" +size); Assert.assertEquals(size, 3); buf.setLength(0); for (int i = 0; i < size; i++) { Position position = positions.get(i); if (i > 0) { buf.append(','); } buf.append(toString(position)); } s = buf.toString(); e = "{a,b,c},{d,e,f},{g,h,i}"; //System.out.println("s=" +s); Assert.assertEquals(s, e); } protected String toString(List position) { StringBuffer buf = new StringBuffer(100); buf.append('{'); boolean firstTimeInner = true; for (Member m : position) { if (! firstTimeInner) { buf.append(','); } buf.append(m); firstTimeInner = false; } buf.append('}'); return buf.toString(); } } // End RolapAxisTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/ModulosTest.java0000644000175000017500000001402111735330606023710 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.TupleList; import mondrian.calc.impl.UnaryTupleList; import mondrian.olap.Axis; import mondrian.olap.Member; import junit.framework.TestCase; import java.util.Arrays; import java.util.Collections; /** * Test that the implementations of the Modulos interface are correct. * * @author Richard M. Emberson */ public class ModulosTest extends TestCase { public ModulosTest() { } public ModulosTest(String name) { super(name); } public void testMany() { Axis[] axes = new Axis[3]; TupleList positions = newPositionList(4); axes[0] = new RolapAxis(positions); positions = newPositionList(3); axes[1] = new RolapAxis(positions); positions = newPositionList(3); axes[2] = new RolapAxis(positions); Modulos modulos = Modulos.Generator.createMany(axes); int ordinal = 23; int[] pos = modulos.getCellPos(ordinal); assertTrue("Pos length equals 3", pos.length == 3); assertTrue("Pos[0] length equals 3", pos[0] == 3); assertTrue("Pos[1] length equals 2", pos[1] == 2); assertTrue("Pos[2] length equals 1", pos[2] == 1); } public void testOne() { Axis[] axes = new Axis[1]; TupleList positions = newPositionList(53); axes[0] = new RolapAxis(positions); Modulos modulosMany = Modulos.Generator.createMany(axes); Modulos modulos = Modulos.Generator.create(axes); int ordinal = 43; int[] posMany = modulosMany.getCellPos(ordinal); int[] pos = modulos.getCellPos(ordinal); assertTrue("Pos are not equal", Arrays.equals(posMany, pos)); ordinal = 23; posMany = modulosMany.getCellPos(ordinal); pos = modulos.getCellPos(ordinal); assertTrue("Pos are not equal", Arrays.equals(posMany, pos)); ordinal = 7; posMany = modulosMany.getCellPos(ordinal); pos = modulos.getCellPos(ordinal); assertTrue("Pos are not equal", Arrays.equals(posMany, pos)); pos[0] = 23; int oMany = modulosMany.getCellOrdinal(pos); int o = modulos.getCellOrdinal(pos); assertTrue("Ordinals are not equal", oMany == o); pos[0] = 11; oMany = modulosMany.getCellOrdinal(pos); o = modulos.getCellOrdinal(pos); assertTrue("Ordinals are not equal", oMany == o); pos[0] = 7; oMany = modulosMany.getCellOrdinal(pos); o = modulos.getCellOrdinal(pos); assertTrue("Ordinals are not equal", oMany == o); } public void testTwo() { Axis[] axes = new Axis[2]; TupleList positions = newPositionList(23); axes[0] = new RolapAxis(positions); positions = newPositionList(13); axes[1] = new RolapAxis(positions); Modulos modulosMany = Modulos.Generator.createMany(axes); Modulos modulos = Modulos.Generator.create(axes); int ordinal = 23; int[] posMany = modulosMany.getCellPos(ordinal); int[] pos = modulos.getCellPos(ordinal); assertTrue("Pos are not equal", Arrays.equals(posMany, pos)); ordinal = 11; posMany = modulosMany.getCellPos(ordinal); pos = modulos.getCellPos(ordinal); assertTrue("Pos are not equal", Arrays.equals(posMany, pos)); ordinal = 7; posMany = modulosMany.getCellPos(ordinal); pos = modulos.getCellPos(ordinal); assertTrue("Pos are not equal", Arrays.equals(posMany, pos)); pos[0] = 3; pos[1] = 2; int oMany = modulosMany.getCellOrdinal(pos); int o = modulos.getCellOrdinal(pos); assertTrue("Ordinals are not equal", oMany == o); pos[0] = 2; oMany = modulosMany.getCellOrdinal(pos); o = modulos.getCellOrdinal(pos); assertTrue("Ordinals are not equal", oMany == o); pos[0] = 1; oMany = modulosMany.getCellOrdinal(pos); o = modulos.getCellOrdinal(pos); assertTrue("Ordinals are not equal", oMany == o); } public void testThree() { Axis[] axes = new Axis[3]; TupleList positions = newPositionList(4); axes[0] = new RolapAxis(positions); positions = newPositionList(3); axes[1] = new RolapAxis(positions); positions = newPositionList(2); axes[2] = new RolapAxis(positions); Modulos modulosMany = Modulos.Generator.createMany(axes); Modulos modulos = Modulos.Generator.create(axes); int ordinal = 23; int[] posMany = modulosMany.getCellPos(ordinal); int[] pos = modulos.getCellPos(ordinal); assertTrue("Pos are not equal", Arrays.equals(posMany, pos)); ordinal = 11; posMany = modulosMany.getCellPos(ordinal); pos = modulos.getCellPos(ordinal); assertTrue("Pos are not equal", Arrays.equals(posMany, pos)); ordinal = 7; posMany = modulosMany.getCellPos(ordinal); pos = modulos.getCellPos(ordinal); assertTrue("Pos are not equal", Arrays.equals(posMany, pos)); pos[0] = 3; pos[1] = 2; pos[2] = 1; int oMany = modulosMany.getCellOrdinal(pos); int o = modulos.getCellOrdinal(pos); assertTrue("Ordinals are not equal", oMany == o); pos[0] = 2; oMany = modulosMany.getCellOrdinal(pos); o = modulos.getCellOrdinal(pos); assertTrue("Ordinals are not equal", oMany == o); pos[0] = 1; oMany = modulosMany.getCellOrdinal(pos); o = modulos.getCellOrdinal(pos); assertTrue("Ordinals are not equal", oMany == o); } TupleList newPositionList(int size) { return new UnaryTupleList( Collections.nCopies(size, null)); } } // End ModulosTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/aggmatcher/0000755000175000017500000000000011735330606022667 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/rolap/aggmatcher/MultipleColsInTupleAggTest.java0000644000175000017500000001314611735330606030733 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.olap.MondrianProperties; import mondrian.olap.Result; /** * Testcase for levels that contain multiple columns and are * collapsed in the agg table. * * @author Will Gorman */ public class MultipleColsInTupleAggTest extends AggTableTestCase { public MultipleColsInTupleAggTest() { super(); } public MultipleColsInTupleAggTest(String name) { super(name); } protected String getFileName() { return "multiple_cols_in_tuple_agg.csv"; } public void testTotal() throws Exception { if (!isApplicable()) { return; } MondrianProperties props = MondrianProperties.instance(); // get value without aggregates propSaver.set(props.UseAggregates, false); propSaver.set(props.ReadAggregates, false); String mdx = "select {[Measures].[Total]} on columns from [Fact]"; Result result = getCubeTestContext().executeQuery(mdx); Object v = result.getCell(new int[]{0}).getValue(); String mdx2 = "select {[Measures].[Total]} on columns from [Fact] where " + "{[Product].[Cat One].[Prod Cat One].[One]}"; Result aresult = getCubeTestContext().executeQuery(mdx2); Object av = aresult.getCell(new int[]{0}).getValue(); // unless there is a way to flush the cache, // I'm skeptical about these results propSaver.set(props.UseAggregates, true); propSaver.set(props.ReadAggregates, false); Result result1 = getCubeTestContext().executeQuery(mdx); Object v1 = result1.getCell(new int[]{0}).getValue(); assertTrue(v.equals(v1)); Result aresult2 = getCubeTestContext().executeQuery(mdx2); Object av1 = aresult2.getCell(new int[]{0}).getValue(); assertTrue(av.equals(av1)); } public void testTupleSelection() throws Exception { if (!isApplicable()) { return; } String mdx = "select " + "{[Measures].[Total]} on columns, " + "non empty CrossJoin({[Product].[Cat One].[Prod Cat One]}," + "{[Store].[All Stores]}) on rows " + "from [Fact]"; getCubeTestContext().assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Total]}\n" + "Axis #2:\n" + "{[Product].[Cat One].[Prod Cat One]," + " [Store].[All Stores]}\n" + "Row #0: 15\n"); } public void testChildSelection() throws Exception { if (!isApplicable()) { return; } String mdx = "select {[Measures].[Total]} on columns, " + "non empty [Product].[Cat One].Children on rows from [Fact]"; getCubeTestContext().assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Total]}\n" + "Axis #2:\n" + "{[Product].[Cat One].[Prod Cat Two]}\n" + "{[Product].[Cat One].[Prod Cat One]}\n" + "Row #0: 18\n" + "Row #1: 15\n"); } protected String getCubeDescription() { return "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
" + "\n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + " \n" + "
\n" + " \n" + "
\n" + "
\n" + " " + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + ""; } } // End MultipleColsInTupleAggTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/aggmatcher/TestRule.xml0000644000175000017500000000671511735330606025171 0ustar drazzibdrazzib ${hierarchy_name}_${level_name} ${hierarchy_name}_${level_column_name} ${usage_prefix}${level_column_name} ${level_column_name} ${measure_name} ${measure_column_name} ${measure_column_name}_${aggregate_name} ${usage_prefix}${hierarchy_name}_${level_name}_${level_column_name} mondrian-3.4.1/testsrc/main/mondrian/rolap/aggmatcher/DefaultRuleTest.java0000644000175000017500000003235211735330606026613 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.recorder.ListRecorder; import junit.framework.TestCase; import org.apache.log4j.Logger; import org.eigenbase.xom.*; import java.io.File; import java.io.FileReader; import java.util.Iterator; /** * Testing the default aggregate table recognizer. * * @author Richard M. Emberson */ public class DefaultRuleTest extends TestCase { private static final Logger LOGGER = Logger.getLogger(DefaultRuleTest.class); private static final String DIRECTORY = "testsrc/main/mondrian/rolap/aggmatcher"; private static final String TEST_RULE_XML = "TestRule.xml"; private DefaultDef.AggRules rules; public DefaultRuleTest() { super(); } public DefaultRuleTest(String name) { super(name); } private DefaultDef.AggRule getAggRule(String tag) { return rules.getAggRule(tag); } protected void setUp() throws Exception { File file = new File(DIRECTORY, TEST_RULE_XML); FileReader reader = new FileReader(file); Parser xmlParser = XOMUtil.createDefaultParser(); final DOMWrapper domWrapper = xmlParser.parse(reader); rules = new DefaultDef.AggRules(domWrapper); ListRecorder msgRecorder = new ListRecorder(); rules.validate(msgRecorder); if (msgRecorder.hasErrors()) { LOGGER.error("HAS ERRORS"); for (Iterator it = msgRecorder.getErrorEntries(); it.hasNext();) { ListRecorder.Entry e = (ListRecorder.Entry) it.next(); LOGGER.error("context=" + e.getContext()); LOGGER.error("message=" + e.getMessage()); } } } protected void tearDown() throws Exception { } private Recognizer.Matcher getTableMatcher(String tag, String tableName) { DefaultDef.AggRule rule = getAggRule(tag); if (rule == null) { LOGGER.info("rule == null for tag=" + tag); } DefaultDef.TableMatch tableMatch = rule.getTableMatch(); if (tableMatch == null) { LOGGER.info( "tableMatch == null for tag=" + tag + ", tableName=" + tableName); } return tableMatch.getMatcher(tableName); } private Recognizer.Matcher getFactCountMatcher(String tag) { DefaultDef.AggRule rule = getAggRule(tag); DefaultDef.FactCountMatch factTableName = rule.getFactCountMatch(); return factTableName.getMatcher(); } private Recognizer.Matcher getForeignKeyMatcher( String tag, String foreignKeyName) { DefaultDef.AggRule rule = getAggRule(tag); DefaultDef.ForeignKeyMatch foreignKeyMatch = rule.getForeignKeyMatch(); return foreignKeyMatch.getMatcher(foreignKeyName); } private Recognizer.Matcher getLevelMatcher( String tag, String usagePrefix, String hierarchyName, String levelName, String levelColumnName) { DefaultDef.AggRule rule = getAggRule(tag); Recognizer.Matcher matcher = rule.getLevelMap().getMatcher( usagePrefix, hierarchyName, levelName, levelColumnName); return matcher; } private Recognizer.Matcher getMeasureMatcher( String tag, String measureName, String measureColumnName, String aggregateName) { DefaultDef.AggRule rule = getAggRule(tag); Recognizer.Matcher matcher = rule.getMeasureMap().getMatcher( measureName, measureColumnName, aggregateName); return matcher; } ////////////////////////////////////////////////////////////////////////// // // tests // // public void testTableNameDefault() { final String tag = "default"; final String factTableName = "FACT_TABLE"; Recognizer.Matcher matcher = getTableMatcher(tag, factTableName); doMatch(matcher, "agg_10_" + factTableName); doMatch(matcher, "AGG_10_" + factTableName); doMatch(matcher, "agg_this_is_ok_" + factTableName); doMatch(matcher, "AGG_THIS_IS_OK_" + factTableName); doMatch(matcher, "agg_10_" + factTableName.toLowerCase()); doMatch(matcher, "AGG_10_" + factTableName.toLowerCase()); doMatch(matcher, "agg_this_is_ok_" + factTableName.toLowerCase()); doMatch(matcher, "AGG_THIS_IS_OK_" + factTableName.toLowerCase()); doNotMatch(matcher, factTableName); doNotMatch(matcher, "agg__" + factTableName); doNotMatch(matcher, "agg_" + factTableName); doNotMatch(matcher, factTableName + "_agg"); doNotMatch(matcher, "agg_10_Mytable"); } public void testTableNameBBBB() { final String tag = "bbbb"; final String factTableName = "FACT_TABLE"; Recognizer.Matcher matcher = getTableMatcher(tag, factTableName); doMatch(matcher, factTableName + "_agg_10"); doMatch(matcher, factTableName + "_agg_this_is_ok"); doNotMatch(matcher, factTableName); doNotMatch(matcher, factTableName + "_agg"); doNotMatch(matcher, factTableName + "__agg"); doNotMatch(matcher, "agg_" + factTableName); doNotMatch(matcher, "Mytable_agg_10"); } public void testTableNameCCCCBAD() { final String tag = "cccc"; final String basename = "WAREHOUSE"; final String factTableName = "RF_" + basename + "_TABLE"; // Note that the "basename" and not the fact table name is // being used. The Matcher that is return will not match anything // because the basename does not match the table basename pattern. Recognizer.Matcher matcher = getTableMatcher(tag, basename); doNotMatch(matcher, "AGG_10_" + basename); doNotMatch(matcher, "agg_this_is_ok_" + basename); doNotMatch(matcher, factTableName); doNotMatch(matcher, "agg__" + basename); doNotMatch(matcher, "agg_" + basename); doNotMatch(matcher, basename + "_agg"); doNotMatch(matcher, "agg_10_Mytable"); } public void testTableNameCCCCGOOD() { final String tag = "cccc"; final String basename = "WAREHOUSE"; final String factTableName = "RF_" + basename + "_TABLE"; Recognizer.Matcher matcher = getTableMatcher(tag, factTableName); doMatch(matcher, "AGG_10_" + basename); doMatch(matcher, "agg_this_is_ok_" + basename); doNotMatch(matcher, factTableName); doNotMatch(matcher, "agg__" + basename); doNotMatch(matcher, "agg_" + basename); doNotMatch(matcher, basename + "_agg"); doNotMatch(matcher, "agg_10_Mytable"); } public void testFactCountDefault() { final String tag = "default"; Recognizer.Matcher matcher = getFactCountMatcher(tag); doMatch(matcher, "fact_count"); doMatch(matcher, "FACT_COUNT"); doNotMatch(matcher, "my_fact_count"); doNotMatch(matcher, "MY_FACT_COUNT"); doNotMatch(matcher, "count"); doNotMatch(matcher, "COUNT"); doNotMatch(matcher, "fact_count_my"); doNotMatch(matcher, "FACT_COUNT_MY"); } public void testFactCountBBBB() { final String tag = "bbbb"; Recognizer.Matcher matcher = getFactCountMatcher(tag); doMatch(matcher, "my_fact_count"); doMatch(matcher, "MY_FACT_COUNT"); doNotMatch(matcher, "fact_count"); doNotMatch(matcher, "FACT_COUNT"); doNotMatch(matcher, "count"); doNotMatch(matcher, "COUNT"); doNotMatch(matcher, "fact_count_my"); doNotMatch(matcher, "FACT_COUNT_MY"); } public void testFactCountCCCC() { final String tag = "cccc"; Recognizer.Matcher matcher = getFactCountMatcher(tag); doMatch(matcher, "MY_FACT_COUNT"); doNotMatch(matcher, "my_fact_count"); doNotMatch(matcher, "fact_count"); doNotMatch(matcher, "FACT_COUNT"); doNotMatch(matcher, "count"); doNotMatch(matcher, "COUNT"); doNotMatch(matcher, "fact_count_my"); doNotMatch(matcher, "FACT_COUNT_MY"); } public void testForeignKeyDefault() { final String tag = "default"; final String foreignKeyName = "foo_key"; Recognizer.Matcher matcher = getForeignKeyMatcher(tag, foreignKeyName); doMatch(matcher, "foo_key"); doMatch(matcher, "FOO_KEY"); doNotMatch(matcher, "foo_key_my"); doNotMatch(matcher, "my_foo_key"); } public void testForeignKeyBBBB() { final String tag = "bbbb"; final String foreignKeyName = "fk_ham_n_eggs"; Recognizer.Matcher matcher = getForeignKeyMatcher(tag, foreignKeyName); doMatch(matcher, "HAM_N_EGGS_FK"); doNotMatch(matcher, "ham_n_eggs_fk"); doNotMatch(matcher, "ham_n_eggs"); doNotMatch(matcher, "fk_ham_n_eggs"); doNotMatch(matcher, "HAM_N_EGGS"); doNotMatch(matcher, "FK_HAM_N_EGGS"); } /* */ public void testForeignKeyCCCC() { final String tag = "cccc"; final String foreignKeyName1 = "fk_toast"; final String foreignKeyName2 = "FK_TOAST"; final String foreignKeyName3 = "FK_ToAsT"; Recognizer.Matcher matcher1 = getForeignKeyMatcher(tag, foreignKeyName1); Recognizer.Matcher matcher2 = getForeignKeyMatcher(tag, foreignKeyName2); Recognizer.Matcher matcher3 = getForeignKeyMatcher(tag, foreignKeyName3); doMatch(matcher1, "toast_fk"); doNotMatch(matcher1, "TOAST_FK"); doMatch(matcher2, "TOAST_FK"); doNotMatch(matcher2, "toast_fk"); doMatch(matcher3, "ToAsT_FK"); doMatch(matcher3, "ToAsT_fk"); doMatch(matcher3, "ToAsT_Fk"); doNotMatch(matcher3, "toast_fk"); doNotMatch(matcher3, "TOAST_FK"); } public void testLevelDefaultOne() { final String tag = "default"; final String usagePrefix = null; final String hierarchyName = "Time"; final String levelName = "Day in Year"; final String levelColumnName = "days"; Recognizer.Matcher matcher = getLevelMatcher( tag, usagePrefix, hierarchyName, levelName, levelColumnName); doMatch(matcher, "days"); doMatch(matcher, "time_day_in_year"); doMatch(matcher, "time_days"); doNotMatch(matcher, "DAYS"); doNotMatch(matcher, "Time Day in Year"); } public void testLevelDefaultTwo() { final String tag = "default"; final String usagePrefix = "boo_"; final String hierarchyName = "Time"; final String levelName = "Day in Year"; final String levelColumnName = "days"; Recognizer.Matcher matcher = getLevelMatcher( tag, usagePrefix, hierarchyName, levelName, levelColumnName); doMatch(matcher, "days"); doMatch(matcher, "boo_days"); doMatch(matcher, "time_day_in_year"); doMatch(matcher, "time_days"); doNotMatch(matcher, "boo_time_day_in_year"); doNotMatch(matcher, "boo_time_days"); doNotMatch(matcher, "DAYS"); doNotMatch(matcher, "Time Day in Year"); } public void testLevelBBBB() { final String tag = "bbbb"; final String usagePrefix = "boo_"; final String hierarchyName = "Time.Period"; final String levelName = "Day in Year"; final String levelColumnName = "days"; Recognizer.Matcher matcher = getLevelMatcher( tag, usagePrefix, hierarchyName, levelName, levelColumnName); doMatch(matcher, "boo_time_DOT_period_day_SP_in_SP_year_days"); } public void testMeasureDefault() { final String tag = "default"; final String measureName = "Total Sales"; final String measureColumnName = "sales"; final String aggregateName = "sum"; Recognizer.Matcher matcher = getMeasureMatcher( tag, measureName, measureColumnName, aggregateName); doMatch(matcher, "total_sales"); doMatch(matcher, "sales"); doMatch(matcher, "sales_sum"); doNotMatch(matcher, "Total Sales"); doNotMatch(matcher, "Total_Sales"); doNotMatch(matcher, "total_sales_sum"); } // ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// // // helpers // // private void doMatch(Recognizer.Matcher matcher, String s) { assertTrue("Recognizer.Matcher: " + s, matcher.matches(s)); } private void doNotMatch(Recognizer.Matcher matcher, String s) { assertTrue("Recognizer.Matcher: " + s, !matcher.matches(s)); } // ////////////////////////////////////////////////////////////////////////// } // End DefaultRuleTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/aggmatcher/Checkin_7634.java0000644000175000017500000001156311735330606025567 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.olap.MondrianProperties; import mondrian.olap.Result; import mondrian.test.TestContext; import mondrian.test.loader.CsvDBTestCase; /** * Checkin 7634 attempted to correct a problem demonstrated by this * junit. The CrossJoinFunDef class has an optimization that kicks in * when the combined lists sizes are greater than 1000. I create a * property here which, if set, can be used to change that size from * 1000 to, in this case, 2. Also, there is a property that disables the * use of the optimization altogether and another that permits the * use of the old optimization, currently the nonEmptyListOld method in * the CrossJoinFunDef class, and the new, checkin 7634, version of the * method called nonEmptyList. * *

The old optimization only looked at the default measure while the * new version looks at all measures appearing in the query. * The example Cube and data for the junit is such that there is no * data for the default measure. Thus the old optimization fails * to produce the correct result. * * @author Richard M. Emberson */ public class Checkin_7634 extends CsvDBTestCase { private static final String DIRECTORY = "testsrc/main/mondrian/rolap/aggmatcher"; private static final String CHECKIN_7634 = "Checkin_7634.csv"; private int crossJoinSize; public Checkin_7634() { super(); } public Checkin_7634(String name) { super(name); } public void testCrossJoin() throws Exception { // explicit use of [Product].[Class1] String mdx = "select {[Measures].[Requested Value]} ON COLUMNS,"+ " NON EMPTY Crossjoin("+ " {[Geography].[All Regions].Children},"+ " {[Product].[All Products].Children}"+ ") ON ROWS"+ " from [Checkin_7634]"; // Execute query but do not used the CrossJoin nonEmptyList optimization propSaver.set( MondrianProperties.instance().CrossJoinOptimizerSize, Integer.MAX_VALUE); Result result1 = getCubeTestContext().executeQuery(mdx); String resultString1 = TestContext.toString(result1); // Execute query using the new version of the CrossJoin // nonEmptyList optimization propSaver.set( MondrianProperties.instance().CrossJoinOptimizerSize, Integer.MAX_VALUE); Result result2 = getCubeTestContext().executeQuery(mdx); String resultString2 = TestContext.toString(result2); // This succeeds. assertEquals(resultString1, resultString2); } protected String getDirectoryName() { return DIRECTORY; } protected String getFileName() { return CHECKIN_7634; } protected String getCubeDescription() { // defines [Product].[Class2] as default (implicit) member return "\n" + "

\n" + "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "\n" + "\n" + ""; } } // End Checkin_7634.java mondrian-3.4.1/testsrc/main/mondrian/rolap/aggmatcher/multiple_cols_in_tuple_agg.csv0000644000175000017500000000251411735330606030776 0ustar drazzibdrazzib# multiple_cols_in_tuple_agg_tbls.csv # @version $Id:$ ## TableName: fact ## ColumnNames: prod_id,store_id,amount ## ColumnTypes: INTEGER,INTEGER,INTEGER ## NosOfRows: 16 1,10,6 1,11,3 2,10,4 2,11,2 3,10,6 3,11,3 4,10,6 4,11,3 5,10,6 5,11,3 6,10,6 6,11,2 7,10,6 7,11,2 8,10,6 8,11,2 # aggregate over prod_id ## TableName: test_lp_xxx_fact ## ColumnNames: category,product_category,amount,fact_count ## ColumnTypes: INTEGER,VARCHAR(30),INTEGER,INTEGER ## NosOfRows: 4 1,Prod Cat One,15,4 1,Prod Cat Two,18,4 2,Prod Cat One,17,4 2,Prod Cat Two,16,4 # store dimension ## TableName: store_csv ## ColumnNames: store_id,value ## ColumnTypes: INTEGER,INTEGER ## NosOfRows: 2 10,4 11,5 # product dimension t1 ## TableName: product_csv ## ColumnNames: prod_id,prod_cat,name1 ## ColumnTypes: INTEGER,INTEGER,VARCHAR(30) ## NosOfRows: 8 1,1,One 2,1,Two 3,2,Three 4,2,Four 5,3,Five 6,3,Six 7,4,Seven 8,4,Eight # product dimension t2 ## TableName: product_cat ## ColumnNames: prod_cat,cat,name2,ord,cap ## ColumnTypes: INTEGER,INTEGER,VARCHAR(30),INTEGER,VARCHAR(30) ## NosOfRows: 4 1,1,Prod Cat One,4,PCOne 2,1,Prod Cat Two,3,PCTwo 3,2,Prod Cat One,2,PCThree 4,2,Prod Cat Two,1,PCFour # product dimension t3 ## TableName: cat ## ColumnNames: cat,name3,ord,cap ## ColumnTypes: INTEGER,VARCHAR(30),INTEGER,VARCHAR(30) ## NosOfRows: 2 1,Cat One,2,COne 2,Cat Two,1,CTwo mondrian-3.4.1/testsrc/main/mondrian/rolap/aggmatcher/BUG_1541077.java0000644000175000017500000001663711735330606025074 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.olap.MondrianProperties; import mondrian.olap.Result; /** * Testcase for * MONDRIAN-214 * (formerly SourceForge bug 1541077) * and a couple of other aggregate table ExplicitRecognizer conditions. * * @author Richard M. Emberson */ public class BUG_1541077 extends AggTableTestCase { private static final String BUG_1541077 = "BUG_1541077.csv"; public BUG_1541077() { super(); } public BUG_1541077(String name) { super(name); } public void testStoreCount() throws Exception { if (!isApplicable()) { return; } MondrianProperties props = MondrianProperties.instance(); // get value without aggregates propSaver.set(props.UseAggregates, false); String mdx = "select {[Measures].[Store Count]} on columns from Cheques"; Result result = getCubeTestContext().executeQuery(mdx); Object v = result.getCell(new int[]{0}).getValue(); propSaver.set(props.UseAggregates, true); Result result1 = getCubeTestContext().executeQuery(mdx); Object v1 = result1.getCell(new int[]{0}).getValue(); assertTrue(v.equals(v1)); } public void testSalesCount() throws Exception { if (!isApplicable()) { return; } MondrianProperties props = MondrianProperties.instance(); // get value without aggregates propSaver.set(props.UseAggregates, false); String mdx = "select {[Measures].[Sales Count]} on columns from Cheques"; Result result = getCubeTestContext().executeQuery(mdx); Object v = result.getCell(new int[]{0}).getValue(); propSaver.set(props.UseAggregates, true); Result result1 = getCubeTestContext().executeQuery(mdx); Object v1 = result1.getCell(new int[]{0}).getValue(); assertTrue(v.equals(v1)); } public void testTotalAmount() throws Exception { if (!isApplicable()) { return; } MondrianProperties props = MondrianProperties.instance(); // get value without aggregates propSaver.set(props.UseAggregates, false); String mdx = "select {[Measures].[Total Amount]} on columns from Cheques"; Result result = getCubeTestContext().executeQuery(mdx); Object v = result.getCell(new int[]{0}).getValue(); propSaver.set(props.UseAggregates, false); Result result1 = getCubeTestContext().executeQuery(mdx); Object v1 = result1.getCell(new int[]{0}).getValue(); assertTrue(v.equals(v1)); } public void testBug1541077() throws Exception { if (!isApplicable()) { return; } MondrianProperties props = MondrianProperties.instance(); // get value without aggregates propSaver.set(props.UseAggregates, false); String mdx = "select {[Measures].[Avg Amount]} on columns from Cheques"; Result result = getCubeTestContext().executeQuery(mdx); Object v = result.getCell(new int[]{0}).getFormattedValue(); // get value with aggregates propSaver.set(props.UseAggregates, true); Result result1 = getCubeTestContext().executeQuery(mdx); Object v1 = result1.getCell(new int[]{0}).getFormattedValue(); assertTrue(v.equals(v1)); } protected String getFileName() { return BUG_1541077; } protected String getCubeDescription() { return "\n" + "
\n" + "\n" + "\n" + "\n" + "\n" /* + "\n" + "\n" + "\n" + "\n" */ + "\n" + "
\n" /* + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" */ /* + "\n" + "\n" */ + "\n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + "
\n" + " \n" + " \n" + "\n" + "\n" + "\n" + "\n" + "\n" + ""; } } // End BUG_1541077.java mondrian-3.4.1/testsrc/main/mondrian/rolap/aggmatcher/NonCollapsedAggTest.java0000644000175000017500000003504111735330606027375 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.olap.MondrianProperties; import mondrian.test.TestContext; /** * Testcase for non-collapsed levels in agg tables. * * @author Luc Boudreau */ public class NonCollapsedAggTest extends AggTableTestCase { private static final String CUBE_1 = "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + "
\n" + " \n" + "
\n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + "
\n" + " \n" + "
\n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + "
\n" + " \n" + "
\n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + "
\n" + " \n" + "
\n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; public NonCollapsedAggTest() { super(); } public NonCollapsedAggTest(String name) { super(name); } @Override protected void setUp() throws Exception { super.setUp(); final MondrianProperties props = MondrianProperties.instance(); propSaver.set(props.UseAggregates, true); propSaver.set(props.ReadAggregates, true); super.getConnection().getCacheControl(null).flushSchemaCache(); } protected String getFileName() { return "non_collapsed_agg_test.csv"; } @Override protected String getCubeDescription() { return CUBE_1; } public void testSingleJoin() throws Exception { if (!isApplicable()) { return; } final String mdx = "select {[Measures].[Unit Sales]} on columns, {[dimension.tenant].[tenant].Members} on rows from [foo]"; final TestContext context = getCubeTestContext(); // We expect the correct cell value + 1 if the agg table is used. context.assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[dimension.tenant].[tenant one]}\n" + "{[dimension.tenant].[tenant two]}\n" + "Row #0: 31\n" + "Row #1: 121\n"); } public void testComplexJoin() throws Exception { if (!isApplicable()) { return; } final String mdx = "select {[Measures].[Unit Sales]} on columns, {[dimension.distributor].[line class].Members} on rows from [foo]"; final TestContext context = getCubeTestContext(); // We expect the correct cell value + 1 if the agg table is used. context.assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[dimension.distributor].[distributor one].[line class one]}\n" + "{[dimension.distributor].[distributor two].[line class two]}\n" + "Row #0: 31\n" + "Row #1: 121\n"); final String mdx2 = "select {[Measures].[Unit Sales]} on columns, {[dimension.network].[line class].Members} on rows from [foo]"; // We expect the correct cell value + 1 if the agg table is used. context.assertQueryReturns( mdx2, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[dimension.network].[network one].[line class one]}\n" + "{[dimension.network].[network two].[line class two]}\n" + "Row #0: 31\n" + "Row #1: 121\n"); } public void testComplexJoinDefaultRecognizer() throws Exception { if (!isApplicable()) { return; } final TestContext context = getCubeTestContext(); // We expect the correct cell value + 2 if the agg table is used. final String mdx = "select {[Measures].[Unit Sales]} on columns, {[dimension.distributor].[line class].Members} on rows from [foo2]"; context.assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[dimension.distributor].[distributor one].[line class one]}\n" + "{[dimension.distributor].[distributor two].[line class two]}\n" + "Row #0: 32\n" + "Row #1: 122\n"); final String mdx2 = "select {[Measures].[Unit Sales]} on columns, {[dimension.network].[line class].Members} on rows from [foo2]"; // We expect the correct cell value + 2 if the agg table is used. context.assertQueryReturns( mdx2, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[dimension.network].[network one].[line class one]}\n" + "{[dimension.network].[network two].[line class two]}\n" + "Row #0: 32\n" + "Row #1: 122\n"); } } // End NonCollapsedAggTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/aggmatcher/AggTableTestCase.java0000644000175000017500000000340011735330606026631 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.olap.MondrianProperties; import mondrian.test.loader.CsvDBTestCase; /** * This abstract class can be used as the basis for writing aggregate table * test in the "testsrc/main/mondrian/rolap/aggmatcher" directory. Taken care * of is the setting of the Caching and Aggregate Read/Use properties and * the reloading of the aggregate tables after the CSV tables are loaded. * The particular cube definition and CSV file to use are abstract methods. * * @author Richard M. Emberson */ public abstract class AggTableTestCase extends CsvDBTestCase { private static final String DIRECTORY = "testsrc/main/mondrian/rolap/aggmatcher"; public AggTableTestCase() { super(); } public AggTableTestCase(String name) { super(name); } protected void setUp() throws Exception { super.setUp(); // store current property values MondrianProperties props = MondrianProperties.instance(); // turn off caching propSaver.set( props.DisableCaching, true); propSaver.set( props.UseAggregates, true); propSaver.set( props.ReadAggregates, false); propSaver.set( props.ReadAggregates, true); } protected String getDirectoryName() { return DIRECTORY; } } // End AggTableTestCase.java mondrian-3.4.1/testsrc/main/mondrian/rolap/aggmatcher/Checkin_7641.java0000644000175000017500000001072511735330606025564 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.olap.Result; import mondrian.test.TestContext; import mondrian.test.loader.CsvDBTestCase; /** * Checkin 7641 attempted to correct a problem demonstrated by this * junit. The original problem involved implicit Time member usage in * on axis and the use of the default Time member in the other axis. * This junit defines a hierarchy Product with a default member 'Class2', * The MDX in one axis explicitly uses the {Product][Class1] member. * Depending upon whether the 7641 code is used or not (its use * depends upon the existance of a System property) one gets different * answers when the mdx is evaluated. * * @author Richard M. Emberson */ public class Checkin_7641 extends CsvDBTestCase { private static final String DIRECTORY = "testsrc/main/mondrian/rolap/aggmatcher"; private static final String CHECKIN_7641 = "Checkin_7641.csv"; public static final String PROP_NAME = "mondrian.test.checkin.7641"; //private boolean useImplicitMembers; public Checkin_7641() { super(); } public Checkin_7641(String name) { super(name); } protected void setUp() throws Exception { super.setUp(); } protected void tearDown() throws Exception { super.tearDown(); } public void testImplicitMember() throws Exception { // explicit use of [Product].[Class1] String mdx = " select NON EMPTY Crossjoin(" + " Hierarchize(Union({[Product].[Class1]}, " + "[Product].[Class1].Children)), " + " {[Measures].[Requested Value], " + " [Measures].[Shipped Value]}" + ") ON COLUMNS," + " NON EMPTY Hierarchize(Union({[Geography].[All Regions]}," + "[Geography].[All Regions].Children)) ON ROWS" + " from [ImplicitMember]"; Result result1 = getCubeTestContext().executeQuery(mdx); String resultString1 = TestContext.toString(result1); Result result2 = getCubeTestContext().executeQuery(mdx); String resultString2 = TestContext.toString(result2); assertEquals(resultString1, resultString2); } protected String getDirectoryName() { return DIRECTORY; } protected String getFileName() { return CHECKIN_7641; } protected String getCubeDescription() { // defines [Product].[Class2] as default (implicit) member return "\n" + "
\n" + "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "\n" + "\n" + ""; } } // End Checkin_7641.java mondrian-3.4.1/testsrc/main/mondrian/rolap/aggmatcher/BUG_1541077.csv0000644000175000017500000000122211735330606024726 0ustar drazzibdrazzib# BUG_1541077.csv # @version $Id:$ ## TableName: cheques ## ColumnNames: prod_id,store_id,amount ## ColumnTypes: INTEGER,INTEGER,DECIMAL(10,2) ## NosOfRows: 6 1,10,6 1,11,3 1,12,2 2,10,4 2,11,2 2,12,2 # aggregate over prod_id ## TableName: agg_lp_xxx_cheques ## ColumnNames: store_id,amount_AVG,FACT_COUNT ## ColumnTypes: INTEGER,DECIMAL(10,2),INTEGER ## NosOfRows: 3 10,5.0,2 11,2.5,2 12,2.0,2 # store dimension ## TableName: store_x ## ColumnNames: store_id,value ## ColumnTypes: INTEGER,DECIMAL(10,2) ## NosOfRows: 3 10,4 11,5 12,6 # product dimension ## TableName: product_x ## ColumnNames: prod_id,name ## ColumnTypes: INTEGER,VARCHAR(30) 1,One 2,Two mondrian-3.4.1/testsrc/main/mondrian/rolap/aggmatcher/non_collapsed_agg_test.csv0000644000175000017500000000404311735330606030102 0ustar drazzibdrazzib# multiple_cols_in_tuple_agg_tbls.csv # @version $Id:$ ## TableName: foo_fact ## ColumnNames: line_id,unit_sales ## ColumnTypes: INTEGER,INTEGER ## NosOfRows: 4 1,10 2,20 3,40 4,80 # line ## TableName: line ## ColumnNames: line_id,line_name ## ColumnTypes: INTEGER,VARCHAR(30) ## NosOfRows: 4 1,line one 2,line two 3,line Three 4,line Four # line tenant ## TableName: line_tenant ## ColumnNames: line_id,tenant_id ## ColumnTypes: INTEGER,INTEGER ## NosOfRows: 4 1,1 2,1 3,2 4,2 # tenant ## TableName: tenant ## ColumnNames: tenant_id,tenant_name ## ColumnTypes: INTEGER,VARCHAR(30) ## NosOfRows: 2 1,tenant one 2,tenant two # line_class ## TableName: line_class ## ColumnNames: line_class_id,line_class_name ## ColumnTypes: INTEGER,VARCHAR(30) ## NosOfRows: 2 1,line class one 2,line class two # line line class ## TableName: line_line_class ## ColumnNames: line_id,line_class_id ## ColumnTypes: INTEGER,INTEGER ## NosOfRows: 4 1,1 2,1 3,2 4,2 # distributor ## TableName: distributor ## ColumnNames: distributor_id,distributor_name ## ColumnTypes: INTEGER,VARCHAR(30) ## NosOfRows: 2 1,distributor one 2,distributor two # line class distributor ## TableName: line_class_distributor ## ColumnNames: line_class_id,distributor_id ## ColumnTypes: INTEGER,INTEGER ## NosOfRows: 2 1,1 2,2 # network ## TableName: network ## ColumnNames: network_id,network_name ## ColumnTypes: INTEGER,VARCHAR(30) ## NosOfRows: 2 1,network one 2,network two # line class network ## TableName: line_class_network ## ColumnNames: line_class_id,network_id ## ColumnTypes: INTEGER,INTEGER ## NosOfRows: 2 1,1 2,2 # agg_tenant ## TableName: agg_tenant ## ColumnNames: tenant_id,unit_sales,fact_count ## ColumnTypes: INTEGER,INTEGER,INTEGER ## NosOfRows: 2 1,31,2 2,121,2 # agg_line_class ## TableName: agg_line_class ## ColumnNames: line_class_id,unit_sales,fact_count ## ColumnTypes: INTEGER,INTEGER,INTEGER ## NosOfRows: 2 1,31,2 2,121,2 # agg_10_foo_fact ## TableName: agg_10_foo_fact ## ColumnNames: line_class_id,unit_sales,fact_count ## ColumnTypes: INTEGER,INTEGER,INTEGER ## NosOfRows: 2 1,32,2 2,122,2mondrian-3.4.1/testsrc/main/mondrian/rolap/aggmatcher/Checkin_7641.csv0000644000175000017500000000315411735330606025434 0ustar drazzibdrazzib# Checkin_7641.csv # @version $Id:$ # Note that there is no data for prod_id=1,2,8,9,10,11,12 ## TableName: checkin7641 ## ColumnNames: cust_loc_id,prod_id,first,request_value,shipped_value ## ColumnTypes: INTEGER,INTEGER,DECIMAL(10,2),DECIMAL(10,2),DECIMAL(10,2) ## NosOfRows: 40 1,3,11,11,11 1,4,12,12,12 1,5,13,13,13 1,6,14,14,14 1,7,15,15,15 2,3,21,21,21 2,4,22,22,22 2,5,23,23,23 2,6,24,24,24 2,7,25,25,25 3,3,31,31,31 3,4,32,32,32 3,5,33,33,33 3,6,34,34,34 3,7,35,35,35 4,3,41,41,41 4,4,42,42,42 4,5,43,43,43 4,6,44,44,44 4,7,45,45,45 5,3,51,51,51 5,4,52,52,52 5,5,53,53,53 5,6,54,54,54 5,7,55,55,55 6,3,61,61,61 6,4,62,62,62 6,5,63,63,63 6,6,64,64,64 6,7,65,65,65 7,3,71,71,71 7,4,72,72,72 7,5,73,73,73 7,6,74,74,74 7,7,75,75,75 8,3,81,81,81 8,4,82,82,82 8,5,83,83,83 8,6,84,84,84 8,7,85,85,85 # geography dimension ## TableName: geography7641 ## ColumnNames: cust_loc_id,state_cd,city_nm,zip_cd ## ColumnTypes: INTEGER,VARCHAR(20),VARCHAR(20),VARCHAR(20) ## NosOfRows: 8 1,New York,New York,1000 2,New York,New York,1001 3,New York,Albany,1010 4,New York,Albany,1011 5,California,San Fransisco,2000 6,California,San Fransisco,2001 7,California,San Diego,2010 8,California,San Diego,2011 # product dimension ## TableName: prod7611 ## ColumnNames: prod_id,class,brand,item ## ColumnTypes: INTEGER,VARCHAR(30),VARCHAR(30),VARCHAR(30) 1,Class0,Brand01,Item011 2,Class0,Brand01,Item011 3,Class1,Brand11,Item111 4,Class1,Brand11,Item112 5,Class1,Brand12,Item121 6,Class1,Brand12,Item122 7,Class1,Brand12,Item123 8,Class2,Brand21,Item211 9,Class2,Brand21,Item212 10,Class2,Brand22,Item121 11,Class2,Brand22,Item122 12,Class2,Brand22,Item123 mondrian-3.4.1/testsrc/main/mondrian/rolap/aggmatcher/Checkin_7634.csv0000644000175000017500000000303711735330606025436 0ustar drazzibdrazzib# Checkin_7634.csv # @version $Id:$ # Note that there is no data for prod_id=1,2,8,9,10,11,12 ## TableName: table7634 ## ColumnNames: cust_loc_id,prod_id,first,request_value,shipped_value ## ColumnTypes: INTEGER,INTEGER,DECIMAL(10,2):null,DECIMAL(10,2),DECIMAL(10,2) ## NosOfRows: 40 1,3,,11,11 1,4,,12,12 1,5,,13,13 1,6,,14,14 1,7,,15,15 2,3,,21,21 2,4,,22,22 2,5,,23,23 2,6,,24,24 2,7,,25,25 3,3,,31,31 3,4,,32,32 3,5,,33,33 3,6,,34,34 3,7,,35,35 4,3,,41,41 4,4,,42,42 4,5,,43,43 4,6,,44,44 4,7,,45,45 5,3,,51,51 5,4,,52,52 5,5,,53,53 5,6,,54,54 5,7,,55,55 6,3,,61,61 6,4,,62,62 6,5,,63,63 6,6,,64,64 6,7,,65,65 7,3,,71,71 7,4,,72,72 7,5,,73,73 7,6,,74,74 7,7,,75,75 8,3,,81,81 8,4,,82,82 8,5,,83,83 8,6,,84,84 8,7,,85,85 # geography dimension ## TableName: geography7631 ## ColumnNames: cust_loc_id,state_cd,city_nm,zip_cd ## ColumnTypes: INTEGER,VARCHAR(20),VARCHAR(20),VARCHAR(20) ## NosOfRows: 8 1,New York,New York,1000 2,New York,New York,1001 3,New York,Albany,1010 4,New York,Albany,1011 5,California,San Fransisco,2000 6,California,San Fransisco,2001 7,California,San Diego,2010 8,California,San Diego,2011 # product dimension ## TableName: prod7631 ## ColumnNames: prod_id,class,brand,item ## ColumnTypes: INTEGER,VARCHAR(30),VARCHAR(30),VARCHAR(30) 1,Class0,Brand01,Item011 2,Class0,Brand01,Item011 3,Class1,Brand11,Item111 4,Class1,Brand11,Item112 5,Class1,Brand12,Item121 6,Class1,Brand12,Item122 7,Class1,Brand12,Item123 8,Class2,Brand21,Item211 9,Class2,Brand21,Item212 10,Class2,Brand22,Item121 11,Class2,Brand22,Item122 12,Class2,Brand22,Item123 mondrian-3.4.1/testsrc/main/mondrian/rolap/aggmatcher/AggGenTest.java0000644000175000017500000000633411735330606025530 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2010 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.olap.*; import mondrian.rolap.RolapConnection; import mondrian.test.FoodMartTestCase; import org.apache.log4j.*; import org.apache.log4j.Level; import java.io.StringWriter; import java.sql.Connection; import java.sql.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.sql.DataSource; /** * Test if lookup columns are there after loading them in * AggGen#addCollapsedColumn(...). * * @author Sherman Wood */ public class AggGenTest extends FoodMartTestCase { public AggGenTest(String name) { super(name); } public void testCallingLoadColumnsInAddCollapsedColumnOrAddzSpecialCollapsedColumn() throws Exception { Logger logger = Logger.getLogger(AggGen.class); StringWriter writer = new StringWriter(); Appender myAppender = new WriterAppender(new SimpleLayout(), writer); logger.addAppender(myAppender); propSaver.setAtLeast(logger, Level.DEBUG); // This modifies the MondrianProperties for the whole of the // test run MondrianProperties props = MondrianProperties.instance(); // If run in Ant and with mondrian.jar, please comment out this line: propSaver.set(props.AggregateRules, "DefaultRules.xml"); propSaver.set(props.UseAggregates, true); propSaver.set(props.ReadAggregates, true); propSaver.set(props.GenerateAggregateSql, true); final RolapConnection rolapConn = (RolapConnection) getConnection(); Query query = rolapConn.parseQuery( "select {[Measures].[Count]} on columns from [HR]"); rolapConn.execute(query); logger.removeAppender(myAppender); final DataSource dataSource = rolapConn.getDataSource(); Connection sqlConnection = null; try { sqlConnection = dataSource.getConnection(); DatabaseMetaData dbmeta = sqlConnection.getMetaData(); JdbcSchema jdbcSchema = JdbcSchema.makeDB(dataSource); final String catalogName = jdbcSchema.getCatalogName(); final String schemaName = jdbcSchema.getSchemaName(); String log = writer.toString(); Pattern p = Pattern.compile( "DEBUG - Init: Column: [^:]+: `(\\w+)`.`(\\w+)`" + Util.nl + "WARN - Can not find column: \\2"); Matcher m = p.matcher(log); while (m.find()) { ResultSet rs = dbmeta.getColumns( catalogName, schemaName, m.group(1), m.group(2)); assertTrue(!rs.next()); } } finally { if (sqlConnection != null) { try { sqlConnection.close(); } catch (SQLException e) { // ignore } } } } } // End AggGenTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/MemberCacheControlTest.ref.xml0000644000175000017500000004246611735330606026432 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/rolap/sql/0000755000175000017500000000000011735330606021364 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/rolap/sql/SelectNotInGroupByTest.java0000644000175000017500000002205111735330606026566 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2008-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.sql; import mondrian.rolap.BatchTestCase; import mondrian.spi.Dialect; import mondrian.test.SqlPattern; import mondrian.test.TestContext; /** * Test that various values of {@link Dialect#allowsSelectNotInGroupBy} * produce correctly optimized SQL. * * @author Eric McDermid */ public class SelectNotInGroupByTest extends BatchTestCase { public static final String storeDimensionLevelIndependent = "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""; public static final String storeDimensionLevelDependent = "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""; public static final String storeDimensionUniqueLevelDependentProp = "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""; public static final String storeDimensionUniqueLevelIndependentProp = "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""; public static final String cubeA = "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + ""; public static final String queryCubeA = "select {[Measures].[Custom Store Sales],[Measures].[Custom Store Cost]} on columns, {[CustomStore].[Store Name].Members} on rows from CustomSales"; public static final String sqlWithAllGroupBy = "select \n" + " `store`.`store_country` as `c0`, \n" + " `store`.`store_city` as `c1`, \n" + " `store`.`store_state` as `c2`, \n" + " `store`.`store_name` as `c3`\n" + "from \n" + " `store` as `store`\n" + "group by \n" + " `store`.`store_country`, \n" + " `store`.`store_city`, \n" + " `store`.`store_state`, \n" + " `store`.`store_name`\n" + "order by \n" + " ISNULL(`store`.`store_country`), `store`.`store_country` ASC, \n" + " ISNULL(`store`.`store_city`), `store`.`store_city` ASC, \n" + " ISNULL(`store`.`store_name`), `store`.`store_name` ASC\n"; public static final String sqlWithNoGroupBy = "select \n" + " `store`.`store_country` as `c0`, \n" + " `store`.`store_city` as `c1`, \n" + " `store`.`store_state` as `c2`, \n" + " `store`.`store_name` as `c3`\n" + "from \n" + " `store` as `store`\n" + "order by \n" + " ISNULL(`store`.`store_country`), `store`.`store_country` ASC, \n" + " ISNULL(`store`.`store_city`), `store`.`store_city` ASC, \n" + " ISNULL(`store`.`store_name`), `store`.`store_name` ASC\n"; public static final String sqlWithLevelGroupBy = "select \n" + " `store`.`store_country` as `c0`, \n" + " `store`.`store_city` as `c1`, \n" + " `store`.`store_state` as `c2`, \n" + " `store`.`store_name` as `c3`\n" + "from \n" + " `store` as `store`\n" + "group by \n" + " `store`.`store_country`, \n" + " `store`.`store_city`, \n" + " `store`.`store_name`\n" + "order by \n" + " ISNULL(`store`.`store_country`), `store`.`store_country` ASC, \n" + " ISNULL(`store`.`store_city`), `store`.`store_city` ASC, \n" + " ISNULL(`store`.`store_name`), `store`.`store_name` ASC\n"; public void testDependentPropertySkipped() { // Property group by should be skipped only if dialect supports it String sqlpat; if (dialectAllowsSelectNotInGroupBy()) { sqlpat = sqlWithLevelGroupBy; } else { sqlpat = sqlWithAllGroupBy; } SqlPattern[] sqlPatterns = { new SqlPattern(Dialect.DatabaseProduct.MYSQL, sqlpat, sqlpat) }; // Use dimension with level-dependent property TestContext tc = TestContext.instance().create( storeDimensionLevelDependent, cubeA, null, null, null, null); assertQuerySqlOrNot(tc, queryCubeA, sqlPatterns, false, false, true); } public void testIndependentPropertyNotSkipped() { SqlPattern[] sqlPatterns = { new SqlPattern( Dialect.DatabaseProduct.MYSQL, sqlWithAllGroupBy, sqlWithAllGroupBy) }; // Use dimension with level-independent property TestContext tc = TestContext.instance().create( storeDimensionLevelIndependent, cubeA, null, null, null, null); assertQuerySqlOrNot(tc, queryCubeA, sqlPatterns, false, false, true); } public void testGroupBySkippedIfUniqueLevel() { // If unique level is included and all properties are level // dependent, then group by can be skipped regardless of dialect SqlPattern[] sqlPatterns = { new SqlPattern( Dialect.DatabaseProduct.MYSQL, sqlWithNoGroupBy, sqlWithNoGroupBy) }; // Use dimension with unique level & level-dependent properties TestContext tc = TestContext.instance().create( storeDimensionUniqueLevelDependentProp, cubeA, null, null, null, null); assertQuerySqlOrNot(tc, queryCubeA, sqlPatterns, false, false, true); } public void testGroupByNotSkippedIfIndependentProperty() { SqlPattern[] sqlPatterns = { new SqlPattern( Dialect.DatabaseProduct.MYSQL, sqlWithAllGroupBy, sqlWithAllGroupBy) }; // Use dimension with unique level but level-indpendent property TestContext tc = TestContext.instance().create( storeDimensionUniqueLevelIndependentProp, cubeA, null, null, null, null); assertQuerySqlOrNot(tc, queryCubeA, sqlPatterns, false, false, true); } private boolean dialectAllowsSelectNotInGroupBy() { final Dialect dialect = getTestContext().getDialect(); return dialect.allowsSelectNotInGroupBy(); } } // End SelectNotInGroupByTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/sql/SqlQueryTest.java0000644000175000017500000006744711735330606024676 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.sql; import mondrian.olap.MondrianProperties; import mondrian.rolap.BatchTestCase; import mondrian.spi.Dialect; import mondrian.test.SqlPattern; import mondrian.test.TestContext; import java.util.*; /** * Test for SqlQuery. * * @author Thiyagu * @since 06-Jun-2007 */ public class SqlQueryTest extends BatchTestCase { private String origWarnIfNoPatternForDialect; private MondrianProperties prop = MondrianProperties.instance(); protected void setUp() throws Exception { super.setUp(); origWarnIfNoPatternForDialect = prop.WarnIfNoPatternForDialect.get(); // This test warns of missing sql patterns for MYSQL. final Dialect dialect = getTestContext().getDialect(); if (prop.WarnIfNoPatternForDialect.get().equals("ANY") || dialect.getDatabaseProduct() == Dialect.DatabaseProduct.MYSQL) { prop.WarnIfNoPatternForDialect.set( dialect.getDatabaseProduct().toString()); } else { // Do not warn unless the dialect is "MYSQL", or // if the test chooses to warn regardless of the dialect. prop.WarnIfNoPatternForDialect.set("NONE"); } } protected void tearDown() throws Exception { super.tearDown(); prop.WarnIfNoPatternForDialect.set(origWarnIfNoPatternForDialect); } public void testToStringForSingleGroupingSetSql() { if (!isGroupingSetsSupported()) { return; } for (boolean b : new boolean[]{false, true}) { Dialect dialect = getTestContext().getDialect(); SqlQuery sqlQuery = new SqlQuery(dialect, b); sqlQuery.addSelect("c1", null); sqlQuery.addSelect("c2", null); sqlQuery.addGroupingFunction("gf0"); sqlQuery.addFromTable("s", "t1", "t1alias", null, null, true); sqlQuery.addWhere("a=b"); ArrayList groupingsetsList = new ArrayList(); groupingsetsList.add("gs1"); groupingsetsList.add("gs2"); groupingsetsList.add("gs3"); sqlQuery.addGroupingSet(groupingsetsList); String expected; String lineSep = System.getProperty("line.separator"); if (!b) { expected = "select c1 as \"c0\", c2 as \"c1\", grouping(gf0) as \"g0\" " + "from \"s\".\"t1\" =as= \"t1alias\" where a=b " + "group by grouping sets ((gs1, gs2, gs3))"; } else { expected = "select" + lineSep + " c1 as \"c0\"," + lineSep + " c2 as \"c1\"," + lineSep + " grouping(gf0) as \"g0\"" + lineSep + "from" + lineSep + " \"s\".\"t1\" =as= \"t1alias\"" + lineSep + "where" + lineSep + " a=b" + lineSep + "group by grouping sets (" + lineSep + " (gs1, gs2, gs3))"; } assertEquals( dialectize(dialect.getDatabaseProduct(), expected), dialectize( sqlQuery.getDialect().getDatabaseProduct(), sqlQuery.toString())); } } public void testToStringForForcedIndexHint() { Map hints = new HashMap(); hints.put("force_index", "myIndex"); String unformattedMysql = "select c1 as `c0`, c2 as `c1` " + "from `s`.`t1` as `t1alias`" + " FORCE INDEX (myIndex)" + " where a=b"; String formattedMysql = "select\n" + " c1 as `c0`,\n" + " c2 as `c1`\n" + "from\n" + " `s`.`t1` as `t1alias` FORCE INDEX (myIndex)\n" + "where\n" + " a=b"; SqlPattern[] unformattedSqlPatterns = { new SqlPattern( Dialect.DatabaseProduct.MYSQL, unformattedMysql, null)}; SqlPattern[] formattedSqlPatterns = { new SqlPattern( Dialect.DatabaseProduct.MYSQL, formattedMysql, null)}; for (boolean formatted : new boolean[]{false, true}) { Dialect dialect = getTestContext().getDialect(); SqlQuery sqlQuery = new SqlQuery(dialect, formatted); sqlQuery.setAllowHints(true); sqlQuery.addSelect("c1", null); sqlQuery.addSelect("c2", null); sqlQuery.addGroupingFunction("gf0"); sqlQuery.addFromTable("s", "t1", "t1alias", null, hints, true); sqlQuery.addWhere("a=b"); SqlPattern[] expected; if (!formatted) { expected = unformattedSqlPatterns; } else { expected = formattedSqlPatterns; } assertSqlQueryToStringMatches(sqlQuery, expected); } } private void assertSqlQueryToStringMatches( SqlQuery query, SqlPattern[] patterns) { Dialect dialect = getTestContext().getDialect(); Dialect.DatabaseProduct d = dialect.getDatabaseProduct(); boolean patternFound = false; for (SqlPattern sqlPattern : patterns) { if (!sqlPattern.hasDatabaseProduct(d)) { // If the dialect is not one in the pattern set, skip the // test. If in the end no pattern is located, print a warning // message if required. continue; } patternFound = true; String trigger = sqlPattern.getTriggerSql(); trigger = dialectize(d, trigger); assertEquals( dialectize(dialect.getDatabaseProduct(), trigger), dialectize( query.getDialect().getDatabaseProduct(), query.toString())); } // Print warning message that no pattern was specified for the current // dialect. if (!patternFound) { String warnDialect = MondrianProperties.instance().WarnIfNoPatternForDialect.get(); if (warnDialect.equals(d.toString())) { System.out.println( "[No expected SQL statements found for dialect \"" + dialect.toString() + "\" and test not run]"); } } } public void testPredicatesAreOptimizedWhenPropertyIsTrue() { if (prop.ReadAggregates.get() && prop.UseAggregates.get()) { // Sql pattner will be different if using aggregate tables. // This test cover predicate generation so it's sufficient to // only check sql pattern when aggregate tables are not used. return; } String mdx = "select {[Time].[1997].[Q1],[Time].[1997].[Q2]," + "[Time].[1997].[Q3]} on 0 from sales"; String accessSql = "select `time_by_day`.`the_year` as `c0`, " + "`time_by_day`.`quarter` as `c1`, " + "sum(`sales_fact_1997`.`unit_sales`) as `m0` " + "from `time_by_day` as `time_by_day`, " + "`sales_fact_1997` as `sales_fact_1997` " + "where `sales_fact_1997`.`time_id` = " + "`time_by_day`.`time_id` and " + "`time_by_day`.`the_year` = 1997 group by " + "`time_by_day`.`the_year`, `time_by_day`.`quarter`"; String mysqlSql = "select " + "`time_by_day`.`the_year` as `c0`, `time_by_day`.`quarter` as `c1`, " + "sum(`sales_fact_1997`.`unit_sales`) as `m0` " + "from " + "`time_by_day` as `time_by_day`, `sales_fact_1997` as `sales_fact_1997` " + "where " + "`sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and " + "`time_by_day`.`the_year` = 1997 " + "group by `time_by_day`.`the_year`, `time_by_day`.`quarter`"; SqlPattern[] sqlPatterns = { new SqlPattern( Dialect.DatabaseProduct.ACCESS, accessSql, accessSql), new SqlPattern(Dialect.DatabaseProduct.MYSQL, mysqlSql, mysqlSql)}; assertSqlEqualsOptimzePredicates(true, mdx, sqlPatterns); } public void testTableNameIsIncludedWithParentChildQuery() { String sql = "select `employee`.`employee_id` as `c0`, " + "`employee`.`full_name` as `c1`, " + "`employee`.`marital_status` as `c2`, " + "`employee`.`position_title` as `c3`, " + "`employee`.`gender` as `c4`, " + "`employee`.`salary` as `c5`, " + "`employee`.`education_level` as `c6`, " + "`employee`.`management_role` as `c7` " + "from `employee` as `employee` " + "where `employee`.`supervisor_id` = 0 " + "group by `employee`.`employee_id`, `employee`.`full_name`, " + "`employee`.`marital_status`, `employee`.`position_title`, " + "`employee`.`gender`, `employee`.`salary`," + " `employee`.`education_level`, `employee`.`management_role`" + " order by Iif(`employee`.`employee_id` IS NULL, 1, 0)," + " `employee`.`employee_id` ASC"; final String mdx = "SELECT " + " GENERATE(" + " {[Employees].[All Employees].[Sheri Nowmer]}," + "{" + " {([Employees].CURRENTMEMBER)}," + " HEAD(" + " ADDCALCULATEDMEMBERS([Employees].CURRENTMEMBER.CHILDREN), 51)" + "}," + "ALL" + ") DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(0) \n" + "FROM [HR] CELL PROPERTIES VALUE, FORMAT_STRING"; SqlPattern[] sqlPatterns = { new SqlPattern(Dialect.DatabaseProduct.ACCESS, sql, sql) }; assertQuerySql(mdx, sqlPatterns); } public void testPredicatesAreNotOptimizedWhenPropertyIsFalse() { if (prop.ReadAggregates.get() && prop.UseAggregates.get()) { // Sql pattner will be different if using aggregate tables. // This test cover predicate generation so it's sufficient to // only check sql pattern when aggregate tables are not used. return; } String mdx = "select {[Time].[1997].[Q1],[Time].[1997].[Q2]," + "[Time].[1997].[Q3]} on 0 from sales"; String accessSql = "select `time_by_day`.`the_year` as `c0`, " + "`time_by_day`.`quarter` as `c1`, " + "sum(`sales_fact_1997`.`unit_sales`) as `m0` " + "from `time_by_day` as `time_by_day`, " + "`sales_fact_1997` as `sales_fact_1997` " + "where `sales_fact_1997`.`time_id` = " + "`time_by_day`.`time_id` and `time_by_day`.`the_year` " + "= 1997 and `time_by_day`.`quarter` in " + "('Q1', 'Q2', 'Q3') group by " + "`time_by_day`.`the_year`, `time_by_day`.`quarter`"; String mysqlSql = "select " + "`time_by_day`.`the_year` as `c0`, `time_by_day`.`quarter` as `c1`, " + "sum(`sales_fact_1997`.`unit_sales`) as `m0` " + "from " + "`time_by_day` as `time_by_day`, `sales_fact_1997` as `sales_fact_1997` " + "where " + "`sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and " + "`time_by_day`.`the_year` = 1997 and " + "`time_by_day`.`quarter` in ('Q1', 'Q2', 'Q3') " + "group by `time_by_day`.`the_year`, `time_by_day`.`quarter`"; SqlPattern[] sqlPatterns = { new SqlPattern( Dialect.DatabaseProduct.ACCESS, accessSql, accessSql), new SqlPattern(Dialect.DatabaseProduct.MYSQL, mysqlSql, mysqlSql)}; assertSqlEqualsOptimzePredicates(false, mdx, sqlPatterns); } public void testPredicatesAreOptimizedWhenAllTheMembersAreIncluded() { if (prop.ReadAggregates.get() && prop.UseAggregates.get()) { // Sql pattner will be different if using aggregate tables. // This test cover predicate generation so it's sufficient to // only check sql pattern when aggregate tables are not used. return; } String mdx = "select {[Time].[1997].[Q1],[Time].[1997].[Q2]," + "[Time].[1997].[Q3],[Time].[1997].[Q4]} on 0 from sales"; String accessSql = "select `time_by_day`.`the_year` as `c0`, " + "`time_by_day`.`quarter` as `c1`, " + "sum(`sales_fact_1997`.`unit_sales`) as `m0` from " + "`time_by_day` as `time_by_day`, `sales_fact_1997` as" + " `sales_fact_1997` where `sales_fact_1997`.`time_id`" + " = `time_by_day`.`time_id` and `time_by_day`." + "`the_year` = 1997 group by `time_by_day`.`the_year`," + " `time_by_day`.`quarter`"; String mysqlSql = "select " + "`time_by_day`.`the_year` as `c0`, `time_by_day`.`quarter` as `c1`, " + "sum(`sales_fact_1997`.`unit_sales`) as `m0` " + "from " + "`time_by_day` as `time_by_day`, `sales_fact_1997` as `sales_fact_1997` " + "where " + "`sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and " + "`time_by_day`.`the_year` = 1997 " + "group by `time_by_day`.`the_year`, `time_by_day`.`quarter`"; SqlPattern[] sqlPatterns = { new SqlPattern( Dialect.DatabaseProduct.ACCESS, accessSql, accessSql), new SqlPattern(Dialect.DatabaseProduct.MYSQL, mysqlSql, mysqlSql)}; assertSqlEqualsOptimzePredicates(true, mdx, sqlPatterns); assertSqlEqualsOptimzePredicates(false, mdx, sqlPatterns); } private void assertSqlEqualsOptimzePredicates( boolean optimizePredicatesValue, String inputMdx, SqlPattern[] sqlPatterns) { boolean intialValueOptimize = prop.OptimizePredicates.get(); try { prop.OptimizePredicates.set(optimizePredicatesValue); assertQuerySql(inputMdx, sqlPatterns); } finally { prop.OptimizePredicates.set(intialValueOptimize); } } public void testToStringForGroupingSetSqlWithEmptyGroup() { if (!isGroupingSetsSupported()) { return; } final Dialect dialect = getTestContext().getDialect(); for (boolean b : new boolean[]{false, true}) { SqlQuery sqlQuery = new SqlQuery(getTestContext().getDialect(), b); sqlQuery.addSelect("c1", null); sqlQuery.addSelect("c2", null); sqlQuery.addFromTable("s", "t1", "t1alias", null, null, true); sqlQuery.addWhere("a=b"); sqlQuery.addGroupingFunction("g1"); sqlQuery.addGroupingFunction("g2"); ArrayList groupingsetsList = new ArrayList(); groupingsetsList.add("gs1"); groupingsetsList.add("gs2"); groupingsetsList.add("gs3"); sqlQuery.addGroupingSet(new ArrayList()); sqlQuery.addGroupingSet(groupingsetsList); String expected; if (b) { expected = "select\n" + " c1 as \"c0\",\n" + " c2 as \"c1\",\n" + " grouping(g1) as \"g0\",\n" + " grouping(g2) as \"g1\"\n" + "from\n" + " \"s\".\"t1\" =as= \"t1alias\"\n" + "where\n" + " a=b\n" + "group by grouping sets (\n" + " (),\n" + " (gs1, gs2, gs3))"; } else { expected = "select c1 as \"c0\", c2 as \"c1\", grouping(g1) as \"g0\", " + "grouping(g2) as \"g1\" from \"s\".\"t1\" =as= \"t1alias\" where a=b " + "group by grouping sets ((), (gs1, gs2, gs3))"; } assertEquals( dialectize(dialect.getDatabaseProduct(), expected), dialectize( sqlQuery.getDialect().getDatabaseProduct(), sqlQuery.toString())); } } public void testToStringForMultipleGroupingSetsSql() { if (!isGroupingSetsSupported()) { return; } final Dialect dialect = getTestContext().getDialect(); for (boolean b : new boolean[]{false, true}) { SqlQuery sqlQuery = new SqlQuery(dialect, b); sqlQuery.addSelect("c0", null); sqlQuery.addSelect("c1", null); sqlQuery.addSelect("c2", null); sqlQuery.addSelect("m1", null, "m1"); sqlQuery.addFromTable("s", "t1", "t1alias", null, null, true); sqlQuery.addWhere("a=b"); sqlQuery.addGroupingFunction("c0"); sqlQuery.addGroupingFunction("c1"); sqlQuery.addGroupingFunction("c2"); ArrayList groupingSetlist1 = new ArrayList(); groupingSetlist1.add("c0"); groupingSetlist1.add("c1"); groupingSetlist1.add("c2"); sqlQuery.addGroupingSet(groupingSetlist1); ArrayList groupingsetsList2 = new ArrayList(); groupingsetsList2.add("c1"); groupingsetsList2.add("c2"); sqlQuery.addGroupingSet(groupingsetsList2); String expected; if (b) { expected = "select\n" + " c0 as \"c0\",\n" + " c1 as \"c1\",\n" + " c2 as \"c2\",\n" + " m1 as \"m1\",\n" + " grouping(c0) as \"g0\",\n" + " grouping(c1) as \"g1\",\n" + " grouping(c2) as \"g2\"\n" + "from\n" + " \"s\".\"t1\" =as= \"t1alias\"\n" + "where\n" + " a=b\n" + "group by grouping sets (\n" + " (c0, c1, c2),\n" + " (c1, c2))"; } else { expected = "select c0 as \"c0\", c1 as \"c1\", c2 as \"c2\", m1 as \"m1\", " + "grouping(c0) as \"g0\", grouping(c1) as \"g1\", grouping(c2) as \"g2\" " + "from \"s\".\"t1\" =as= \"t1alias\" where a=b " + "group by grouping sets ((c0, c1, c2), (c1, c2))"; } assertEquals( dialectize(dialect.getDatabaseProduct(), expected), dialectize( sqlQuery.getDialect().getDatabaseProduct(), sqlQuery.toString())); } } /** * Verifies that the correct SQL string is generated for literals of * SQL type "double". * *

Mondrian only generates SQL DOUBLE values in a special format for * LucidDB; therefore, this test is a no-op on other databases. */ public void testDoubleInList() { final Dialect dialect = getTestContext().getDialect(); if (dialect.getDatabaseProduct() != Dialect.DatabaseProduct.LUCIDDB) { return; } propSaver.set(prop.IgnoreInvalidMembers, true); propSaver.set(prop.IgnoreInvalidMembersDuringQuery, true); // assertQuerySql(testContext, query, patterns); // Test when the double value itself cotnains "E". String dimensionSqlExpression = "cast(cast(\"salary\" as double)*cast(1000.0 as double)/cast(3.1234567890123456 as double) as double)\n"; String cubeFirstPart = "\n" + "

\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n"; String cubeSecondPart = " \n" + " \n" + " \n" + " \n" + " " + " \n" + ""; String cube = cubeFirstPart + dimensionSqlExpression + cubeSecondPart; String query = "select " + "{[StoreEmpSalary].[All Salary].[6403.162057613773],[StoreEmpSalary].[All Salary].[1184584.980658548],[StoreEmpSalary].[All Salary].[1344664.0320988924], " + " [StoreEmpSalary].[All Salary].[1376679.8423869612],[StoreEmpSalary].[All Salary].[1408695.65267503],[StoreEmpSalary].[All Salary].[1440711.462963099], " + " [StoreEmpSalary].[All Salary].[1456719.3681071333],[StoreEmpSalary].[All Salary].[1472727.2732511677],[StoreEmpSalary].[All Salary].[1488735.1783952022], " + " [StoreEmpSalary].[All Salary].[1504743.0835392366],[StoreEmpSalary].[All Salary].[1536758.8938273056],[StoreEmpSalary].[All Salary].[1600790.5144034433], " + " [StoreEmpSalary].[All Salary].[1664822.134979581],[StoreEmpSalary].[All Salary].[1888932.806996063],[StoreEmpSalary].[All Salary].[1952964.4275722008], " + " [StoreEmpSalary].[All Salary].[1984980.2378602696],[StoreEmpSalary].[All Salary].[2049011.8584364073],[StoreEmpSalary].[All Salary].[2081027.6687244761], " + " [StoreEmpSalary].[All Salary].[2113043.479012545],[StoreEmpSalary].[All Salary].[2145059.289300614],[StoreEmpSalary].[All Salary].[2.5612648230455093E7]} " + " on rows, {[Measures].[Store Cost]} on columns from [Sales 3]"; // Notice there are a few members missing in this sql. This is a LucidDB // bug wrt comparison involving "approximate number literals". // Mondrian properties "IgnoreInvalidMembers" and // "IgnoreInvalidMembersDuringQuery" are required for this MDX to // finish, even though the the generated sql(below) and the final result // are both incorrect. String loadSqlLucidDB = "select cast(cast(\"salary\" as double)*cast(1000.0 as double)/cast(3.1234567890123456 as double) as double) as \"c0\", " + "sum(\"sales_fact_1997\".\"store_cost\") as \"m0\" " + "from \"employee\" as \"employee\", \"sales_fact_1997\" as \"sales_fact_1997\" " + "where \"sales_fact_1997\".\"store_id\" = \"employee\".\"store_id\" and " + "cast(cast(\"salary\" as double)*cast(1000.0 as double)/cast(3.1234567890123456 as double) as double) in " + "(6403.162057613773E0, 1184584.980658548E0, 1344664.0320988924E0, " + "1376679.8423869612E0, 1408695.65267503E0, 1440711.462963099E0, " + "1456719.3681071333E0, 1488735.1783952022E0, " + "1504743.0835392366E0, 1536758.8938273056E0, " + "1664822.134979581E0, 1888932.806996063E0, 1952964.4275722008E0, " + "1984980.2378602696E0, 2049011.8584364073E0, " + "2113043.479012545E0, 2145059.289300614E0, 2.5612648230455093E7) " + "group by cast(cast(\"salary\" as double)*cast(1000.0 as double)/cast(3.1234567890123456 as double) as double)"; SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.LUCIDDB, loadSqlLucidDB, loadSqlLucidDB) }; TestContext testContext = TestContext.instance().create( null, cube, null, null, null, null); assertQuerySql(testContext, query, patterns); } /** * Testcase for * bug MONDRIAN-457, * "Strange SQL condition appears when executing query". The fix * implemented MatchType.EXACT_SCHEMA, which only * queries known schema objects. This prevents SQL such as * "UPPER(`store`.`store_country`) = UPPER('Time.Weekly')" from being * generated. */ public void testInvalidSqlMemberLookup() { String sqlMySql = "select `store`.`store_type` as `c0` from `store` as `store` " + "where UPPER(`store`.`store_type`) = UPPER('Time.Weekly') " + "group by `store`.`store_type` " + "order by ISNULL(`store`.`store_type`), `store`.`store_type` ASC"; String sqlOracle = "select \"store\".\"store_type\" as \"c0\" from \"store\" \"store\" " + "where UPPER(\"store\".\"store_type\") = UPPER('Time.Weekly') " + "group by \"store\".\"store_type\" " + "order by \"store\".\"store_type\" ASC"; SqlPattern[] patterns = { new SqlPattern(Dialect.DatabaseProduct.MYSQL, sqlMySql, sqlMySql), new SqlPattern( Dialect.DatabaseProduct.ORACLE, sqlOracle, sqlOracle), }; assertNoQuerySql( "select {[Time.Weekly].[All Time.Weeklys]} ON COLUMNS from [Sales]", patterns); } /** * This test makes sure that a level which specifies an * approxRowCount property prevents Mondrian from executing a * count() sql query. It was discovered in bug MONDRIAN-711 * that the aggregate tables predicates optimization code was * not considering the approxRowCount property. It is fixed and * this test will ensure it won't happen again. */ public void testApproxRowCountOverridesCount() { final String cubeSchema = " \n" + "
\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " " + " \n" + ""; final String mdxQuery = "SELECT {[Gender].[Gender].Members} ON ROWS, {[Measures].[Unit Sales]} ON COLUMNS FROM [ApproxTest]"; final String forbiddenSqlOracle = "select count(distinct \"customer\".\"gender\") as \"c0\" from \"customer\" \"customer\""; final String forbiddenSqlMysql = "select count(distinct `customer`.`gender`) as `c0` from `customer` `customer`;"; SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.ORACLE, forbiddenSqlOracle, null), new SqlPattern( Dialect.DatabaseProduct.MYSQL, forbiddenSqlMysql, null) }; final TestContext testContext = TestContext.instance().create( null, cubeSchema, null, null, null, null); assertQuerySqlOrNot( testContext, mdxQuery, patterns, true, true, true); } } // End SqlQueryTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/RolapCubeTest.java0000644000175000017500000002776111735330606024161 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2011 Pentaho and others // All Rights Reserved. // // mkambol, 25 January, 2007 */ package mondrian.rolap; import mondrian.olap.*; import mondrian.test.FoodMartTestCase; import mondrian.test.TestContext; import java.util.*; /** * Unit test for {@link RolapCube}. * * @author mkambol * @since 25 January, 2007 */ public class RolapCubeTest extends FoodMartTestCase { public void testProcessFormatStringAttributeToIgnoreNullFormatString() { RolapCube cube = (RolapCube) getConnection().getSchema().lookupCube("Sales", false); StringBuilder builder = new StringBuilder(); cube.processFormatStringAttribute( new MondrianDef.CalculatedMember(), builder); assertEquals(0, builder.length()); } public void testProcessFormatStringAttribute() { RolapCube cube = (RolapCube) getConnection().getSchema().lookupCube("Sales", false); StringBuilder builder = new StringBuilder(); MondrianDef.CalculatedMember xmlCalcMember = new MondrianDef.CalculatedMember(); String format = "FORMAT"; xmlCalcMember.formatString = format; cube.processFormatStringAttribute(xmlCalcMember, builder); assertEquals( "," + Util.nl + "FORMAT_STRING = \"" + format + "\"", builder.toString()); } public void testGetCalculatedMembersWithNoRole() { String[] expectedCalculatedMembers = { "[Measures].[Profit]", "[Measures].[Average Warehouse Sale]", "[Measures].[Profit Growth]", "[Measures].[Profit Per Unit Shipped]" }; Connection connection = getTestContext().getConnection(); try { Cube warehouseAndSalesCube = cubeByName(connection, "Warehouse and Sales"); SchemaReader schemaReader = warehouseAndSalesCube.getSchemaReader(null); List calculatedMembers = schemaReader.getCalculatedMembers(); assertEquals( expectedCalculatedMembers.length, calculatedMembers.size()); assertCalculatedMemberExists( expectedCalculatedMembers, calculatedMembers); } finally { connection.close(); } } public void testGetCalculatedMembersForCaliforniaManager() { String[] expectedCalculatedMembers = new String[] { "[Measures].[Profit]", "[Measures].[Profit last Period]", "[Measures].[Profit Growth]" }; Connection connection = getTestContext().withRole("California manager") .getConnection(); try { Cube salesCube = cubeByName(connection, "Sales"); SchemaReader schemaReader = salesCube .getSchemaReader(connection.getRole()); List calculatedMembers = schemaReader.getCalculatedMembers(); assertEquals( expectedCalculatedMembers.length, calculatedMembers.size()); assertCalculatedMemberExists( expectedCalculatedMembers, calculatedMembers); } finally { connection.close(); } } public void testGetCalculatedMembersReturnsOnlyAccessibleMembers() { String[] expectedCalculatedMembers = { "[Measures].[Profit]", "[Measures].[Profit last Period]", "[Measures].[Profit Growth]", "[Product].[~Missing]" }; TestContext testContext = createTestContextWithAdditionalMembersAndARole(); Connection connection = testContext.getConnection(); try { Cube salesCube = cubeByName(connection, "Sales"); SchemaReader schemaReader = salesCube.getSchemaReader(connection.getRole()); List calculatedMembers = schemaReader.getCalculatedMembers(); assertEquals( expectedCalculatedMembers.length, calculatedMembers.size()); assertCalculatedMemberExists( expectedCalculatedMembers, calculatedMembers); } finally { connection.close(); } } public void testGetCalculatedMembersReturnsOnlyAccessibleMembersForHierarchy() { String[] expectedCalculatedMembersFromProduct = { "[Product].[~Missing]" }; TestContext testContext = createTestContextWithAdditionalMembersAndARole(); Connection connection = testContext.getConnection(); try { Cube salesCube = cubeByName(connection, "Sales"); SchemaReader schemaReader = salesCube.getSchemaReader(connection.getRole()); // Product.~Missing accessible List calculatedMembers = schemaReader.getCalculatedMembers( getDimensionWithName( "Product", salesCube.getDimensions()).getHierarchy()); assertEquals( expectedCalculatedMembersFromProduct.length, calculatedMembers.size()); assertCalculatedMemberExists( expectedCalculatedMembersFromProduct, calculatedMembers); // Gender.~Missing not accessible calculatedMembers = schemaReader.getCalculatedMembers( getDimensionWithName( "Gender", salesCube.getDimensions()).getHierarchy()); assertEquals(0, calculatedMembers.size()); } finally { connection.close(); } } public void testGetCalculatedMembersReturnsOnlyAccessibleMembersForLevel() { String[] expectedCalculatedMembersFromProduct = new String[]{ "[Product].[~Missing]" }; TestContext testContext = createTestContextWithAdditionalMembersAndARole(); Connection connection = testContext.getConnection(); try { Cube salesCube = cubeByName(connection, "Sales"); SchemaReader schemaReader = salesCube.getSchemaReader(connection.getRole()); // Product.~Missing accessible List calculatedMembers = schemaReader.getCalculatedMembers( getDimensionWithName( "Product", salesCube.getDimensions()) .getHierarchy().getLevels()[0]); assertEquals( expectedCalculatedMembersFromProduct.length, calculatedMembers.size()); assertCalculatedMemberExists( expectedCalculatedMembersFromProduct, calculatedMembers); // Gender.~Missing not accessible calculatedMembers = schemaReader.getCalculatedMembers( getDimensionWithName( "Gender", salesCube.getDimensions()) .getHierarchy().getLevels()[0]); assertEquals(0, calculatedMembers.size()); } finally { connection.close(); } } public void testNonJoiningDimensions() { TestContext testContext = this.getTestContext(); Connection connection = testContext.getConnection(); try { RolapCube salesCube = (RolapCube) cubeByName(connection, "Sales"); RolapCube warehouseAndSalesCube = (RolapCube) cubeByName(connection, "Warehouse and Sales"); SchemaReader readerWarehouseAndSales = warehouseAndSalesCube.getSchemaReader().withLocus(); List members = new ArrayList(); List warehouseMembers = warehouseMembersCanadaMexicoUsa(readerWarehouseAndSales); Dimension warehouseDim = warehouseMembers.get(0).getDimension(); members.addAll(warehouseMembers); List storeMembers = storeMembersCAAndOR(readerWarehouseAndSales).slice(0); Dimension storeDim = storeMembers.get(0).getDimension(); members.addAll(storeMembers); Set nonJoiningDims = salesCube.nonJoiningDimensions(members.toArray(new Member[0])); assertFalse(nonJoiningDims.contains(storeDim)); assertTrue(nonJoiningDims.contains(warehouseDim)); } finally { connection.close(); } } public void testRolapCubeDimensionEquality() { TestContext testContext = getTestContext(); Connection connection1 = testContext.getConnection(); Connection connection2 = TestContext.instance().withSchema(null).getConnection(); try { RolapCube salesCube1 = (RolapCube) cubeByName(connection1, "Sales"); SchemaReader readerSales1 = salesCube1.getSchemaReader().withLocus(); List storeMembersSales = storeMembersCAAndOR(readerSales1).slice(0); Dimension storeDim1 = storeMembersSales.get(0).getDimension(); assertEquals(storeDim1, storeDim1); RolapCube salesCube2 = (RolapCube) cubeByName(connection2, "Sales"); SchemaReader readerSales2 = salesCube2.getSchemaReader().withLocus(); List storeMembersSales2 = storeMembersCAAndOR(readerSales2).slice(0); Dimension storeDim2 = storeMembersSales2.get(0).getDimension(); assertEquals(storeDim1, storeDim2); RolapCube warehouseAndSalesCube = (RolapCube) cubeByName(connection1, "Warehouse and Sales"); SchemaReader readerWarehouseAndSales = warehouseAndSalesCube.getSchemaReader().withLocus(); List storeMembersWarehouseAndSales = storeMembersCAAndOR(readerWarehouseAndSales).slice(0); Dimension storeDim3 = storeMembersWarehouseAndSales.get(0).getDimension(); assertFalse(storeDim1.equals(storeDim3)); List warehouseMembers = warehouseMembersCanadaMexicoUsa(readerWarehouseAndSales); Dimension warehouseDim = warehouseMembers.get(0).getDimension(); assertFalse(storeDim3.equals(warehouseDim)); } finally { connection1.close(); connection2.close(); } } private TestContext createTestContextWithAdditionalMembersAndARole() { String nonAccessibleMember = " \n" + " 100\n" + " \n"; String accessibleMember = " \n" + " 100\n" + " \n"; TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", null, nonAccessibleMember + accessibleMember); return testContext.withRole("California manager"); } private void assertCalculatedMemberExists( String[] expectedCalculatedMembers, List calculatedMembers) { List expectedCalculatedMemberNames = Arrays.asList(expectedCalculatedMembers); for (Member calculatedMember : calculatedMembers) { String calculatedMemberName = calculatedMember.getUniqueName(); assertTrue( "Calculated member name not found: " + calculatedMemberName, expectedCalculatedMemberNames.contains(calculatedMemberName)); } } } // End RolapCubeTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/NonEmptyTest.java0000644000175000017500000070354511735330606024057 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.olap.Level; import mondrian.rolap.RolapConnection.NonEmptyResult; import mondrian.rolap.RolapNative.*; import mondrian.rolap.cache.HardSmartCache; import mondrian.rolap.sql.MemberChildrenConstraint; import mondrian.rolap.sql.TupleConstraint; import mondrian.spi.Dialect; import mondrian.test.SqlPattern; import mondrian.test.TestContext; import mondrian.util.Bug; import mondrian.util.Pair; import junit.framework.Assert; import org.apache.log4j.*; import org.apache.log4j.spi.LoggingEvent; import org.eigenbase.util.property.BooleanProperty; import org.eigenbase.util.property.StringProperty; import java.util.ArrayList; import java.util.List; /** * Tests for NON EMPTY Optimization, includes SqlConstraint type hierarchy and * RolapNative classes. * * @author av * @since Nov 21, 2005 */ public class NonEmptyTest extends BatchTestCase { private static Logger logger = Logger.getLogger(NonEmptyTest.class); SqlConstraintFactory scf = SqlConstraintFactory.instance(); TestContext localTestContext; private static final String STORE_TYPE_LEVEL = TestContext.levelName("Store Type", "Store Type", "Store Type"); private static final String EDUCATION_LEVEL_LEVEL = TestContext.levelName( "Education Level", "Education Level", "Education Level"); public NonEmptyTest() { super(); } public NonEmptyTest(String name) { super(name); } @Override protected void setUp() throws Exception { super.setUp(); propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, true); propSaver.set( MondrianProperties.instance().EnableNativeNonEmpty, true); } @Override protected void tearDown() throws Exception { super.tearDown(); localTestContext = null; // allow gc } @Override public TestContext getTestContext() { return localTestContext != null ? localTestContext : super.getTestContext(); } public void setTestContext(TestContext testContext) { localTestContext = testContext; } public void testBugMondrian584EnumOrder() { // The interpreter results include males before females, which is // correct because it is consistent with the explicit order present // in the query. Native evaluation returns the females before males, // which is probably a reflection of the database ordering. // if (Bug.BugMondrian584Fixed) { checkNative( 4, 4, "SELECT non empty { CrossJoin( " + " {Gender.M, Gender.F}, " + " { [Marital Status].[Marital Status].members } " + ") } on 0 from sales"); } } public void testBugCantRestrictSlicerToCalcMember() throws Exception { TestContext ctx = getTestContext(); ctx.assertQueryReturns( "WITH Member [Time].[Time].[Aggr] AS 'Aggregate({[Time].[1998].[Q1], [Time].[1998].[Q2]})' " + "SELECT {[Measures].[Store Sales]} ON COLUMNS, " + "NON EMPTY Order(TopCount([Customers].[Name].Members,3,[Measures].[Store Sales]),[Measures].[Store Sales],BASC) ON ROWS " + "FROM [Sales] " + "WHERE ([Time].[Aggr])", "Axis #0:\n" + "{[Time].[Aggr]}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n"); } /** * Test case for an issue where mondrian failed to use native evaluation * for evaluating crossjoin. With the issue, performance is poor because * mondrian is doing crossjoins in memory; and the test case throws because * the result limit is exceeded. */ public void testAnalyzerPerformanceIssue() { final MondrianProperties mondrianProperties = MondrianProperties.instance(); propSaver.set(mondrianProperties.EnableNativeCrossJoin, true); propSaver.set(mondrianProperties.EnableNativeTopCount, false); propSaver.set(mondrianProperties.EnableNativeFilter, true); propSaver.set(mondrianProperties.EnableNativeNonEmpty, false); propSaver.set(mondrianProperties.ResultLimit, 5000000); assertQueryReturns( "with set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Education Level], NonEmptyCrossJoin([*BASE_MEMBERS_Product], NonEmptyCrossJoin([*BASE_MEMBERS_Customers], [*BASE_MEMBERS_Time])))' " + "set [*METRIC_CJ_SET] as 'Filter([*NATIVE_CJ_SET], ([Measures].[*TOP_Unit Sales_SEL~SUM] <= 2.0))' " + "set [*SORTED_ROW_AXIS] as 'Order([*CJ_ROW_AXIS], [Product].CurrentMember.OrderKey, BASC, Ancestor([Product].CurrentMember, [Product].[Brand Name]).OrderKey, BASC, [Customers].CurrentMember.OrderKey, BASC, Ancestor([Customers].CurrentMember, [Customers].[City]).OrderKey, BASC)' " + "set [*SORTED_COL_AXIS] as 'Order([*CJ_COL_AXIS], [Education Level].CurrentMember.OrderKey, BASC)' " + "set [*BASE_MEMBERS_Time] as '{[Time].[1997].[Q1]}' " + "set [*NATIVE_MEMBERS_Customers] as 'Generate([*NATIVE_CJ_SET], {[Customers].CurrentMember})' " + "set [*TOP_SET] as 'Order(Generate([*NATIVE_CJ_SET], {[Product].CurrentMember}), ([Measures].[Unit Sales], [Customers].[*CTX_MEMBER_SEL~SUM], [Education Level].[*CTX_MEMBER_SEL~SUM], [Time].[*CTX_MEMBER_SEL~AGG]), BDESC)' " + "set [*BASE_MEMBERS_Education Level] as '[Education Level].[Education Level].Members' " + "set [*NATIVE_MEMBERS_Education Level] as 'Generate([*NATIVE_CJ_SET], {[Education Level].CurrentMember})' " + "set [*METRIC_MEMBERS_Time] as 'Generate([*METRIC_CJ_SET], {[Time].[Time].CurrentMember})' " + "set [*NATIVE_MEMBERS_Time] as 'Generate([*NATIVE_CJ_SET], {[Time].[Time].CurrentMember})' " + "set [*BASE_MEMBERS_Customers] as '[Customers].[Name].Members' " + "set [*BASE_MEMBERS_Product] as '[Product].[Product Name].Members' " + "set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}' " + "set [*CJ_COL_AXIS] as 'Generate([*METRIC_CJ_SET], {[Education Level].CurrentMember})' " + "set [*CJ_ROW_AXIS] as 'Generate([*METRIC_CJ_SET], {([Product].CurrentMember, [Customers].CurrentMember)})' " + "member [Customers].[*DEFAULT_MEMBER] as '[Customers].DefaultMember', SOLVE_ORDER = (- 500.0) " + "member [Product].[*TOTAL_MEMBER_SEL~SUM] as 'Sum(Generate([*METRIC_CJ_SET], {([Product].CurrentMember, [Customers].CurrentMember)}))', SOLVE_ORDER = (- 100.0) " + "member [Customers].[*TOTAL_MEMBER_SEL~SUM] as 'Sum(Generate(Exists([*METRIC_CJ_SET], {[Product].CurrentMember}), {([Product].CurrentMember, [Customers].CurrentMember)}))', SOLVE_ORDER = (- 101.0) " + "member [Measures].[*TOP_Unit Sales_SEL~SUM] as 'Rank([Product].CurrentMember, [*TOP_SET])', SOLVE_ORDER = 300.0 " + "member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Unit Sales]', FORMAT_STRING = \"Standard\", SOLVE_ORDER = 400.0 " + "member [Customers].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Customers].[All Customers]})', SOLVE_ORDER = (- 101.0) " + "member [Education Level].[*TOTAL_MEMBER_SEL~SUM] as 'Sum(Generate([*METRIC_CJ_SET], {[Education Level].CurrentMember}))', SOLVE_ORDER = (- 102.0) " + "member [Education Level].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Education Level].[All Education Levels]})', SOLVE_ORDER = (- 102.0) " + "member [Time].[Time].[*CTX_MEMBER_SEL~AGG] as 'Aggregate([*NATIVE_MEMBERS_Time])', SOLVE_ORDER = (- 402.0) " + "member [Time].[Time].[*SLICER_MEMBER] as 'Aggregate([*METRIC_MEMBERS_Time])', SOLVE_ORDER = (- 400.0) " + "select Union(Crossjoin({[Education Level].[*TOTAL_MEMBER_SEL~SUM]}, [*BASE_MEMBERS_Measures]), Crossjoin([*SORTED_COL_AXIS], [*BASE_MEMBERS_Measures])) ON COLUMNS, " + "NON EMPTY Union(Crossjoin({[Product].[*TOTAL_MEMBER_SEL~SUM]}, {[Customers].[*DEFAULT_MEMBER]}), Union(Crossjoin(Generate([*METRIC_CJ_SET], {[Product].CurrentMember}), {[Customers].[*TOTAL_MEMBER_SEL~SUM]}), [*SORTED_ROW_AXIS])) ON ROWS " + "from [Sales] " + "where [Time].[*SLICER_MEMBER] ", "Axis #0:\n" + "{[Time].[*SLICER_MEMBER]}\n" + "Axis #1:\n" + "{[Education Level].[*TOTAL_MEMBER_SEL~SUM], [Measures].[*FORMATTED_MEASURE_0]}\n" + "{[Education Level].[Bachelors Degree], [Measures].[*FORMATTED_MEASURE_0]}\n" + "{[Education Level].[Graduate Degree], [Measures].[*FORMATTED_MEASURE_0]}\n" + "{[Education Level].[High School Degree], [Measures].[*FORMATTED_MEASURE_0]}\n" + "{[Education Level].[Partial College], [Measures].[*FORMATTED_MEASURE_0]}\n" + "{[Education Level].[Partial High School], [Measures].[*FORMATTED_MEASURE_0]}\n" + "Axis #2:\n" + "{[Product].[*TOTAL_MEMBER_SEL~SUM], [Customers].[*DEFAULT_MEMBER]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[*TOTAL_MEMBER_SEL~SUM]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[*TOTAL_MEMBER_SEL~SUM]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[WA].[Puyallup].[Cheryl Herring]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[OR].[Salem].[Robert Ahlering]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[WA].[Port Orchard].[Judy Zugelder]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[WA].[Marysville].[Brian Johnston]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[OR].[Corvallis].[Judy Doolittle]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[WA].[Spokane].[Greg Morgan]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[CA].[West Covina].[Sandra Young]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[CA].[Long Beach].[Dana Chappell]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[CA].[La Mesa].[Georgia Thompson]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[WA].[Tacoma].[Jessica Dugan]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[OR].[Milwaukie].[Adrian Torrez]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[WA].[Spokane].[Grace McLaughlin]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[WA].[Bremerton].[Julia Stewart]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[WA].[Port Orchard].[Maureen Overholser]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[WA].[Yakima].[Mary Craig]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[CA].[Spring Valley].[Deborah Adams]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[CA].[Woodland Hills].[Warren Kaufman]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[OR].[Woodburn].[David Moss]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[CA].[Newport Beach].[Michael Sample]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[OR].[Portland].[Ofelia Trembath]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[WA].[Bremerton].[Alexander Case]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[WA].[Bremerton].[Gloria Duncan]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[WA].[Olympia].[Jeanette Foster]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper], [Customers].[USA].[CA].[Lakewood].[Shyla Bettis]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[OR].[Portland].[Tomas Manzanares]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Bremerton].[Kerry Westgaard]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Yakima].[Beatrice Barney]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Seattle].[James La Monica]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Spokane].[Martha Griego]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Bremerton].[Michelle Neri]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Spokane].[Herman Webb]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Spokane].[Bob Alexander]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Issaquah].[Gery Scott]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Spokane].[Grace McLaughlin]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Kirkland].[Brandon Rohlke]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Port Orchard].[Elwood Carter]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[CA].[Beverly Hills].[Samuel Arden]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[OR].[Woodburn].[Ida Cezar]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Olympia].[Barbara Smith]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Spokane].[Matt Bellah]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Sedro Woolley].[William Akin]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[OR].[Albany].[Karie Taylor]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[OR].[Milwaukie].[Bertie Wherrett]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[CA].[Lincoln Acres].[L. Troy Barnes]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Tacoma].[Patricia Martin]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Bremerton].[Martha Clifton]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic], [Customers].[USA].[WA].[Bremerton].[Marla Bell]}\n" + "Row #0: 170\n" + "Row #0: 45\n" + "Row #0: 7\n" + "Row #0: 47\n" + "Row #0: 16\n" + "Row #0: 55\n" + "Row #1: 87\n" + "Row #1: 25\n" + "Row #1: 5\n" + "Row #1: 21\n" + "Row #1: 8\n" + "Row #1: 28\n" + "Row #2: 83\n" + "Row #2: 20\n" + "Row #2: 2\n" + "Row #2: 26\n" + "Row #2: 8\n" + "Row #2: 27\n" + "Row #3: 4\n" + "Row #3: \n" + "Row #3: \n" + "Row #3: \n" + "Row #3: 4\n" + "Row #3: \n" + "Row #4: 4\n" + "Row #4: \n" + "Row #4: \n" + "Row #4: \n" + "Row #4: 4\n" + "Row #4: \n" + "Row #5: 3\n" + "Row #5: 3\n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #6: 4\n" + "Row #6: 4\n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #7: 4\n" + "Row #7: \n" + "Row #7: \n" + "Row #7: \n" + "Row #7: \n" + "Row #7: 4\n" + "Row #8: 4\n" + "Row #8: 4\n" + "Row #8: \n" + "Row #8: \n" + "Row #8: \n" + "Row #8: \n" + "Row #9: 3\n" + "Row #9: \n" + "Row #9: \n" + "Row #9: \n" + "Row #9: \n" + "Row #9: 3\n" + "Row #10: 2\n" + "Row #10: 2\n" + "Row #10: \n" + "Row #10: \n" + "Row #10: \n" + "Row #10: \n" + "Row #11: 3\n" + "Row #11: \n" + "Row #11: \n" + "Row #11: \n" + "Row #11: \n" + "Row #11: 3\n" + "Row #12: 3\n" + "Row #12: \n" + "Row #12: \n" + "Row #12: 3\n" + "Row #12: \n" + "Row #12: \n" + "Row #13: 4\n" + "Row #13: 4\n" + "Row #13: \n" + "Row #13: \n" + "Row #13: \n" + "Row #13: \n" + "Row #14: 4\n" + "Row #14: \n" + "Row #14: \n" + "Row #14: 4\n" + "Row #14: \n" + "Row #14: \n" + "Row #15: 3\n" + "Row #15: \n" + "Row #15: \n" + "Row #15: \n" + "Row #15: \n" + "Row #15: 3\n" + "Row #16: 4\n" + "Row #16: \n" + "Row #16: \n" + "Row #16: 4\n" + "Row #16: \n" + "Row #16: \n" + "Row #17: 5\n" + "Row #17: \n" + "Row #17: 5\n" + "Row #17: \n" + "Row #17: \n" + "Row #17: \n" + "Row #18: 4\n" + "Row #18: \n" + "Row #18: \n" + "Row #18: \n" + "Row #18: \n" + "Row #18: 4\n" + "Row #19: 3\n" + "Row #19: \n" + "Row #19: \n" + "Row #19: 3\n" + "Row #19: \n" + "Row #19: \n" + "Row #20: 3\n" + "Row #20: \n" + "Row #20: \n" + "Row #20: 3\n" + "Row #20: \n" + "Row #20: \n" + "Row #21: 4\n" + "Row #21: \n" + "Row #21: \n" + "Row #21: 4\n" + "Row #21: \n" + "Row #21: \n" + "Row #22: 4\n" + "Row #22: 4\n" + "Row #22: \n" + "Row #22: \n" + "Row #22: \n" + "Row #22: \n" + "Row #23: 4\n" + "Row #23: \n" + "Row #23: \n" + "Row #23: \n" + "Row #23: \n" + "Row #23: 4\n" + "Row #24: 4\n" + "Row #24: \n" + "Row #24: \n" + "Row #24: \n" + "Row #24: \n" + "Row #24: 4\n" + "Row #25: 3\n" + "Row #25: \n" + "Row #25: \n" + "Row #25: \n" + "Row #25: \n" + "Row #25: 3\n" + "Row #26: 4\n" + "Row #26: 4\n" + "Row #26: \n" + "Row #26: \n" + "Row #26: \n" + "Row #26: \n" + "Row #27: 4\n" + "Row #27: 4\n" + "Row #27: \n" + "Row #27: \n" + "Row #27: \n" + "Row #27: \n" + "Row #28: 4\n" + "Row #28: 4\n" + "Row #28: \n" + "Row #28: \n" + "Row #28: \n" + "Row #28: \n" + "Row #29: 3\n" + "Row #29: \n" + "Row #29: \n" + "Row #29: 3\n" + "Row #29: \n" + "Row #29: \n" + "Row #30: 2\n" + "Row #30: \n" + "Row #30: \n" + "Row #30: \n" + "Row #30: \n" + "Row #30: 2\n" + "Row #31: 4\n" + "Row #31: \n" + "Row #31: \n" + "Row #31: \n" + "Row #31: 4\n" + "Row #31: \n" + "Row #32: 5\n" + "Row #32: \n" + "Row #32: \n" + "Row #32: 5\n" + "Row #32: \n" + "Row #32: \n" + "Row #33: 3\n" + "Row #33: \n" + "Row #33: \n" + "Row #33: \n" + "Row #33: \n" + "Row #33: 3\n" + "Row #34: 4\n" + "Row #34: 4\n" + "Row #34: \n" + "Row #34: \n" + "Row #34: \n" + "Row #34: \n" + "Row #35: 3\n" + "Row #35: 3\n" + "Row #35: \n" + "Row #35: \n" + "Row #35: \n" + "Row #35: \n" + "Row #36: 4\n" + "Row #36: \n" + "Row #36: \n" + "Row #36: 4\n" + "Row #36: \n" + "Row #36: \n" + "Row #37: 4\n" + "Row #37: \n" + "Row #37: \n" + "Row #37: 4\n" + "Row #37: \n" + "Row #37: \n" + "Row #38: 3\n" + "Row #38: \n" + "Row #38: \n" + "Row #38: 3\n" + "Row #38: \n" + "Row #38: \n" + "Row #39: 3\n" + "Row #39: 3\n" + "Row #39: \n" + "Row #39: \n" + "Row #39: \n" + "Row #39: \n" + "Row #40: 2\n" + "Row #40: \n" + "Row #40: 2\n" + "Row #40: \n" + "Row #40: \n" + "Row #40: \n" + "Row #41: 4\n" + "Row #41: \n" + "Row #41: \n" + "Row #41: \n" + "Row #41: 4\n" + "Row #41: \n" + "Row #42: 4\n" + "Row #42: \n" + "Row #42: \n" + "Row #42: \n" + "Row #42: \n" + "Row #42: 4\n" + "Row #43: 2\n" + "Row #43: 2\n" + "Row #43: \n" + "Row #43: \n" + "Row #43: \n" + "Row #43: \n" + "Row #44: 3\n" + "Row #44: \n" + "Row #44: \n" + "Row #44: 3\n" + "Row #44: \n" + "Row #44: \n" + "Row #45: 4\n" + "Row #45: \n" + "Row #45: \n" + "Row #45: 4\n" + "Row #45: \n" + "Row #45: \n" + "Row #46: 4\n" + "Row #46: \n" + "Row #46: \n" + "Row #46: \n" + "Row #46: \n" + "Row #46: 4\n" + "Row #47: 3\n" + "Row #47: \n" + "Row #47: \n" + "Row #47: \n" + "Row #47: \n" + "Row #47: 3\n" + "Row #48: 4\n" + "Row #48: \n" + "Row #48: \n" + "Row #48: \n" + "Row #48: \n" + "Row #48: 4\n" + "Row #49: 7\n" + "Row #49: \n" + "Row #49: \n" + "Row #49: \n" + "Row #49: \n" + "Row #49: 7\n"); } public void testBug1961163() throws Exception { assertQueryReturns( "with member [Measures].[AvgRevenue] as 'Avg([Store].[Store Name].Members, [Measures].[Store Sales])' " + "select NON EMPTY {[Measures].[Store Sales], [Measures].[AvgRevenue]} ON COLUMNS, " + "NON EMPTY Filter([Store].[Store Name].Members, ([Measures].[AvgRevenue] < [Measures].[Store Sales])) ON ROWS " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[AvgRevenue]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "Row #0: 45,750.24\n" + "Row #0: 43,479.86\n" + "Row #1: 54,545.28\n" + "Row #1: 43,479.86\n" + "Row #2: 54,431.14\n" + "Row #2: 43,479.86\n" + "Row #3: 55,058.79\n" + "Row #3: 43,479.86\n" + "Row #4: 87,218.28\n" + "Row #4: 43,479.86\n" + "Row #5: 52,896.30\n" + "Row #5: 43,479.86\n" + "Row #6: 52,644.07\n" + "Row #6: 43,479.86\n" + "Row #7: 49,634.46\n" + "Row #7: 43,479.86\n" + "Row #8: 74,843.96\n" + "Row #8: 43,479.86\n"); } public void testTopCountWithCalcMemberInSlicer() { // Internal error: can not restrict SQL to calculated Members TestContext ctx = getTestContext(); ctx.assertQueryReturns( "with member [Time].[Time].[First Term] as 'Aggregate({[Time].[1997].[Q1], [Time].[1997].[Q2]})' " + "select {[Measures].[Unit Sales]} ON COLUMNS, " + "TopCount([Product].[Product Subcategory].Members, 3, [Measures].[Unit Sales]) ON ROWS " + "from [Sales] " + "where ([Time].[First Term]) ", "Axis #0:\n" + "{[Time].[First Term]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables]}\n" + "{[Product].[Food].[Produce].[Fruit].[Fresh Fruit]}\n" + "{[Product].[Food].[Canned Foods].[Canned Soup].[Soup]}\n" + "Row #0: 10,215\n" + "Row #1: 5,711\n" + "Row #2: 3,926\n"); } public void testTopCountCacheKeyMustIncludeCount() { /** * When caching topcount results, the number of elements must * be part of the cache key */ TestContext ctx = getTestContext(); // fill cache ctx.assertQueryReturns( "select {[Measures].[Unit Sales]} ON COLUMNS, " + "TopCount([Product].[Product Subcategory].Members, 2, [Measures].[Unit Sales]) ON ROWS " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables]}\n" + "{[Product].[Food].[Produce].[Fruit].[Fresh Fruit]}\n" + "Row #0: 20,739\n" + "Row #1: 11,767\n"); // run again with different count ctx.assertQueryReturns( "select {[Measures].[Unit Sales]} ON COLUMNS, " + "TopCount([Product].[Product Subcategory].Members, 3, [Measures].[Unit Sales]) ON ROWS " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables]}\n" + "{[Product].[Food].[Produce].[Fruit].[Fresh Fruit]}\n" + "{[Product].[Food].[Canned Foods].[Canned Soup].[Soup]}\n" + "Row #0: 20,739\n" + "Row #1: 11,767\n" + "Row #2: 8,006\n"); } public void testStrMeasure() { TestContext ctx = TestContext.instance().create( null, " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n", null, null, null, null); ctx.assertQueryReturns( "select {[Measures].[Media]} on columns " + "from [StrMeasure]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Media]}\n" + "Row #0: TV\n"); } public void testBug1515302() { TestContext ctx = TestContext.instance().create( null, " \n" + "
\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n", null, null, null, null); ctx.assertQueryReturns( "select {[Measures].[Unit Sales]} on columns, " + "non empty crossjoin({[Promotions].[Big Promo]}, " + "Descendants([Customers].[USA], [City], " + "SELF_AND_BEFORE)) on rows " + "from [Bug1515302]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Promotions].[Big Promo], [Customers].[USA]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA].[Anacortes]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA].[Ballard]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA].[Bellingham]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA].[Burien]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA].[Everett]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA].[Issaquah]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA].[Kirkland]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA].[Lynnwood]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA].[Marysville]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA].[Olympia]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA].[Puyallup]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA].[Redmond]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA].[Renton]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA].[Seattle]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA].[Sedro Woolley]}\n" + "{[Promotions].[Big Promo], [Customers].[USA].[WA].[Tacoma]}\n" + "Row #0: 1,789\n" + "Row #1: 1,789\n" + "Row #2: 20\n" + "Row #3: 35\n" + "Row #4: 15\n" + "Row #5: 18\n" + "Row #6: 60\n" + "Row #7: 42\n" + "Row #8: 36\n" + "Row #9: 79\n" + "Row #10: 58\n" + "Row #11: 520\n" + "Row #12: 438\n" + "Row #13: 14\n" + "Row #14: 20\n" + "Row #15: 65\n" + "Row #16: 3\n" + "Row #17: 366\n"); } /** * Must not use native sql optimization because it chooses the wrong * RolapStar in SqlContextConstraint/SqlConstraintUtils. Test ensures that * no exception is thrown. */ public void testVirtualCube() { if (MondrianProperties.instance().TestExpDependencies.get() > 0) { return; } TestCase c = new TestCase( 99, 3, "select NON EMPTY {[Measures].[Unit Sales], [Measures].[Warehouse Sales]} ON COLUMNS, " + "NON EMPTY [Product].[All Products].Children ON ROWS " + "from [Warehouse and Sales]"); c.run(); } public void testVirtualCubeMembers() throws Exception { if (MondrianProperties.instance().TestExpDependencies.get() > 0) { return; } // ok to use native sql optimization for members on a virtual cube TestCase c = new TestCase( 6, 3, "select NON EMPTY {[Measures].[Unit Sales], [Measures].[Warehouse Sales]} ON COLUMNS, " + "NON EMPTY {[Product].[Product Family].Members} ON ROWS " + "from [Warehouse and Sales]"); c.run(); } /** * verifies that redundant set braces do not prevent native evaluation * for example, {[Store].[Store Name].members} and * {{[Store Type].[Store Type].members}} */ public void testNativeCJWithRedundantSetBraces() { propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; checkNative( 0, 20, "select non empty {CrossJoin({[Store].[Store Name].members}, " + " {{" + STORE_TYPE_LEVEL + ".members}})}" + " on rows, " + "{[Measures].[Store Sqft]} on columns " + "from [Store]", null, requestFreshConnection); } /** * Verifies that CrossJoins with two non native inputs can be natively * evaluated. */ public void testExpandAllNonNativeInputs() { // This query will not run natively unless the .Children // expression is expanded to a member list. // // Note: Both dimensions only have one hierarchy, which has the All // member. .Children is interpreted as the children of // the All member. propSaver.set(MondrianProperties.instance().ExpandNonNative, true); propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; checkNative( 0, 2, "select " + "NonEmptyCrossJoin([Gender].Children, [Store].Children) on columns " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[F], [Store].[USA]}\n" + "{[Gender].[M], [Store].[USA]}\n" + "Row #0: 131,558\n" + "Row #0: 135,215\n", requestFreshConnection); } /** * Verifies that CrossJoins with one non native inputs can be natively * evaluated. */ public void testExpandOneNonNativeInput() { // This query will not be evaluated natively unless the Filter // expression is expanded to a member list. propSaver.set(MondrianProperties.instance().ExpandNonNative, true); propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; checkNative( 0, 1, "With " + "Set [*Filtered_Set] as Filter([Product].[Product Name].Members, [Product].CurrentMember IS [Product].[Product Name].[Fast Raisins]) " + "Set [*NECJ_Set] as NonEmptyCrossJoin([Store].[Store Country].Members, [*Filtered_Set]) " + "select [*NECJ_Set] on columns " + "From [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA], [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fast].[Fast Raisins]}\n" + "Row #0: 152\n", requestFreshConnection); } /** * Check that the ExpandNonNative does not create Joins with input lists * containing large number of members. */ public void testExpandNonNativeResourceLimitFailure() { propSaver.set(MondrianProperties.instance().ExpandNonNative, true); propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, true); propSaver.set(MondrianProperties.instance().ResultLimit, 2); try { executeQuery( "select " + "NonEmptyCrossJoin({[Gender].Children, [Gender].[F]}, {[Store].Children, [Store].[Mexico]}) on columns " + "from [Sales]"); fail("Expected error did not occur"); } catch (Throwable e) { String expectedErrorMsg = "Mondrian Error:Size of CrossJoin result (3) exceeded limit (2)"; assertEquals(expectedErrorMsg, e.getMessage()); } } /** * Verify that the presence of All member in all the inputs disables native * evaluation, even when ExpandNonNative is true. */ public void testExpandAllMembersInAllInputs() { // This query will not be evaluated natively, even if the Hierarchize // expression is expanded to a member list. The reason is that the // expanded list contains ALL members. propSaver.set(MondrianProperties.instance().ExpandNonNative, true); propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, true); checkNotNative( 1, "select NON EMPTY {[Time].[1997]} ON COLUMNS,\n" + " NON EMPTY Crossjoin(Hierarchize(Union({[Store].[All Stores]},\n" + " [Store].[USA].[CA].[San Francisco].[Store 14].Children)), {[Product].[All Products]}) \n" + " ON ROWS\n" + " from [Sales]\n" + " where [Measures].[Unit Sales]", "Axis #0:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "Axis #2:\n" + "{[Store].[All Stores], [Product].[All Products]}\n" + "Row #0: 266,773\n"); } /** * Verifies that the presence of calculated member in all the inputs * disables native evaluation, even when ExpandNonNative is true. */ public void testExpandCalcMembersInAllInputs() { // This query will not be evaluated natively, even if the Hierarchize // expression is expanded to a member list. The reason is that the // expanded list contains ALL members. propSaver.set(MondrianProperties.instance().ExpandNonNative, true); propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, true); checkNotNative( 1, "With " + "Member [Product].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[Product Family].Members})' " + "Member [Gender].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Gender].[All Gender]})' " + "Select " + "NonEmptyCrossJoin({[Gender].[*CTX_MEMBER_SEL~SUM]},{[Product].[*CTX_MEMBER_SEL~SUM]}) " + "on columns " + "From [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[*CTX_MEMBER_SEL~SUM], [Product].[*CTX_MEMBER_SEL~SUM]}\n" + "Row #0: 266,773\n"); } /** * Check that if both inputs to NECJ are either * AllMember(currentMember, defaultMember are also AllMember) * or Calcculated member * native CJ is not used. */ public void testExpandCalcMemberInputNECJ() { propSaver.set(MondrianProperties.instance().ExpandNonNative, true); checkNotNative( 1, "With \n" + "Member [Product].[All Products].[Food].[CalcSum] as \n" + "'Sum({[Product].[All Products].[Food]})', SOLVE_ORDER=-100\n" + "Select\n" + "{[Measures].[Store Cost]} on columns,\n" + "NonEmptyCrossJoin({[Product].[All Products].[Food].[CalcSum]},\n" + " {[Education Level].DefaultMember}) on rows\n" + "From [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Cost]}\n" + "Axis #2:\n" + "{[Product].[Food].[CalcSum], [Education Level].[All Education Levels]}\n" + "Row #0: 163,270.72\n"); } /** * Native evaluation is no longer possible after the fix to * {@link #testCjEnumCalcMembersBug()} test. */ public void testExpandCalcMembers() { propSaver.set(MondrianProperties.instance().ExpandNonNative, true); checkNotNative( 9, "with " + "member [Store Type].[All Store Types].[S] as sum({[Store Type].[All Store Types]}) " + "set [Enum Store Types] as {" + " [Store Type].[All Store Types].[Small Grocery], " + " [Store Type].[All Store Types].[Supermarket], " + " [Store Type].[All Store Types].[HeadQuarters], " + " [Store Type].[All Store Types].[S]} " + "set [Filtered Enum Store Types] as Filter([Enum Store Types], [Measures].[Unit Sales] > 0)" + "select NonEmptyCrossJoin([Product].[All Products].Children, [Filtered Enum Store Types]) on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Drink], [Store Type].[Small Grocery]}\n" + "{[Product].[Drink], [Store Type].[Supermarket]}\n" + "{[Product].[Drink], [Store Type].[All Store Types].[S]}\n" + "{[Product].[Food], [Store Type].[Small Grocery]}\n" + "{[Product].[Food], [Store Type].[Supermarket]}\n" + "{[Product].[Food], [Store Type].[All Store Types].[S]}\n" + "{[Product].[Non-Consumable], [Store Type].[Small Grocery]}\n" + "{[Product].[Non-Consumable], [Store Type].[Supermarket]}\n" + "{[Product].[Non-Consumable], [Store Type].[All Store Types].[S]}\n" + "Row #0: 574\n" + "Row #0: 14,092\n" + "Row #0: 24,597\n" + "Row #0: 4,764\n" + "Row #0: 108,188\n" + "Row #0: 191,940\n" + "Row #0: 1,219\n" + "Row #0: 28,275\n" + "Row #0: 50,236\n"); } /** * Verify that evaluation is native for expressions with nested non native * inputs that preduce MemberList results. */ public void testExpandNestedNonNativeInputs() { propSaver.set(MondrianProperties.instance().ExpandNonNative, true); propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; checkNative( 0, 6, "select " + "NonEmptyCrossJoin(" + " NonEmptyCrossJoin([Gender].Children, [Store].Children), " + " [Product].Children) on columns " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[F], [Store].[USA], [Product].[Drink]}\n" + "{[Gender].[F], [Store].[USA], [Product].[Food]}\n" + "{[Gender].[F], [Store].[USA], [Product].[Non-Consumable]}\n" + "{[Gender].[M], [Store].[USA], [Product].[Drink]}\n" + "{[Gender].[M], [Store].[USA], [Product].[Food]}\n" + "{[Gender].[M], [Store].[USA], [Product].[Non-Consumable]}\n" + "Row #0: 12,202\n" + "Row #0: 94,814\n" + "Row #0: 24,542\n" + "Row #0: 12,395\n" + "Row #0: 97,126\n" + "Row #0: 25,694\n", requestFreshConnection); } /** * Verify that a low value for maxConstraints disables native evaluation, * even when ExpandNonNative is true. */ public void testExpandLowMaxConstraints() { propSaver.set(MondrianProperties.instance().MaxConstraints, 2); propSaver.set(MondrianProperties.instance().ExpandNonNative, true); checkNotNative( 12, "select NonEmptyCrossJoin(" + " Filter([Store Type].Children, [Measures].[Unit Sales] > 10000), " + " [Product].Children) on columns " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store Type].[Deluxe Supermarket], [Product].[Drink]}\n" + "{[Store Type].[Deluxe Supermarket], [Product].[Food]}\n" + "{[Store Type].[Deluxe Supermarket], [Product].[Non-Consumable]}\n" + "{[Store Type].[Gourmet Supermarket], [Product].[Drink]}\n" + "{[Store Type].[Gourmet Supermarket], [Product].[Food]}\n" + "{[Store Type].[Gourmet Supermarket], [Product].[Non-Consumable]}\n" + "{[Store Type].[Mid-Size Grocery], [Product].[Drink]}\n" + "{[Store Type].[Mid-Size Grocery], [Product].[Food]}\n" + "{[Store Type].[Mid-Size Grocery], [Product].[Non-Consumable]}\n" + "{[Store Type].[Supermarket], [Product].[Drink]}\n" + "{[Store Type].[Supermarket], [Product].[Food]}\n" + "{[Store Type].[Supermarket], [Product].[Non-Consumable]}\n" + "Row #0: 6,827\n" + "Row #0: 55,358\n" + "Row #0: 14,652\n" + "Row #0: 1,945\n" + "Row #0: 15,438\n" + "Row #0: 3,950\n" + "Row #0: 1,159\n" + "Row #0: 8,192\n" + "Row #0: 2,140\n" + "Row #0: 14,092\n" + "Row #0: 108,188\n" + "Row #0: 28,275\n"); } /** * Verify that native evaluation is not enabled if expanded member list will * contain members from different levels, even if ExpandNonNative is set. */ public void testExpandDifferentLevels() { propSaver.set(MondrianProperties.instance().ExpandNonNative, true); checkNotNative( 278, "select NonEmptyCrossJoin(" + " Descendants([Customers].[All Customers].[USA].[WA].[Yakima]), " + " [Product].Children) on columns " + "from [Sales]", null); } /** * Verify that native evaluation is turned off for tuple inputs, even if * ExpandNonNative is set. */ public void testExpandTupleInputs1() { propSaver.set(MondrianProperties.instance().ExpandNonNative, true); checkNotNative( 1, "with " + "set [Tuple Set] as {([Store Type].[All Store Types].[HeadQuarters], [Product].[All Products].[Drink]), ([Store Type].[All Store Types].[Supermarket], [Product].[All Products].[Food])} " + "set [Filtered Tuple Set] as Filter([Tuple Set], 1=1) " + "set [NECJ] as NonEmptyCrossJoin([Store].Children, [Filtered Tuple Set]) " + "select [NECJ] on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA], [Store Type].[Supermarket], [Product].[Food]}\n" + "Row #0: 108,188\n"); } /** * Verify that native evaluation is turned off for tuple inputs, even if * ExpandNonNative is set. */ public void testExpandTupleInputs2() { propSaver.set(MondrianProperties.instance().ExpandNonNative, true); checkNotNative( 1, "with " + "set [Tuple Set] as {([Store Type].[All Store Types].[HeadQuarters], [Product].[All Products].[Drink]), ([Store Type].[All Store Types].[Supermarket], [Product].[All Products].[Food])} " + "set [Filtered Tuple Set] as Filter([Tuple Set], 1=1) " + "set [NECJ] as NonEmptyCrossJoin([Filtered Tuple Set], [Store].Children) " + "select [NECJ] on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store Type].[Supermarket], [Product].[Food], [Store].[USA]}\n" + "Row #0: 108,188\n"); } /** * Verify that native evaluation is on when ExpendNonNative is set, even if * the input list is empty. */ public void testExpandWithOneEmptyInput() { propSaver.set(MondrianProperties.instance().ExpandNonNative, true); boolean requestFreshConnection = true; // Query should return empty result. checkNative( 0, 0, "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Gender],[*BASE_MEMBERS_Product])' " + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}' " + "Set [*BASE_MEMBERS_Gender] as 'Filter([Gender].[Gender].Members,[Gender].CurrentMember.Name Matches (\"abc\"))' " + "Set [*NATIVE_MEMBERS_Gender] as 'Generate([*NATIVE_CJ_SET], {[Gender].CurrentMember})' " + "Set [*BASE_MEMBERS_Product] as '[Product].[Product Name].Members' " + "Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' " + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Unit Sales]', FORMAT_STRING = '#,##0', SOLVE_ORDER=400 " + "Select " + "[*BASE_MEMBERS_Measures] on columns, " + "Non Empty Generate([*NATIVE_CJ_SET], {([Gender].CurrentMember,[Product].CurrentMember)}) on rows " + "From [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*FORMATTED_MEASURE_0]}\n" + "Axis #2:\n", requestFreshConnection); } public void testExpandWithTwoEmptyInputs() { getConnection().getCacheControl(null).flushSchemaCache(); propSaver.set(MondrianProperties.instance().ExpandNonNative, true); // Query should return empty result. checkNotNative( 0, "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Gender],[*BASE_MEMBERS_Product])' " + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}' " + "Set [*BASE_MEMBERS_Gender] as '{}' " + "Set [*NATIVE_MEMBERS_Gender] as 'Generate([*NATIVE_CJ_SET], {[Gender].CurrentMember})' " + "Set [*BASE_MEMBERS_Product] as '{}' " + "Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' " + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Unit Sales]', FORMAT_STRING = '#,##0', SOLVE_ORDER=400 " + "Select " + "[*BASE_MEMBERS_Measures] on columns, " + "Non Empty Generate([*NATIVE_CJ_SET], {([Gender].CurrentMember,[Product].CurrentMember)}) on rows " + "From [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*FORMATTED_MEASURE_0]}\n" + "Axis #2:\n"); } /** * Verify that native MemberLists inputs are subject to SQL constriant * limitation. If mondrian.rolap.maxConstraints is set too low, native * evaluations will be turned off. */ public void testEnumLowMaxConstraints() { propSaver.set(MondrianProperties.instance().MaxConstraints, 2); checkNotNative( 12, "with " + "set [All Store Types] as {" + "[Store Type].[Deluxe Supermarket], " + "[Store Type].[Gourmet Supermarket], " + "[Store Type].[Mid-Size Grocery], " + "[Store Type].[Small Grocery], " + "[Store Type].[Supermarket]} " + "set [All Products] as {" + "[Product].[Drink], " + "[Product].[Food], " + "[Product].[Non-Consumable]} " + "select " + "NonEmptyCrossJoin(" + "Filter([All Store Types], ([Measures].[Unit Sales] > 10000)), " + "[All Products]) on columns " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store Type].[Deluxe Supermarket], [Product].[Drink]}\n" + "{[Store Type].[Deluxe Supermarket], [Product].[Food]}\n" + "{[Store Type].[Deluxe Supermarket], [Product].[Non-Consumable]}\n" + "{[Store Type].[Gourmet Supermarket], [Product].[Drink]}\n" + "{[Store Type].[Gourmet Supermarket], [Product].[Food]}\n" + "{[Store Type].[Gourmet Supermarket], [Product].[Non-Consumable]}\n" + "{[Store Type].[Mid-Size Grocery], [Product].[Drink]}\n" + "{[Store Type].[Mid-Size Grocery], [Product].[Food]}\n" + "{[Store Type].[Mid-Size Grocery], [Product].[Non-Consumable]}\n" + "{[Store Type].[Supermarket], [Product].[Drink]}\n" + "{[Store Type].[Supermarket], [Product].[Food]}\n" + "{[Store Type].[Supermarket], [Product].[Non-Consumable]}\n" + "Row #0: 6,827\n" + "Row #0: 55,358\n" + "Row #0: 14,652\n" + "Row #0: 1,945\n" + "Row #0: 15,438\n" + "Row #0: 3,950\n" + "Row #0: 1,159\n" + "Row #0: 8,192\n" + "Row #0: 2,140\n" + "Row #0: 14,092\n" + "Row #0: 108,188\n" + "Row #0: 28,275\n"); } /** * Verify that the presence of All member in all the inputs disables native * evaluation. */ public void testAllMembersNECJ1() { // This query cannot be evaluated natively because of the "All" member. propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, true); checkNotNative( 1, "select " + "NonEmptyCrossJoin({[Store].[All Stores]}, {[Product].[All Products]}) on columns " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[All Stores], [Product].[All Products]}\n" + "Row #0: 266,773\n"); } /** * Verify that the native evaluation is possible if one input does not * contain the All member. */ public void testAllMembersNECJ2() { // This query can be evaluated natively because there is at least one // non "All" member. // // It can also be rewritten to use // Filter([Product].Children, Is // NotEmpty([Measures].[Unit Sales])) // which can be natively evaluated propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; checkNative( 0, 3, "select " + "NonEmptyCrossJoin([Product].[All Products].Children, {[Store].[All Stores]}) on columns " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Drink], [Store].[All Stores]}\n" + "{[Product].[Food], [Store].[All Stores]}\n" + "{[Product].[Non-Consumable], [Store].[All Stores]}\n" + "Row #0: 24,597\n" + "Row #0: 191,940\n" + "Row #0: 50,236\n", requestFreshConnection); } /** * getMembersInLevel where Level = (All) */ public void testAllLevelMembers() { checkNative( 14, 14, "select {[Measures].[Store Sales]} ON COLUMNS, " + "NON EMPTY Crossjoin([Product].[(All)].Members, [Promotion Media].[All Media].Children) ON ROWS " + "from [Sales]"); } /** * enum sets {} containing ALL */ public void testCjDescendantsEnumAllOnly() { checkNative( 9, 9, "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NON EMPTY Crossjoin(" + " Descendants([Customers].[All Customers].[USA], [Customers].[City]), " + " {[Product].[All Products]}) ON ROWS " + "from [Sales] " + "where ([Promotions].[All Promotions].[Bag Stuffers])"); } /** * checks that crossjoin returns a modifiable copy from cache * because its modified during sort */ public void testResultIsModifyableCopy() { checkNative( 3, 3, "select {[Measures].[Store Sales]} on columns," + " NON EMPTY Order(" + " CrossJoin([Customers].[All Customers].[USA].children, [Promotions].[Promotion Name].Members), " + " [Measures].[Store Sales]) ON ROWS" + " from [Sales] where (" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"); } /** * Checks that TopCount is executed natively unless disabled. */ public void testNativeTopCount() { switch (getTestContext().getDialect().getDatabaseProduct()) { case INFOBRIGHT: // Hits same Infobright bug as NamedSetTest.testNamedSetOnMember. return; } String query = "select {[Measures].[Store Sales]} on columns," + " NON EMPTY TopCount(" + " CrossJoin([Customers].[All Customers].[USA].children, [Promotions].[Promotion Name].Members), " + " 3, (3 * [Measures].[Store Sales]) - 100) ON ROWS" + " from [Sales] where (" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"; propSaver.set(MondrianProperties.instance().EnableNativeTopCount, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; checkNative(3, 3, query, null, requestFreshConnection); } /** * Checks that TopCount is executed natively with calculated member. */ public void testCmNativeTopCount() { switch (getTestContext().getDialect().getDatabaseProduct()) { case INFOBRIGHT: // Hits same Infobright bug as NamedSetTest.testNamedSetOnMember. return; } String query = "with member [Measures].[Store Profit Rate] as '([Measures].[Store Sales]-[Measures].[Store Cost])/[Measures].[Store Cost]', format = '#.00%' " + "select {[Measures].[Store Sales]} on columns," + " NON EMPTY TopCount(" + " [Customers].[All Customers].[USA].children, " + " 3, [Measures].[Store Profit Rate] / 2) ON ROWS" + " from [Sales]"; propSaver.set(MondrianProperties.instance().EnableNativeTopCount, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. boolean requestFreshConnection = true; checkNative(3, 3, query, null, requestFreshConnection); } public void testMeasureAndAggregateInSlicer() { assertQueryReturns( "with member [Store Type].[All Store Types].[All Types] as 'Aggregate({[Store Type].[All Store Types].[Deluxe Supermarket], " + "[Store Type].[All Store Types].[Gourmet Supermarket], " + "[Store Type].[All Store Types].[HeadQuarters], " + "[Store Type].[All Store Types].[Mid-Size Grocery], " + "[Store Type].[All Store Types].[Small Grocery], " + "[Store Type].[All Store Types].[Supermarket]})' " + "select NON EMPTY {[Time].[1997]} ON COLUMNS, " + "NON EMPTY [Store].[All Stores].[USA].[CA].Children ON ROWS " + "from [Sales] " + "where ([Store Type].[All Store Types].[All Types], [Measures].[Unit Sales], [Customers].[All Customers].[USA], [Product].[All Products].[Drink]) ", "Axis #0:\n" + "{[Store Type].[All Store Types].[All Types], [Measures].[Unit Sales], [Customers].[USA], [Product].[Drink]}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "Row #0: 1,945\n" + "Row #1: 2,422\n" + "Row #2: 2,560\n" + "Row #3: 175\n"); } public void testMeasureInSlicer() { assertQueryReturns( "select NON EMPTY {[Time].[1997]} ON COLUMNS, " + "NON EMPTY [Store].[All Stores].[USA].[CA].Children ON ROWS " + "from [Sales] " + "where ([Measures].[Unit Sales], [Customers].[All Customers].[USA], [Product].[All Products].[Drink])", "Axis #0:\n" + "{[Measures].[Unit Sales], [Customers].[USA], [Product].[Drink]}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "Row #0: 1,945\n" + "Row #1: 2,422\n" + "Row #2: 2,560\n" + "Row #3: 175\n"); } /** * Calc Member in TopCount: this topcount can not be calculated native * because its set contains calculated members. */ public void testCmInTopCount() { checkNotNative( 1, "with member [Time].[Time].[Jan] as " + "'Aggregate({[Time].[1998].[Q1].[1], [Time].[1997].[Q1].[1]})' " + "select NON EMPTY {[Measures].[Unit Sales]} ON columns, " + "NON EMPTY TopCount({[Time].[Jan]}, 2) ON rows from [Sales] "); } /** * Calc member in slicer cannot be executed natively. */ public void testCmInSlicer() { checkNotNative( 3, "with member [Time].[Time].[Jan] as " + "'Aggregate({[Time].[1998].[Q1].[1], [Time].[1997].[Q1].[1]})' " + "select NON EMPTY {[Measures].[Unit Sales]} ON columns, " + "NON EMPTY [Product].Children ON rows from [Sales] " + "where ([Time].[Jan]) "); } public void testCjMembersMembersMembers() { checkNative( 67, 67, "select {[Measures].[Store Sales]} on columns," + " NON EMPTY Crossjoin(" + " Crossjoin(" + " [Customers].[Name].Members," + " [Product].[Product Name].Members), " + " [Promotions].[Promotion Name].Members) ON rows " + " from [Sales] where (" + " [Store].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"); } public void testCjMembersWithHideIfBlankLeafAndNoAll() { setTestContext(TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + "")); // No 'all' level, and ragged because [Product Name] is hidden if // blank. Native evaluation should be able to handle this query. checkNative( 9999, // Don't know why resultLimit needs to be so high. 67, "select {[Measures].[Store Sales]} on columns," + " NON EMPTY Crossjoin(" + " Crossjoin(" + " [Customers].[Name].Members," + " [Product Ragged].[Product Name].Members), " + " [Promotions].[Promotion Name].Members) ON rows " + " from [Sales] where (" + " [Store].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"); } public void testCjMembersWithHideIfBlankLeaf() { setTestContext(TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + "")); // [Product Name] can be hidden if it is blank, but native evaluation // should be able to handle the query. checkNative( 67, 67, "select {[Measures].[Store Sales]} on columns," + " NON EMPTY Crossjoin(" + " Crossjoin(" + " [Customers].[Name].Members," + " [Product Ragged].[Product Name].Members), " + " [Promotions].[Promotion Name].Members) ON rows " + " from [Sales] where (" + " [Store].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"); } public void testCjMembersWithHideIfParentsNameLeaf() { setTestContext(TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + "")); // [Product Name] can be hidden if it it matches its parent name, so // native evaluation can not handle this query. checkNotNative( 67, "select {[Measures].[Store Sales]} on columns," + " NON EMPTY Crossjoin(" + " Crossjoin(" + " [Customers].[Name].Members," + " [Product Ragged].[Product Name].Members), " + " [Promotions].[Promotion Name].Members) ON rows " + " from [Sales] where (" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"); } public void testCjMembersWithHideIfBlankNameAncestor() { setTestContext(TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + "")); // Since the parent of [Product Name] can be hidden, native evaluation // can't handle the query. checkNative( 67, 67, "select {[Measures].[Store Sales]} on columns," + " NON EMPTY Crossjoin(" + " Crossjoin(" + " [Customers].[Name].Members," + " [Product Ragged].[Product Name].Members), " + " [Promotions].[Promotion Name].Members) ON rows " + " from [Sales] where (" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"); } public void testCjMembersWithHideIfParentsNameAncestor() { setTestContext(TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + "")); // Since the parent of [Product Name] can be hidden, native evaluation // can't handle the query. checkNative( 67, 67, "select {[Measures].[Store Sales]} on columns," + " NON EMPTY Crossjoin(" + " Crossjoin(" + " [Customers].[Name].Members," + " [Product Ragged].[Product Name].Members), " + " [Promotions].[Promotion Name].Members) ON rows " + " from [Sales] where (" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"); } public void testCjEnumWithHideIfBlankLeaf() { setTestContext(TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + "")); // [Product Name] can be hidden if it is blank, but native evaluation // should be able to handle the query. // Note there's an existing bug with result ordering in native // non-empty evaluation of enumerations. This test intentionally // avoids this bug by explicitly lilsting [High Top Cauliflower] // before [Sphinx Bagels]. checkNative( 999, 7, "select {[Measures].[Store Sales]} on columns," + " NON EMPTY Crossjoin(" + " Crossjoin(" + " [Customers].[Name].Members," + " { [Product Ragged].[Kiwi].[Kiwi Scallops]," + " [Product Ragged].[Fast].[Fast Avocado Dip]," + " [Product Ragged].[High Top].[High Top Lemons]," + " [Product Ragged].[Moms].[Moms Sliced Turkey]," + " [Product Ragged].[High Top].[High Top Cauliflower]," + " [Product Ragged].[Sphinx].[Sphinx Bagels]" + " }), " + " [Promotions].[Promotion Name].Members) ON rows " + " from [Sales] where (" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"); } /** * use SQL even when all members are known */ public void testCjEnumEnum() { // Make sure maxConstraint settting is high enough int minConstraints = 2; if (MondrianProperties.instance().MaxConstraints.get() < minConstraints) { propSaver.set( MondrianProperties.instance().MaxConstraints, minConstraints); } checkNative( 4, 4, "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NonEmptyCrossjoin({[Product].[All Products].[Drink].[Beverages], [Product].[All Products].[Drink].[Dairy]}, {[Customers].[All Customers].[USA].[OR].[Portland], [Customers].[All Customers].[USA].[OR].[Salem]}) ON ROWS " + "from [Sales] "); } /** * Set containing only null member should not prevent usage of native. */ public void testCjNullInEnum() { propSaver.set( MondrianProperties.instance().IgnoreInvalidMembersDuringQuery, true); checkNative( 20, 0, "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NON EMPTY Crossjoin({[Gender].[All Gender].[emale]}, [Customers].[All Customers].[USA].children) ON ROWS " + "from [Sales] "); } /** * enum sets {} containing members from different levels can not be computed * natively currently. */ public void testCjDescendantsEnumAll() { checkNotNative( 13, "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NON EMPTY Crossjoin(" + " Descendants([Customers].[All Customers].[USA], [Customers].[City]), " + " {[Product].[All Products], [Product].[All Products].[Drink].[Dairy]}) ON ROWS " + "from [Sales] " + "where ([Promotions].[All Promotions].[Bag Stuffers])"); } public void testCjDescendantsEnum() { // Make sure maxConstraint settting is high enough int minConstraints = 2; if (MondrianProperties.instance().MaxConstraints.get() < minConstraints) { propSaver.set( MondrianProperties.instance().MaxConstraints, minConstraints); } checkNative( 11, 11, "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NON EMPTY Crossjoin(" + " Descendants([Customers].[All Customers].[USA], [Customers].[City]), " + " {[Product].[All Products].[Drink].[Beverages], [Product].[All Products].[Drink].[Dairy]}) ON ROWS " + "from [Sales] " + "where ([Promotions].[All Promotions].[Bag Stuffers])"); } public void testCjEnumChildren() { // Make sure maxConstraint settting is high enough // Make sure maxConstraint settting is high enough int minConstraints = 2; if (MondrianProperties.instance().MaxConstraints.get() < minConstraints) { propSaver.set( MondrianProperties.instance().MaxConstraints, minConstraints); } checkNative( 3, 3, "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NON EMPTY Crossjoin(" + " {[Product].[All Products].[Drink].[Beverages], [Product].[All Products].[Drink].[Dairy]}, " + " [Customers].[All Customers].[USA].[WA].Children) ON ROWS " + "from [Sales] " + "where ([Promotions].[All Promotions].[Bag Stuffers])"); } /** * {} contains members from different levels, this can not be handled by * the current native crossjoin. */ public void testCjEnumDifferentLevelsChildren() { // Don't run the test if we're testing expression dependencies. // Expression dependencies cause spurious interval calls to // 'level.getMembers()' which create false negatives in this test. if (MondrianProperties.instance().TestExpDependencies.get() > 0) { return; } TestCase c = new TestCase( 8, 5, "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NON EMPTY Crossjoin(" + " {[Product].[All Products].[Food], [Product].[All Products].[Drink].[Dairy]}, " + " [Customers].[All Customers].[USA].[WA].Children) ON ROWS " + "from [Sales] " + "where ([Promotions].[All Promotions].[Bag Stuffers])"); c.run(); } public void testCjDescendantsMembers() { checkNative( 67, 67, "select {[Measures].[Store Sales]} on columns," + " NON EMPTY Crossjoin(" + " Descendants([Customers].[All Customers].[USA].[CA], [Customers].[Name])," + " [Product].[Product Name].Members) ON rows " + " from [Sales] where (" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"); } public void testCjMembersDescendants() { checkNative( 67, 67, "select {[Measures].[Store Sales]} on columns," + " NON EMPTY Crossjoin(" + " [Product].[Product Name].Members," + " Descendants([Customers].[All Customers].[USA].[CA], [Customers].[Name])) ON rows " + " from [Sales] where (" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"); } // testcase for bug MONDRIAN-506 public void testCjMembersDescendantsWithNumericArgument() { checkNative( 67, 67, "select {[Measures].[Store Sales]} on columns," + " NON EMPTY Crossjoin(" + " {[Product].[Product Name].Members}," + " {Descendants([Customers].[All Customers].[USA].[CA], 2)}) ON rows " + " from [Sales] where (" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"); } public void testCjChildrenMembers() { checkNative( 67, 67, "select {[Measures].[Store Sales]} on columns," + " NON EMPTY Crossjoin([Customers].[All Customers].[USA].[CA].children," + " [Product].[Product Name].Members) ON rows " + " from [Sales] where (" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"); } public void testCjMembersChildren() { checkNative( 67, 67, "select {[Measures].[Store Sales]} on columns," + " NON EMPTY Crossjoin([Product].[Product Name].Members," + " [Customers].[All Customers].[USA].[CA].children) ON rows " + " from [Sales] where (" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"); } public void testCjMembersMembers() { checkNative( 67, 67, "select {[Measures].[Store Sales]} on columns," + " NON EMPTY Crossjoin([Customers].[Name].Members," + " [Product].[Product Name].Members) ON rows " + " from [Sales] where (" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"); } public void testCjChildrenChildren() { checkNative( 3, 3, "select {[Measures].[Store Sales]} on columns, " + " NON EMPTY Crossjoin(" + " [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].children, " + " [Customers].[All Customers].[USA].[CA].CHILDREN) ON rows" + " from [Sales] where (" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"); } /** * Checks that multi-level member list generates compact form of SQL where * clause: * (1) Use IN list if possible * (2) Group members sharing the same parent * (3) Only need to compare up to the first unique parent level. */ public void testMultiLevelMemberConstraintNonNullParent() { String query = "with " + "set [Filtered Store City Set] as " + "{[Store].[USA].[OR].[Portland], " + " [Store].[USA].[OR].[Salem], " + " [Store].[USA].[CA].[San Francisco], " + " [Store].[USA].[WA].[Tacoma]} " + "set [NECJ] as NonEmptyCrossJoin([Filtered Store City Set], {[Product].[Product Family].Food}) " + "select [NECJ] on columns from [Sales]"; String necjSqlDerby = "select " + "\"store\".\"store_country\", \"store\".\"store_state\", \"store\".\"store_city\", " + "\"product_class\".\"product_family\" " + "from " + "\"store\" as \"store\", \"sales_fact_1997\" as \"sales_fact_1997\", " + "\"product\" as \"product\", \"product_class\" as \"product_class\" " + "where " + "\"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" " + "and \"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" " + "and \"sales_fact_1997\".\"product_id\" = \"product\".\"product_id\" " + "and ((\"store\".\"store_state\" = 'OR' and \"store\".\"store_city\" in ('Portland', 'Salem'))" + " or (\"store\".\"store_state\" = 'CA' and \"store\".\"store_city\" = 'San Francisco')" + " or (\"store\".\"store_state\" = 'WA' and \"store\".\"store_city\" = 'Tacoma')) " + "and (\"product_class\".\"product_family\" = 'Food') " + "group by \"store\".\"store_country\", \"store\".\"store_state\", \"store\".\"store_city\", \"product_class\".\"product_family\" " + "order by CASE WHEN \"store\".\"store_country\" IS NULL THEN 1 ELSE 0 END, \"store\".\"store_country\" ASC, CASE WHEN \"store\".\"store_state\" IS NULL THEN 1 ELSE 0 END, \"store\".\"store_state\" ASC, CASE WHEN \"store\".\"store_city\" IS NULL THEN 1 ELSE 0 END, \"store\".\"store_city\" ASC, CASE WHEN \"product_class\".\"product_family\" IS NULL THEN 1 ELSE 0 END, \"product_class\".\"product_family\" ASC"; String necjSqlMySql = "select " + "`store`.`store_country` as `c0`, `store`.`store_state` as `c1`, " + "`store`.`store_city` as `c2`, `product_class`.`product_family` as `c3` " + "from " + "`store` as `store`, `sales_fact_1997` as `sales_fact_1997`, " + "`product` as `product`, `product_class` as `product_class` " + "where " + "`sales_fact_1997`.`store_id` = `store`.`store_id` " + "and `product`.`product_class_id` = `product_class`.`product_class_id` " + "and `sales_fact_1997`.`product_id` = `product`.`product_id` " + "and ((`store`.`store_city`, `store`.`store_state`) in (('Portland', 'OR'), ('Salem', 'OR'), ('San Francisco', 'CA'), ('Tacoma', 'WA'))) " + "and (`product_class`.`product_family` = 'Food') " + "group by `store`.`store_country`, `store`.`store_state`, `store`.`store_city`, `product_class`.`product_family` " + "order by ISNULL(`store`.`store_country`) ASC, `store`.`store_country` ASC, ISNULL(`store`.`store_state`) ASC, `store`.`store_state` ASC, " + "ISNULL(`store`.`store_city`) ASC, `store`.`store_city` ASC, ISNULL(`product_class`.`product_family`) ASC, `product_class`.`product_family` ASC"; if (MondrianProperties.instance().UseAggregates.get() && MondrianProperties.instance().ReadAggregates.get()) { // slightly different sql expected, uses agg table now for join necjSqlMySql = necjSqlMySql.replaceAll( "sales_fact_1997", "agg_c_14_sales_fact_1997"); necjSqlDerby = necjSqlDerby.replaceAll( "sales_fact_1997", "agg_c_14_sales_fact_1997"); } if (!MondrianProperties.instance().FilterChildlessSnowflakeMembers .get()) { necjSqlMySql = necjSqlMySql.replaceAll( "`product` as `product`, `product_class` as `product_class`", "`product_class` as `product_class`, `product` as `product`"); necjSqlMySql = necjSqlMySql.replaceAll( "`product`.`product_class_id` = `product_class`.`product_class_id` and " + "`sales_fact_1997`.`product_id` = `product`.`product_id` and ", "`sales_fact_1997`.`product_id` = `product`.`product_id` and " + "`product`.`product_class_id` = `product_class`.`product_class_id` and "); necjSqlDerby = necjSqlDerby.replaceAll( "\"product\" as \"product\", \"product_class\" as \"product_class\"", "\"product_class\" as \"product_class\", \"product\" as \"product\""); necjSqlDerby = necjSqlDerby.replaceAll( "\"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" and " + "\"sales_fact_1997\".\"product_id\" = \"product\".\"product_id\" and ", "\"sales_fact_1997\".\"product_id\" = \"product\".\"product_id\" and " + "\"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" and "); } SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.DERBY, necjSqlDerby, necjSqlDerby), new SqlPattern( Dialect.DatabaseProduct.MYSQL, necjSqlMySql, necjSqlMySql) }; assertQuerySql(query, patterns); } /** * Checks that multi-level member list generates compact form of SQL where * clause: * (1) Use IN list if possible(not possible if there are null values because * NULLs in IN lists do not match) * (2) Group members sharing the same parent, including parents with NULLs. * (3) If parent levels include NULLs, comparision includes any unique * level. */ public void testMultiLevelMemberConstraintNullParent() { if (!isDefaultNullMemberRepresentation()) { return; } if (!MondrianProperties.instance().FilterChildlessSnowflakeMembers .get()) { return; } String dimension = "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; String cube = "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + ""; String query = "with\n" + "set [Filtered Warehouse Set] as " + "{[Warehouse2].[#null].[#null].[5617 Saclan Terrace].[Arnold and Sons]," + " [Warehouse2].[#null].[#null].[3377 Coachman Place].[Jones International]} " + "set [NECJ] as NonEmptyCrossJoin([Filtered Warehouse Set], {[Product].[Product Family].Food}) " + "select [NECJ] on columns from [Warehouse2]"; String necjSqlDerby = "select \"warehouse\".\"wa_address3\", \"warehouse\".\"wa_address2\", \"warehouse\".\"wa_address1\", \"warehouse\".\"warehouse_name\", \"product_class\".\"product_family\" " + "from \"warehouse\" as \"warehouse\", \"inventory_fact_1997\" as \"inventory_fact_1997\", \"product\" as \"product\", \"product_class\" as \"product_class\" " + "where \"inventory_fact_1997\".\"warehouse_id\" = \"warehouse\".\"warehouse_id\" and \"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" " + "and \"inventory_fact_1997\".\"product_id\" = \"product\".\"product_id\" and " + "((\"warehouse\".\"wa_address1\" = '5617 Saclan Terrace' and \"warehouse\".\"wa_address2\" is null and \"warehouse\".\"warehouse_name\" = 'Arnold and Sons') " + "or (\"warehouse\".\"wa_address1\" = '3377 Coachman Place' and \"warehouse\".\"wa_address2\" is null and \"warehouse\".\"warehouse_name\" = 'Jones International')) " + "and (\"product_class\".\"product_family\" = 'Food') group by \"warehouse\".\"wa_address3\", \"warehouse\".\"wa_address2\", \"warehouse\".\"wa_address1\", " + "\"warehouse\".\"warehouse_name\", \"product_class\".\"product_family\" " + "order by CASE WHEN \"warehouse\".\"wa_address3\" IS NULL THEN 1 ELSE 0 END, \"warehouse\".\"wa_address3\" ASC, CASE WHEN \"warehouse\".\"wa_address2\" IS NULL THEN 1 ELSE 0 END, \"warehouse\".\"wa_address2\" ASC, CASE WHEN \"warehouse\".\"wa_address1\" IS NULL THEN 1 ELSE 0 END, \"warehouse\".\"wa_address1\" ASC, CASE WHEN \"warehouse\".\"warehouse_name\" IS NULL THEN 1 ELSE 0 END, \"warehouse\".\"warehouse_name\" ASC, CASE WHEN \"product_class\".\"product_family\" IS NULL THEN 1 ELSE 0 END, \"product_class\".\"product_family\" ASC"; String necjSqlMySql = "select `warehouse`.`wa_address3` as `c0`, `warehouse`.`wa_address2` as `c1`, `warehouse`.`wa_address1` as `c2`, `warehouse`.`warehouse_name` as `c3`, " + "`product_class`.`product_family` as `c4` from `warehouse` as `warehouse`, `inventory_fact_1997` as `inventory_fact_1997`, `product` as `product`, " + "`product_class` as `product_class` where `inventory_fact_1997`.`warehouse_id` = `warehouse`.`warehouse_id` and " + "`product`.`product_class_id` = `product_class`.`product_class_id` and `inventory_fact_1997`.`product_id` = `product`.`product_id` and " + "((`warehouse`.`wa_address2` is null and (`warehouse`.`warehouse_name`, `warehouse`.`wa_address1`) in (('Arnold and Sons', '5617 Saclan Terrace'), " + "('Jones International', '3377 Coachman Place')))) and (`product_class`.`product_family` = 'Food') group by `warehouse`.`wa_address3`, " + "`warehouse`.`wa_address2`, `warehouse`.`wa_address1`, `warehouse`.`warehouse_name`, `product_class`.`product_family` " + "order by ISNULL(`warehouse`.`wa_address3`) ASC, `warehouse`.`wa_address3` ASC, ISNULL(`warehouse`.`wa_address2`) ASC, `warehouse`.`wa_address2` ASC, " + "ISNULL(`warehouse`.`wa_address1`) ASC, `warehouse`.`wa_address1` ASC, ISNULL(`warehouse`.`warehouse_name`) ASC, `warehouse`.`warehouse_name` ASC, " + "ISNULL(`product_class`.`product_family`) ASC, `product_class`.`product_family` ASC"; TestContext testContext = TestContext.instance().create( dimension, cube, null, null, null, null); SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.DERBY, necjSqlDerby, necjSqlDerby), new SqlPattern( Dialect.DatabaseProduct.MYSQL, necjSqlMySql, necjSqlMySql) }; assertQuerySql(testContext, query, patterns); } /** * Check that multi-level member list generates compact form of SQL where * clause: * (1) Use IN list if possible(not possible if there are null values because * NULLs in IN lists do not match) * (2) Group members sharing the same parent, including parents with NULLs. * (3) If parent levels include NULLs, comparision includes any unique * level. * (4) Can handle predicates correctly if the member list contains both NULL * and non NULL parent levels. */ public void testMultiLevelMemberConstraintMixedNullNonNullParent() { if (!isDefaultNullMemberRepresentation()) { return; } if (!MondrianProperties.instance().FilterChildlessSnowflakeMembers .get()) { return; } String dimension = "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "\n"; String cube = "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + ""; String query = "with\n" + "set [Filtered Warehouse Set] as " + "{[Warehouse2].[#null].[234 West Covina Pkwy].[Freeman And Co]," + " [Warehouse2].[971-555-6213].[3377 Coachman Place].[Jones International]} " + "set [NECJ] as NonEmptyCrossJoin([Filtered Warehouse Set], {[Product].[Product Family].Food}) " + "select [NECJ] on columns from [Warehouse2]"; String necjSqlDerby = "select \"warehouse\".\"warehouse_fax\", \"warehouse\".\"wa_address1\", \"warehouse\".\"warehouse_name\", \"product_class\".\"product_family\" " + "from \"warehouse\" as \"warehouse\", \"inventory_fact_1997\" as \"inventory_fact_1997\", \"product\" as \"product\", \"product_class\" as \"product_class\" " + "where \"inventory_fact_1997\".\"warehouse_id\" = \"warehouse\".\"warehouse_id\" and \"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" " + "and \"inventory_fact_1997\".\"product_id\" = \"product\".\"product_id\" and " + "((\"warehouse\".\"wa_address1\" = '234 West Covina Pkwy' and \"warehouse\".\"warehouse_fax\" is null and \"warehouse\".\"warehouse_name\" = 'Freeman And Co') " + "or (\"warehouse\".\"wa_address1\" = '3377 Coachman Place' and \"warehouse\".\"warehouse_fax\" = '971-555-6213' and \"warehouse\".\"warehouse_name\" = 'Jones International')) " + "and (\"product_class\".\"product_family\" = 'Food') " + "group by \"warehouse\".\"warehouse_fax\", \"warehouse\".\"wa_address1\", \"warehouse\".\"warehouse_name\", \"product_class\".\"product_family\" " + "order by CASE WHEN \"warehouse\".\"warehouse_fax\" IS NULL THEN 1 ELSE 0 END, \"warehouse\".\"warehouse_fax\" ASC, CASE WHEN \"warehouse\".\"wa_address1\" IS NULL THEN 1 ELSE 0 END, \"warehouse\".\"wa_address1\" ASC, CASE WHEN \"warehouse\".\"warehouse_name\" IS NULL THEN 1 ELSE 0 END, \"warehouse\".\"warehouse_name\" ASC, CASE WHEN \"product_class\".\"product_family\" IS NULL THEN 1 ELSE 0 END, \"product_class\".\"product_family\" ASC"; String necjSqlMySql = "select `warehouse`.`warehouse_fax` as `c0`, `warehouse`.`wa_address1` as `c1`, " + "`warehouse`.`warehouse_name` as `c2`, `product_class`.`product_family` as `c3` " + "from `warehouse` as `warehouse`, `inventory_fact_1997` as `inventory_fact_1997`, " + "`product` as `product`, `product_class` as `product_class` " + "where `inventory_fact_1997`.`warehouse_id` = `warehouse`.`warehouse_id` and " + "`product`.`product_class_id` = `product_class`.`product_class_id` and " + "`inventory_fact_1997`.`product_id` = `product`.`product_id` and " + "((`warehouse`.`warehouse_name`, `warehouse`.`wa_address1`, `warehouse`.`warehouse_fax`) in (('Jones International', '3377 Coachman Place', '971-555-6213')) " + "or (`warehouse`.`warehouse_fax` is null and " + "(`warehouse`.`warehouse_name`, `warehouse`.`wa_address1`) in (('Freeman And Co', '234 West Covina Pkwy')))) " + "and (`product_class`.`product_family` = 'Food') " + "group by `warehouse`.`warehouse_fax`, `warehouse`.`wa_address1`, `warehouse`.`warehouse_name`, `product_class`.`product_family` " + "order by ISNULL(`warehouse`.`warehouse_fax`) ASC, `warehouse`.`warehouse_fax` ASC, " + "ISNULL(`warehouse`.`wa_address1`) ASC, `warehouse`.`wa_address1` ASC, ISNULL(`warehouse`.`warehouse_name`) ASC, " + "`warehouse`.`warehouse_name` ASC, ISNULL(`product_class`.`product_family`) ASC, `product_class`.`product_family` ASC"; TestContext testContext = TestContext.instance().create( dimension, cube, null, null, null, null); SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.DERBY, necjSqlDerby, necjSqlDerby), new SqlPattern( Dialect.DatabaseProduct.MYSQL, necjSqlMySql, necjSqlMySql) }; assertQuerySql(testContext, query, patterns); } /** * Check that multi-level member list generates compact form of SQL where * clause: * (1) Use IN list if possible(not possible if there are null values because * NULLs in IN lists do not match) * (2) Group members sharing the same parent * (3) Only need to compare up to the first unique parent level. * (4) Can handle predicates correctly if the member list contains both NULL * and non NULL child levels. */ public void testMultiLevelMemberConstraintWithMixedNullNonNullChild() { if (!isDefaultNullMemberRepresentation()) { return; } if (!MondrianProperties.instance().FilterChildlessSnowflakeMembers .get()) { return; } String dimension = "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "\n"; String cube = "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + ""; String query = "with\n" + "set [Filtered Warehouse Set] as " + "{[Warehouse2].[#null].[#null].[#null]," + " [Warehouse2].[#null].[#null].[971-555-6213]} " + "set [NECJ] as NonEmptyCrossJoin([Filtered Warehouse Set], {[Product].[Product Family].Food}) " + "select [NECJ] on columns from [Warehouse2]"; String necjSqlDerby = "select \"warehouse\".\"wa_address3\", \"warehouse\".\"wa_address2\", \"warehouse\".\"warehouse_fax\", \"product_class\".\"product_family\" " + "from \"warehouse\" as \"warehouse\", \"inventory_fact_1997\" as \"inventory_fact_1997\", \"product\" as \"product\", \"product_class\" as \"product_class\" " + "where \"inventory_fact_1997\".\"warehouse_id\" = \"warehouse\".\"warehouse_id\" and " + "\"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" and \"inventory_fact_1997\".\"product_id\" = \"product\".\"product_id\" " + "and ((\"warehouse\".\"warehouse_fax\" = '971-555-6213' or \"warehouse\".\"warehouse_fax\" is null) and " + "\"warehouse\".\"wa_address2\" is null and \"warehouse\".\"wa_address3\" is null) and " + "(\"product_class\".\"product_family\" = 'Food') " + "group by \"warehouse\".\"wa_address3\", \"warehouse\".\"wa_address2\", \"warehouse\".\"warehouse_fax\", \"product_class\".\"product_family\" " + "order by CASE WHEN \"warehouse\".\"wa_address3\" IS NULL THEN 1 ELSE 0 END, \"warehouse\".\"wa_address3\" ASC, CASE WHEN \"warehouse\".\"wa_address2\" IS NULL THEN 1 ELSE 0 END, \"warehouse\".\"wa_address2\" ASC, CASE WHEN \"warehouse\".\"warehouse_fax\" IS NULL THEN 1 ELSE 0 END, \"warehouse\".\"warehouse_fax\" ASC, CASE WHEN \"product_class\".\"product_family\" IS NULL THEN 1 ELSE 0 END, \"product_class\".\"product_family\" ASC"; String necjSqlMySql = "select `warehouse`.`wa_address3` as `c0`, `warehouse`.`wa_address2` as `c1`, `warehouse`.`warehouse_fax` as `c2`, " + "`product_class`.`product_family` as `c3` from `warehouse` as `warehouse`, `inventory_fact_1997` as `inventory_fact_1997`, " + "`product` as `product`, `product_class` as `product_class` " + "where `inventory_fact_1997`.`warehouse_id` = `warehouse`.`warehouse_id` and `product`.`product_class_id` = `product_class`.`product_class_id` and " + "`inventory_fact_1997`.`product_id` = `product`.`product_id` and " + "((`warehouse`.`warehouse_fax` = '971-555-6213' or `warehouse`.`warehouse_fax` is null) and " + "`warehouse`.`wa_address2` is null and `warehouse`.`wa_address3` is null) and " + "(`product_class`.`product_family` = 'Food') " + "group by `warehouse`.`wa_address3`, `warehouse`.`wa_address2`, `warehouse`.`warehouse_fax`, " + "`product_class`.`product_family` " + "order by ISNULL(`warehouse`.`wa_address3`) ASC, `warehouse`.`wa_address3` ASC, ISNULL(`warehouse`.`wa_address2`) ASC, " + "`warehouse`.`wa_address2` ASC, ISNULL(`warehouse`.`warehouse_fax`) ASC, `warehouse`.`warehouse_fax` ASC, " + "ISNULL(`product_class`.`product_family`) ASC, `product_class`.`product_family` ASC"; TestContext testContext = TestContext.instance().create( dimension, cube, null, null, null, null); SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.DERBY, necjSqlDerby, necjSqlDerby), new SqlPattern( Dialect.DatabaseProduct.MYSQL, necjSqlMySql, necjSqlMySql) }; assertQuerySql(testContext, query, patterns); } public void testNonEmptyUnionQuery() { Result result = executeQuery( "select {[Measures].[Unit Sales], [Measures].[Store Cost], [Measures].[Store Sales]} on columns,\n" + " NON EMPTY Hierarchize(\n" + " Union(\n" + " Crossjoin(\n" + " Crossjoin([Gender].[All Gender].children,\n" + " [Marital Status].[All Marital Status].children),\n" + " Crossjoin([Customers].[All Customers].children,\n" + " [Product].[All Products].children) ),\n" + " Crossjoin({([Gender].[All Gender].[M], [Marital Status].[All Marital Status].[M])},\n" + " Crossjoin(\n" + " [Customers].[All Customers].[USA].children,\n" + " [Product].[All Products].children) ) )) on rows\n" + "from Sales where ([Time].[1997])"); final Axis rowsAxis = result.getAxes()[1]; Assert.assertEquals(21, rowsAxis.getPositions().size()); } /** * when Mondrian parses a string like * "[Store].[All Stores].[USA].[CA].[San Francisco]" * it shall not lookup additional members. */ public void testLookupMemberCache() { if (MondrianProperties.instance().TestExpDependencies.get() > 0) { // Dependency testing causes extra SQL reads, and screws up this // test. return; } // there currently isn't a cube member to children cache, only // a shared cache so use the shared smart member reader SmartMemberReader smr = getSmartMemberReader("Store"); MemberCacheHelper smrch = smr.cacheHelper; MemberCacheHelper rcsmrch = ((RolapCubeHierarchy.RolapCubeHierarchyMemberReader) smr) .getRolapCubeMemberCacheHelper(); SmartMemberReader ssmr = getSharedSmartMemberReader("Store"); MemberCacheHelper ssmrch = ssmr.cacheHelper; clearAndHardenCache(smrch); clearAndHardenCache(rcsmrch); clearAndHardenCache(ssmrch); RolapResult result = (RolapResult) executeQuery( "select {[Store].[All Stores].[USA].[CA].[San Francisco]} on columns from [Sales]"); assertTrue( "no additional members should be read:" + ssmrch.mapKeyToMember.size(), ssmrch.mapKeyToMember.size() <= 5); RolapMember sf = (RolapMember) result.getAxes()[0].getPositions().get(0).get(0); RolapMember ca = sf.getParentMember(); // convert back to shared members ca = ((RolapCubeMember) ca).getRolapMember(); sf = ((RolapCubeMember) sf).getRolapMember(); List list = ssmrch.mapMemberToChildren.get( ca, scf.getMemberChildrenConstraint(null)); assertNull("children of [CA] are not in cache", list); list = ssmrch.mapMemberToChildren.get( ca, scf.getChildByNameConstraint( ca, new Id.Segment("San Francisco", Id.Quoting.QUOTED))); assertNotNull("child [San Francisco] of [CA] is in cache", list); assertEquals("[San Francisco] expected", sf, list.get(0)); } /** * When looking for [Month] Mondrian generates SQL that tries to find * 'Month' as a member of the time dimension. This resulted in an * SQLException because the year level is numeric and the constant 'Month' * in the WHERE condition is not. Its probably a bug that Mondrian does not * take into account [Time].[1997] when looking up [Month]. */ public void testLookupMember() { // ok if no exception occurs executeQuery( "SELECT DESCENDANTS([Time].[1997], [Month]) ON COLUMNS FROM [Sales]"); } /** * Non Empty CrossJoin (A,B) gets turned into CrossJoin (Non Empty(A), Non * Empty(B)). Verify that there is no crash when the length of B could be * non-zero length before the non empty and 0 after the non empty. */ public void testNonEmptyCrossJoinList() { propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, false); boolean oldEnableNativeNonEmpty = MondrianProperties.instance().EnableNativeNonEmpty.get(); MondrianProperties.instance().EnableNativeNonEmpty.set(false); executeQuery( "select non empty CrossJoin([Customers].[Name].Members, " + "{[Promotions].[All Promotions].[Fantastic Discounts]}) " + "ON COLUMNS FROM [Sales]"); MondrianProperties.instance().EnableNativeNonEmpty.set( oldEnableNativeNonEmpty); } /** * SQL Optimization must be turned off in ragged hierarchies. */ public void testLookupMember2() { // ok if no exception occurs executeQuery( "select {[Store].[USA].[Washington]} on columns from [Sales Ragged]"); } /** * Make sure that the Crossjoin in [Measures].[CustomerCount] * is not evaluated in NON EMPTY context. */ public void testCalcMemberWithNonEmptyCrossJoin() { getConnection().getCacheControl(null).flushSchemaCache(); Result result = executeQuery( "with member [Measures].[CustomerCount] as \n" + "'Count(CrossJoin({[Product].[All Products]}, [Customers].[Name].Members))'\n" + "select \n" + "NON EMPTY{[Measures].[CustomerCount]} ON columns,\n" + "NON EMPTY{[Product].[All Products]} ON rows\n" + "from [Sales]\n" + "where ([Store].[All Stores].[USA].[CA].[San Francisco].[Store 14], [Time].[1997].[Q1].[1])"); Cell c = result.getCell(new int[] {0, 0}); // we expect 10281 customers, although there are only 20 non-empty ones // @see #testLevelMembers assertEquals("10,281", c.getFormattedValue()); } public void testLevelMembers() { if (MondrianProperties.instance().TestExpDependencies.get() > 0) { // Dependency testing causes extra SQL reads, and screws up this // test. return; } SmartMemberReader smr = getSmartMemberReader("Customers"); // use the RolapCubeHierarchy's member cache for levels MemberCacheHelper smrch = ((RolapCubeHierarchy.CacheRolapCubeHierarchyMemberReader) smr) .rolapCubeCacheHelper; clearAndHardenCache(smrch); MemberCacheHelper smrich = smr.cacheHelper; clearAndHardenCache(smrich); // use the shared member cache for mapMemberToChildren SmartMemberReader ssmr = getSharedSmartMemberReader("Customers"); MemberCacheHelper ssmrch = ssmr.cacheHelper; clearAndHardenCache(ssmrch); TestCase c = new TestCase( 50, 21, "select \n" + "{[Measures].[Unit Sales]} ON columns,\n" + "NON EMPTY {[Customers].[All Customers], [Customers].[Name].Members} ON rows\n" + "from [Sales]\n" + "where ([Store].[All Stores].[USA].[CA].[San Francisco].[Store 14], [Time].[1997].[Q1].[1])"); Result r = c.run(); Level[] levels = smr.getHierarchy().getLevels(); Level nameLevel = levels[levels.length - 1]; // evaluator for [All Customers], [Store 14], [1/1/1997] Evaluator context = getEvaluator(r, new int[]{0, 0}); // make sure that [Customers].[Name].Members is NOT in cache TupleConstraint lmc = scf.getLevelMembersConstraint(null); assertNull(smrch.mapLevelToMembers.get((RolapLevel) nameLevel, lmc)); // make sure that NON EMPTY [Customers].[Name].Members IS in cache context.setNonEmpty(true); lmc = scf.getLevelMembersConstraint(context); List list = smrch.mapLevelToMembers.get((RolapLevel) nameLevel, lmc); if (MondrianProperties.instance().EnableRolapCubeMemberCache.get()) { assertNotNull(list); assertEquals(20, list.size()); } // make sure that the parent/child for the context are cached // [Customers].[USA].[CA].[Burlingame].[Peggy Justice] Member member = r.getAxes()[1].getPositions().get(1).get(0); Member parent = member.getParentMember(); parent = ((RolapCubeMember) parent).getRolapMember(); member = ((RolapCubeMember) member).getRolapMember(); // lookup all children of [Burlingame] -> not in cache MemberChildrenConstraint mcc = scf.getMemberChildrenConstraint(null); assertNull(ssmrch.mapMemberToChildren.get((RolapMember) parent, mcc)); // lookup NON EMPTY children of [Burlingame] -> yes these are in cache mcc = scf.getMemberChildrenConstraint(context); list = smrich.mapMemberToChildren.get((RolapMember) parent, mcc); assertNotNull(list); assertTrue(list.contains(member)); } public void testLevelMembersWithoutNonEmpty() { SmartMemberReader smr = getSmartMemberReader("Customers"); MemberCacheHelper smrch = ((RolapCubeHierarchy.CacheRolapCubeHierarchyMemberReader) smr) .rolapCubeCacheHelper; clearAndHardenCache(smrch); MemberCacheHelper smrich = smr.cacheHelper; clearAndHardenCache(smrich); SmartMemberReader ssmr = getSharedSmartMemberReader("Customers"); MemberCacheHelper ssmrch = ssmr.cacheHelper; clearAndHardenCache(ssmrch); Result r = executeQuery( "select \n" + "{[Measures].[Unit Sales]} ON columns,\n" + "{[Customers].[All Customers], [Customers].[Name].Members} ON rows\n" + "from [Sales]\n" + "where ([Store].[All Stores].[USA].[CA].[San Francisco].[Store 14], [Time].[1997].[Q1].[1])"); Level[] levels = smr.getHierarchy().getLevels(); Level nameLevel = levels[levels.length - 1]; // evaluator for [All Customers], [Store 14], [1/1/1997] Evaluator context = getEvaluator(r, new int[] {0, 0}); // make sure that [Customers].[Name].Members IS in cache TupleConstraint lmc = scf.getLevelMembersConstraint(null); List list = smrch.mapLevelToMembers.get((RolapLevel) nameLevel, lmc); if (MondrianProperties.instance().EnableRolapCubeMemberCache.get()) { assertNotNull(list); assertEquals(10281, list.size()); } // make sure that NON EMPTY [Customers].[Name].Members is NOT in cache context.setNonEmpty(true); lmc = scf.getLevelMembersConstraint(context); assertNull(smrch.mapLevelToMembers.get((RolapLevel) nameLevel, lmc)); // make sure that the parent/child for the context are cached // [Customers].[Canada].[BC].[Burnaby] Member member = r.getAxes()[1].getPositions().get(1).get(0); Member parent = member.getParentMember(); parent = ((RolapCubeMember) parent).getRolapMember(); member = ((RolapCubeMember) member).getRolapMember(); // lookup all children of [Burnaby] -> yes, found in cache MemberChildrenConstraint mcc = scf.getMemberChildrenConstraint(null); list = ssmrch.mapMemberToChildren.get((RolapMember) parent, mcc); assertNotNull(list); assertTrue(list.contains(member)); // lookup NON EMPTY children of [Burlingame] -> not in cache mcc = scf.getMemberChildrenConstraint(context); list = ssmrch.mapMemberToChildren.get((RolapMember) parent, mcc); assertNull(list); } /** * Tests that .Members exploits the same optimization as * .Members. */ public void testDimensionMembers() { // No query should return more than 20 rows. (1 row at 'all' level, // 1 row at nation level, 1 at state level, 20 at city level, and 11 // at customers level = 34.) TestCase c = new TestCase( 34, 34, "select \n" + "{[Measures].[Unit Sales]} ON columns,\n" + "NON EMPTY [Customers].Members ON rows\n" + "from [Sales]\n" + "where ([Store].[All Stores].[USA].[CA].[San Francisco].[Store 14], [Time].[1997].[Q1].[1])"); c.run(); } /** * Tests non empty children of rolap member */ public void testMemberChildrenOfRolapMember() { TestCase c = new TestCase( 50, 4, "select \n" + "{[Measures].[Unit Sales]} ON columns,\n" + "NON EMPTY [Customers].[All Customers].[USA].[CA].[Palo Alto].Children ON rows\n" + "from [Sales]\n" + "where ([Store].[All Stores].[USA].[CA].[San Francisco].[Store 14], [Time].[1997].[Q1].[1])"); c.run(); } /** * Tests non empty children of All member */ public void testMemberChildrenOfAllMember() { TestCase c = new TestCase( 50, 14, "select {[Measures].[Unit Sales]} ON columns,\n" + "NON EMPTY [Promotions].[All Promotions].Children ON rows from [Sales]\n" + "where ([Time].[1997].[Q1].[1])"); c.run(); } /** * Tests non empty children of All member w/o WHERE clause */ public void testMemberChildrenNoWhere() { // The time dimension is joined because there is no (All) level in the // Time hierarchy: // // select // `promotion`.`promotion_name` as `c0` // from // `time_by_day` as `time_by_day`, // `sales_fact_1997` as `sales_fact_1997`, // `promotion` as `promotion` // where `sales_fact_1997`.`time_id` = `time_by_day`.`time_id` // and `time_by_day`.`the_year` = 1997 // and `sales_fact_1997`.`promotion_id` // = `promotion`.`promotion_id` // group by // `promotion`.`promotion_name` // order by // `promotion`.`promotion_name` TestCase c = new TestCase( 50, 48, "select {[Measures].[Unit Sales]} ON columns,\n" + "NON EMPTY [Promotions].[All Promotions].Children ON rows " + "from [Sales]\n"); c.run(); } /** * Testcase for bug 1379068, which causes no children of [Time].[1997].[Q2] * to be found, because it incorrectly constrains on the level's key column * rather than name column. */ public void testMemberChildrenNameCol() { // Expression dependency testing casues false negatives. if (MondrianProperties.instance().TestExpDependencies.get() > 0) { return; } TestCase c = new TestCase( 3, 1, "select " + " {[Measures].[Count]} ON columns," + " {[Time].[1997].[Q2].[April]} on rows " + "from [HR]"); c.run(); } /** * When a member is expanded in JPivot with mulitple hierarchies visible it * generates a * CrossJoin({[member from left hierarchy]}, [member to * expand].Children) * *

This should behave the same as if [member from left * hierarchy] was put into the slicer. */ public void testCrossjoin() { if (MondrianProperties.instance().TestExpDependencies.get() > 0) { // Dependency testing causes extra SQL reads, and makes this // test fail. return; } TestCase c = new TestCase( 45, 4, "select \n" + "{[Measures].[Unit Sales]} ON columns,\n" + "NON EMPTY Crossjoin(" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}," + " [Customers].[USA].[CA].[Palo Alto].Children) ON rows\n" + "from [Sales] where ([Time].[1997].[Q1].[1])"); c.run(); } /** * Ensures that NON EMPTY Descendants is optimized. * Ensures that Descendants as a side effect collects MemberChildren that * may be looked up in the cache. */ public void testNonEmptyDescendants() { // Don't run the test if we're testing expression dependencies. // Expression dependencies cause spurious interval calls to // 'level.getMembers()' which create false negatives in this test. if (MondrianProperties.instance().TestExpDependencies.get() > 0) { return; } Connection con = getTestContext().withSchemaPool(false).getConnection(); SmartMemberReader smr = getSmartMemberReader(con, "Customers"); MemberCacheHelper smrch = smr.cacheHelper; clearAndHardenCache(smrch); SmartMemberReader ssmr = getSmartMemberReader(con, "Customers"); MemberCacheHelper ssmrch = ssmr.cacheHelper; clearAndHardenCache(ssmrch); TestCase c = new TestCase( con, 45, 21, "select \n" + "{[Measures].[Unit Sales]} ON columns, " + "NON EMPTY {[Customers].[All Customers], Descendants([Customers].[All Customers].[USA].[CA], [Customers].[Name])} on rows " + "from [Sales] " + "where ([Store].[All Stores].[USA].[CA].[San Francisco].[Store 14], [Time].[1997].[Q1].[1])"); Result result = c.run(); // [Customers].[All Customers].[USA].[CA].[Burlingame].[Peggy Justice] RolapMember peggy = (RolapMember) result.getAxes()[1].getPositions().get(1).get(0); RolapMember burlingame = peggy.getParentMember(); peggy = ((RolapCubeMember) peggy).getRolapMember(); burlingame = ((RolapCubeMember) burlingame).getRolapMember(); // all children of burlingame are not in cache MemberChildrenConstraint mcc = scf.getMemberChildrenConstraint(null); assertNull(ssmrch.mapMemberToChildren.get(burlingame, mcc)); // but non empty children is Evaluator evaluator = getEvaluator(result, new int[] {0, 0}); evaluator.setNonEmpty(true); mcc = scf.getMemberChildrenConstraint(evaluator); List list = ssmrch.mapMemberToChildren.get(burlingame, mcc); assertNotNull(list); assertTrue(list.contains(peggy)); // now we run the same query again, this time everything must come out // of the cache RolapNativeRegistry reg = getRegistry(con); reg.setListener( new Listener() { public void foundEvaluator(NativeEvent e) { } public void foundInCache(TupleEvent e) { } public void executingSql(TupleEvent e) { fail("expected caching"); } }); try { c.run(); } finally { reg.setListener(null); } } public void testBug1412384() { // Bug 1412384 causes a NPE in SqlConstraintUtils. assertQueryReturns( "select NON EMPTY {[Time].[1997]} ON COLUMNS,\n" + "NON EMPTY Hierarchize(Union({[Customers].[All Customers]},\n" + "[Customers].[All Customers].Children)) ON ROWS\n" + "from [Sales]\n" + "where [Measures].[Profit]", "Axis #0:\n" + "{[Measures].[Profit]}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "Axis #2:\n" + "{[Customers].[All Customers]}\n" + "{[Customers].[USA]}\n" + "Row #0: $339,610.90\n" + "Row #1: $339,610.90\n"); } public void testVirtualCubeCrossJoin() { checkNative( 18, 3, "select " + "{[Measures].[Units Ordered], [Measures].[Store Sales]} on columns, " + "non empty crossjoin([Product].[All Products].children, " + "[Store].[All Stores].children) on rows " + "from [Warehouse and Sales]"); } public void testVirtualCubeNonEmptyCrossJoin() { checkNative( 18, 3, "select " + "{[Measures].[Units Ordered], [Measures].[Store Sales]} on columns, " + "NonEmptyCrossJoin([Product].[All Products].children, " + "[Store].[All Stores].children) on rows " + "from [Warehouse and Sales]"); } public void testVirtualCubeNonEmptyCrossJoin3Args() { checkNative( 3, 3, "select " + "{[Measures].[Store Sales]} on columns, " + "nonEmptyCrossJoin([Product].[All Products].children, " + "nonEmptyCrossJoin([Customers].[All Customers].children," + "[Store].[All Stores].children)) on rows " + "from [Warehouse and Sales]"); } public void testNotNativeVirtualCubeCrossJoin1() { switch (getTestContext().getDialect().getDatabaseProduct()) { case INFOBRIGHT: // Hits same Infobright bug as NamedSetTest.testNamedSetOnMember. return; } // for this test, verify that no alert is raised even though // native evaluation isn't supported, because query // doesn't use explicit NonEmptyCrossJoin propSaver.set( MondrianProperties.instance().AlertNativeEvaluationUnsupported, "ERROR"); // native cross join cannot be used due to AllMembers checkNotNative( 3, "select " + "{[Measures].AllMembers} on columns, " + "non empty crossjoin([Product].[All Products].children, " + "[Store].[All Stores].children) on rows " + "from [Warehouse and Sales]"); } public void testNotNativeVirtualCubeCrossJoin2() { // native cross join cannot be used due to the range operator checkNotNative( 3, "select " + "{[Measures].[Sales Count] : [Measures].[Unit Sales]} on columns, " + "non empty crossjoin([Product].[All Products].children, " + "[Store].[All Stores].children) on rows " + "from [Warehouse and Sales]"); } public void testNotNativeVirtualCubeCrossJoinUnsupported() { switch (getTestContext().getDialect().getDatabaseProduct()) { case INFOBRIGHT: // Hits same Infobright bug as NamedSetTest.testNamedSetOnMember. return; } final BooleanProperty enableProperty = MondrianProperties.instance().EnableNativeCrossJoin; final StringProperty alertProperty = MondrianProperties.instance().AlertNativeEvaluationUnsupported; if (!enableProperty.get()) { // When native cross joins are explicitly disabled, no alerts // are supposed to be raised. return; } String mdx = "select " + "{[Measures].AllMembers} on columns, " + "NonEmptyCrossJoin([Product].[All Products].children, " + "[Store].[All Stores].children) on rows " + "from [Warehouse and Sales]"; final List events = new ArrayList(); // set up log4j listener to detect alerts Appender alertListener = new AppenderSkeleton() { protected void append(LoggingEvent event) { events.add(event); } public void close() { } public boolean requiresLayout() { return false; } }; final Logger rolapUtilLogger = Logger.getLogger(RolapUtil.class); propSaver.setAtLeast(rolapUtilLogger, org.apache.log4j.Level.WARN); rolapUtilLogger.addAppender(alertListener); String expectedMessage = "Unable to use native SQL evaluation for 'NonEmptyCrossJoin'"; // verify that exception is thrown if alerting is set to ERROR propSaver.set( alertProperty, org.apache.log4j.Level.ERROR.toString()); try { checkNotNative(3, mdx); fail("Expected NativeEvaluationUnsupportedException"); } catch (Exception ex) { Throwable t = ex; while (t.getCause() != null && t != t.getCause()) { t = t.getCause(); } if (!(t instanceof NativeEvaluationUnsupportedException)) { fail(); } // Expected } finally { propSaver.reset(); propSaver.setAtLeast(rolapUtilLogger, org.apache.log4j.Level.WARN); } // should have gotten one ERROR int nEvents = countFilteredEvents( events, org.apache.log4j.Level.ERROR, expectedMessage); assertEquals("logged error count check", 1, nEvents); events.clear(); // verify that exactly one warning is posted but execution succeeds // if alerting is set to WARN propSaver.set( alertProperty, org.apache.log4j.Level.WARN.toString()); try { checkNotNative(3, mdx); } finally { propSaver.reset(); propSaver.setAtLeast(rolapUtilLogger, org.apache.log4j.Level.WARN); } // should have gotten one WARN nEvents = countFilteredEvents( events, org.apache.log4j.Level.WARN, expectedMessage); assertEquals("logged warning count check", 1, nEvents); events.clear(); // verify that no warning is posted if native evaluation is // explicitly disabled propSaver.set( alertProperty, org.apache.log4j.Level.WARN.toString()); propSaver.set( enableProperty, false); try { checkNotNative(3, mdx); } finally { propSaver.reset(); propSaver.setAtLeast(rolapUtilLogger, org.apache.log4j.Level.WARN); } // should have gotten no WARN nEvents = countFilteredEvents( events, org.apache.log4j.Level.WARN, expectedMessage); assertEquals("logged warning count check", 0, nEvents); events.clear(); // no biggie if we don't get here for some reason; just being // half-heartedly clean rolapUtilLogger.removeAppender(alertListener); } private int countFilteredEvents( List events, org.apache.log4j.Level level, String pattern) { int filteredEventCount = 0; for (LoggingEvent event : events) { if (!event.getLevel().equals(level)) { continue; } if (event.getMessage().toString().indexOf(pattern) == -1) { continue; } filteredEventCount++; } return filteredEventCount; } public void testVirtualCubeCrossJoinCalculatedMember1() { // calculated member appears in query checkNative( 18, 3, "WITH MEMBER [Measures].[Total Cost] as " + "'[Measures].[Store Cost] + [Measures].[Warehouse Cost]' " + "select " + "{[Measures].[Total Cost]} on columns, " + "non empty crossjoin([Product].[All Products].children, " + "[Store].[All Stores].children) on rows " + "from [Warehouse and Sales]"); } public void testVirtualCubeCrossJoinCalculatedMember2() { // calculated member defined in schema checkNative( 18, 3, "select " + "{[Measures].[Profit Per Unit Shipped]} on columns, " + "non empty crossjoin([Product].[All Products].children, " + "[Store].[All Stores].children) on rows " + "from [Warehouse and Sales]"); } public void testNotNativeVirtualCubeCrossJoinCalculatedMember() { // native cross join cannot be used due to CurrentMember in the // calculated member checkNotNative( 3, "WITH MEMBER [Measures].[CurrMember] as " + "'[Measures].CurrentMember' " + "select " + "{[Measures].[CurrMember]} on columns, " + "non empty crossjoin([Product].[All Products].children, " + "[Store].[All Stores].children) on rows " + "from [Warehouse and Sales]"); } public void testCjEnumCalcMembers() { // 3 cross joins -- 2 of the 4 arguments to the cross joins are // enumerated sets with calculated members // should be non-native due to the fix to testCjEnumCalcMembersBug() checkNotNative( 30, "with " + "member [Product].[All Products].[Drink].[*SUBTOTAL_MEMBER_SEL~SUM] as " + " 'sum({[Product].[All Products].[Drink]})' " + "member [Product].[All Products].[Non-Consumable].[*SUBTOTAL_MEMBER_SEL~SUM] as " + " 'sum({[Product].[All Products].[Non-Consumable]})' " + "member [Customers].[All Customers].[USA].[CA].[*SUBTOTAL_MEMBER_SEL~SUM] as " + " 'sum({[Customers].[All Customers].[USA].[CA]})' " + "member [Customers].[All Customers].[USA].[OR].[*SUBTOTAL_MEMBER_SEL~SUM] as " + " 'sum({[Customers].[All Customers].[USA].[OR]})' " + "member [Customers].[All Customers].[USA].[WA].[*SUBTOTAL_MEMBER_SEL~SUM] as " + " 'sum({[Customers].[All Customers].[USA].[WA]})' " + "select " + "{[Measures].[Unit Sales]} on columns, " + "non empty " + " crossjoin(" + " crossjoin(" + " crossjoin(" + " {[Product].[All Products].[Drink].[*SUBTOTAL_MEMBER_SEL~SUM], " + " [Product].[All Products].[Non-Consumable].[*SUBTOTAL_MEMBER_SEL~SUM]}, " + " " + EDUCATION_LEVEL_LEVEL + ".Members), " + " {[Customers].[All Customers].[USA].[CA].[*SUBTOTAL_MEMBER_SEL~SUM], " + " [Customers].[All Customers].[USA].[OR].[*SUBTOTAL_MEMBER_SEL~SUM], " + " [Customers].[All Customers].[USA].[WA].[*SUBTOTAL_MEMBER_SEL~SUM]}), " + " [Time].[Year].members)" + " on rows " + "from [Sales]"); } public void testCjEnumCalcMembersBug() { // make sure NECJ is forced to be non-native // before the fix, the query is natively evaluated and result // has empty rows for [Store Type].[All Store Types].[HeadQuarters] propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, true); propSaver.set(MondrianProperties.instance().ExpandNonNative, true); checkNotNative( 9, "with " + "member [Store Type].[All Store Types].[S] as sum({[Store Type].[All Store Types]}) " + "set [Enum Store Types] as {" + " [Store Type].[All Store Types].[HeadQuarters], " + " [Store Type].[All Store Types].[Small Grocery], " + " [Store Type].[All Store Types].[Supermarket], " + " [Store Type].[All Store Types].[S]}" + "select [Measures] on columns,\n" + " NonEmptyCrossJoin([Product].[All Products].Children, [Enum Store Types]) on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink], [Store Type].[Small Grocery]}\n" + "{[Product].[Drink], [Store Type].[Supermarket]}\n" + "{[Product].[Drink], [Store Type].[All Store Types].[S]}\n" + "{[Product].[Food], [Store Type].[Small Grocery]}\n" + "{[Product].[Food], [Store Type].[Supermarket]}\n" + "{[Product].[Food], [Store Type].[All Store Types].[S]}\n" + "{[Product].[Non-Consumable], [Store Type].[Small Grocery]}\n" + "{[Product].[Non-Consumable], [Store Type].[Supermarket]}\n" + "{[Product].[Non-Consumable], [Store Type].[All Store Types].[S]}\n" + "Row #0: 574\n" + "Row #1: 14,092\n" + "Row #2: 24,597\n" + "Row #3: 4,764\n" + "Row #4: 108,188\n" + "Row #5: 191,940\n" + "Row #6: 1,219\n" + "Row #7: 28,275\n" + "Row #8: 50,236\n"); } public void testCjEnumEmptyCalcMembers() { // Make sure maxConstraint settting is high enough int minConstraints = 3; if (MondrianProperties.instance().MaxConstraints.get() < minConstraints) { propSaver.set( MondrianProperties.instance().MaxConstraints, minConstraints); } // enumerated list of calculated members results in some empty cells checkNotNative( 5, "with " + "member [Customers].[All Customers].[USA].[*SUBTOTAL_MEMBER_SEL~SUM] as " + " 'sum({[Customers].[All Customers].[USA]})' " + "member [Customers].[All Customers].[Mexico].[*SUBTOTAL_MEMBER_SEL~SUM] as " + " 'sum({[Customers].[All Customers].[Mexico]})' " + "member [Customers].[All Customers].[Canada].[*SUBTOTAL_MEMBER_SEL~SUM] as " + " 'sum({[Customers].[All Customers].[Canada]})' " + "select " + "{[Measures].[Unit Sales]} on columns, " + "non empty " + " crossjoin(" + " {[Customers].[All Customers].[Mexico].[*SUBTOTAL_MEMBER_SEL~SUM], " + " [Customers].[All Customers].[Canada].[*SUBTOTAL_MEMBER_SEL~SUM], " + " [Customers].[All Customers].[USA].[*SUBTOTAL_MEMBER_SEL~SUM]}, " + " " + EDUCATION_LEVEL_LEVEL + ".Members) " + " on rows " + "from [Sales]"); } public void testCjUnionEnumCalcMembers() { // non-native due to the fix to testCjEnumCalcMembersBug() checkNotNative( 46, "with " + "member [Education Level].[*SUBTOTAL_MEMBER_SEL~SUM] as " + " 'sum({[Education Level].[All Education Levels]})' " + "member [Education Level].[*SUBTOTAL_MEMBER_SEL~AVG] as " + " 'avg([Education Level].[Education Level].Members)' select " + "{[Measures].[Unit Sales]} on columns, " + "non empty union (Crossjoin(" + " [Product].[Product Department].Members, " + " {[Education Level].[*SUBTOTAL_MEMBER_SEL~AVG]}), " + "crossjoin(" + " [Product].[Product Department].Members, " + " {[Education Level].[*SUBTOTAL_MEMBER_SEL~SUM]})) on rows " + "from [Sales]"); } /** * Tests the behavior if you have NON EMPTY on both axes, and the default * member of a hierarchy is not 'all' or the first child. */ public void testNonEmptyWithWeirdDefaultMember() { if (!Bug.BugMondrian229Fixed) { return; } TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "

\n" + " \n" + " \n" + " \n" + " \n" + " "); // Check that the grand total is different than when [Time].[1997] is // the default member. testContext.assertQueryReturns( "select from [Sales]", "Axis #0:\n" + "{}\n" + "21,628"); // Results of this query agree with MSAS 2000 SP1. // The query gives the same results if the default member of [Time] // is [Time].[1997] or [Time].[1997].[Q1].[1]. testContext.assertQueryReturns( "select\n" + "NON EMPTY Crossjoin({[Time].[1997].[Q2].[4]}, [Customers].[Country].members) on columns,\n" + "NON EMPTY [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth].children on rows\n" + "from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997].[Q2].[4], [Customers].[USA]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth].[Portsmouth Imported Beer]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth].[Portsmouth Light Beer]}\n" + "Row #0: 3\n" + "Row #1: 21\n"); } public void testCrossJoinNamedSets1() { checkNative( 3, 3, "with " + "SET [ProductChildren] as '[Product].[All Products].children' " + "SET [StoreMembers] as '[Store].[Store Country].members' " + "select {[Measures].[Store Sales]} on columns, " + "non empty crossjoin([ProductChildren], [StoreMembers]) " + "on rows from [Sales]"); } public void testCrossJoinNamedSets2() { // Make sure maxConstraint settting is high enough int minConstraints = 3; if (MondrianProperties.instance().MaxConstraints.get() < minConstraints) { propSaver.set( MondrianProperties.instance().MaxConstraints, minConstraints); } checkNative( 3, 3, "with " + "SET [ProductChildren] as '{[Product].[All Products].[Drink], " + "[Product].[All Products].[Food], " + "[Product].[All Products].[Non-Consumable]}' " + "SET [StoreChildren] as '[Store].[All Stores].children' " + "select {[Measures].[Store Sales]} on columns, " + "non empty crossjoin([ProductChildren], [StoreChildren]) on rows from " + "[Sales]"); } public void testCrossJoinSetWithDifferentParents() { // Verify that only the members explicitly referenced in the set // are returned. Note that different members are referenced in // each level in the time dimension. checkNative( 5, 5, "select " + "{[Measures].[Unit Sales]} on columns, " + "NonEmptyCrossJoin(" + EDUCATION_LEVEL_LEVEL + ".Members, " + "{[Time].[1997].[Q1], [Time].[1998].[Q2]}) on rows from Sales"); } public void testCrossJoinSetWithCrossProdMembers() { // Make sure maxConstraint settting is high enough int minConstraints = 6; if (MondrianProperties.instance().MaxConstraints.get() < minConstraints) { propSaver.set( MondrianProperties.instance().MaxConstraints, minConstraints); } // members in set are a cross product of (1997, 1998) and (Q1, Q2, Q3) checkNative( 15, 15, "select " + "{[Measures].[Unit Sales]} on columns, " + "NonEmptyCrossJoin(" + EDUCATION_LEVEL_LEVEL + ".Members, " + "{[Time].[1997].[Q1], [Time].[1997].[Q2], [Time].[1997].[Q3], " + "[Time].[1998].[Q1], [Time].[1998].[Q2], [Time].[1998].[Q3]})" + "on rows from Sales"); } public void testCrossJoinSetWithSameParent() { // Make sure maxConstraint settting is high enough int minConstraints = 2; if (MondrianProperties.instance().MaxConstraints.get() < minConstraints) { propSaver.set( MondrianProperties.instance().MaxConstraints, minConstraints); } // members in set have the same parent checkNative( 10, 10, "select " + "{[Measures].[Unit Sales]} on columns, " + "NonEmptyCrossJoin(" + EDUCATION_LEVEL_LEVEL + ".Members, " + "{[Store].[All Stores].[USA].[CA].[Beverly Hills], " + "[Store].[All Stores].[USA].[CA].[San Francisco]}) " + "on rows from Sales"); } public void testCrossJoinSetWithUniqueLevel() { // Make sure maxConstraint settting is high enough int minConstraints = 2; if (MondrianProperties.instance().MaxConstraints.get() < minConstraints) { propSaver.set( MondrianProperties.instance().MaxConstraints, minConstraints); } // members in set have different parents but there is a unique level checkNative( 10, 10, "select " + "{[Measures].[Unit Sales]} on columns, " + "NonEmptyCrossJoin(" + EDUCATION_LEVEL_LEVEL + ".Members, " + "{[Store].[All Stores].[USA].[CA].[Beverly Hills].[Store 6], " + "[Store].[All Stores].[USA].[WA].[Bellingham].[Store 2]}) " + "on rows from Sales"); } public void testCrossJoinMultiInExprAllMember() { checkNative( 10, 10, "select " + "{[Measures].[Unit Sales]} on columns, " + "NonEmptyCrossJoin(" + EDUCATION_LEVEL_LEVEL + ".Members, " + "{[Product].[All Products].[Drink].[Alcoholic Beverages], " + "[Product].[All Products].[Food].[Breakfast Foods]}) " + "on rows from Sales"); } public void testCrossJoinEvaluatorContext1() { // This test ensures that the proper measure members context is // set when evaluating a non-empty cross join. The context should // not include the calculated measure [*TOP_BOTTOM_SET]. If it // does, the query will result in an infinite loop because the cross // join will try evaluating the calculated member (when it shouldn't) // and the calculated member references the cross join, resulting // in the loop assertQueryReturns( "With " + "Set [*NATIVE_CJ_SET] as " + "'NonEmptyCrossJoin([*BASE_MEMBERS_Store], [*BASE_MEMBERS_Products])' " + "Set [*TOP_BOTTOM_SET] as " + "'Order([*GENERATED_MEMBERS_Store], ([Measures].[Unit Sales], " + "[Product].[All Products].[*TOP_BOTTOM_MEMBER]), BDESC)' " + "Set [*BASE_MEMBERS_Store] as '[Store].members' " + "Set [*GENERATED_MEMBERS_Store] as 'Generate([*NATIVE_CJ_SET], {[Store].CurrentMember})' " + "Set [*BASE_MEMBERS_Products] as " + "'{[Product].[All Products].[Food], [Product].[All Products].[Drink], " + "[Product].[All Products].[Non-Consumable]}' " + "Set [*GENERATED_MEMBERS_Products] as " + "'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' " + "Member [Product].[All Products].[*TOP_BOTTOM_MEMBER] as " + "'Aggregate([*GENERATED_MEMBERS_Products])'" + "Member [Measures].[*TOP_BOTTOM_MEMBER] as 'Rank([Store].CurrentMember,[*TOP_BOTTOM_SET])' " + "Member [Store].[All Stores].[*SUBTOTAL_MEMBER_SEL~SUM] as " + "'sum(Filter([*GENERATED_MEMBERS_Store], [Measures].[*TOP_BOTTOM_MEMBER] <= 10))'" + "Select {[Measures].[Store Cost]} on columns, " + "Non Empty Filter(Generate([*NATIVE_CJ_SET], {([Store].CurrentMember)}), " + "[Measures].[*TOP_BOTTOM_MEMBER] <= 10) on rows From [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Cost]}\n" + "Axis #2:\n" + "{[Store].[All Stores]}\n" + "{[Store].[USA]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[OR].[Portland]}\n" + "{[Store].[USA].[OR].[Salem]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Store].[USA].[WA]}\n" + "{[Store].[USA].[WA].[Tacoma]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "Row #0: 225,627.23\n" + "Row #1: 225,627.23\n" + "Row #2: 63,530.43\n" + "Row #3: 56,772.50\n" + "Row #4: 21,948.94\n" + "Row #5: 34,823.56\n" + "Row #6: 34,823.56\n" + "Row #7: 105,324.31\n" + "Row #8: 29,959.28\n" + "Row #9: 29,959.28\n"); } public void testCrossJoinEvaluatorContext2() { // Make sure maxConstraint settting is high enough int minConstraints = 2; if (MondrianProperties.instance().MaxConstraints.get() < minConstraints) { propSaver.set( MondrianProperties.instance().MaxConstraints, minConstraints); } // calculated measure contains a calculated member assertQueryReturns( "With Set [*NATIVE_CJ_SET] as " + "'NonEmptyCrossJoin([*BASE_MEMBERS_Dates], [*BASE_MEMBERS_Stores])' " + "Set [*BASE_MEMBERS_Dates] as '{[Time].[1997].[Q1], [Time].[1997].[Q2]}' " + "Set [*GENERATED_MEMBERS_Dates] as " + "'Generate([*NATIVE_CJ_SET], {[Time].[Time].CurrentMember})' " + "Set [*GENERATED_MEMBERS_Measures] as '{[Measures].[*SUMMARY_METRIC_0]}' " + "Set [*BASE_MEMBERS_Stores] as '{[Store].[USA].[CA], [Store].[USA].[WA]}' " + "Set [*GENERATED_MEMBERS_Stores] as " + "'Generate([*NATIVE_CJ_SET], {[Store].CurrentMember})' " + "Member [Time].[Time].[*SM_CTX_SEL] as 'Aggregate([*GENERATED_MEMBERS_Dates])' " + "Member [Measures].[*SUMMARY_METRIC_0] as " + "'[Measures].[Unit Sales]/([Measures].[Unit Sales],[Time].[*SM_CTX_SEL])', " + "FORMAT_STRING = '0.00%' " + "Member [Time].[Time].[*SUBTOTAL_MEMBER_SEL~SUM] as 'sum([*GENERATED_MEMBERS_Dates])' " + "Member [Store].[*SUBTOTAL_MEMBER_SEL~SUM] as " + "'sum(Filter([*GENERATED_MEMBERS_Stores], " + "([Measures].[Unit Sales], [Time].[*SUBTOTAL_MEMBER_SEL~SUM]) > 0.0))' " + "Select Union " + "(CrossJoin " + "(Filter " + "(Generate([*NATIVE_CJ_SET], {([Time].[Time].CurrentMember)}), " + "Not IsEmpty ([Measures].[Unit Sales])), " + "[*GENERATED_MEMBERS_Measures]), " + "CrossJoin " + "(Filter " + "({[Time].[*SUBTOTAL_MEMBER_SEL~SUM]}, " + "Not IsEmpty ([Measures].[Unit Sales])), " + "[*GENERATED_MEMBERS_Measures])) on columns, " + "Non Empty Union " + "(Filter " + "(Filter " + "(Generate([*NATIVE_CJ_SET], " + "{([Store].CurrentMember)}), " + "([Measures].[Unit Sales], " + "[Time].[*SUBTOTAL_MEMBER_SEL~SUM]) > 0.0), " + "Not IsEmpty ([Measures].[Unit Sales])), " + "Filter(" + "{[Store].[*SUBTOTAL_MEMBER_SEL~SUM]}, " + "Not IsEmpty ([Measures].[Unit Sales]))) on rows " + "From [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997].[Q1], [Measures].[*SUMMARY_METRIC_0]}\n" + "{[Time].[1997].[Q2], [Measures].[*SUMMARY_METRIC_0]}\n" + "{[Time].[*SUBTOTAL_MEMBER_SEL~SUM], [Measures].[*SUMMARY_METRIC_0]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[WA]}\n" + "{[Store].[*SUBTOTAL_MEMBER_SEL~SUM]}\n" + "Row #0: 48.34%\n" + "Row #0: 51.66%\n" + "Row #0: 100.00%\n" + "Row #1: 50.53%\n" + "Row #1: 49.47%\n" + "Row #1: 100.00%\n" + "Row #2: 49.72%\n" + "Row #2: 50.28%\n" + "Row #2: 100.00%\n"); } public void testVCNativeCJWithIsEmptyOnMeasure() { // Don't use checkNative method here because in the case where // native cross join isn't used, the query causes a stack overflow. // // A measures member is referenced in the IsEmpty() function. This // shouldn't prevent native cross join from being used. assertQueryReturns( "with " + "set BM_PRODUCT as {[Product].[All Products].[Drink]} " + "set BM_EDU as [Education Level].[Education Level].Members " + "set BM_GENDER as {[Gender].[Gender].[M]} " + "set CJ as NonEmptyCrossJoin(BM_GENDER,NonEmptyCrossJoin(BM_EDU,BM_PRODUCT)) " + "set GM_PRODUCT as Generate(CJ, {[Product].CurrentMember}) " + "set GM_EDU as Generate(CJ, {[Education Level].CurrentMember}) " + "set GM_GENDER as Generate(CJ, {[Gender].CurrentMember}) " + "set GM_MEASURE as {[Measures].[Unit Sales]} " + "member [Education Level].FILTER1 as Aggregate(GM_EDU) " + "member [Gender].FILTER2 as Aggregate(GM_GENDER) " + "select " + "Filter(GM_PRODUCT, Not IsEmpty([Measures].[Unit Sales])) on rows, " + "GM_MEASURE on columns " + "from [Warehouse and Sales] " + "where ([Education Level].FILTER1, [Gender].FILTER2)", "Axis #0:\n" + "{[Education Level].[FILTER1], [Gender].[FILTER2]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "Row #0: 12,395\n"); } public void testVCNativeCJWithTopPercent() { // The reference to [Store Sales] inside the topPercent function // should not prevent native cross joins from being used checkNative( 92, 1, "select {topPercent(nonemptycrossjoin([Product].[Product Department].members, " + "[Time].[1997].children),10,[Measures].[Store Sales])} on columns, " + "{[Measures].[Store Sales]} on rows from " + "[Warehouse and Sales]"); } public void testVCOrdinalExpression() { // [Customers].[Name] is an ordinal expression. Make sure ordering // is done on the column corresponding to that expression. checkNative( 67, 67, "select {[Measures].[Store Sales]} on columns," + " NON EMPTY Crossjoin([Customers].[Name].Members," + " [Product].[Product Name].Members) ON rows " + " from [Warehouse and Sales] where (" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]," + " [Time].[1997].[Q1].[1])"); } /** * Test for bug #1696772 * Modified which calculations are tested for non native, non empty joins */ public void testNonEmptyWithCalcMeasure() { checkNative( 15, 6, "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Store],NonEmptyCrossJoin([*BASE_MEMBERS_Education Level],[*BASE_MEMBERS_Product]))' " + "Set [*METRIC_CJ_SET] as 'Filter([*NATIVE_CJ_SET],[Measures].[*Store Sales_SEL~SUM] > 50000.0 And [Measures].[*Unit Sales_SEL~MAX] > 50000.0)' " + "Set [*BASE_MEMBERS_Store] as '[Store].[Store Country].Members' " + "Set [*NATIVE_MEMBERS_Store] as 'Generate([*NATIVE_CJ_SET], {[Store].CurrentMember})' " + "Set [*METRIC_MEMBERS_Store] as 'Generate([*METRIC_CJ_SET], {[Store].CurrentMember})' " + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[Store Sales],[Measures].[Unit Sales]}' " + "Set [*BASE_MEMBERS_Education Level] as '" + EDUCATION_LEVEL_LEVEL + ".Members' " + "Set [*NATIVE_MEMBERS_Education Level] as 'Generate([*NATIVE_CJ_SET], {[Education Level].CurrentMember})' " + "Set [*METRIC_MEMBERS_Education Level] as 'Generate([*METRIC_CJ_SET], {[Education Level].CurrentMember})' " + "Set [*BASE_MEMBERS_Product] as '[Product].[Product Family].Members' " + "Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' " + "Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' " + "Member [Product].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Product].[All Products]})' " + "Member [Store].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Store].[All Stores]})' " + "Member [Measures].[*Store Sales_SEL~SUM] as '([Measures].[Store Sales],[Education Level].CurrentMember,[Product].[*CTX_METRIC_MEMBER_SEL~SUM],[Store].[*CTX_METRIC_MEMBER_SEL~SUM])' " + "Member [Product].[*CTX_METRIC_MEMBER_SEL~MAX] as 'Max([*NATIVE_MEMBERS_Product])' " + "Member [Store].[*CTX_METRIC_MEMBER_SEL~MAX] as 'Max([*NATIVE_MEMBERS_Store])' " + "Member [Measures].[*Unit Sales_SEL~MAX] as '([Measures].[Unit Sales],[Education Level].CurrentMember,[Product].[*CTX_METRIC_MEMBER_SEL~MAX],[Store].[*CTX_METRIC_MEMBER_SEL~MAX])' " + "Select " + "CrossJoin(Generate([*METRIC_CJ_SET], {([Store].CurrentMember)}),[*BASE_MEMBERS_Measures]) on columns, " + "Non Empty Generate([*METRIC_CJ_SET], {([Education Level].CurrentMember,[Product].CurrentMember)}) on rows " + "From [Sales]"); } public void testCalculatedSlicerMember() { // This test verifies that members(the FILTER members in the query // below) on the slicer are ignored in CrossJoin emptiness check. // Otherwise, if they are not ignored, stack over flow will occur // because emptiness check depends on a calculated slicer member // which references the non-empty set being computed. // // Bcause native evaluation already ignores calculated members on // the slicer, both native and non-native evaluation should return // the same result. checkNative( 20, 1, "With " + "Set BM_PRODUCT as '{[Product].[All Products].[Drink]}' " + "Set BM_EDU as '" + EDUCATION_LEVEL_LEVEL + ".Members' " + "Set BM_GENDER as '{[Gender].[Gender].[M]}' " + "Set NECJ_SET as 'NonEmptyCrossJoin(BM_GENDER, NonEmptyCrossJoin(BM_EDU,BM_PRODUCT))' " + "Set GM_PRODUCT as 'Generate(NECJ_SET, {[Product].CurrentMember})' " + "Set GM_EDU as 'Generate(NECJ_SET, {[Education Level].CurrentMember})' " + "Set GM_GENDER as 'Generate(NECJ_SET, {[Gender].CurrentMember})' " + "Set GM_MEASURE as '{[Measures].[Unit Sales]}' " + "Member [Education Level].FILTER1 as 'Aggregate(GM_EDU)' " + "Member [Gender].FILTER2 as 'Aggregate(GM_GENDER)' " + "Select " + "GM_PRODUCT on rows, GM_MEASURE on columns " + "From [Sales] Where ([Education Level].FILTER1, [Gender].FILTER2)"); } // next two verify that when NECJ references dimension from slicer, // slicer is correctly ignored for purposes of evaluating NECJ emptiness, // regardless of whether evaluation is native or non-native public void testIndependentSlicerMemberNonNative() { checkIndependentSlicerMemberNative(false); } public void testIndependentSlicerMemberNative() { checkIndependentSlicerMemberNative(true); } private void checkIndependentSlicerMemberNative(boolean useNative) { propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, useNative); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. final TestContext context = getTestContext().withFreshConnection(); context.assertQueryReturns( "with set [p] as '[Product].[Product Family].members' " + "set [s] as '[Store].[Store Country].members' " + "set [ne] as 'nonemptycrossjoin([p],[s])' " + "set [nep] as 'Generate([ne],{[Product].CurrentMember})' " + "select [nep] on columns from sales " + "where ([Store].[Store Country].[Mexico])", "Axis #0:\n" + "{[Store].[Mexico]}\n" + "Axis #1:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n"); } public void testDependentSlicerMemberNonNative() { propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, false); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. final TestContext context = getTestContext().withFreshConnection(); context.assertQueryReturns( "with set [p] as '[Product].[Product Family].members' " + "set [s] as '[Store].[Store Country].members' " + "set [ne] as 'nonemptycrossjoin([p],[s])' " + "set [nep] as 'Generate([ne],{[Product].CurrentMember})' " + "select [nep] on columns from sales " + "where ([Time].[1998])", "Axis #0:\n" + "{[Time].[1998]}\n" + "Axis #1:\n"); } public void testDependentSlicerMemberNative() { propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. final TestContext context = getTestContext().withFreshConnection(); context.assertQueryReturns( "with set [p] as '[Product].[Product Family].members' " + "set [s] as '[Store].[Store Country].members' " + "set [ne] as 'nonemptycrossjoin([p],[s])' " + "set [nep] as 'Generate([ne],{[Product].CurrentMember})' " + "select [nep] on columns from sales " + "where ([Time].[1998])", "Axis #0:\n" + "{[Time].[1998]}\n" + "Axis #1:\n"); } /** * Tests bug 1791609, "CrossJoin non empty optimizer eliminates calculated * member". */ public void testBug1791609NonEmptyCrossJoinEliminatesCalcMember() { if (!Bug.BugMondrian328Fixed) { return; } // From the bug: // With NON EMPTY (mondrian.rolap.nonempty) behavior set to true // the following mdx return no result. The same mdx returns valid // result when NON EMPTY is turned off. assertQueryReturns( "WITH \n" + "MEMBER Measures.Calc AS '[Measures].[Profit] * 2', SOLVE_ORDER=1000\n" + "MEMBER Product.Conditional as 'Iif (Measures.CurrentMember IS Measures.[Calc], " + "Measures.CurrentMember, null)', SOLVE_ORDER=2000\n" + "SET [S2] AS '{[Store].MEMBERS}' \n" + "SET [S1] AS 'CROSSJOIN({[Customers].[All Customers]},{Product.Conditional})' \n" + "SELECT \n" + "NON EMPTY GENERATE({Measures.[Calc]}, \n" + " CROSSJOIN(HEAD( {([Measures].CURRENTMEMBER)}, \n" + " 1\n" + " ), \n" + " {[S1]}\n" + " ), \n" + " ALL\n" + " ) \n" + " ON AXIS(0), \n" + "NON EMPTY [S2] ON AXIS(1) \n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Calc], [Customers].[All Customers], [Product].[Conditional]}\n" + "Axis #2:\n" + "{[Store].[All Stores]}\n" + "{[Store].[USA]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[OR].[Portland]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Store].[USA].[OR].[Salem]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Store].[USA].[WA]}\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "{[Store].[USA].[WA].[Bellingham].[Store 2]}\n" + "{[Store].[USA].[WA].[Bremerton]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Seattle]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15]}\n" + "{[Store].[USA].[WA].[Spokane]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16]}\n" + "{[Store].[USA].[WA].[Tacoma]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "{[Store].[USA].[WA].[Walla Walla]}\n" + "{[Store].[USA].[WA].[Walla Walla].[Store 22]}\n" + "{[Store].[USA].[WA].[Yakima]}\n" + "{[Store].[USA].[WA].[Yakima].[Store 23]}\n" + "Row #0: $679,221.79\n" + "Row #1: $679,221.79\n" + "Row #2: $191,274.83\n" + "Row #3: $54,967.60\n" + "Row #4: $54,967.60\n" + "Row #5: $65,547.49\n" + "Row #6: $65,547.49\n" + "Row #7: $65,435.21\n" + "Row #8: $65,435.21\n" + "Row #9: $5,324.53\n" + "Row #10: $5,324.53\n" + "Row #11: $171,009.14\n" + "Row #12: $66,219.69\n" + "Row #13: $66,219.69\n" + "Row #14: $104,789.45\n" + "Row #15: $104,789.45\n" + "Row #16: $316,937.82\n" + "Row #17: $5,685.23\n" + "Row #18: $5,685.23\n" + "Row #19: $63,548.67\n" + "Row #20: $63,548.67\n" + "Row #21: $63,374.53\n" + "Row #22: $63,374.53\n" + "Row #23: $59,677.94\n" + "Row #24: $59,677.94\n" + "Row #25: $89,769.36\n" + "Row #26: $89,769.36\n" + "Row #27: $5,651.26\n" + "Row #28: $5,651.26\n" + "Row #29: $29,230.83\n" + "Row #30: $29,230.83\n"); } /** * Test that executes <Level>.Members and applies a non-empty * constraint. Must work regardless of whether * {@link MondrianProperties#EnableNativeNonEmpty native} is enabled. * Testcase for bug * 1722959, "NON EMPTY Level.MEMBERS fails if nonempty.enable=false" */ public void testNonEmptyLevelMembers() { boolean currentNativeNonEmpty = MondrianProperties.instance().EnableNativeNonEmpty.get(); boolean currentNonEmptyOnAllAxis = MondrianProperties.instance().EnableNonEmptyOnAllAxis.get(); try { MondrianProperties.instance().EnableNativeNonEmpty.set(false); MondrianProperties.instance().EnableNonEmptyOnAllAxis.set(true); assertQueryReturns( "WITH MEMBER [Measures].[One] AS '1' " + "SELECT " + "NON EMPTY {[Measures].[One], [Measures].[Store Sales]} ON rows, " + "NON EMPTY [Store].[Store State].MEMBERS on columns " + "FROM sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[Canada].[BC]}\n" + "{[Store].[Mexico].[DF]}\n" + "{[Store].[Mexico].[Guerrero]}\n" + "{[Store].[Mexico].[Jalisco]}\n" + "{[Store].[Mexico].[Veracruz]}\n" + "{[Store].[Mexico].[Yucatan]}\n" + "{[Store].[Mexico].[Zacatecas]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Axis #2:\n" + "{[Measures].[One]}\n" + "{[Measures].[Store Sales]}\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: 159,167.84\n" + "Row #1: 142,277.07\n" + "Row #1: 263,793.22\n"); if (Bug.BugMondrian446Fixed) { MondrianProperties.instance().EnableNativeNonEmpty.set(true); assertQueryReturns( "WITH MEMBER [Measures].[One] AS '1' " + "SELECT " + "NON EMPTY {[Measures].[One], [Measures].[Store Sales]} ON rows, " + "NON EMPTY [Store].[Store State].MEMBERS on columns " + "FROM sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[Canada].[BC]}\n" + "{[Store].[Mexico].[DF]}\n" + "{[Store].[Mexico].[Guerrero]}\n" + "{[Store].[Mexico].[Jalisco]}\n" + "{[Store].[Mexico].[Veracruz]}\n" + "{[Store].[Mexico].[Yucatan]}\n" + "{[Store].[Mexico].[Zacatecas]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Axis #2:\n" + "{[Measures].[One]}\n" + "{[Measures].[Store Sales]}\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: 159,167.84\n" + "Row #1: 142,277.07\n" + "Row #1: 263,793.22\n"); } } finally { MondrianProperties.instance().EnableNativeNonEmpty.set( currentNativeNonEmpty); MondrianProperties.instance().EnableNonEmptyOnAllAxis.set( currentNonEmptyOnAllAxis); } } public void testNonEmptyResults() { // This unit test was failing with a NullPointerException in JPivot // after the highcardinality feature was added, I've included it // here to make sure it continues to work. assertQueryReturns( "select NON EMPTY {[Measures].[Unit Sales], [Measures].[Store Cost]} ON columns, " + "NON EMPTY Filter([Product].[Brand Name].Members, ([Measures].[Unit Sales] > 100000.0)) ON rows " + "from [Sales] where [Time].[1997]", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "Axis #2:\n"); } /** * Test case for * MONDRIAN-412, "NON EMPTY and Filter() breaking aggregate * calculations". */ public void testBugMondrian412() { TestContext ctx = getTestContext(); ctx.assertQueryReturns( "with member [Measures].[AvgRevenue] as 'Avg([Store].[Store Name].Members, [Measures].[Store Sales])' " + "select NON EMPTY {[Measures].[Store Sales], [Measures].[AvgRevenue]} ON COLUMNS, " + "NON EMPTY Filter([Store].[Store Name].Members, ([Measures].[AvgRevenue] < [Measures].[Store Sales])) ON ROWS " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[AvgRevenue]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "Row #0: 45,750.24\n" + "Row #0: 43,479.86\n" + "Row #1: 54,545.28\n" + "Row #1: 43,479.86\n" + "Row #2: 54,431.14\n" + "Row #2: 43,479.86\n" + "Row #3: 55,058.79\n" + "Row #3: 43,479.86\n" + "Row #4: 87,218.28\n" + "Row #4: 43,479.86\n" + "Row #5: 52,896.30\n" + "Row #5: 43,479.86\n" + "Row #6: 52,644.07\n" + "Row #6: 43,479.86\n" + "Row #7: 49,634.46\n" + "Row #7: 43,479.86\n" + "Row #8: 74,843.96\n" + "Row #8: 43,479.86\n"); } public void testNonEmpyOnVirtualCubeWithNonJoiningDimension() { assertQueryReturns( "select non empty {[Warehouse].[Warehouse name].members} on 0," + "{[Measures].[Units Shipped],[Measures].[Unit Sales]} on 1" + " from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse]}\n" + "{[Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.]}\n" + "{[Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage]}\n" + "{[Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.]}\n" + "{[Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.]}\n" + "{[Warehouse].[USA].[OR].[Salem].[Treehouse Distribution]}\n" + "{[Warehouse].[USA].[WA].[Bellingham].[Foster Products]}\n" + "{[Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.]}\n" + "{[Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking]}\n" + "{[Warehouse].[USA].[WA].[Spokane].[Jones International]}\n" + "{[Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.]}\n" + "{[Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing]}\n" + "{[Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods]}\n" + "Axis #2:\n" + "{[Measures].[Units Shipped]}\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 10759.0\n" + "Row #0: 24587.0\n" + "Row #0: 23835.0\n" + "Row #0: 1696.0\n" + "Row #0: 8515.0\n" + "Row #0: 32393.0\n" + "Row #0: 2348.0\n" + "Row #0: 22734.0\n" + "Row #0: 24110.0\n" + "Row #0: 11889.0\n" + "Row #0: 32411.0\n" + "Row #0: 1860.0\n" + "Row #0: 10589.0\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n"); } public void testNonEmptyOnNonJoiningValidMeasure() { assertQueryReturns( "with member [Measures].[vm] as 'ValidMeasure([Measures].[Unit Sales])'" + "select non empty {[Warehouse].[Warehouse name].members} on 0," + "{[Measures].[Units Shipped],[Measures].[vm]} on 1" + " from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse]}\n" + "{[Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.]}\n" + "{[Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage]}\n" + "{[Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.]}\n" + "{[Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.]}\n" + "{[Warehouse].[USA].[OR].[Salem].[Treehouse Distribution]}\n" + "{[Warehouse].[USA].[WA].[Bellingham].[Foster Products]}\n" + "{[Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.]}\n" + "{[Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking]}\n" + "{[Warehouse].[USA].[WA].[Spokane].[Jones International]}\n" + "{[Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.]}\n" + "{[Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing]}\n" + "{[Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods]}\n" + "Axis #2:\n" + "{[Measures].[Units Shipped]}\n" + "{[Measures].[vm]}\n" + "Row #0: 10759.0\n" + "Row #0: 24587.0\n" + "Row #0: 23835.0\n" + "Row #0: 1696.0\n" + "Row #0: 8515.0\n" + "Row #0: 32393.0\n" + "Row #0: 2348.0\n" + "Row #0: 22734.0\n" + "Row #0: 24110.0\n" + "Row #0: 11889.0\n" + "Row #0: 32411.0\n" + "Row #0: 1860.0\n" + "Row #0: 10589.0\n" + "Row #1: 266,773\n" + "Row #1: 266,773\n" + "Row #1: 266,773\n" + "Row #1: 266,773\n" + "Row #1: 266,773\n" + "Row #1: 266,773\n" + "Row #1: 266,773\n" + "Row #1: 266,773\n" + "Row #1: 266,773\n" + "Row #1: 266,773\n" + "Row #1: 266,773\n" + "Row #1: 266,773\n" + "Row #1: 266,773\n"); } public void testCrossjoinWithTwoDimensionsJoiningToOppositeBaseCubes() { assertQueryReturns( "with member [Measures].[vm] as 'ValidMeasure([Measures].[Unit Sales])'\n" + "select non empty Crossjoin([Warehouse].[Warehouse Name].members, [Gender].[Gender].members) on 0,\n" + "{[Measures].[Units Shipped],[Measures].[vm]} on 1\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "Axis #2:\n" + "{[Measures].[Units Shipped]}\n" + "{[Measures].[vm]}\n"); } public void testCrossjoinWithOneDimensionThatDoesNotJoinToBothBaseCubes() { assertQueryReturns( "with member [Measures].[vm] as 'ValidMeasure([Measures].[Units Shipped])'" + "select non empty Crossjoin([Store].[Store Name].members, [Gender].[Gender].members) on 0," + "{[Measures].[Unit Sales],[Measures].[vm]} on 1" + " from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6], [Gender].[F]}\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6], [Gender].[M]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7], [Gender].[F]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7], [Gender].[M]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24], [Gender].[F]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24], [Gender].[M]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14], [Gender].[F]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14], [Gender].[M]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11], [Gender].[F]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11], [Gender].[M]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13], [Gender].[F]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13], [Gender].[M]}\n" + "{[Store].[USA].[WA].[Bellingham].[Store 2], [Gender].[F]}\n" + "{[Store].[USA].[WA].[Bellingham].[Store 2], [Gender].[M]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3], [Gender].[F]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3], [Gender].[M]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15], [Gender].[F]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15], [Gender].[M]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16], [Gender].[F]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16], [Gender].[M]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17], [Gender].[F]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17], [Gender].[M]}\n" + "{[Store].[USA].[WA].[Walla Walla].[Store 22], [Gender].[F]}\n" + "{[Store].[USA].[WA].[Walla Walla].[Store 22], [Gender].[M]}\n" + "{[Store].[USA].[WA].[Yakima].[Store 23], [Gender].[F]}\n" + "{[Store].[USA].[WA].[Yakima].[Store 23], [Gender].[M]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[vm]}\n" + "Row #0: 10,771\n" + "Row #0: 10,562\n" + "Row #0: 12,089\n" + "Row #0: 13,574\n" + "Row #0: 12,835\n" + "Row #0: 12,800\n" + "Row #0: 1,064\n" + "Row #0: 1,053\n" + "Row #0: 12,488\n" + "Row #0: 13,591\n" + "Row #0: 20,548\n" + "Row #0: 21,032\n" + "Row #0: 1,096\n" + "Row #0: 1,141\n" + "Row #0: 11,640\n" + "Row #0: 12,936\n" + "Row #0: 13,513\n" + "Row #0: 11,498\n" + "Row #0: 12,068\n" + "Row #0: 11,523\n" + "Row #0: 17,420\n" + "Row #0: 17,837\n" + "Row #0: 1,019\n" + "Row #0: 1,184\n" + "Row #0: 5,007\n" + "Row #0: 6,484\n" + "Row #1: 10759.0\n" + "Row #1: 10759.0\n" + "Row #1: 24587.0\n" + "Row #1: 24587.0\n" + "Row #1: 23835.0\n" + "Row #1: 23835.0\n" + "Row #1: 1696.0\n" + "Row #1: 1696.0\n" + "Row #1: 8515.0\n" + "Row #1: 8515.0\n" + "Row #1: 32393.0\n" + "Row #1: 32393.0\n" + "Row #1: 2348.0\n" + "Row #1: 2348.0\n" + "Row #1: 22734.0\n" + "Row #1: 22734.0\n" + "Row #1: 24110.0\n" + "Row #1: 24110.0\n" + "Row #1: 11889.0\n" + "Row #1: 11889.0\n" + "Row #1: 32411.0\n" + "Row #1: 32411.0\n" + "Row #1: 1860.0\n" + "Row #1: 1860.0\n" + "Row #1: 10589.0\n" + "Row #1: 10589.0\n"); } public void testLeafMembersOfParentChildDimensionAreNativelyEvaluated() { final String query = "SELECT" + " NON EMPTY " + "Crossjoin(" + "{" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Pat Chin].[Gabriel Walton]," + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Pat Chin].[Bishop Meastas]," + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Pat Chin].[Paula Duran]," + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Pat Chin].[Margaret Earley]," + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Pat Chin].[Elizabeth Horne]" + "}," + "[Store].[Store Name].members" + ") on 0 from hr"; checkNative(50, 5, query); } public void testNonLeafMembersOfPCDimensionAreNotNativelyEvaluated() { final String query = "SELECT" + " NON EMPTY " + "Crossjoin(" + "{" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker]," + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Pat Chin].[Gabriel Walton]," + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Pat Chin]," + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays]," + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley]," + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Pat Chin].[Elizabeth Horne]" + "}," + "[Store].[Store Name].members" + ") on 0 from hr"; checkNotNative(9, query); } public void testNativeWithOverriddenNullMemberRepAndNullConstraint() { String preMdx = "SELECT FROM [Sales]"; String mdx = "SELECT \n" + " [Gender].[Gender].MEMBERS ON ROWS\n" + " ,{[Measures].[Unit Sales]} ON COLUMNS\n" + "FROM [Sales]\n" + "WHERE \n" + " [Store Size in SQFT].[All Store Size in SQFTs].[~Missing ]"; // run an mdx query with the default NullMemberRepresentation executeQuery(preMdx); propSaver.set( MondrianProperties.instance().NullMemberRepresentation, "~Missing "); propSaver.set( MondrianProperties.instance().EnableNonEmptyOnAllAxis, true); RolapUtil.reloadNullLiteral(); executeQuery(mdx); } /** * Test case for * MONDRIAN-321, "CrossJoin has no nulls when * EnableNativeNonEmpty=true". */ public void testBugMondrian321() { assertQueryReturns( "WITH SET [#DataSet#] AS 'Crossjoin({Descendants([Customers].[All Customers], 2)}, {[Product].[All Products]})' \n" + "SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} on columns, \n" + "Hierarchize({[#DataSet#]}) on rows FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Customers].[USA].[CA], [Product].[All Products]}\n" + "{[Customers].[USA].[OR], [Product].[All Products]}\n" + "{[Customers].[USA].[WA], [Product].[All Products]}\n" + "Row #0: 74,748\n" + "Row #0: 159,167.84\n" + "Row #1: 67,659\n" + "Row #1: 142,277.07\n" + "Row #2: 124,366\n" + "Row #2: 263,793.22\n"); } public void testNativeCrossjoinWillConstrainUsingArgsFromAllAxes() { String mdx = "select " + "non empty Crossjoin({[Gender].[Gender].[F]},{[Measures].[Unit Sales]}) on 0," + "non empty Crossjoin({[Time].[1997]},{[Promotions].[All Promotions].[Bag Stuffers],[Promotions].[All Promotions].[Best Savings]}) on 1" + " from [Warehouse and Sales]"; SqlPattern accessPattern = new SqlPattern( Dialect.DatabaseProduct.ACCESS, "select `time_by_day`.`the_year` as `c0`, " + "`promotion`.`promotion_name` as `c1` " + "from `time_by_day` as `time_by_day`, " + "`sales_fact_1997` as `sales_fact_1997`, " + "`promotion` as `promotion`, " + "`customer` as `customer` " + "where " + "`sales_fact_1997`.`time_id` = `time_by_day`.`time_id` " + "and `sales_fact_1997`.`promotion_id` = `promotion`.`promotion_id` " + "and `sales_fact_1997`.`customer_id` = `customer`.`customer_id` " + "and (`customer`.`gender` = 'F') " + "and (`time_by_day`.`the_year` = 1997) " + "and (`promotion`.`promotion_name` in ('Bag Stuffers', 'Best Savings')) " + "group by `time_by_day`.`the_year`, `promotion`.`promotion_name`" + " order by 1 ASC, 2 ASC", 623); SqlPattern oraclePattern = new SqlPattern( Dialect.DatabaseProduct.ORACLE, "select \"time_by_day\".\"the_year\" as \"c0\", " + "\"promotion\".\"promotion_name\" as \"c1\" " + "from \"time_by_day\" \"time_by_day\", " + "\"sales_fact_1997\" \"sales_fact_1997\", " + "\"promotion\" \"promotion\", " + "\"customer\" \"customer\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" " + "and \"sales_fact_1997\".\"promotion_id\" = \"promotion\".\"promotion_id\" " + "and \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "and (\"customer\".\"gender\" = 'F') " + "and (\"time_by_day\".\"the_year\" = 1997) " + "and (\"promotion\".\"promotion_name\" in ('Bag Stuffers', 'Best Savings')) " + "group by \"time_by_day\".\"the_year\", \"promotion\".\"promotion_name\" " + "order by 1 ASC, 2 ASC", 611); assertQuerySql(mdx, new SqlPattern[]{oraclePattern, accessPattern}); } public void testLevelMembersWillConstrainUsingArgsFromAllAxes() { String mdx = "select " + "non empty Crossjoin({[Gender].[Gender].[F]},{[Measures].[Unit Sales]}) on 0," + "non empty [Promotions].[Promotions].members on 1" + " from [Warehouse and Sales]"; SqlPattern accessPattern = new SqlPattern( Dialect.DatabaseProduct.ACCESS, "select `promotion`.`promotion_name` as `c0` " + "from `promotion` as `promotion`, " + "`sales_fact_1997` as `sales_fact_1997`, " + "`customer` as `customer` " + "where `sales_fact_1997`.`promotion_id` = `promotion`.`promotion_id` " + "and `sales_fact_1997`.`customer_id` = `customer`.`customer_id` " + "and (`customer`.`gender` = 'F') " + "group by `promotion`.`promotion_name` " + "order by 1 ASC", 357); SqlPattern oraclePattern = new SqlPattern( Dialect.DatabaseProduct.ORACLE, "select \"promotion\".\"promotion_name\" as \"c0\" " + "from \"promotion\" \"promotion\", " + "\"sales_fact_1997\" \"sales_fact_1997\", " + "\"customer\" \"customer\" " + "where \"sales_fact_1997\".\"promotion_id\" = \"promotion\".\"promotion_id\" " + "and \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "and (\"customer\".\"gender\" = 'F') " + "group by \"promotion\".\"promotion_name\" " + "order by 1 ASC", 347); assertQuerySql(mdx, new SqlPattern[]{oraclePattern, accessPattern}); } public void testNativeCrossjoinWillExpandFirstLastChild() { String mdx = "select " + "non empty Crossjoin({[Gender].firstChild,[Gender].lastChild},{[Measures].[Unit Sales]}) on 0," + "non empty Crossjoin({[Time].[1997]},{[Promotions].[All Promotions].[Bag Stuffers],[Promotions].[All Promotions].[Best Savings]}) on 1" + " from [Warehouse and Sales]"; final SqlPattern pattern = new SqlPattern( Dialect.DatabaseProduct.ORACLE, "select \"time_by_day\".\"the_year\" as \"c0\", " + "\"promotion\".\"promotion_name\" as \"c1\" " + "from \"time_by_day\" \"time_by_day\", " + "\"sales_fact_1997\" \"sales_fact_1997\", " + "\"promotion\" \"promotion\", " + "\"customer\" \"customer\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" " + "and \"sales_fact_1997\".\"promotion_id\" = \"promotion\".\"promotion_id\" " + "and \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "and (\"customer\".\"gender\" in ('F', 'M')) " + "and (\"time_by_day\".\"the_year\" = 1997) " + "and (\"promotion\".\"promotion_name\" in ('Bag Stuffers', 'Best Savings')) " + "group by \"time_by_day\".\"the_year\", \"promotion\".\"promotion_name\" " + "order by 1 ASC, 2 ASC", 611); assertQuerySql(mdx, new SqlPattern[]{pattern}); } public void testNativeCrossjoinWillExpandLagInNamedSet() { String mdx = "with set [blah] as '{[Gender].lastChild.lag(1),[Gender].[M]}' " + "select " + "non empty Crossjoin([blah],{[Measures].[Unit Sales]}) on 0," + "non empty Crossjoin({[Time].[1997]},{[Promotions].[All Promotions].[Bag Stuffers],[Promotions].[All Promotions].[Best Savings]}) on 1" + " from [Warehouse and Sales]"; final SqlPattern pattern = new SqlPattern( Dialect.DatabaseProduct.ORACLE, "select \"time_by_day\".\"the_year\" as \"c0\", " + "\"promotion\".\"promotion_name\" as \"c1\" " + "from \"time_by_day\" \"time_by_day\", " + "\"sales_fact_1997\" \"sales_fact_1997\", " + "\"promotion\" \"promotion\", " + "\"customer\" \"customer\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" " + "and \"sales_fact_1997\".\"promotion_id\" = \"promotion\".\"promotion_id\" " + "and \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "and (\"customer\".\"gender\" in ('F', 'M')) " + "and (\"time_by_day\".\"the_year\" = 1997) " + "and (\"promotion\".\"promotion_name\" in ('Bag Stuffers', 'Best Savings')) " + "group by \"time_by_day\".\"the_year\", \"promotion\".\"promotion_name\" " + "order by 1 ASC, 2 ASC", 611); assertQuerySql(mdx, new SqlPattern[]{pattern}); } public void testConstrainedMeasureGetsOptimized() { String mdx = "with member [Measures].[unit sales Male] as '([Measures].[Unit Sales],[Gender].[Gender].[M])' " + "member [Measures].[unit sales Female] as '([Measures].[Unit Sales],[Gender].[Gender].[F])' " + "member [Measures].[store sales Female] as '([Measures].[Store Sales],[Gender].[Gender].[F])' " + "member [Measures].[literal one] as '1' " + "select " + "non empty {{[Measures].[unit sales Male]}, {([Measures].[literal one])}, " + "[Measures].[unit sales Female], [Measures].[store sales Female]} on 0, " + "non empty [Customers].[name].members on 1 " + "from Sales"; final String sqlOracle = MondrianProperties.instance().UseAggregates.get() ? "select \"customer\".\"country\" as \"c0\"," + " \"customer\".\"state_province\" as \"c1\", \"customer\".\"city\" as \"c2\", \"customer\".\"customer_id\" as \"c3\", \"fname\" || ' ' || \"lname\" as \"c4\", \"fname\" || ' ' || \"lname\" as \"c5\", \"customer\".\"gender\" as \"c6\", \"customer\".\"marital_status\" as \"c7\", \"customer\".\"education\" as \"c8\", \"customer\".\"yearly_income\" as \"c9\" from \"customer\" \"customer\", \"agg_l_03_sales_fact_1997\" \"agg_l_03_sales_fact_1997\" where \"agg_l_03_sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" and (\"customer\".\"gender\" in ('M', 'F')) group by \"customer\".\"country\", \"customer\".\"state_province\", \"customer\".\"city\", \"customer\".\"customer_id\", \"fname\" || ' ' || \"lname\", \"customer\".\"gender\", \"customer\".\"marital_status\", \"customer\".\"education\", \"customer\".\"yearly_income\" order by \"customer\".\"country\" ASC NULLS LAST, \"customer\".\"state_province\" ASC NULLS LAST, \"customer\".\"city\" ASC NULLS LAST, \"fname\" || ' ' || \"lname\" ASC NULLS LAST" : "select \"customer\".\"country\" as \"c0\"," + " \"customer\".\"state_province\" as \"c1\", \"customer\".\"city\" as \"c2\", \"customer\".\"customer_id\" as \"c3\", \"fname\" || ' ' || \"lname\" as \"c4\", \"fname\" || ' ' || \"lname\" as \"c5\", \"customer\".\"gender\" as \"c6\", \"customer\".\"marital_status\" as \"c7\", \"customer\".\"education\" as \"c8\", \"customer\".\"yearly_income\" as \"c9\" from \"customer\" \"customer\", \"sales_fact_1997\" \"sales_fact_1997\" where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" and (\"customer\".\"gender\" in ('M', 'F')) group by \"customer\".\"country\", \"customer\".\"state_province\", \"customer\".\"city\", \"customer\".\"customer_id\", \"fname\" || ' ' || \"lname\", \"customer\".\"gender\", \"customer\".\"marital_status\", \"customer\".\"education\", \"customer\".\"yearly_income\" order by \"customer\".\"country\" ASC NULLS LAST, \"customer\".\"state_province\" ASC NULLS LAST, \"customer\".\"city\" ASC NULLS LAST, \"fname\" || ' ' || \"lname\" ASC NULLS LAST"; assertQuerySql( mdx, new SqlPattern[]{ new SqlPattern( Dialect.DatabaseProduct.ORACLE, sqlOracle, sqlOracle.length())}); } public void testNestedMeasureConstraintsGetOptimized() { String mdx = "with member [Measures].[unit sales Male] as '([Measures].[Unit Sales],[Gender].[Gender].[M])' " + "member [Measures].[unit sales Male Married] as '([Measures].[unit sales Male],[Marital Status].[Marital Status].[M])' " + "select " + "non empty {[Measures].[unit sales Male Married]} on 0, " + "non empty [Customers].[name].members on 1 " + "from Sales"; final String sqlOracle = MondrianProperties.instance().UseAggregates.get() ? "select \"customer\".\"country\" as \"c0\"," + " \"customer\".\"state_province\" as \"c1\", \"customer\".\"city\" as \"c2\", \"customer\".\"customer_id\" as \"c3\", \"fname\" || ' ' || \"lname\" as \"c4\", \"fname\" || ' ' || \"lname\" as \"c5\", \"customer\".\"gender\" as \"c6\", \"customer\".\"marital_status\" as \"c7\", \"customer\".\"education\" as \"c8\", \"customer\".\"yearly_income\" as \"c9\" from \"customer\" \"customer\", \"agg_l_03_sales_fact_1997\" \"agg_l_03_sales_fact_1997\" where \"agg_l_03_sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" and (\"customer\".\"gender\" = 'M') and (\"customer\".\"marital_status\" = 'M') group by \"customer\".\"country\", \"customer\".\"state_province\", \"customer\".\"city\", \"customer\".\"customer_id\", \"fname\" || ' ' || \"lname\", \"customer\".\"gender\", \"customer\".\"marital_status\", \"customer\".\"education\", \"customer\".\"yearly_income\" order by \"customer\".\"country\" ASC NULLS LAST, \"customer\".\"state_province\" ASC NULLS LAST, \"customer\".\"city\" ASC NULLS LAST, \"fname\" || ' ' || \"lname\" ASC NULLS LAST" : "select \"customer\".\"country\" as \"c0\", " + "\"customer\".\"state_province\" as \"c1\", " + "\"customer\".\"city\" as \"c2\", " + "\"customer\".\"customer_id\" as \"c3\", " + "\"fname\" || \" \" || \"lname\" as \"c4\", " + "\"fname\" || \" \" || \"lname\" as \"c5\", " + "\"customer\".\"gender\" as \"c6\", " + "\"customer\".\"marital_status\" as \"c7\", " + "\"customer\".\"education\" as \"c8\", " + "\"customer\".\"yearly_income\" as \"c9\" " + "from \"customer\" \"customer\", " + "\"sales_fact_1997\" \"sales_fact_1997\" " + "where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "and (\"customer\".\"gender\" = \"M\") " + "and (\"customer\".\"marital_status\" = \"M\") " + "group by \"customer\".\"country\", " + "\"customer\".\"state_province\", " + "\"customer\".\"city\", " + "\"customer\".\"customer_id\", " + "\"fname\" || \" \" || \"lname\", " + "\"customer\".\"gender\", " + "\"customer\".\"marital_status\", " + "\"customer\".\"education\", " + "\"customer\".\"yearly_income\" " + "order by \"customer\".\"country\" ASC NULLS LAST, " + "\"customer\".\"state_province\" ASC NULLS LAST, " + "\"customer\".\"city\" ASC NULLS LAST, " + "\"fname\" || \" \" || \"lname\" ASC NULLS LAST"; SqlPattern pattern = new SqlPattern( Dialect.DatabaseProduct.ORACLE, sqlOracle, sqlOracle.length()); assertQuerySql(mdx, new SqlPattern[]{pattern}); } public void testNonUniformNestedMeasureConstraintsGetOptimized() { if (MondrianProperties.instance().UseAggregates.get()) { // This test can't work with aggregates becaused // the aggregate table doesn't include member properties. return; } String mdx = "with member [Measures].[unit sales Male] as '([Measures].[Unit Sales],[Gender].[Gender].[M])' " + "member [Measures].[unit sales Female] as '([Measures].[Unit Sales],[Gender].[Gender].[F])' " + "member [Measures].[unit sales Male Married] as '([Measures].[unit sales Male],[Marital Status].[Marital Status].[M])' " + "select " + "non empty {[Measures].[unit sales Male Married],[Measures].[unit sales Female]} on 0, " + "non empty [Customers].[name].members on 1 " + "from Sales"; final SqlPattern pattern = new SqlPattern( Dialect.DatabaseProduct.ORACLE, "select \"customer\".\"country\" as \"c0\", " + "\"customer\".\"state_province\" as \"c1\", " + "\"customer\".\"city\" as \"c2\", " + "\"customer\".\"customer_id\" as \"c3\", " + "\"fname\" || ' ' || \"lname\" as \"c4\", " + "\"fname\" || ' ' || \"lname\" as \"c5\", " + "\"customer\".\"gender\" as \"c6\", " + "\"customer\".\"marital_status\" as \"c7\", " + "\"customer\".\"education\" as \"c8\", " + "\"customer\".\"yearly_income\" as \"c9\" " + "from \"customer\" \"customer\", \"sales_fact_1997\" \"sales_fact_1997\" " + "where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "and (\"customer\".\"gender\" in ('M', 'F')) " + "group by \"customer\".\"country\", " + "\"customer\".\"state_province\", " + "\"customer\".\"city\", " + "\"customer\".\"customer_id\", " + "\"fname\" || ' ' || \"lname\", " + "\"customer\".\"gender\", " + "\"customer\".\"marital_status\", " + "\"customer\".\"education\", " + "\"customer\".\"yearly_income\" " + "order by \"customer\".\"country\" ASC NULLS LAST," + " \"customer\".\"state_province\" ASC NULLS LAST," + " \"customer\".\"city\" ASC NULLS LAST, " + "\"fname\" || ' ' || \"lname\" ASC NULLS LAST", 852); assertQuerySql(mdx, new SqlPattern[]{pattern}); } public void testNonUniformConstraintsAreNotUsedForOptimization() { String mdx = "with member [Measures].[unit sales Male] as '([Measures].[Unit Sales],[Gender].[Gender].[M])' " + "member [Measures].[unit sales Married] as '([Measures].[Unit Sales],[Marital Status].[Marital Status].[M])' " + "select " + "non empty {[Measures].[unit sales Male], [Measures].[unit sales Married]} on 0, " + "non empty [Customers].[name].members on 1 " + "from Sales"; final String sqlOracle = "select \"customer\".\"country\" as \"c0\", \"customer\".\"state_province\" as \"c1\", \"customer\".\"city\" as \"c2\", \"customer\".\"customer_id\" as \"c3\", \"fname\" || ' ' || \"lname\" as \"c4\", \"fname\" || ' ' || \"lname\" as \"c5\", \"customer\".\"gender\" as \"c6\", \"customer\".\"marital_status\" as \"c7\", \"customer\".\"education\" as \"c8\", \"customer\".\"yearly_income\" as \"c9\" from \"customer\" \"customer\", \"sales_fact_1997\" \"sales_fact_1997\" where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" and (\"customer\".\"gender\" in ('M', 'F')) group by \"customer\".\"country\", \"customer\".\"state_province\", \"customer\".\"city\", \"customer\".\"customer_id\", \"fname\" || ' ' || \"lname\", \"customer\".\"gender\", \"customer\".\"marital_status\", \"customer\".\"education\", \"customer\".\"yearly_income\" order by \"customer\".\"country\" ASC NULLS LAST, \"customer\".\"state_province\" ASC NULLS LAST, \"customer\".\"city\" ASC NULLS LAST, \"fname\" || ' ' || \"lname\" ASC NULLS LAST"; final SqlPattern pattern = new SqlPattern( Dialect.DatabaseProduct.ORACLE, sqlOracle, sqlOracle.length()); assertQuerySqlOrNot( getTestContext(), mdx, new SqlPattern[]{pattern},true, false, true); } public void testMeasureConstraintsInACrossjoinHaveCorrectResults() { //http://jira.pentaho.com/browse/MONDRIAN-715 propSaver.set(MondrianProperties.instance().EnableNativeNonEmpty, true); String mdx = "with " + " member [Measures].[aa] as '([Measures].[Store Cost],[Gender].[M])'" + " member [Measures].[bb] as '([Measures].[Store Cost],[Gender].[F])'" + " select" + " non empty " + " crossjoin({[Store].[All Stores].[USA].[CA]}," + " {[Measures].[aa], [Measures].[bb]}) on columns," + " non empty " + " [Marital Status].[Marital Status].members on rows" + " from sales"; assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA], [Measures].[aa]}\n" + "{[Store].[USA].[CA], [Measures].[bb]}\n" + "Axis #2:\n" + "{[Marital Status].[M]}\n" + "{[Marital Status].[S]}\n" + "Row #0: 15,339.94\n" + "Row #0: 15,941.98\n" + "Row #1: 16,598.87\n" + "Row #1: 15,649.64\n"); } public void testContextAtAllWorksWithConstraint() { TestContext ctx = TestContext.instance().create( null, " \n" + "
\n" + "\n" + " \n" + "
\n" + " \n" + " \n" + " " + " \n" + " \n", null, null, null, null); String mdx = " select " + " NON EMPTY {[Measures].[Unit Sales]} ON COLUMNS, " + " NON EMPTY {[Gender].[Gender].Members} ON ROWS " + " from [onlyGender] "; ctx.assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 131,558\n" + "Row #1: 135,215\n"); } /*** * Before the fix this test would throw an IndexOutOfBounds exception * in SqlConstraintUtils.removeDefaultMembers. The method assumed that the * first member in the list would exist and be a measure. But, when the * default measure is calculated, it would have already been removed from * the list by removeCalculatedMembers, and thus the assumption was wrong. */ public void testCalculatedDefaultMeasureOnVirtualCubeNoThrowException() { propSaver.set(MondrianProperties.instance().EnableNativeNonEmpty, true); final TestContext context = TestContext.instance().withSchema( "" + " " + " " + "
" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "
" + " " + " " + " " + " 1" + " " + " " + " " + " " + " " + " " + " " + ""); context.assertQueryReturns( "select " + " [Measures].[Unit Sales] on COLUMNS, " + " NON EMPTY {[Store].[Store State].Members} ON ROWS " + " from [virtual] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 74,748\n" + "Row #1: 67,659\n" + "Row #2: 124,366\n"); } /** * Test case for * MONDRIAN-734, "Exception thrown when creating a "New Analysis View" with * JPivot". */ public void testExpandNonNativeWithEnableNativeCrossJoin() { final MondrianProperties mondrianProperties = MondrianProperties.instance(); propSaver.set(mondrianProperties.EnableNativeCrossJoin, true); propSaver.set(mondrianProperties.ExpandNonNative, true); String mdx = "select NON EMPTY {[Measures].[Unit Sales]} ON COLUMNS," + " NON EMPTY Crossjoin(Hierarchize(Crossjoin({[Store].[All Stores]}, Crossjoin({[Store Size in SQFT].[All Store Size in SQFTs]}, Crossjoin({[Store Type].[All Store Types]}, Union(Crossjoin({[Time].[1997]}, {[Product].[All Products]}), Crossjoin({[Time].[1997]}, [Product].[All Products].Children)))))), {([Promotion Media].[All Media], [Promotions].[All Promotions], [Customers].[All Customers], [Education Level].[All Education Levels], [Gender].[All Gender], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes])}) ON ROWS" + " from [Sales]"; assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[All Stores], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[All Store Types], [Time].[1997], [Product].[All Products], [Promotion Media].[All Media], [Promotions].[All Promotions], [Customers].[All Customers], [Education Level].[All Education Levels], [Gender].[All Gender], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes]}\n" + "{[Store].[All Stores], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[All Store Types], [Time].[1997], [Product].[Drink], [Promotion Media].[All Media], [Promotions].[All Promotions], [Customers].[All Customers], [Education Level].[All Education Levels], [Gender].[All Gender], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes]}\n" + "{[Store].[All Stores], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[All Store Types], [Time].[1997], [Product].[Food], [Promotion Media].[All Media], [Promotions].[All Promotions], [Customers].[All Customers], [Education Level].[All Education Levels], [Gender].[All Gender], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes]}\n" + "{[Store].[All Stores], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[All Store Types], [Time].[1997], [Product].[Non-Consumable], [Promotion Media].[All Media], [Promotions].[All Promotions], [Customers].[All Customers], [Education Level].[All Education Levels], [Gender].[All Gender], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes]}\n" + "Row #0: 266,773\n" + "Row #1: 24,597\n" + "Row #2: 191,940\n" + "Row #3: 50,236\n"); } /** * Test case for * MONDRIAN-695, "Unexpected data set may returned when MDX slicer contains * multiple dimensions". */ public void testNonEmptyCJWithMultiPositionSlicer() { final String mdx = "select NON EMPTY NonEmptyCrossJoin([Measures].[Sales Count], [Store].[USA].Children) ON COLUMNS, " + " NON EMPTY CrossJoin({[Customers].[All Customers]}, {([Promotions].[Bag Stuffers] : [Promotions].[Bye Bye Baby])}) ON ROWS " + "from [Sales Ragged] " + "where ({[Product].[Drink]} * {[Time].[1997].[Q1], [Time].[1997].[Q2]})"; final String expected = "Axis #0:\n" + "{[Product].[Drink], [Time].[1997].[Q1]}\n" + "{[Product].[Drink], [Time].[1997].[Q2]}\n" + "Axis #1:\n" + "{[Measures].[Sales Count], [Store].[USA].[CA]}\n" + "{[Measures].[Sales Count], [Store].[USA].[USA].[Washington]}\n" + "{[Measures].[Sales Count], [Store].[USA].[WA]}\n" + "Axis #2:\n" + "{[Customers].[All Customers], [Promotions].[Bag Stuffers]}\n" + "{[Customers].[All Customers], [Promotions].[Best Savings]}\n" + "{[Customers].[All Customers], [Promotions].[Big Promo]}\n" + "{[Customers].[All Customers], [Promotions].[Big Time Savings]}\n" + "{[Customers].[All Customers], [Promotions].[Bye Bye Baby]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 2\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: 13\n" + "Row #2: \n" + "Row #2: \n" + "Row #2: 9\n" + "Row #3: \n" + "Row #3: 12\n" + "Row #3: \n" + "Row #4: 1\n" + "Row #4: 21\n" + "Row #4: \n"; propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, true); propSaver.set( MondrianProperties.instance().ExpandNonNative, true); // Get a fresh connection; Otherwise the mondrian property setting // is not refreshed for this parameter. checkNative( 0, 5, mdx, expected, true); } void clearAndHardenCache(MemberCacheHelper helper) { helper.mapLevelToMembers.setCache( new HardSmartCache, List>()); helper.mapMemberToChildren.setCache( new HardSmartCache, List>()); helper.mapKeyToMember.clear(); } SmartMemberReader getSmartMemberReader(String hierName) { Connection con = getTestContext().getConnection(); return getSmartMemberReader(con, hierName); } SmartMemberReader getSmartMemberReader(Connection con, String hierName) { RolapCube cube = (RolapCube) con.getSchema().lookupCube("Sales", true); RolapSchemaReader schemaReader = (RolapSchemaReader) cube.getSchemaReader(); RolapHierarchy hierarchy = (RolapHierarchy) cube.lookupHierarchy( new Id.Segment(hierName, Id.Quoting.UNQUOTED), false); assertNotNull(hierarchy); return (SmartMemberReader) hierarchy.createMemberReader(schemaReader.getRole()); } SmartMemberReader getSharedSmartMemberReader(String hierName) { Connection con = getTestContext().getConnection(); return getSharedSmartMemberReader(con, hierName); } SmartMemberReader getSharedSmartMemberReader( Connection con, String hierName) { RolapCube cube = (RolapCube) con.getSchema().lookupCube("Sales", true); RolapSchemaReader schemaReader = (RolapSchemaReader) cube.getSchemaReader(); RolapCubeHierarchy hierarchy = (RolapCubeHierarchy) cube.lookupHierarchy( new Id.Segment(hierName, Id.Quoting.UNQUOTED), false); assertNotNull(hierarchy); return (SmartMemberReader) hierarchy.getRolapHierarchy() .createMemberReader(schemaReader.getRole()); } RolapEvaluator getEvaluator(Result res, int[] pos) { while (res instanceof NonEmptyResult) { res = ((NonEmptyResult) res).underlying; } return (RolapEvaluator) ((RolapResult) res).getEvaluator(pos); } public void testFilterChildlessSnowflakeMembers2() { if (MondrianProperties.instance().FilterChildlessSnowflakeMembers.get()) { // If FilterChildlessSnowflakeMembers is true, then // [Product].[Drink].[Baking Goods].[Coffee] does not even exist! return; } // children across a snowflake boundary assertQueryReturns( "select [Product].[Drink].[Baking Goods].[Dry Goods].[Coffee].Children on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n"); } public void testFilterChildlessSnowflakeMembers() { propSaver.set( MondrianProperties.instance().FilterChildlessSnowflakeMembers, false); SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.MYSQL, "select `product_class`.`product_family` as `c0` " + "from `product_class` as `product_class` " + "group by `product_class`.`product_family` " + "order by ISNULL(`product_class`.`product_family`) ASC," + " `product_class`.`product_family` ASC", null) }; final TestContext context = getTestContext().withFreshConnection(); try { assertQuerySql( context, "select [Product].[Product Family].Members on 0\n" + "from [Sales]", patterns); // note that returns an extra member, // [Product].[Drink].[Baking Goods] context.assertQueryReturns( "select [Product].[Drink].Children on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "{[Product].[Drink].[Baking Goods]}\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Drink].[Dairy]}\n" + "Row #0: 6,838\n" + "Row #0: \n" + "Row #0: 13,573\n" + "Row #0: 4,186\n"); // [Product].[Drink].[Baking Goods] has one child, but no fact data context.assertQueryReturns( "select [Product].[Drink].[Baking Goods].Children on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Drink].[Baking Goods].[Dry Goods]}\n" + "Row #0: \n"); // NON EMPTY filters out that child context.assertQueryReturns( "select non empty [Product].[Drink].[Baking Goods].Children on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n"); // [Product].[Drink].[Baking Goods].[Dry Goods] has one child, but // no fact data context.assertQueryReturns( "select [Product].[Drink].[Baking Goods].[Dry Goods].Children on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Drink].[Baking Goods].[Dry Goods].[Coffee]}\n" + "Row #0: \n"); // NON EMPTY filters out that child context.assertQueryReturns( "select non empty [Product].[Drink].[Baking Goods].[Dry Goods].Children on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n"); // [Coffee] has no children context.assertQueryReturns( "select [Product].[Drink].[Baking Goods].[Dry Goods].[Coffee].Children on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n"); context.assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + " [Product].[Product Family].Members on 1\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 24,597\n" + "Row #1: 191,940\n" + "Row #2: 50,236\n"); } finally { context.close(); } } /** * Test case for * MONDRIAN-897, "ClassCastException in * CrossJoinArgFactory.allArgsCheapToExpand when defining a NamedSet as * another NamedSet". */ public void testBugMondrian897DoubleNamedSetDefinitions() { TestContext ctx = getTestContext(); ctx.assertQueryReturns( "WITH SET [CustomerSet] as {[Customers].[Canada].[BC].[Burnaby].[Alexandra Wellington], [Customers].[USA].[WA].[Tacoma].[Eric Coleman]} " + "SET [InterestingCustomers] as [CustomerSet] " + "SET [TimeRange] as {[Time].[1998].[Q1], [Time].[1998].[Q2]} " + "SELECT {[Measures].[Store Sales]} ON COLUMNS, " + "CrossJoin([InterestingCustomers], [TimeRange]) ON ROWS " + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Customers].[Canada].[BC].[Burnaby].[Alexandra Wellington], [Time].[1998].[Q1]}\n" + "{[Customers].[Canada].[BC].[Burnaby].[Alexandra Wellington], [Time].[1998].[Q2]}\n" + "{[Customers].[USA].[WA].[Tacoma].[Eric Coleman], [Time].[1998].[Q1]}\n" + "{[Customers].[USA].[WA].[Tacoma].[Eric Coleman], [Time].[1998].[Q2]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: \n" + "Row #3: \n"); } } // End NonEmptyTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/RolapResultTest.csv0000644000175000017500000000117011735330606024415 0ustar drazzibdrazzib# RolapResultTest.csv # @version $Id:$ # fact table ## TableName: FT1 ## ColumnNames: d1_id,d2_id,value ## ColumnTypes: INTEGER,INTEGER,DECIMAL(10,2) ## NosOfRows: 3 1,1,5 2,2,10 3,3,15 ## TableName: FT2 ## ColumnNames: d1_id,d2_id,value,vextra ## ColumnTypes: INTEGER,INTEGER,DECIMAL(10,2),DECIMAL(10,2):null ## NosOfRows: 9 1,1,5 2,2,10 3,3,15 4,1,2 4,2,4 4,3,8 1,4,3 2,4,9 3,4,27 # dimension ## TableName: D1 ## ColumnNames: d1_id,name ## ColumnTypes: INTEGER,VARCHAR(20) ## NosOfRows: 4 1,a 2,b 3,c 4,d # dimension ## TableName: D2 ## ColumnNames: d2_id,name ## ColumnTypes: INTEGER,VARCHAR(20) ## NosOfRows: 4 1,x 2,y 3,z 4,w mondrian-3.4.1/testsrc/main/mondrian/rolap/HighDimensionsTest.java0000644000175000017500000004424611735330606025212 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.ResultStyle; import mondrian.olap.*; import mondrian.test.FoodMartTestCase; import mondrian.test.TestContext; import mondrian.util.Bug; import junit.framework.Assert; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.List; /** * Unit-test for non cacheable elementos of high dimensions. * * @author jlopez, lcanals * @since May, 2008 */ public class HighDimensionsTest extends FoodMartTestCase { public HighDimensionsTest() { } public HighDimensionsTest(String name) { super(name); } public void testBug1971406() throws Exception { if (!MondrianProperties.instance().EnableNativeCrossJoin.get()) { return; } final Connection connection = TestContext.instance() .getConnection(); Query query = connection.parseQuery( "with set necj as " + "NonEmptyCrossJoin(NonEmptyCrossJoin(" + "[Customers].[Name].members,[Store].[Store Name].members)," + "[Product].[Product Name].members) " + "select {[Measures].[Unit Sales]} on columns," + "tail(intersect(necj,necj,ALL),5) on rows from sales"); final long t0 = System.currentTimeMillis(); Result result = connection.execute(query); for (final Position o : result.getAxes()[0].getPositions()) { assertNotNull(o.get(0)); } final long t1 = System.currentTimeMillis(); final long elapsed = t1 - t0; // scale up for slower CPUs double scaleFactor = computeCpuScaleFactor(); final long target = (long) (90000 * scaleFactor); assertTrue( "Query execution took " + elapsed + " milliseconds, " + "which is outside target of " + target + " milliseconds", elapsed <= target); } /** * Computes a scale factor indicating the relative system speed. This is * necessary for environments that might run code coverage, etc. * *

The method performs a benchmark computation, and returns is the ratio * of the elapsed time for the current system versus the baseline. If the * current system takes, say, 2 seconds to perform the benchmark * computation, the method returns 2.0, meaning that the system would be * expected to take twice as long to do a typical CPU-intensive task. * * @return Multiplier for how long this system would require to do a typical * CPU-intensive task versus the baseline system */ private static double computeCpuScaleFactor() { final long nt0 = System.currentTimeMillis(); int t = 0; for (int i = 0; i < Integer.MAX_VALUE; i++) { t += i; } final long nt1 = System.currentTimeMillis(); long benchmarkTime = 0; // this check is here to make sure the compiler doesn't optimize // out the above loop if (t > 0) { benchmarkTime = nt1 - nt0; } // The benchmark takes takes about 1 second on the baseline system, a // Dell Latitude D820 Laptop. double scaleFactor = (double) benchmarkTime / 1000d; if (false) { System.out.println("scale factor = " + scaleFactor); } return scaleFactor; } public void testPromotionsTwoDimensions() throws Exception { if (!Bug.BugMondrian486Fixed) { return; } execHighCardTest( "select {[Measures].[Unit Sales]} on columns,\n" + "{[Promotions].[Promotion Name].Members} on rows\n" + "from [Sales Ragged]", 1, "Promotions", highCardResults, null, true, 51); } public void testHead() throws Exception { if (!Bug.BugMondrian486Fixed) { return; } execHighCardTest( "select {[Measures].[Unit Sales]} on columns,\n" + "head({[Promotions].[Promotion Name].Members},40) " + "on rows from [Sales Ragged]", 1, "Promotions", first40HighCardResults, null, true, 40); } // disabled pending fix of bug MONDRIAN-527 public void _testTopCount() throws Exception { final Connection connection = TestContext.instance() .getConnection(); final StringBuffer buffer = new StringBuffer(); Query query = connection.parseQuery( "select {[Measures].[Unit Sales]} on columns,\n" + "TopCount({[Promotions].[Promotion Name].Members},41, " + "[Measures].[Unit Sales]) " + "on rows from [Sales Ragged]"); Result result = connection.execute(query); int i = 0; String topcount40HighCardResults = null; String topcount41HighCardResults = null; for (final Position o : result.getAxes()[1].getPositions()) { buffer.append(o.get(0)); i++; if (i == 40) { topcount40HighCardResults = buffer.toString(); } } topcount41HighCardResults = buffer.toString(); execHighCardTest( "select {[Measures].[Unit Sales]} on columns,\n" + "TopCount({[Promotions].[Promotion Name].Members},40, " + "[Measures].[Unit Sales]) " + "on rows from [Sales Ragged]", 1, "Promotions", topcount40HighCardResults, topcount40Cells, false, 40); execHighCardTest( "select {[Measures].[Unit Sales]} on columns,\n" + "TopCount({[Promotions].[Promotion Name].Members},41, " + "[Measures].[Unit Sales]) " + "on rows from [Sales Ragged]", 1, "Promotions", topcount41HighCardResults, topcount41Cells, false, 41); execHighCardTest( "select {[Measures].[Unit Sales]} on columns,\n" + "TopCount({[Promotions].[Promotion Name].Members},40, " + "[Measures].[Unit Sales]) " + "on rows from [Sales Ragged]", 1, "Promotions", topcount40HighCardResults, topcount40Cells, false, 40); } public void testNonEmpty() throws Exception { if (!Bug.BugMondrian486Fixed) { return; } execHighCardTest( "select {[Measures].[Unit Sales]} on columns,\n" + "non empty {[Promotions].[Promotion Name].Members} " + "on rows from [Sales Ragged]", 1, "Promotions", nonEmptyHighCardResults, nonEmptyCells, true, 48); } public void testFilter() throws Exception { if (!Bug.BugMondrian486Fixed) { return; } execHighCardTest( "select [Measures].[Unit Sales] on columns, " + "filter([Promotions].[Promotion Name].Members, " + "[Measures].[Unit Sales]>0) " + "on rows from [Sales Ragged]", 1, "Promotions", nonEmptyHighCardResults, nonEmptyCells, true, 48); execHighCardTest( "select [Measures].[Unit Sales] on columns, " + "filter([Promotions].[Promotion Name].Members, " + "[Measures].[Unit Sales]>4000) " + "on rows from [Sales Ragged]", 1, "Promotions", moreThan4000highCardResults, moreThan4000Cells, true, 3); } // // Private Stuff -------------------------------------------- // /** * Executes query test trying to [Promotions].[Promotion Name] elements * into an axis from the results. */ private void execHighCardTest( final String queryString, final int axisIndex, final String highDimensionName, final String results, final String results2, final boolean shouldForget, final int resultLimit) throws Exception { propSaver.set(MondrianProperties.instance().ResultLimit, resultLimit); final TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales Ragged", "" + " " + "

" + " " + " " + ""); final Connection connection = testContext.getConnection(); final Query query = connection.parseQuery(queryString); query.setResultStyle(ResultStyle.ITERABLE); Result result = connection.execute(query); StringBuffer buffer = new StringBuffer(); StringBuffer buffer2 = new StringBuffer(); final List softReferences = new ArrayList(); // Tests results aren't got from database before this point int ii = 0; for (final Position o : result.getAxes()[axisIndex].getPositions()) { assertNotNull(o.get(0)); buffer2.append(result.getCell( new int[]{0, ii}).getValue().toString()); ii++; softReferences.add(new SoftReference(o.get(0))); buffer.append(o.get(0).toString()); } assertEquals(buffer.toString().length(), results.length()); if (results2 != null) { assertEquals(buffer2.toString().length(), results2.length()); } buffer2 = null; buffer = null; if (!shouldForget) { return; } // Tests that really results over ResultLimit are erased from // memory final List overloader = new ArrayList(); try { for (;;) { overloader.add(new long[99999999]); } } catch (OutOfMemoryError out) { // OK, outofmemory } System.gc(); for (int i = 4; i < ii - 40; i++) { assertNull(softReferences.get(i).get()); } for (int i = 4; i < ii - 40; i++) { try { result.getAxes()[axisIndex].getPositions().get(i).get(0); Assert.fail("Expected exception"); } catch (RuntimeException nsee) { // Everything is ok: RuntimeException of type // RuntimeException is expected. } } } private static final String first40HighCardResults = "[Promotions].[Bag Stuffers]" + "[Promotions].[Best Savings]" + "[Promotions].[Big Promo]" + "[Promotions].[Big Time Discounts]" + "[Promotions].[Big Time Savings]" + "[Promotions].[Bye Bye Baby]" + "[Promotions].[Cash Register Lottery]" + "[Promotions].[Coupon Spectacular]" + "[Promotions].[Dimes Off]" + "[Promotions].[Dollar Cutters]" + "[Promotions].[Dollar Days]" + "[Promotions].[Double Down Sale]" + "[Promotions].[Double Your Savings]" + "[Promotions].[Fantastic Discounts]" + "[Promotions].[Free For All]" + "[Promotions].[Go For It]" + "[Promotions].[Green Light Days]" + "[Promotions].[Green Light Special]" + "[Promotions].[High Roller Savings]" + "[Promotions].[I Cant Believe It Sale]" + "[Promotions].[Money Grabbers]" + "[Promotions].[Money Savers]" + "[Promotions].[Mystery Sale]" + "[Promotions].[No Promotion]" + "[Promotions].[One Day Sale]" + "[Promotions].[Pick Your Savings]" + "[Promotions].[Price Cutters]" + "[Promotions].[Price Destroyers]" + "[Promotions].[Price Savers]" + "[Promotions].[Price Slashers]" + "[Promotions].[Price Smashers]" + "[Promotions].[Price Winners]" + "[Promotions].[Sale Winners]" + "[Promotions].[Sales Days]" + "[Promotions].[Sales Galore]" + "[Promotions].[Save-It Sale]" + "[Promotions].[Saving Days]" + "[Promotions].[Savings Galore]" + "[Promotions].[Shelf Clearing Days]" + "[Promotions].[Shelf Emptiers]"; private static final String nonEmptyHighCardResults = "[Promotions].[Bag Stuffers]" + "[Promotions].[Best Savings]" + "[Promotions].[Big Promo]" + "[Promotions].[Big Time Discounts]" + "[Promotions].[Big Time Savings]" + "[Promotions].[Bye Bye Baby]" + "[Promotions].[Cash Register Lottery]" + "[Promotions].[Dimes Off]" + "[Promotions].[Dollar Cutters]" + "[Promotions].[Dollar Days]" + "[Promotions].[Double Down Sale]" + "[Promotions].[Double Your Savings]" + "[Promotions].[Free For All]" + "[Promotions].[Go For It]" + "[Promotions].[Green Light Days]" + "[Promotions].[Green Light Special]" + "[Promotions].[High Roller Savings]" + "[Promotions].[I Cant Believe It Sale]" + "[Promotions].[Money Savers]" + "[Promotions].[Mystery Sale]" + "[Promotions].[No Promotion]" + "[Promotions].[One Day Sale]" + "[Promotions].[Pick Your Savings]" + "[Promotions].[Price Cutters]" + "[Promotions].[Price Destroyers]" + "[Promotions].[Price Savers]" + "[Promotions].[Price Slashers]" + "[Promotions].[Price Smashers]" + "[Promotions].[Price Winners]" + "[Promotions].[Sale Winners]" + "[Promotions].[Sales Days]" + "[Promotions].[Sales Galore]" + "[Promotions].[Save-It Sale]" + "[Promotions].[Saving Days]" + "[Promotions].[Savings Galore]" + "[Promotions].[Shelf Clearing Days]" + "[Promotions].[Shelf Emptiers]" + "[Promotions].[Super Duper Savers]" + "[Promotions].[Super Savers]" + "[Promotions].[Super Wallet Savers]" + "[Promotions].[Three for One]" + "[Promotions].[Tip Top Savings]" + "[Promotions].[Two Day Sale]" + "[Promotions].[Two for One]" + "[Promotions].[Unbeatable Price Savers]" + "[Promotions].[Wallet Savers]" + "[Promotions].[Weekend Markdown]" + "[Promotions].[You Save Days]"; private static final String highCardResults = "[Promotions].[Bag Stuffers]" + "[Promotions].[Best Savings]" + "[Promotions].[Big Promo]" + "[Promotions].[Big Time Discounts]" + "[Promotions].[Big Time Savings]" + "[Promotions].[Bye Bye Baby]" + "[Promotions].[Cash Register Lottery]" + "[Promotions].[Coupon Spectacular]" + "[Promotions].[Dimes Off]" + "[Promotions].[Dollar Cutters]" + "[Promotions].[Dollar Days]" + "[Promotions].[Double Down Sale]" + "[Promotions].[Double Your Savings]" + "[Promotions].[Fantastic Discounts]" + "[Promotions].[Free For All]" + "[Promotions].[Go For It]" + "[Promotions].[Green Light Days]" + "[Promotions].[Green Light Special]" + "[Promotions].[High Roller Savings]" + "[Promotions].[I Cant Believe It Sale]" + "[Promotions].[Money Grabbers]" + "[Promotions].[Money Savers]" + "[Promotions].[Mystery Sale]" + "[Promotions].[No Promotion]" + "[Promotions].[One Day Sale]" + "[Promotions].[Pick Your Savings]" + "[Promotions].[Price Cutters]" + "[Promotions].[Price Destroyers]" + "[Promotions].[Price Savers]" + "[Promotions].[Price Slashers]" + "[Promotions].[Price Smashers]" + "[Promotions].[Price Winners]" + "[Promotions].[Sale Winners]" + "[Promotions].[Sales Days]" + "[Promotions].[Sales Galore]" + "[Promotions].[Save-It Sale]" + "[Promotions].[Saving Days]" + "[Promotions].[Savings Galore]" + "[Promotions].[Shelf Clearing Days]" + "[Promotions].[Shelf Emptiers]" + "[Promotions].[Super Duper Savers]" + "[Promotions].[Super Savers]" + "[Promotions].[Super Wallet Savers]" + "[Promotions].[Three for One]" + "[Promotions].[Tip Top Savings]" + "[Promotions].[Two Day Sale]" + "[Promotions].[Two for One]" + "[Promotions].[Unbeatable Price Savers]" + "[Promotions].[Wallet Savers]" + "[Promotions].[Weekend Markdown]" + "[Promotions].[You Save Days]"; private static final String moreThan4000highCardResults = "[Promotions].[Cash Register Lottery]" + "[Promotions].[No Promotion]" + "[Promotions].[Price Savers]"; private static final String moreThan4000Cells = "4792.0195448.04094.0"; private static final String nonEmptyCells = "901.02081.01789.0932.0700.0921.04792.01219.0" + "781.01652.01959.0843.01638.0689.01607.0436.0" + "2654.0253.0899.01021.0195448.01973.0323.01624.0" + "2173.04094.01148.0504.01294.0444.02055.02572.0" + "2203.01446.01382.0754.02118.02628.02497.01183.0" + "1155.0525.02053.0335.02100.0916.0914.03145.0"; private static final String topcount40Cells = "195448.04792.04094.03145.02654.02628.02572.02497.0" + "2203.02173.02118.02100.02081.02055.02053.01973.0" + "1959.01789.01652.01638.01624.01607.01446.01382.0" + "1294.01219.01183.01155.01148.01021.0932.0921.0" + "916.0914.0901.0899.0843.0781.0754.0700.0"; private static final String topcount41Cells = "195448.04792.04094.03145.02654.02628.02572.02497.02203.0" + "2173.02118.02100.02081.02055.02053.01973.01959.01789.0" + "1652.01638.01624.01607.01446.01382.01294.01219.01183.0" + "1155.01148.01021.0932.0921.0916.0914.0901.0899.0843.0781" + ".0754.0700.0689.0"; } // End HighDimensionsTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/IndexedValuesTest.java0000644000175000017500000000441011735330606025027 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2010 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.test.FoodMartTestCase; import mondrian.util.Bug; /** * Test case for '&[..]' capability in MDX identifiers. * * @see Bug#BugMondrian485Fixed * * @author pierluiggi@users.sourceforge.net */ public class IndexedValuesTest extends FoodMartTestCase { public IndexedValuesTest(final String name) { super(name); } public IndexedValuesTest() { super(); } public void testQueryWithIndex() { final String desiredResult = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Org Salary]}\n" + "{[Measures].[Count]}\n" + "Axis #2:\n" + "{[Employees].[Sheri Nowmer]}\n" + "Row #0: $39,431.67\n" + "Row #0: 7,392\n"; // Query using name assertQueryReturns( "SELECT {[Measures].[Org Salary], [Measures].[Count]} " + "ON COLUMNS, " + "{[Employees].[Sheri Nowmer]} " + "ON ROWS FROM [HR]", desiredResult); // Query using key; expect same result. assertQueryReturns( "SELECT {[Measures].[Org Salary], [Measures].[Count]} " + "ON COLUMNS, " + "{[Employees].&[1]} " + "ON ROWS FROM [HR]", desiredResult); if (!Bug.BugMondrian485Fixed) { return; } // Cannot find members that are not at root of hierarchy. // (We should fix this.) assertQueryReturns( "SELECT {[Measures].[Org Salary], [Measures].[Count]} " + "ON COLUMNS, " + "{[Employees].&[4]} " + "ON ROWS FROM [HR]", "something"); // "level.&key" syntax assertQueryReturns( "SELECT [Measures] ON COLUMNS, " + "{[Product].[Product Name].&[9]} " + "ON ROWS FROM [Sales]", "something"); } } // End IndexedValuesTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/GroupingSetQueryTest.java0000644000175000017500000006562611735330606025603 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.MondrianProperties; import mondrian.rolap.agg.CellRequest; import mondrian.spi.Dialect; import mondrian.test.SqlPattern; import org.olap4j.impl.Olap4jUtil; import java.util.*; /** * Test support for generating SQL queries with the GROUPING SETS * construct, if the DBMS supports it. * * @author Thiyagu * @since 08-Jun-2007 */ public class GroupingSetQueryTest extends BatchTestCase { private MondrianProperties prop = MondrianProperties.instance(); private static final String cubeNameSales2 = "Sales 2"; private static final String measureStoreSales = "[Measures].[Store Sales]"; private static final String fieldNameMaritalStatus = "marital_status"; private static final String measureCustomerCount = "[Measures].[Customer Count]"; private boolean useGroupingSets; private boolean formattedSql; private String origWarnIfNoPatternForDialect; private static final Set ORACLE_TERADATA = Olap4jUtil.enumSetOf( Dialect.DatabaseProduct.ORACLE, Dialect.DatabaseProduct.TERADATA); protected void setUp() throws Exception { super.setUp(); useGroupingSets = prop.EnableGroupingSets.get(); formattedSql = prop.GenerateFormattedSql.get(); origWarnIfNoPatternForDialect = prop.WarnIfNoPatternForDialect.get(); prop.GenerateFormattedSql.set(false); // This test warns of missing sql patterns for // // ACCESS // ORACLE final Dialect dialect = getTestContext().getDialect(); if (prop.WarnIfNoPatternForDialect.get().equals("ANY") || dialect.getDatabaseProduct() == Dialect.DatabaseProduct.ACCESS || dialect.getDatabaseProduct() == Dialect.DatabaseProduct.ORACLE) { prop.WarnIfNoPatternForDialect.set( dialect.getDatabaseProduct().toString()); } else { // Do not warn unless the dialect is "ACCESS" or "ORACLE", or // if the test chooses to warn regardless of the dialect. prop.WarnIfNoPatternForDialect.set("NONE"); } } protected void tearDown() throws Exception { super.tearDown(); prop.EnableGroupingSets.set(useGroupingSets); prop.GenerateFormattedSql.set(formattedSql); prop.WarnIfNoPatternForDialect.set(origWarnIfNoPatternForDialect); } public void testGroupingSetsWithAggregateOverDefaultMember() { // testcase for MONDRIAN-705 if (getTestContext().getDialect().supportsGroupingSets()) { propSaver.set(prop.EnableGroupingSets, true); } assertQueryReturns( "with member [Gender].[agg] as ' " + " Aggregate({[Gender].DefaultMember}, [Measures].[Store Cost])' " + "select " + " {[Measures].[Store Cost]} ON COLUMNS, " + " {[Gender].[Gender].Members, [Gender].[agg]} ON ROWS " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Cost]}\n" + "Axis #2:\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "{[Gender].[agg]}\n" + "Row #0: 111,777.48\n" + "Row #1: 113,849.75\n" + "Row #2: 225,627.23\n"); } public void testGroupingSetForSingleColumnConstraint() { propSaver.set(prop.DisableCaching, false); CellRequest request1 = createRequest( cubeNameSales2, measureUnitSales, tableCustomer, fieldGender, "M"); CellRequest request2 = createRequest( cubeNameSales2, measureUnitSales, tableCustomer, fieldGender, "F"); CellRequest request3 = createRequest( cubeNameSales2, measureUnitSales, null, "", ""); SqlPattern[] patternsWithGsets = { new SqlPattern( ORACLE_TERADATA, "select \"customer\".\"gender\" as \"c0\", sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\", " + "grouping(\"customer\".\"gender\") as \"g0\" " + "from \"customer\" =as= \"customer\", \"sales_fact_1997\" =as= \"sales_fact_1997\" " + "where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "group by grouping sets ((\"customer\".\"gender\"), ())", 26) }; // If aggregates are enabled, mondrian should use them. Results should // be the same with or without grouping sets enabled. SqlPattern[] patternsWithAggs = { new SqlPattern( ORACLE_TERADATA, "select sum(\"agg_c_10_sales_fact_1997\".\"unit_sales\") as \"m0\"" + " from \"agg_c_10_sales_fact_1997\" \"agg_c_10_sales_fact_1997\"", null), new SqlPattern( ORACLE_TERADATA, "select \"agg_g_ms_pcat_sales_fact_1997\".\"gender\" as \"c0\"," + " sum(\"agg_g_ms_pcat_sales_fact_1997\".\"unit_sales\") as \"m0\" " + "from \"agg_g_ms_pcat_sales_fact_1997\" \"agg_g_ms_pcat_sales_fact_1997\" " + "group by \"agg_g_ms_pcat_sales_fact_1997\".\"gender\"", null) }; SqlPattern[] patternsWithoutGsets = { new SqlPattern( Dialect.DatabaseProduct.ACCESS, "select \"customer\".\"gender\" as \"c0\", sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\" " + "from \"customer\" as \"customer\", \"sales_fact_1997\" as \"sales_fact_1997\" " + "where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "group by \"customer\".\"gender\"", 26), new SqlPattern( ORACLE_TERADATA, "select \"customer\".\"gender\" as \"c0\", sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\" " + "from \"customer\" =as= \"customer\", \"sales_fact_1997\" =as= \"sales_fact_1997\" " + "where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "group by \"customer\".\"gender\"", 26) }; propSaver.set(prop.EnableGroupingSets, true); if (prop.ReadAggregates.get() && prop.UseAggregates.get()) { assertRequestSql( new CellRequest[] {request3, request1, request2}, patternsWithAggs); } else { assertRequestSql( new CellRequest[] {request3, request1, request2}, patternsWithGsets); } propSaver.set(prop.EnableGroupingSets, false); if (prop.ReadAggregates.get() && prop.UseAggregates.get()) { assertRequestSql( new CellRequest[] {request3, request1, request2}, patternsWithAggs); } else { assertRequestSql( new CellRequest[] {request3, request1, request2}, patternsWithoutGsets); } } public void testNotUsingGroupingSetWhenGroupUsesDifferentAggregateTable() { if (!(prop.UseAggregates.get() && prop.ReadAggregates.get())) { return; } CellRequest request1 = createRequest( cubeNameSales, measureUnitSales, tableCustomer, fieldGender, "M"); CellRequest request2 = createRequest( cubeNameSales, measureUnitSales, tableCustomer, fieldGender, "F"); CellRequest request3 = createRequest( cubeNameSales, measureUnitSales, null, "", ""); propSaver.set(prop.EnableGroupingSets, true); SqlPattern[] patternsWithoutGsets = { new SqlPattern( Dialect.DatabaseProduct.ACCESS, "select \"agg_g_ms_pcat_sales_fact_1997\".\"gender\" as \"c0\", " + "sum(\"agg_g_ms_pcat_sales_fact_1997\".\"unit_sales\") as \"m0\" " + "from \"agg_g_ms_pcat_sales_fact_1997\" as \"agg_g_ms_pcat_sales_fact_1997\" " + "group by \"agg_g_ms_pcat_sales_fact_1997\".\"gender\"", 26), new SqlPattern( ORACLE_TERADATA, "select \"agg_g_ms_pcat_sales_fact_1997\".\"gender\" as \"c0\", " + "sum(\"agg_g_ms_pcat_sales_fact_1997\".\"unit_sales\") as \"m0\" " + "from \"agg_g_ms_pcat_sales_fact_1997\" \"agg_g_ms_pcat_sales_fact_1997\" " + "group by \"agg_g_ms_pcat_sales_fact_1997\".\"gender\"", 26) }; assertRequestSql( new CellRequest[] {request3, request1, request2}, patternsWithoutGsets); } public void testNotUsingGroupingSet() { if (prop.ReadAggregates.get() && prop.UseAggregates.get()) { return; } propSaver.set(prop.EnableGroupingSets, true); CellRequest request1 = createRequest( cubeNameSales2, measureUnitSales, tableCustomer, fieldGender, "M"); CellRequest request2 = createRequest( cubeNameSales2, measureUnitSales, tableCustomer, fieldGender, "F"); SqlPattern[] patternsWithGsets = { new SqlPattern( ORACLE_TERADATA, "select \"customer\".\"gender\" as \"c0\", sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\" " + "from \"customer\" =as= \"customer\", \"sales_fact_1997\" =as= \"sales_fact_1997\" " + "where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "group by \"customer\".\"gender\"", 72) }; assertRequestSql( new CellRequest[] {request1, request2}, patternsWithGsets); propSaver.set(prop.EnableGroupingSets, false); SqlPattern[] patternsWithoutGsets = { new SqlPattern( Dialect.DatabaseProduct.ACCESS, "select \"customer\".\"gender\" as \"c0\", sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\" " + "from \"customer\" as \"customer\", \"sales_fact_1997\" as \"sales_fact_1997\" " + "where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "group by \"customer\".\"gender\"", 72), new SqlPattern( ORACLE_TERADATA, "select \"customer\".\"gender\" as \"c0\", sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\" " + "from \"customer\" =as= \"customer\", \"sales_fact_1997\" =as= \"sales_fact_1997\" " + "where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "group by \"customer\".\"gender\"", 72) }; assertRequestSql( new CellRequest[] {request1, request2}, patternsWithoutGsets); } public void testGroupingSetForMultipleMeasureAndSingleConstraint() { if (prop.ReadAggregates.get() && prop.UseAggregates.get()) { return; } propSaver.set(prop.EnableGroupingSets, true); CellRequest request1 = createRequest( cubeNameSales2, measureUnitSales, tableCustomer, fieldGender, "M"); CellRequest request2 = createRequest( cubeNameSales2, measureUnitSales, tableCustomer, fieldGender, "F"); CellRequest request3 = createRequest( cubeNameSales2, measureUnitSales, null, "", ""); CellRequest request4 = createRequest( cubeNameSales2, measureStoreSales, tableCustomer, fieldGender, "M"); CellRequest request5 = createRequest( cubeNameSales2, measureStoreSales, tableCustomer, fieldGender, "F"); CellRequest request6 = createRequest( cubeNameSales2, measureStoreSales, null, "", ""); SqlPattern[] patternsWithGsets = { new SqlPattern( ORACLE_TERADATA, "select \"customer\".\"gender\" as \"c0\", sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\", " + "sum(\"sales_fact_1997\".\"store_sales\") as \"m1\", grouping(\"customer\".\"gender\") as \"g0\" " + "from \"customer\" =as= \"customer\", \"sales_fact_1997\" =as= \"sales_fact_1997\" " + "where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "group by grouping sets ((\"customer\".\"gender\"), ())", 26) }; assertRequestSql( new CellRequest[] { request1, request2, request3, request4, request5, request6}, patternsWithGsets); propSaver.set(prop.EnableGroupingSets, false); SqlPattern[] patternsWithoutGsets = { new SqlPattern( Dialect.DatabaseProduct.ACCESS, "select \"customer\".\"gender\" as \"c0\", sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\", " + "sum(\"sales_fact_1997\".\"store_sales\") as \"m1\" " + "from \"customer\" as \"customer\", \"sales_fact_1997\" as \"sales_fact_1997\" " + "where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "group by \"customer\".\"gender\"", 26), new SqlPattern( ORACLE_TERADATA, "select \"customer\".\"gender\" as \"c0\", sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\", " + "sum(\"sales_fact_1997\".\"store_sales\") as \"m1\" " + "from \"customer\" =as= \"customer\", \"sales_fact_1997\" =as= \"sales_fact_1997\" " + "where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "group by \"customer\".\"gender\"", 26) }; assertRequestSql( new CellRequest[] { request1, request2, request3, request4, request5, request6}, patternsWithoutGsets); } public void testGroupingSetForASummaryCanBeGroupedWith2DetailBatch() { if (prop.ReadAggregates.get() && prop.UseAggregates.get()) { return; } propSaver.set(prop.EnableGroupingSets, true); CellRequest request1 = createRequest( cubeNameSales2, measureUnitSales, tableCustomer, fieldGender, "M"); CellRequest request2 = createRequest( cubeNameSales2, measureUnitSales, tableCustomer, fieldGender, "F"); CellRequest request3 = createRequest( cubeNameSales2, measureUnitSales, null, "", ""); CellRequest request4 = createRequest( cubeNameSales2, measureUnitSales, tableCustomer, fieldNameMaritalStatus, "M"); CellRequest request5 = createRequest( cubeNameSales2, measureUnitSales, tableCustomer, fieldNameMaritalStatus, "S"); CellRequest request6 = createRequest( cubeNameSales2, measureUnitSales, null, "", ""); SqlPattern[] patternWithGsets = { new SqlPattern( ORACLE_TERADATA, "select \"customer\".\"gender\" as \"c0\", sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\", " + "grouping(\"customer\".\"gender\") as \"g0\" " + "from \"customer\" =as= \"customer\", \"sales_fact_1997\" =as= \"sales_fact_1997\" " + "where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "group by grouping sets ((\"customer\".\"gender\"), ())", 26), new SqlPattern( ORACLE_TERADATA, "select \"customer\".\"marital_status\" as \"c0\", sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\" " + "from \"customer\" =as= \"customer\", \"sales_fact_1997\" =as= \"sales_fact_1997\" " + "where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "group by \"customer\".\"marital_status\"", 26), }; assertRequestSql( new CellRequest[] { request1, request2, request3, request4, request5, request6}, patternWithGsets); propSaver.set(prop.EnableGroupingSets, false); SqlPattern[] patternWithoutGsets = { new SqlPattern( Dialect.DatabaseProduct.ACCESS, "select sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\" " + "from \"sales_fact_1997\" as \"sales_fact_1997\"", 40), new SqlPattern( ORACLE_TERADATA, "select sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\" " + "from \"sales_fact_1997\" =as= \"sales_fact_1997\"", 40) }; assertRequestSql( new CellRequest[] { request1, request2, request3, request4, request5, request6}, patternWithoutGsets); } public void testGroupingSetForMultipleColumnConstraint() { if (prop.ReadAggregates.get() && prop.UseAggregates.get()) { return; } propSaver.set(prop.EnableGroupingSets, true); CellRequest request1 = createRequest( cubeNameSales2, measureUnitSales, new String[]{tableCustomer, tableTime}, new String[]{fieldGender, fieldYear}, new String[]{"M", "1997"}); CellRequest request2 = createRequest( cubeNameSales2, measureUnitSales, new String[]{tableCustomer, tableTime}, new String[]{fieldGender, fieldYear}, new String[]{"F", "1997"}); CellRequest request3 = createRequest( cubeNameSales2, measureUnitSales, tableTime, fieldYear, "1997"); SqlPattern[] patternsWithGsets = { new SqlPattern( ORACLE_TERADATA, "select \"time_by_day\".\"the_year\" as \"c0\", \"customer\".\"gender\" as \"c1\", " + "sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\", grouping(\"customer\".\"gender\") as \"g0\" " + "from \"time_by_day\" =as= \"time_by_day\", \"sales_fact_1997\" =as= \"sales_fact_1997\", \"customer\" =as= \"customer\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"time_by_day\".\"the_year\" = 1997 " + "and \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "group by grouping sets ((\"time_by_day\".\"the_year\", \"customer\".\"gender\"), (\"time_by_day\".\"the_year\"))", 150) }; // Sometimes this query causes Oracle 10.2 XE to give // ORA-12516, TNS:listener could not find available handler with // matching protocol stack // // You need to configure Oracle: // $ su - oracle // $ sqlplus / as sysdba // SQL> ALTER SYSTEM SET sessions=320 SCOPE=SPFILE; // SQL> SHUTDOWN assertRequestSql( new CellRequest[] {request3, request1, request2}, patternsWithGsets); propSaver.set(prop.EnableGroupingSets, false); SqlPattern[] patternsWithoutGsets = { new SqlPattern( Dialect.DatabaseProduct.ACCESS, "select \"time_by_day\".\"the_year\" as \"c0\", \"customer\".\"gender\" as \"c1\", " + "sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\" " + "from \"time_by_day\" as \"time_by_day\", \"sales_fact_1997\" as \"sales_fact_1997\", " + "\"customer\" as \"customer\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and " + "\"time_by_day\".\"the_year\" = 1997 and " + "\"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "group by \"time_by_day\".\"the_year\", \"customer\".\"gender\"", 50), new SqlPattern( ORACLE_TERADATA, "select \"time_by_day\".\"the_year\" as \"c0\", \"customer\".\"gender\" as \"c1\", " + "sum(\"sales_fact_1997\".\"unit_sales\") as \"m0\" " + "from \"time_by_day\" =as= \"time_by_day\", \"sales_fact_1997\" =as= \"sales_fact_1997\", " + "\"customer\" =as= \"customer\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and " + "\"time_by_day\".\"the_year\" = 1997 " + "and \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "group by \"time_by_day\".\"the_year\", \"customer\".\"gender\"", 50) }; assertRequestSql( new CellRequest[]{request3, request1, request2}, patternsWithoutGsets); } public void testGroupingSetForMultipleColumnConstraintAndCompoundConstraint() { if (prop.ReadAggregates.get() && prop.UseAggregates.get()) { return; } List compoundMembers = new ArrayList(); compoundMembers.add(new String[] {"USA", "OR"}); compoundMembers.add(new String[] {"CANADA", "BC"}); CellRequestConstraint constraint = makeConstraintCountryState(compoundMembers); CellRequest request1 = createRequest( cubeNameSales2, measureCustomerCount, new String[]{tableCustomer, tableTime}, new String[]{fieldGender, fieldYear}, new String[]{"M", "1997"}, constraint); CellRequest request2 = createRequest( cubeNameSales2, measureCustomerCount, new String[]{tableCustomer, tableTime}, new String[]{fieldGender, fieldYear}, new String[]{"F", "1997"}, constraint); CellRequest request3 = createRequest( cubeNameSales2, measureCustomerCount, tableTime, fieldYear, "1997", constraint); String sqlWithoutGS = "select \"time_by_day\".\"the_year\" as \"c0\", \"customer\".\"gender\" as \"c1\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" from \"time_by_day\" =as= \"time_by_day\", " + "\"sales_fact_1997\" =as= \"sales_fact_1997\", \"customer\" =as= \"customer\", \"store\" =as= \"store\" " + "where \"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and \"time_by_day\".\"the_year\" = 1997 " + "and \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" and " + "\"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" and " + "((\"store\".\"store_country\" = 'USA' and \"store\".\"store_state\" = 'OR') or " + "(\"store\".\"store_country\" = 'CANADA' and \"store\".\"store_state\" = 'BC')) " + "group by \"time_by_day\".\"the_year\", \"customer\".\"gender\""; SqlPattern[] patternsGSDisabled = { new SqlPattern(ORACLE_TERADATA, sqlWithoutGS, sqlWithoutGS) }; // as of change 12310 GS has been removed from distinct count queries, // since there is little or no performance benefit and there is a bug // related to it (2207515) SqlPattern[] patternsGSEnabled = patternsGSDisabled; propSaver.set(prop.EnableGroupingSets, true); assertRequestSql( new CellRequest[] {request3, request1, request2}, patternsGSEnabled); propSaver.set(prop.EnableGroupingSets, false); assertRequestSql( new CellRequest[]{request3, request1, request2}, patternsGSDisabled); } /** * Testcase for bug 2004202, "Except not working with grouping sets". */ public void testBug2004202() { assertQueryReturns( "with member store.allbutwallawalla as\n" + " 'aggregate(\n" + " except(\n" + " store.[store name].members,\n" + " { [Store].[All Stores].[USA].[WA].[Walla Walla].[Store 22]}))'\n" + "select {\n" + " store.[store name].members,\n" + " store.allbutwallawalla,\n" + " store.[all stores]} on 0,\n" + " {measures.[customer count]} on 1\n" + "from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[Canada].[BC].[Vancouver].[Store 19]}\n" + "{[Store].[Canada].[BC].[Victoria].[Store 20]}\n" + "{[Store].[Mexico].[DF].[Mexico City].[Store 9]}\n" + "{[Store].[Mexico].[DF].[San Andres].[Store 21]}\n" + "{[Store].[Mexico].[Guerrero].[Acapulco].[Store 1]}\n" + "{[Store].[Mexico].[Jalisco].[Guadalajara].[Store 5]}\n" + "{[Store].[Mexico].[Veracruz].[Orizaba].[Store 10]}\n" + "{[Store].[Mexico].[Yucatan].[Merida].[Store 8]}\n" + "{[Store].[Mexico].[Zacatecas].[Camacho].[Store 4]}\n" + "{[Store].[Mexico].[Zacatecas].[Hidalgo].[Store 12]}\n" + "{[Store].[Mexico].[Zacatecas].[Hidalgo].[Store 18]}\n" + "{[Store].[USA].[CA].[Alameda].[HQ]}\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Store].[USA].[WA].[Bellingham].[Store 2]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "{[Store].[USA].[WA].[Walla Walla].[Store 22]}\n" + "{[Store].[USA].[WA].[Yakima].[Store 23]}\n" + "{[Store].[allbutwallawalla]}\n" + "{[Store].[All Stores]}\n" + "Axis #2:\n" + "{[Measures].[Customer Count]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 1,059\n" + "Row #0: 1,147\n" + "Row #0: 962\n" + "Row #0: 296\n" + "Row #0: 563\n" + "Row #0: 474\n" + "Row #0: 190\n" + "Row #0: 179\n" + "Row #0: 906\n" + "Row #0: 84\n" + "Row #0: 278\n" + "Row #0: 96\n" + "Row #0: 95\n" + "Row #0: 5,485\n" + "Row #0: 5,581\n"); } } // End GroupingSetQueryTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/MemberCacheControlTest.java0000644000175000017500000013072311735330606025772 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.olap.CacheControl.MemberEditCommand; import mondrian.olap.Hierarchy; import mondrian.rolap.RolapSchema.Pool; import mondrian.rolap.agg.AggregationManager; import mondrian.server.Execution; import mondrian.server.Locus; import mondrian.server.Statement; import mondrian.test.*; import org.apache.log4j.*; import org.apache.log4j.Level; import java.io.PrintWriter; import java.io.StringWriter; import java.util.*; /** * Unit tests for flushing member cache and editing cached member properties. * *

The purpose of the cache control API is to clear the cache so that * changes made to the DBMS can be seen. However, it is difficult to write * tests that modify the database. So these tests just check that the relevant * caches have been cleared. It is assumed that the updated values will be * loaded next time mondrian goes to the database. * * @author mberkowitz * @since Jan 2008 */ public class MemberCacheControlTest extends FoodMartTestCase { private Locus locus; // TODO: add multi-thread tests. // TODO: test set properties negative: refer to invalid property // TODO: test set properties negative: set prop to invalid value // TODO: edit a different member not known to be in cache -- will it be // fetched? public MemberCacheControlTest() { } public MemberCacheControlTest(String name) { super(name); } protected void setUp() throws Exception { super.setUp(); propSaver.set( MondrianProperties.instance().EnableRolapCubeMemberCache, false); Pool.instance().clear(); final RolapConnection conn = (RolapConnection) getConnection(); final Statement statement = conn.getInternalStatement(); final Execution execution = new Execution(statement, 0); locus = new Locus(execution, getName(), null); Locus.push(locus); } protected void tearDown() throws Exception { super.tearDown(); Pool.instance().clear(); Locus.pop(locus); locus = null; } // ~ Utility methods ------------------------------------------------------ DiffRepository getDiffRepos() { return DiffRepository.lookup(MemberCacheControlTest.class); } public TestContext getTestContext() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", // Reduced size Store dimension. Omits the 'Store Country' level, // and adds properties to non-leaf levels. " \n" + " \n" + "

\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " "); return testContext; } /** * Creates a map. * * @param keys Keys * @param values Values * @return Map */ private static Map createMap(List keys, List values) { assert keys.size() == values.size(); final Map map = new HashMap(keys.size()); for (int i = 0; i < keys.size(); ++i) { map.put(keys.get(i), values.get(i)); } return map; } /** * Finds a Member by its name and the name of its containing cube. * * @param tc Test context * @param cubeName Cube name * @param names the full-qualified Member name * @return the Member * @throws MondrianException when not found. */ protected static RolapMember findMember( TestContext tc, String cubeName, String... names) { Cube cube = tc.getConnection().getSchema().lookupCube(cubeName, true); SchemaReader scr = cube.getSchemaReader(null).withLocus(); return (RolapMember) scr.getMemberByUniqueName(Id.Segment.toList(names), true); } /** * Prints all properties of a Member. * * @param pw Print writer * @param member Member * @return the same print writer */ private static PrintWriter printMemberProperties( PrintWriter pw, Member member) { pw.print(member.getUniqueName()); pw.print(" {"); int k = -1; for (Property p : member.getLevel().getProperties()) { if (++k > 0) { pw.print(","); } pw.println(); String name = p.getName(); Object value = member.getPropertyValue(name); // Fixup value for different database representations of boolean and // numeric values. if (value == null) { // no fixup needed } else if (name.equals("Has coffee bar")) { if (value instanceof Number) { value = ((Number) value).intValue() != 0; } } else if (name.endsWith(" Sqft")) { Number number = (Number) value; value = number.equals(number.intValue()) ? number.intValue() : Math.round(number.floatValue()); } pw.print(" ["); pw.print(name); pw.print("]=["); pw.print(value); pw.print("]"); } pw.println("}"); return pw; } /** * Prints properties of all Members on an Axis. * * @param pw Print writer * @param axis Axis * @return the same print writer */ private static PrintWriter printMemberProperties( PrintWriter pw, Axis axis) { for (Position pos : axis.getPositions()) { for (Member m : pos) { printMemberProperties(pw, m).println(); } } return pw; } /** * Prints properties of the Row Axis from a Result. * * @param pw Print writer * @param result Result * @return the same print writer */ private static PrintWriter printRowMemberProperties( PrintWriter pw, Result result) { return printMemberProperties( pw, result.getAxes()[ AxisOrdinal.StandardAxisOrdinal.ROWS.logicalOrdinal()]); } private static String getRowMemberPropertiesAsString(Result r) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); printRowMemberProperties(pw, r); pw.flush(); return sw.toString(); } private CacheControl.MemberSet createInterestingMemberSet( TestContext tc, CacheControl cc) { return cc.createUnionSet( // all stores in OR cc.createMemberSet(findMember(tc, "Sales", "Retail", "OR"), true), // all stores in Hidalgo, Zacatecas cc.createMemberSet( findMember(tc, "Sales", "Retail", "Zacatecas", "Hidalgo"), true), // a single store cc.createMemberSet( findMember(tc, "Sales", "Retail", "CA", "Alameda", "HQ"), false), // a range of stores cc.createMemberSet( true, findMember(tc, "Sales", "Retail", "WA", "Bremerton"), true, findMember(tc, "Sales", "Retail", "Yucatan", "Merida"), false), // all stores in a range of states cc.createMemberSet( true, findMember(tc, "Sales", "Retail", "DF"), true, findMember(tc, "Sales", "Retail", "Jalisco"), true)); } // ~ Tests ---------------------------------------------------------------- /** * Tests operations on member sets, in particular the * {@link mondrian.olap.CacheControl#filter} method. */ public void testFilter() { final TestContext tc = getTestContext(); final Connection conn = tc.getConnection(); final DiffRepository dr = getDiffRepos(); final CacheControl cc = conn.getCacheControl(null); CacheControl.MemberSet memberSet = createInterestingMemberSet(tc, cc); dr.assertEquals("before", "${before}", memberSet.toString()); final Member orMember = findMember(tc, "Sales", "Retail", "OR"); final CacheControl.MemberSet filteredMemberSet = cc.filter(orMember.getLevel(), memberSet); dr.assertEquals("after", "${after}", filteredMemberSet.toString()); } /** * Tests that member operations fail if cache is enabled. */ public void testMemberOpsFailIfCacheEnabled() { propSaver.set( MondrianProperties.instance().EnableRolapCubeMemberCache, true); final TestContext tc = getTestContext(); final Connection conn = tc.getConnection(); final CacheControl cc = conn.getCacheControl(null); final CacheControl.MemberEditCommand command = cc.createDeleteCommand(findMember(tc, "Sales", "Retail", "OR")); try { cc.execute(command); fail("expected exception"); } catch (IllegalArgumentException e) { assertEquals( "Member cache control operations are not allowed unless " + "property mondrian.rolap.EnableRolapCubeMemberCache is " + "false", e.getMessage()); } } /** * Test that edits the properties of a single leaf Member. */ public void testSetPropertyCommandOnLeafMember() { final TestContext tc = getTestContext(); final Connection conn = tc.getConnection(); final DiffRepository dr = getDiffRepos(); final CacheControl cc = conn.getCacheControl(null); // A query that refers to a single leaf Member fetches the Member. // Changing Member properties does not affect Cell boundaries, so we // check that the MDX results are invariant. String mdx = "SELECT {[Measures].[Unit Sales]} ON COLUMNS," + " {[Store].[USA].[CA].[San Francisco].[Store 14]}" + " ON ROWS FROM [Sales]"; Query q = conn.parseQuery(mdx); Result r = conn.execute(q); dr.assertEquals( "props before", "${props before}", getRowMemberPropertiesAsString(r)); final String resultString = TestContext.toString(r); dr.assertEquals( "result before", "${result before}", resultString); // Change properties Member m = findMember( tc, "Sales", "Store", "USA", "CA", "San Francisco", "Store 14"); cc.execute(cc.createSetPropertyCommand(m, "Store Manager", "Higgins")); cc.execute( cc.createCompoundCommand( Arrays.asList( cc.createSetPropertyCommand( m, "Street address", "770 Mission St"), cc.createSetPropertyCommand(m, "Store Sqft", 6000), cc.createSetPropertyCommand( m, "Has coffee bar", "false")))); // Repeat same query; verify properties are changed. // Changing properties does not affect measures, so results unchanged. r = conn.execute(q); dr.assertEquals( "props after", "${props after}", getRowMemberPropertiesAsString(r)); assertEquals( resultString, TestContext.toString(r)); } /** * Test that edits properties of Members at various Levels (use Retail * Dimension), but leaves grouping unchanged, so results not changed. */ public void testSetPropertyCommandOnNonLeafMember() { final TestContext tc = getTestContext(); final Connection conn = tc.getConnection(); final DiffRepository dr = getDiffRepos(); final CacheControl cc = tc.getConnection().getCacheControl(null); String mdx = "SELECT {[Measures].[Unit Sales]} ON COLUMNS," + " {[Retail].Members} ON ROWS " + "FROM [Sales]"; Query q = conn.parseQuery(mdx); Result r = conn.execute(q); dr.assertEquals( "props before", "${props before}", getRowMemberPropertiesAsString(r)); final String resultString = TestContext.toString(r); dr.assertEquals( "result before", "${result before}", resultString); // Change some properties (TODO: change dimension table first) // set 2 properties (TODO: set both with one command) // try all ways to construct MemberSets CacheControl.MemberSet memberSet = createInterestingMemberSet(tc, cc); final Map propertyValues = createMap( Arrays.asList("Has coffee bar", "Store Sqft"), Arrays.asList((Object) "true", 123)); CacheControl.MemberEditCommand command; // first, the member set contains members of various levels try { command = cc.createSetPropertyCommand(memberSet, propertyValues); fail("expected exception, got " + command); } catch (IllegalArgumentException e) { assertEquals( "all members in set must belong to same level", e.getMessage()); } // after we filter set to just members of store level we're ok final Member hqMember = findMember(tc, "Sales", "Retail", "CA", "Alameda", "HQ"); final CacheControl.MemberSet filteredMemberSet = cc.filter(hqMember.getLevel(), memberSet); command = cc.createSetPropertyCommand(filteredMemberSet, propertyValues); cc.execute(command); // Repeat same query; verify properties were changed. // Changing properties does not affect measures, so results unchanged. r = conn.execute(q); dr.assertEquals( "props after", "${props after}", getRowMemberPropertiesAsString(r)); assertEquals( resultString, TestContext.toString(r)); } public void testAddCommand() { final TestContext tc = getTestContext(); final Connection conn = tc.getConnection(); final CacheControl cc = conn.getCacheControl(null); final RolapCubeMember caCubeMember = (RolapCubeMember) findMember(tc, "Sales", "Retail", "CA"); final RolapMember caMember = caCubeMember.member; final RolapMember rootMember = caMember.getParentMember(); final RolapHierarchy hierarchy = caMember.getHierarchy(); final RolapMember berkeleyMember = (RolapMember) hierarchy.createMember( caMember, caMember.getLevel().getChildLevel(), "Berkeley", null); final RolapBaseCubeMeasure unitSalesCubeMember = (RolapBaseCubeMeasure) findMember( tc, "Sales", "Measures", "Unit Sales"); final RolapCubeMember yearCubeMember = (RolapCubeMember) findMember( tc, "Sales", "Time", "Year", "1997"); final Member[] cacheRegionMembers = new Member[] { unitSalesCubeMember, caCubeMember, yearCubeMember }; tc.assertQueryReturns( "select {[Retail].[City].Members} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Retail].[BC].[Vancouver]}\n" + "{[Retail].[BC].[Victoria]}\n" + "{[Retail].[CA].[Alameda]}\n" + "{[Retail].[CA].[Beverly Hills]}\n" + "{[Retail].[CA].[Los Angeles]}\n" + "{[Retail].[CA].[San Diego]}\n" + "{[Retail].[CA].[San Francisco]}\n" + "{[Retail].[DF].[Mexico City]}\n" + "{[Retail].[DF].[San Andres]}\n" + "{[Retail].[Guerrero].[Acapulco]}\n" + "{[Retail].[Jalisco].[Guadalajara]}\n" + "{[Retail].[OR].[Portland]}\n" + "{[Retail].[OR].[Salem]}\n" + "{[Retail].[Veracruz].[Orizaba]}\n" + "{[Retail].[WA].[Bellingham]}\n" + "{[Retail].[WA].[Bremerton]}\n" + "{[Retail].[WA].[Seattle]}\n" + "{[Retail].[WA].[Spokane]}\n" + "{[Retail].[WA].[Tacoma]}\n" + "{[Retail].[WA].[Walla Walla]}\n" + "{[Retail].[WA].[Yakima]}\n" + "{[Retail].[Yucatan].[Merida]}\n" + "{[Retail].[Zacatecas].[Camacho]}\n" + "{[Retail].[Zacatecas].[Hidalgo]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 21,333\n" + "Row #0: 25,663\n" + "Row #0: 25,635\n" + "Row #0: 2,117\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 26,079\n" + "Row #0: 41,580\n" + "Row #0: \n" + "Row #0: 2,237\n" + "Row #0: 24,576\n" + "Row #0: 25,011\n" + "Row #0: 23,591\n" + "Row #0: 35,257\n" + "Row #0: 2,203\n" + "Row #0: 11,491\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n"); tc.assertAxisReturns( "[Retail].[CA].Children", "[Retail].[CA].[Alameda]\n" + "[Retail].[CA].[Beverly Hills]\n" + "[Retail].[CA].[Los Angeles]\n" + "[Retail].[CA].[San Diego]\n" + "[Retail].[CA].[San Francisco]"); final MemberReader memberReader = hierarchy.getMemberReader(); final MemberCache memberCache = ((SmartMemberReader) memberReader).getMemberCache(); List caChildren = memberCache.getChildrenFromCache(caMember, null); assertEquals(5, caChildren.size()); // Load cell data and check it is in cache executeQuery( "select {[Measures].[Unit Sales]} on columns, {[Retail].[CA]} on rows from [Sales]"); final AggregationManager aggMgr = ((RolapConnection) conn).getServer().getAggregationManager(); assertEquals( Double.valueOf("74748"), aggMgr.getCellFromAllCaches( AggregationManager.makeRequest(cacheRegionMembers))); // Now tell the cache that [CA].[Berkeley] is new final CacheControl.MemberEditCommand command = cc.createAddCommand(berkeleyMember); cc.execute(command); // test that cells have been removed assertNull( aggMgr.getCellFromAllCaches( AggregationManager.makeRequest(cacheRegionMembers))); tc.assertAxisReturns( "[Retail].[CA].Children", "[Retail].[CA].[Alameda]\n" + "[Retail].[CA].[Beverly Hills]\n" + "[Retail].[CA].[Los Angeles]\n" + "[Retail].[CA].[San Diego]\n" + "[Retail].[CA].[San Francisco]\n" + "[Retail].[CA].[Berkeley]"); tc.assertQueryReturns( "select {[Retail].[City].Members} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Retail].[BC].[Vancouver]}\n" + "{[Retail].[BC].[Victoria]}\n" + "{[Retail].[CA].[Alameda]}\n" + "{[Retail].[CA].[Berkeley]}\n" + "{[Retail].[CA].[Beverly Hills]}\n" + "{[Retail].[CA].[Los Angeles]}\n" + "{[Retail].[CA].[San Diego]}\n" + "{[Retail].[CA].[San Francisco]}\n" + "{[Retail].[DF].[Mexico City]}\n" + "{[Retail].[DF].[San Andres]}\n" + "{[Retail].[Guerrero].[Acapulco]}\n" + "{[Retail].[Jalisco].[Guadalajara]}\n" + "{[Retail].[OR].[Portland]}\n" + "{[Retail].[OR].[Salem]}\n" + "{[Retail].[Veracruz].[Orizaba]}\n" + "{[Retail].[WA].[Bellingham]}\n" + "{[Retail].[WA].[Bremerton]}\n" + "{[Retail].[WA].[Seattle]}\n" + "{[Retail].[WA].[Spokane]}\n" + "{[Retail].[WA].[Tacoma]}\n" + "{[Retail].[WA].[Walla Walla]}\n" + "{[Retail].[WA].[Yakima]}\n" + "{[Retail].[Yucatan].[Merida]}\n" + "{[Retail].[Zacatecas].[Camacho]}\n" + "{[Retail].[Zacatecas].[Hidalgo]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 21,333\n" + "Row #0: 25,663\n" + "Row #0: 25,635\n" + "Row #0: 2,117\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 26,079\n" + "Row #0: 41,580\n" + "Row #0: \n" + "Row #0: 2,237\n" + "Row #0: 24,576\n" + "Row #0: 25,011\n" + "Row #0: 23,591\n" + "Row #0: 35,257\n" + "Row #0: 2,203\n" + "Row #0: 11,491\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n"); tc.assertQueryReturns( "select [Retail].Children on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Retail].[BC]}\n" + "{[Retail].[CA]}\n" + "{[Retail].[DF]}\n" + "{[Retail].[Guerrero]}\n" + "{[Retail].[Jalisco]}\n" + "{[Retail].[OR]}\n" + "{[Retail].[Veracruz]}\n" + "{[Retail].[WA]}\n" + "{[Retail].[Yucatan]}\n" + "{[Retail].[Zacatecas]}\n" + "Row #0: \n" + "Row #0: 74,748\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 67,659\n" + "Row #0: \n" + "Row #0: 124,366\n" + "Row #0: \n" + "Row #0: \n"); List rootChildren = memberCache.getChildrenFromCache(rootMember, null); if (rootChildren != null) { // might be null due to gc assertEquals( 10, rootChildren.size()); } } public void testDeleteCommand() { final TestContext tc = getTestContext(); final Connection conn = tc.getConnection(); final CacheControl cc = conn.getCacheControl(null); final RolapCubeMember sfCubeMember = (RolapCubeMember) findMember( tc, "Sales", "Retail", "CA", "San Francisco"); final RolapMember caMember = sfCubeMember.member.getParentMember(); final RolapHierarchy hierarchy = caMember.getHierarchy(); final RolapBaseCubeMeasure unitSalesCubeMember = (RolapBaseCubeMeasure) findMember( tc, "Sales", "Measures", "Unit Sales"); final RolapCubeMember yearCubeMember = (RolapCubeMember) findMember( tc, "Sales", "Time", "Year", "1997"); final Member[] cacheRegionMembers = new Member[] { unitSalesCubeMember, sfCubeMember, yearCubeMember }; tc.assertAxisReturns( "[Retail].[CA].Children", "[Retail].[CA].[Alameda]\n" + "[Retail].[CA].[Beverly Hills]\n" + "[Retail].[CA].[Los Angeles]\n" + "[Retail].[CA].[San Diego]\n" + "[Retail].[CA].[San Francisco]"); final MemberReader memberReader = hierarchy.getMemberReader(); final MemberCache memberCache = ((SmartMemberReader) memberReader).getMemberCache(); List caChildren = memberCache.getChildrenFromCache(caMember, null); assertEquals(5, caChildren.size()); // Load cell data and check it is in cache executeQuery( "select {[Measures].[Unit Sales]} on columns, {[Retail].[CA].[Alameda]} on rows from [Sales]"); final AggregationManager aggMgr = ((RolapConnection) conn).getServer().getAggregationManager(); assertEquals( Double.valueOf("2117"), aggMgr.getCellFromAllCaches( AggregationManager.makeRequest(cacheRegionMembers))); // Now tell the cache that [CA].[San Francisco] has been removed. final CacheControl.MemberEditCommand command = cc.createDeleteCommand(sfCubeMember); cc.execute(command); // Children of CA should be 4 assertEquals( 4, memberCache.getChildrenFromCache(caMember, null).size()); // test that cells have been removed assertNull( aggMgr.getCellFromAllCaches( AggregationManager.makeRequest(cacheRegionMembers))); // The list of children should be updated. tc.assertAxisReturns( "[Retail].[CA].Children", "[Retail].[CA].[Alameda]\n" + "[Retail].[CA].[Beverly Hills]\n" + "[Retail].[CA].[Los Angeles]\n" + "[Retail].[CA].[San Diego]"); } public void testMoveCommand() { final TestContext tc = getTestContext(); final Connection conn = tc.getConnection(); final CacheControl cc = conn.getCacheControl(null); final RolapCubeMember caCubeMember = (RolapCubeMember) findMember(tc, "Sales", "Retail", "CA"); final RolapMember caMember = caCubeMember.member; final RolapHierarchy hierarchy = caMember.getHierarchy(); final MemberReader memberReader = hierarchy.getMemberReader(); final MemberCache memberCache = ((SmartMemberReader) memberReader).getMemberCache(); final RolapMember alamedaMember = (RolapMember) hierarchy.createMember( caMember, caMember.getLevel().getChildLevel(), "Alameda", null); final RolapMember sfMember = (RolapMember) hierarchy.createMember( caMember, caMember.getLevel().getChildLevel(), "San Francisco", null); final RolapMember storeMember = (RolapMember) hierarchy.createMember( sfMember, sfMember.getLevel().getChildLevel(), "Store 14", null); // test axis contents tc.assertAxisReturns( "[Retail].[CA].Children", "[Retail].[CA].[Alameda]\n" + "[Retail].[CA].[Beverly Hills]\n" + "[Retail].[CA].[Los Angeles]\n" + "[Retail].[CA].[San Diego]\n" + "[Retail].[CA].[San Francisco]"); tc.assertAxisReturns( "[Retail].[CA].[Alameda].Children", "[Retail].[CA].[Alameda].[HQ]"); tc.assertAxisReturns( "[Retail].[CA].[San Francisco].Children", "[Retail].[CA].[San Francisco].[Store 14]"); List sfChildren = memberCache.getChildrenFromCache(sfMember, null); assertEquals(1, sfChildren.size()); List alamedaChildren = memberCache.getChildrenFromCache(alamedaMember, null); assertEquals(1, alamedaChildren.size()); assertTrue( storeMember.getParentMember().equals(sfMember)); // Now tell the cache that Store 14 moved to Alameda final MemberEditCommand command = cc.createMoveCommand(storeMember, alamedaMember); cc.execute(command); // The list of SF children should contain 0 elements assertEquals( 0, memberCache.getChildrenFromCache(sfMember, null).size()); // Check Alameda's children. It should be null as the parent's list // should be cleared. alamedaChildren = memberCache.getChildrenFromCache(alamedaMember, null); assertEquals(2, alamedaChildren.size()); // test axis contents tc.assertAxisReturns( "[Retail].[CA].[San Francisco].Children", ""); tc.assertAxisReturns( "[Retail].[CA].[Alameda].Children", "[Retail].[CA].[Alameda].[HQ]\n" + "[Retail].[CA].[Alameda].[Store 14]"); // Test parent object assertTrue( storeMember.getParentMember().equals(alamedaMember)); } public void testMoveFailBadLevel() { final TestContext tc = getTestContext(); final Connection conn = tc.getConnection(); final CacheControl cc = conn.getCacheControl(null); final RolapCubeMember caCubeMember = (RolapCubeMember) findMember(tc, "Sales", "Retail", "CA"); final RolapMember caMember = caCubeMember.member; final RolapHierarchy hierarchy = caMember.getHierarchy(); final MemberReader memberReader = hierarchy.getMemberReader(); final MemberCache memberCache = ((SmartMemberReader) memberReader).getMemberCache(); final RolapMember sfMember = (RolapMember) hierarchy.createMember( caMember, caMember.getLevel().getChildLevel(), "San Francisco", null); final RolapMember storeMember = (RolapMember) hierarchy.createMember( sfMember, sfMember.getLevel().getChildLevel(), "Store 14", null); // test axis contents tc.assertAxisReturns( "[Retail].[CA].Children", "[Retail].[CA].[Alameda]\n" + "[Retail].[CA].[Beverly Hills]\n" + "[Retail].[CA].[Los Angeles]\n" + "[Retail].[CA].[San Diego]\n" + "[Retail].[CA].[San Francisco]"); tc.assertAxisReturns( "[Retail].[CA].[San Francisco].Children", "[Retail].[CA].[San Francisco].[Store 14]"); List sfChildren = memberCache.getChildrenFromCache(sfMember, null); assertEquals(1, sfChildren.size()); assertTrue( storeMember.getParentMember().equals(sfMember)); // Now tell the cache that Store 14 moved to CA final MemberEditCommand command = cc.createMoveCommand(storeMember, caMember); try { cc.execute(command); fail("Should have failed due to improper level"); } catch (MondrianException e) { assertEquals( "new parent belongs to different level than old", e.getCause().getMessage()); } // The list of SF children should still contain 1 element assertEquals( 1, memberCache.getChildrenFromCache(sfMember, null).size()); // test axis contents. should not have been modified tc.assertAxisReturns( "[Retail].[CA].[San Francisco].Children", "[Retail].[CA].[San Francisco].[Store 14]"); tc.assertAxisReturns( "[Retail].[CA].Children", "[Retail].[CA].[Alameda]\n" + "[Retail].[CA].[Beverly Hills]\n" + "[Retail].[CA].[Los Angeles]\n" + "[Retail].[CA].[San Diego]\n" + "[Retail].[CA].[San Francisco]"); // Test parent object. should be the same assertTrue( storeMember.getParentMember().equals(sfMember)); } /** * Tests a variety of negative cases including add/delete/move null members * add/delete/move members in parent-child hierarchies. */ public void testAddCommandNegative() { final TestContext tc = getTestContext(); final Connection conn = tc.getConnection(); final CacheControl cc = conn.getCacheControl(null); CacheControl.MemberEditCommand command; try { command = cc.createAddCommand(null); fail("expected exception, got " + command); } catch (IllegalArgumentException e) { assertEquals("cannot add null member", e.getMessage()); } final RolapCubeMember alamedaCubeMember = (RolapCubeMember) findMember( tc, "Sales", "Retail", "CA", "Alameda"); final RolapMember alamedaMember = alamedaCubeMember.member; final RolapMember caMember = alamedaMember.getParentMember(); final RolapCubeMember empCubeMember = (RolapCubeMember) findMember( tc, "HR", "Employees", "Sheri Nowmer", "Michael Spence"); final RolapMember empMember = empCubeMember.member; try { command = cc.createMoveCommand(null, alamedaMember); fail("expected exception, got " + command); } catch (IllegalArgumentException e) { assertEquals("cannot move null member", e.getMessage()); } try { command = cc.createMoveCommand(alamedaMember, null); fail("expected exception, got " + command); } catch (IllegalArgumentException e) { assertEquals("cannot move member to null location", e.getMessage()); } try { command = cc.createDeleteCommand((Member) null); fail("expected exception, got " + command); } catch (IllegalArgumentException e) { assertEquals("cannot delete null member", e.getMessage()); } try { command = cc.createSetPropertyCommand(null, "foo", 1); fail("expected exception, got " + command); } catch (IllegalArgumentException e) { assertEquals( "cannot set properties on null member", e.getMessage()); } try { command = cc.createAddCommand(empMember); fail("expected exception, got " + command); } catch (IllegalArgumentException e) { assertEquals( "add member not supported for parent-child hierarchy", e.getMessage()); } try { command = cc.createMoveCommand(empMember, null); fail("expected exception, got " + command); } catch (IllegalArgumentException e) { assertEquals( "move member not supported for parent-child hierarchy", e.getMessage()); } try { command = cc.createDeleteCommand(empMember); fail("expected exception, got " + command); } catch (IllegalArgumentException e) { assertEquals( "delete member not supported for parent-child hierarchy", e.getMessage()); } try { command = cc.createSetPropertyCommand(empMember, "foo", "bar"); fail("expected exception, got " + command); } catch (IllegalArgumentException e) { assertEquals( "set properties not supported for parent-child hierarchy", e.getMessage()); } try { command = cc.createSetPropertyCommand( cc.createUnionSet( cc.createMemberSet(alamedaMember, false), cc.createMemberSet(caMember, false)), Collections.emptyMap()); fail("expected exception, got " + command); } catch (IllegalArgumentException e) { assertEquals( "all members in set must belong to same level", e.getMessage()); } } /** * Test case for bug * MONDRIAN-1076, * "Add CacheControl API to flush members from dimension cache". */ public void testFlushHierarchy() { final TestContext testContext = getTestContext(); CacheControlTest.flushCache(testContext); final CacheControl cacheControl = testContext.getConnection().getCacheControl(null); final Cube salesCube = testContext.getConnection() .getSchema().lookupCube("Sales", true); final Logger logger = RolapUtil.SQL_LOGGER; final Level level = logger.getLevel(); final StringWriter sw = new StringWriter(); final WriterAppender appender = new WriterAppender(new SimpleLayout(), sw); try { logger.setLevel(Level.DEBUG); logger.addAppender(appender); final Hierarchy storeHierarchy = salesCube.getDimensions()[1].getHierarchies()[0]; assertEquals("Store", storeHierarchy.getName()); final CacheControl.MemberSet storeMemberSet = cacheControl.createMemberSet( storeHierarchy.getAllMember(), true); final Runnable storeFlusher = new Runnable() { public void run() { cacheControl.flush(storeMemberSet); } }; final Result result = testContext.executeQuery( "select [Store].[Mexico].[Yucatan] on 0 from [Sales]"); final Member storeYucatanMember = result.getAxes()[0].getPositions().get(0).get(0); final CacheControl.MemberSet storeYucatanMemberSet = cacheControl.createMemberSet( storeYucatanMember, true); final Runnable storeYucatanFlusher = new Runnable() { public void run() { cacheControl.flush(storeYucatanMemberSet); } }; checkFlushHierarchy( sw, true, storeFlusher, new Runnable() { public void run() { // Check that .Children uses cache when applied // to an 'all' member. testContext.assertAxisReturns( "[Store].Children", "[Store].[Canada]\n" + "[Store].[Mexico]\n" + "[Store].[USA]"); } }); checkFlushHierarchy( sw, true, storeFlusher, new Runnable() { public void run() { // Check that .Children uses cache when applied // to regular member. testContext.assertAxisReturns( "[Store].[USA].[CA].Children", "[Store].[USA].[CA].[Alameda]\n" + "[Store].[USA].[CA].[Beverly Hills]\n" + "[Store].[USA].[CA].[Los Angeles]\n" + "[Store].[USA].[CA].[San Diego]\n" + "[Store].[USA].[CA].[San Francisco]"); } }); // In contrast to preceding, flushing Yucatan should not affect // California. checkFlushHierarchy( sw, false, storeYucatanFlusher, new Runnable() { public void run() { // Check that .Children uses cache when applied // to regular member. testContext.assertAxisReturns( "[Store].[USA].[CA].Children", "[Store].[USA].[CA].[Alameda]\n" + "[Store].[USA].[CA].[Beverly Hills]\n" + "[Store].[USA].[CA].[Los Angeles]\n" + "[Store].[USA].[CA].[San Diego]\n" + "[Store].[USA].[CA].[San Francisco]"); } }); checkFlushHierarchy( sw, true, storeFlusher, new Runnable() { public void run() { // Check that .Members uses cache. testContext.assertExprReturns( "Count([Store].Members)", "63"); } }); checkFlushHierarchy( sw, true, storeFlusher, new Runnable() { public void run() { // Check that .Members uses cache. testContext.assertExprReturns( "Count([Store].[Store Name].Members)", "25"); } }); // Time hierarchy is interesting because it has public 'all' member. // But you can still use the private all member for purposes like // flushing. final Hierarchy timeHierarchy = salesCube.getDimensions()[4].getHierarchies()[0]; assertEquals("Time", timeHierarchy.getName()); final CacheControl.MemberSet timeMemberSet = cacheControl.createMemberSet( timeHierarchy.getAllMember(), true); final Runnable timeFlusher = new Runnable() { public void run() { cacheControl.flush(timeMemberSet); } }; checkFlushHierarchy( sw, true, timeFlusher, new Runnable() { public void run() { // Check that .Members uses cache. testContext.assertExprReturns( "Count([Time].[Month].Members)", "24"); } }); checkFlushHierarchy( sw, true, timeFlusher, new Runnable() { public void run() { // Check that .Members uses cache. testContext.assertAxisReturns( "[Time].[1997].[Q2].Children", "[Time].[1997].[Q2].[4]\n" + "[Time].[1997].[Q2].[5]\n" + "[Time].[1997].[Q2].[6]"); } }); } finally { logger.setLevel(level); logger.removeAppender(appender); } } /** * Runs the same command ({@code foo(testContext, k)}) three times. Between * the 2nd and the 3rd, flushes the cache, and makes sure that the 3rd time * causes SQL to be executed. * * @param writer Writer, written into each time a SQL statement is executed * @param affected Whether the cache flush affects the command * @param flusher Functor that performs cache flushing action to be tested * @param command Command to execute that requires cache contents */ private void checkFlushHierarchy( StringWriter writer, boolean affected, Runnable flusher, Runnable command) { // Run command for first time. command.run(); // Now cache is primed, running the command for second time should not // require any additional SQL. (There is a small chance that GC will // kick in and we'll lose the cache, but we've never seen that happen // in the wild.) int length1 = writer.getBuffer().length(); command.run(); final String since1 = writer.getBuffer().substring(length1); assertEquals("", since1); flusher.run(); // Now cache has been flushed, it should be impossible to execute the // command without running additional SQL. int length2 = writer.getBuffer().length(); command.run(); final String since2 = writer.getBuffer().substring(length2); if (affected) { assertNotSame("", since2); } } } // End MemberCacheControlTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/DataSourceChangeListenerTest.java0000644000175000017500000005377011735330606027152 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2008 Bart Pappyn // Copyright (C) 2007-2012 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.rolap.cache.HardSmartCache; import mondrian.spi.impl.*; import mondrian.test.FoodMartTestCase; import mondrian.test.TestContext; import mondrian.util.Bug; import mondrian.util.Pair; import junit.framework.TestCase; import java.util.*; /** * Tests for testing the DataSourceChangeListener plugin. * * @author Bart Pappyn * @since Jan 05, 2007 */ public class DataSourceChangeListenerTest extends FoodMartTestCase { public DataSourceChangeListenerTest() { super(); } public DataSourceChangeListenerTest(String name) { super(name); } /** * Tests whether the data source plugin is able to tell mondrian * to read the hierarchy and aggregates again. */ public void testDataSourceChangeListenerPlugin() { final MondrianProperties properties = MondrianProperties.instance(); if (properties.TestExpDependencies.get() > 0) { // Dependency testing produces side-effects in the cache. return; } // got to clean out the cache final TestContext testContext = getTestContext(); final mondrian.olap.CacheControl cacheControl = testContext.getConnection().getCacheControl(null); // Flush the entire cache. final Connection connection = testContext.getConnection(); final mondrian.olap.Cube salesCube = connection.getSchema().lookupCube("Sales", true); final mondrian.olap.CacheControl.CellRegion measuresRegion = cacheControl.createMeasuresRegion(salesCube); cacheControl.flush(measuresRegion); // turn on caching propSaver.set(properties.DisableCaching, false); cacheControl.flushSchemaCache(); // Use hard caching for testing. When using soft references, we can not // test caching because things may be garbage collected during the // tests. SmartMemberReader smr = getSmartMemberReader("Store"); MemberCacheHelper smrch = (MemberCacheHelper)smr.getMemberCache(); smrch.mapLevelToMembers.setCache( new HardSmartCache, List>()); smrch.mapMemberToChildren.setCache( new HardSmartCache, List>()); smrch.mapKeyToMember = new HardSmartCache(); MemberCacheHelper rcsmrch = ((RolapCubeHierarchy.RolapCubeHierarchyMemberReader) smr) .getRolapCubeMemberCacheHelper(); rcsmrch.mapLevelToMembers.setCache( new HardSmartCache, List>()); rcsmrch.mapMemberToChildren.setCache( new HardSmartCache, List>()); rcsmrch.mapKeyToMember = new HardSmartCache(); SmartMemberReader ssmr = getSharedSmartMemberReader("Store"); MemberCacheHelper ssmrch = (MemberCacheHelper)ssmr.getMemberCache(); ssmrch.mapLevelToMembers.setCache( new HardSmartCache, List>()); ssmrch.mapMemberToChildren.setCache( new HardSmartCache, List>()); ssmrch.mapKeyToMember = new HardSmartCache(); // Create a dummy DataSource which will throw a 'bomb' if it is asked // to execute a particular SQL statement, but will otherwise behave // exactly the same as the current DataSource. SqlLogger sqlLogger = new SqlLogger(); RolapUtil.setHook(sqlLogger); try { String s1, s2, s3, s4, s5, s6; // Flush the cache, to ensure that the query gets executed. Result r1 = executeQuery( "select {[Store].[All Stores].[USA].[CA].[San Francisco]} on columns from [Sales]"); Util.discard(r1); s1 = sqlLogger.getSqlQueries().toString(); sqlLogger.clear(); // s1 should not be empty assertFalse("[]".equals(s1)); // Run query again, to make sure only cache is used Result r2 = executeQuery( "select {[Store].[All Stores].[USA].[CA].[San Francisco]} on columns from [Sales]"); Util.discard(r2); s2 = sqlLogger.getSqlQueries().toString(); sqlLogger.clear(); assertEquals("[]", s2); // Attach dummy change listener that tells mondrian the // datasource is never changed. smrch.changeListener = new DataSourceChangeListenerImpl(); ssmrch.changeListener = new DataSourceChangeListenerImpl(); rcsmrch.changeListener = new DataSourceChangeListenerImpl(); // Run query again, to make sure only cache is used Result r3 = executeQuery( "select {[Store].[All Stores].[USA].[CA].[San Francisco]} on columns from [Sales]"); Util.discard(r3); s3 = sqlLogger.getSqlQueries().toString(); sqlLogger.clear(); assertEquals("[]", s3); // Manually clear the cache to make compare sql result later on smrch.mapKeyToMember.clear(); smrch.mapLevelToMembers.clear(); smrch.mapMemberToChildren.clear(); ssmrch.mapKeyToMember.clear(); ssmrch.mapLevelToMembers.clear(); ssmrch.mapMemberToChildren.clear(); rcsmrch.mapKeyToMember.clear(); rcsmrch.mapLevelToMembers.clear(); rcsmrch.mapMemberToChildren.clear(); // Run query again, to make sure only cache is used Result r4 = executeQuery( "select {[Store].[All Stores].[USA].[CA].[San Francisco]} " + "on columns from [Sales]"); Util.discard(r4); s4 = sqlLogger.getSqlQueries().toString(); sqlLogger.clear(); assertFalse("[]".equals(s4)); // Attach dummy change listener that tells mondrian the // datasource is always changed. smrch.changeListener = new DataSourceChangeListenerImpl2(); ssmrch.changeListener = new DataSourceChangeListenerImpl2(); rcsmrch.changeListener = new DataSourceChangeListenerImpl2(); // Run query again, to make sure only cache is used Result r5 = executeQuery( "select {[Store].[All Stores].[USA].[CA].[San Francisco]} " + "on columns from [Sales]"); Util.discard(r5); s5 = sqlLogger.getSqlQueries().toString(); sqlLogger.clear(); assertEquals(s4, s5); // Attach dummy change listener that tells mondrian the datasource // is always changed and tells that aggregate cache is always // cached. smrch.changeListener = new DataSourceChangeListenerImpl3(); ssmrch.changeListener = new DataSourceChangeListenerImpl3(); rcsmrch.changeListener = new DataSourceChangeListenerImpl3(); RolapStar star = getStar("Sales"); star.setChangeListener(smrch.changeListener); // Run query again, to make sure only cache is used Result r6 = executeQuery( "select {[Store].[All Stores].[USA].[CA].[San Francisco]} " + "on columns from [Sales]"); Util.discard(r6); s6 = sqlLogger.getSqlQueries().toString(); sqlLogger.clear(); assertEquals(s1, s6); } finally { smrch.changeListener = null; ssmrch.changeListener = null; rcsmrch.changeListener = null; RolapStar star = getStar("Sales"); star.setChangeListener(null); RolapUtil.setHook(null); } } /** * Tests whether the flushing of the cache is thread safe. */ public void testParallelDataSourceChangeListenerPlugin() { if (Bug.avoidSlowTestOnLucidDB(getTestContext().getDialect())) { return; } // 5 threads, 8 cycles each checkCacheFlushing(5, 8); } /** * Tests several threads, each of which is creating connections and * periodically flushing the schema cache. * * @param workerCount Number of worker threads * @param cycleCount Number of cycles each thread should perform */ private void checkCacheFlushing( final int workerCount, final int cycleCount) { final Random random = new Random(123456); Worker[] workers = new Worker[workerCount]; Thread[] threads = new Thread[workerCount]; final String[] queries = { "with member [Store Type].[All Types] as 'Aggregate({[Store Type].[All Store Types].[Deluxe Supermarket], " + "[Store Type].[All Store Types].[Gourmet Supermarket], " + "[Store Type].[All Store Types].[HeadQuarters], " + "[Store Type].[All Store Types].[Mid-Size Grocery], " + "[Store Type].[All Store Types].[Small Grocery], " + "[Store Type].[All Store Types].[Supermarket]})' " + "select NON EMPTY {[Time].[1997]} ON COLUMNS, " + "NON EMPTY [Store].[All Stores].[USA].[CA].Children ON ROWS " + "from [Sales] " + "where ([Store Type].[All Types], [Measures].[Unit Sales], [Customers].[All Customers].[USA], [Product].[All Products].[Drink]) ", "with member [Measures].[Shipped per Ordered] as ' [Measures].[Units Shipped] / [Measures].[Unit Sales] ', format_string='#.00%'\n" + " member [Measures].[Profit per Unit Shipped] as ' [Measures].[Profit] / [Measures].[Units Shipped] '\n" + "select\n" + " {[Measures].[Unit Sales], \n" + " [Measures].[Units Shipped],\n" + " [Measures].[Shipped per Ordered],\n" + " [Measures].[Profit per Unit Shipped]} on 0,\n" + " NON EMPTY Crossjoin([Product].Children, [Time].[1997].Children) on 1\n" + "from [Warehouse and Sales]", "select {[Measures].[Profit Per Unit Shipped]} ON COLUMNS, " + "{[Store].[All Stores].[USA].[CA], [Store].[All Stores].[USA].[OR], [Store].[All Stores].[USA].[WA]} ON ROWS " + "from [Warehouse and Sales Format Expression Cube No Cache] " + "where [Time].[1997]", "select {[Store].[All Stores].[USA].[CA].[San Francisco]} on columns from [Sales]" }; final String[] results = { "Axis #0:\n" + "{[Store Type].[All Types], [Measures].[Unit Sales], [Customers].[USA], [Product].[Drink]}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "Row #0: 1,945\n" + "Row #1: 2,422\n" + "Row #2: 2,560\n" + "Row #3: 175\n", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Units Shipped]}\n" + "{[Measures].[Shipped per Ordered]}\n" + "{[Measures].[Profit per Unit Shipped]}\n" + "Axis #2:\n" + "{[Product].[Drink], [Time].[1997].[Q1]}\n" + "{[Product].[Drink], [Time].[1997].[Q2]}\n" + "{[Product].[Drink], [Time].[1997].[Q3]}\n" + "{[Product].[Drink], [Time].[1997].[Q4]}\n" + "{[Product].[Food], [Time].[1997].[Q1]}\n" + "{[Product].[Food], [Time].[1997].[Q2]}\n" + "{[Product].[Food], [Time].[1997].[Q3]}\n" + "{[Product].[Food], [Time].[1997].[Q4]}\n" + "{[Product].[Non-Consumable], [Time].[1997].[Q1]}\n" + "{[Product].[Non-Consumable], [Time].[1997].[Q2]}\n" + "{[Product].[Non-Consumable], [Time].[1997].[Q3]}\n" + "{[Product].[Non-Consumable], [Time].[1997].[Q4]}\n" + "Row #0: 5,976\n" + "Row #0: 4637.0\n" + "Row #0: 77.59%\n" + "Row #0: $1.50\n" + "Row #1: 5,895\n" + "Row #1: 4501.0\n" + "Row #1: 76.35%\n" + "Row #1: $1.60\n" + "Row #2: 6,065\n" + "Row #2: 6258.0\n" + "Row #2: 103.18%\n" + "Row #2: $1.15\n" + "Row #3: 6,661\n" + "Row #3: 5802.0\n" + "Row #3: 87.10%\n" + "Row #3: $1.38\n" + "Row #4: 47,809\n" + "Row #4: 37153.0\n" + "Row #4: 77.71%\n" + "Row #4: $1.64\n" + "Row #5: 44,825\n" + "Row #5: 35459.0\n" + "Row #5: 79.11%\n" + "Row #5: $1.62\n" + "Row #6: 47,440\n" + "Row #6: 41545.0\n" + "Row #6: 87.57%\n" + "Row #6: $1.47\n" + "Row #7: 51,866\n" + "Row #7: 34706.0\n" + "Row #7: 66.91%\n" + "Row #7: $1.91\n" + "Row #8: 12,506\n" + "Row #8: 9161.0\n" + "Row #8: 73.25%\n" + "Row #8: $1.76\n" + "Row #9: 11,890\n" + "Row #9: 9227.0\n" + "Row #9: 77.60%\n" + "Row #9: $1.65\n" + "Row #10: 12,343\n" + "Row #10: 9986.0\n" + "Row #10: 80.90%\n" + "Row #10: $1.59\n" + "Row #11: 13,497\n" + "Row #11: 9291.0\n" + "Row #11: 68.84%\n" + "Row #11: $1.86\n", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Profit Per Unit Shipped]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: |1.6|style=red\n" + "Row #1: |2.1|style=green\n" + "Row #2: |1.5|style=red\n", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "Row #0: 2,117\n" }; final TestContext testContext = TestContext.instance().create( null, null, "\n" + "
\n" + "\n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " [Measures].[Store Sales] - [Measures].[Store Cost]\n" + " \n" + " \n" + " [Measures].[Profit] / [Measures].[Units Shipped]\n" + " 2.0), '|0.#|style=green', '|0.#|style=red')\"/>\n" + " \n" + "", null, null, null); SmartMemberReader smrStore = getSmartMemberReader(testContext.getConnection(), "Store"); MemberCacheHelper smrStoreCacheHelper = (MemberCacheHelper) smrStore.getMemberCache(); SmartMemberReader smrProduct = getSmartMemberReader(testContext.getConnection(), "Product"); MemberCacheHelper smrProductCacheHelper = (MemberCacheHelper) smrProduct.getMemberCache(); // 1/500 of the time, the hierarchies are flushed // 1/50 of the time, the aggregates are flushed smrStoreCacheHelper.changeListener = new DataSourceChangeListenerImpl4(500, 50); smrProductCacheHelper.changeListener = smrStoreCacheHelper.changeListener; RolapStar star = getStar(testContext.getConnection(), "Sales"); star.setChangeListener(smrStoreCacheHelper.changeListener); star = getStar(testContext.getConnection(), "Warehouse No Cache"); star.setChangeListener(smrStoreCacheHelper.changeListener); for (int i = 0; i < workerCount; i++) { workers[i] = new Worker() { public void runSafe() { for (int i = 0; i < cycleCount; ++i) { cycle(); try { // Sleep up to 100ms. Thread.sleep(random.nextInt(100)); } catch (InterruptedException e) { throw Util.newInternal(e, "interrupted"); } } } private void cycle() { int idx = random.nextInt(4); String query = queries[idx]; String result = results[idx]; testContext.assertQueryReturns(query, result); } }; threads[i] = new Thread(workers[i]); } for (int i = 0; i < workerCount; i++) { threads[i].start(); } for (int i = 0; i < workerCount; i++) { try { threads[i].join(); } catch (InterruptedException e) { throw Util.newInternal(e, "while joining thread #" + i); } } List messages = new ArrayList(); for (Worker worker : workers) { for (Throwable throwable : worker.failures) { messages.add(TestContext.getStackTrace(throwable)); } } if (!messages.isEmpty()) { TestCase.fail(messages.size() + " threads failed\n" + messages); } } private static abstract class Worker implements Runnable { final List failures = new ArrayList(); public void run() { try { runSafe(); } catch (Throwable e) { synchronized (failures) { failures.add(e); } } } public abstract void runSafe(); } private static class SqlLogger implements RolapUtil.ExecuteQueryHook { private final List sqlQueries; public SqlLogger() { this.sqlQueries = new ArrayList(); } public void clear() { sqlQueries.clear(); } public List getSqlQueries() { return sqlQueries; } public void onExecuteQuery(String sql) { // We can safely ignore the count requests. These are // generated by the segment builder and are not always // needed across queries, despite the cache flushing. if (!sql.startsWith("select count(")) { sqlQueries.add(sql); } } } Result executeQuery(String mdx, Connection connection) { Query query = connection.parseQuery(mdx); return connection.execute(query); } SmartMemberReader getSmartMemberReader(String hierName) { Connection con = getTestContext().getConnection(); return getSmartMemberReader(con, hierName); } SmartMemberReader getSmartMemberReader(Connection con, String hierName) { RolapCube cube = (RolapCube) con.getSchema().lookupCube("Sales", true); RolapSchemaReader schemaReader = (RolapSchemaReader) cube.getSchemaReader(); RolapHierarchy hierarchy = (RolapHierarchy) cube.lookupHierarchy( new Id.Segment(hierName, Id.Quoting.UNQUOTED), false); assertNotNull(hierarchy); return (SmartMemberReader) hierarchy.createMemberReader( schemaReader.getRole()); } SmartMemberReader getSharedSmartMemberReader(String hierName) { Connection con = getTestContext().getConnection(); return getSharedSmartMemberReader(con, hierName); } SmartMemberReader getSharedSmartMemberReader( Connection con, String hierName) { RolapCube cube = (RolapCube) con.getSchema().lookupCube("Sales", true); RolapSchemaReader schemaReader = (RolapSchemaReader) cube.getSchemaReader(); RolapCubeHierarchy hierarchy = (RolapCubeHierarchy) cube.lookupHierarchy( new Id.Segment(hierName, Id.Quoting.UNQUOTED), false); assertNotNull(hierarchy); return (SmartMemberReader) hierarchy.getRolapHierarchy() .createMemberReader(schemaReader.getRole()); } RolapStar getStar(String starName) { Connection con = getTestContext().getConnection(); return getStar(con, starName); } RolapStar getStar(Connection con, String starName) { RolapCube cube = (RolapCube) con.getSchema().lookupCube(starName, true); return cube.getStar(); } } // End DataSourceChangeListenerTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/CellKeyTest.java0000644000175000017500000002666311735330606023635 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.MondrianProperties; import mondrian.test.FoodMartTestCase; import mondrian.test.TestContext; /** * Test that the implementations of the CellKey interface are correct. * * @author Richard M. Emberson */ public class CellKeyTest extends FoodMartTestCase { public CellKeyTest() { } public CellKeyTest(String name) { super(name); } public void testMany() { CellKey key = CellKey.Generator.newManyCellKey(5); assertTrue("CellKey size", key.size() == 5); CellKey copy = key.copy(); assertTrue("CellKey equals", key.equals(copy)); int[] ordinals = key.getOrdinals(); copy = CellKey.Generator.newCellKey(ordinals); assertTrue("CellKey equals", key.equals(copy)); boolean gotException = false; try { key.setAxis(6, 1); } catch (Exception ex) { gotException = true; } assertTrue("CellKey axis too big", gotException); gotException = false; try { key.setOrdinals(new int[6]); } catch (Exception ex) { gotException = true; } assertTrue("CellKey array too big", gotException); gotException = false; try { key.setOrdinals(new int[4]); } catch (Exception ex) { gotException = true; } assertTrue("CellKey array too small", gotException); key.setAxis(0, 1); key.setAxis(1, 3); key.setAxis(2, 5); key.setAxis(3, 7); key.setAxis(4, 13); assertTrue("CellKey not equals", !key.equals(copy)); copy = key.copy(); assertTrue("CellKey equals", key.equals(copy)); ordinals = key.getOrdinals(); copy = CellKey.Generator.newCellKey(ordinals); assertTrue("CellKey equals", key.equals(copy)); } public void testZero() { CellKey key = CellKey.Generator.newCellKey(new int[0]); CellKey key2 = CellKey.Generator.newCellKey(new int[0]); assertTrue(key == key2); // all 0-dimensional keys have same singleton assertEquals(0, key.size()); CellKey copy = key.copy(); assertEquals(copy, key); boolean gotException = false; try { key.setAxis(0, 0); } catch (Exception ex) { gotException = true; } assertTrue("CellKey axis too big", gotException); int[] ordinals = key.getOrdinals(); assertEquals(ordinals.length, 0); copy = CellKey.Generator.newCellKey(ordinals); assertTrue("CellKey equals", key.equals(copy)); } public void testOne() { CellKey key = CellKey.Generator.newCellKey(1); assertTrue("CellKey size", key.size() == 1); CellKey copy = key.copy(); assertTrue("CellKey equals", key.equals(copy)); int[] ordinals = key.getOrdinals(); copy = CellKey.Generator.newCellKey(ordinals); assertTrue("CellKey equals", key.equals(copy)); boolean gotException = false; try { key.setAxis(3, 1); } catch (Exception ex) { gotException = true; } assertTrue("CellKey axis too big", gotException); gotException = false; try { key.setOrdinals(new int[3]); } catch (Exception ex) { gotException = true; } assertTrue("CellKey array too big", gotException); gotException = false; try { key.setOrdinals(new int[0]); } catch (Exception ex) { gotException = true; } assertTrue("CellKey array too small", gotException); key.setAxis(0, 1); copy = key.copy(); assertTrue("CellKey equals", key.equals(copy)); ordinals = key.getOrdinals(); copy = CellKey.Generator.newCellKey(ordinals); assertTrue("CellKey equals", key.equals(copy)); } public void testTwo() { CellKey key = CellKey.Generator.newCellKey(2); assertTrue("CellKey size", key.size() == 2); CellKey copy = key.copy(); assertTrue("CellKey equals", key.equals(copy)); int[] ordinals = key.getOrdinals(); copy = CellKey.Generator.newCellKey(ordinals); assertTrue("CellKey equals", key.equals(copy)); boolean gotException = false; try { key.setAxis(3, 1); } catch (Exception ex) { gotException = true; } assertTrue("CellKey axis too big", gotException); gotException = false; try { key.setOrdinals(new int[3]); } catch (Exception ex) { gotException = true; } assertTrue("CellKey array too big", gotException); gotException = false; try { key.setOrdinals(new int[1]); } catch (Exception ex) { gotException = true; } assertTrue("CellKey array too small", gotException); key.setAxis(0, 1); key.setAxis(1, 3); copy = key.copy(); assertTrue("CellKey equals", key.equals(copy)); ordinals = key.getOrdinals(); copy = CellKey.Generator.newCellKey(ordinals); assertTrue("CellKey equals", key.equals(copy)); } public void testThree() { CellKey key = CellKey.Generator.newCellKey(3); assertTrue("CellKey size", key.size() == 3); CellKey copy = key.copy(); assertTrue("CellKey equals", key.equals(copy)); int[] ordinals = key.getOrdinals(); copy = CellKey.Generator.newCellKey(ordinals); assertTrue("CellKey equals", key.equals(copy)); boolean gotException = false; try { key.setAxis(3, 1); } catch (Exception ex) { gotException = true; } assertTrue("CellKey axis too big", gotException); gotException = false; try { key.setOrdinals(new int[4]); } catch (Exception ex) { gotException = true; } assertTrue("CellKey array too big", gotException); gotException = false; try { key.setOrdinals(new int[1]); } catch (Exception ex) { gotException = true; } assertTrue("CellKey array too small", gotException); key.setAxis(0, 1); key.setAxis(1, 3); key.setAxis(2, 5); copy = key.copy(); assertTrue("CellKey equals", key.equals(copy)); ordinals = key.getOrdinals(); copy = CellKey.Generator.newCellKey(ordinals); assertTrue("CellKey equals", key.equals(copy)); } public void testFour() { CellKey key = CellKey.Generator.newCellKey(4); assertTrue("CellKey size", key.size() == 4); CellKey copy = key.copy(); assertTrue("CellKey equals", key.equals(copy)); int[] ordinals = key.getOrdinals(); copy = CellKey.Generator.newCellKey(ordinals); assertTrue("CellKey equals", key.equals(copy)); boolean gotException = false; try { key.setAxis(4, 1); } catch (Exception ex) { gotException = true; } assertTrue("CellKey axis too big", gotException); gotException = false; try { key.setOrdinals(new int[5]); } catch (Exception ex) { gotException = true; } assertTrue("CellKey array too big", gotException); gotException = false; try { key.setOrdinals(new int[1]); } catch (Exception ex) { gotException = true; } assertTrue("CellKey array too small", gotException); key.setAxis(0, 1); key.setAxis(1, 3); key.setAxis(2, 5); key.setAxis(3, 7); copy = key.copy(); assertTrue("CellKey equals", key.equals(copy)); ordinals = key.getOrdinals(); copy = CellKey.Generator.newCellKey(ordinals); assertTrue("CellKey equals", key.equals(copy)); } public void testCellLookup() { if (!isDefaultNullMemberRepresentation()) { return; } String cubeDef = "\n" + "
\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + ""; String query = "With Set [*NATIVE_CJ_SET] as NonEmptyCrossJoin([Gender].Children, [Address2].Children) " + "Select Generate([*NATIVE_CJ_SET], {([Gender].CurrentMember, [Address2].CurrentMember)}) on columns " + "From [SalesTest] where ([City].[Redwood City])"; String result = "Axis #0:\n" + "{[City].[Redwood City]}\n" + "Axis #1:\n" + "{[Gender].[F], [Address2].[#null]}\n" + "{[Gender].[F], [Address2].[#2]}\n" + "{[Gender].[F], [Address2].[Unit H103]}\n" + "{[Gender].[M], [Address2].[#null]}\n" + "{[Gender].[M], [Address2].[#208]}\n" + "Row #0: 71\n" + "Row #0: 10\n" + "Row #0: 3\n" + "Row #0: 52\n" + "Row #0: 8\n"; /* * Make sure ExpandNonNative is not set. Otherwise, the query is * evaluated natively. For the given data set(which contains NULL * members), native evaluation produces results in a different order * from the non-native evaluation. */ propSaver.set( MondrianProperties.instance().ExpandNonNative, false); TestContext testContext = TestContext.instance().create( null, cubeDef, null, null, null, null); testContext.assertQueryReturns(query, result); } public void testSize() { for (int i = 1; i < 20; i++) { assertEquals(i, CellKey.Generator.newCellKey(new int[i]).size()); assertEquals(i, CellKey.Generator.newCellKey(i).size()); } } } // End CellKeyTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/RolapSchemaReaderTest.java0000644000175000017500000001477011735330606025622 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.test.FoodMartTestCase; import mondrian.test.TestContext; import java.util.*; /** * Unit test for {@link SchemaReader}. */ public class RolapSchemaReaderTest extends FoodMartTestCase { public RolapSchemaReaderTest(String name) { super(name); } public void testGetCubesWithNoHrCubes() { String[] expectedCubes = new String[] { "Sales", "Warehouse", "Warehouse and Sales", "Store", "Sales Ragged", "Sales 2" }; Connection connection = getTestContext().withRole("No HR Cube").getConnection(); try { SchemaReader reader = connection.getSchemaReader().withLocus(); Cube[] cubes = reader.getCubes(); assertEquals(expectedCubes.length, cubes.length); assertCubeExists(expectedCubes, cubes); } finally { connection.close(); } } public void testGetCubesWithNoRole() { String[] expectedCubes = new String[] { "Sales", "Warehouse", "Warehouse and Sales", "Store", "Sales Ragged", "Sales 2", "HR" }; Connection connection = getTestContext().getConnection(); try { SchemaReader reader = connection.getSchemaReader().withLocus(); Cube[] cubes = reader.getCubes(); assertEquals(expectedCubes.length, cubes.length); assertCubeExists(expectedCubes, cubes); } finally { connection.close(); } } public void testGetCubesForCaliforniaManager() { String[] expectedCubes = new String[] { "Sales" }; Connection connection = getTestContext().withRole("California manager").getConnection(); try { SchemaReader reader = connection.getSchemaReader().withLocus(); Cube[] cubes = reader.getCubes(); assertEquals(expectedCubes.length, cubes.length); assertCubeExists(expectedCubes, cubes); } finally { connection.close(); } } public void testConnectUseContentChecksum() { Util.PropertyList properties = TestContext.instance().getConnectionProperties().clone(); properties.put( RolapConnectionProperties.UseContentChecksum.name(), "true"); try { DriverManager.getConnection( properties, null); } catch (MondrianException e) { e.printStackTrace(); fail("unexpected exception for UseContentChecksum"); } } private void assertCubeExists(String[] expectedCubes, Cube[] cubes) { List cubesAsList = Arrays.asList(expectedCubes); for (Cube cube : cubes) { String cubeName = cube.getName(); assertTrue( "Cube name not found: " + cubeName, cubesAsList.contains(cubeName)); } } /** * Test case for {@link SchemaReader#getCubeDimensions(mondrian.olap.Cube)} * and {@link SchemaReader#getDimensionHierarchies(mondrian.olap.Dimension)} * methods. * *

Test case for bug * MONDRIAN-691, * "RolapSchemaReader is not enforcing access control on two APIs". */ public void testGetCubeDimensions() { final String timeWeekly = TestContext.hierarchyName("Time", "Weekly"); final String timeTime = TestContext.hierarchyName("Time", "Time"); final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "") .withRole("REG1"); Connection connection = testContext.getConnection(); try { SchemaReader reader = connection.getSchemaReader().withLocus(); final Map cubes = new HashMap(); for (Cube cube : reader.getCubes()) { cubes.put(cube.getName(), cube); } assertTrue(cubes.containsKey("Sales")); // granted access assertFalse(cubes.containsKey("HR")); // denied access assertFalse(cubes.containsKey("Bad")); // not exist final Cube salesCube = cubes.get("Sales"); final Map dimensions = new HashMap(); final Map hierarchies = new HashMap(); for (Dimension dimension : reader.getCubeDimensions(salesCube)) { dimensions.put(dimension.getName(), dimension); for (Hierarchy hierarchy : reader.getDimensionHierarchies(dimension)) { hierarchies.put(hierarchy.getUniqueName(), hierarchy); } } assertFalse(dimensions.containsKey("Store")); // denied access assertTrue(dimensions.containsKey("Marital Status")); // implicit assertTrue(dimensions.containsKey("Time")); // implicit assertFalse(dimensions.containsKey("Bad dimension")); // not exist assertFalse(hierarchies.containsKey("[Foo]")); assertTrue(hierarchies.containsKey("[Product]")); assertTrue(hierarchies.containsKey(timeWeekly)); assertFalse(hierarchies.containsKey("[Time]")); assertFalse(hierarchies.containsKey("[Time].[Time]")); } finally { connection.close(); } } } // End RolapSchemaReaderTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/BitKeyTest.java0000644000175000017500000007631311735330606023471 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.Util; import junit.framework.TestCase; import java.util.BitSet; import java.util.Iterator; /** * Unit test for {@link BitKey}. * * @author Richard Emberson */ public class BitKeyTest extends TestCase { public BitKeyTest(String name) { super(name); } /** * Test that negative size throws IllegalArgumentException. * */ public void testBadSize() { int size = -1; boolean gotException = false; BitKey bitKey = null; try { bitKey = BitKey.Factory.makeBitKey(size); Util.discard(bitKey); } catch (IllegalArgumentException e) { gotException = true; } assertTrue("BitKey negative size " + size, (gotException)); size = -10; gotException = false; try { bitKey = BitKey.Factory.makeBitKey(size); } catch (IllegalArgumentException e) { gotException = true; } assertTrue("BitKey negative size " + size, (gotException)); } /** * Test that non-negative sizes do not throw IllegalArgumentException */ public void testGoodSize() { int size = 0; boolean gotException = false; BitKey bitKey = null; try { bitKey = BitKey.Factory.makeBitKey(size); Util.discard(bitKey); } catch (IllegalArgumentException e) { gotException = true; } assertTrue("BitKey size " + size, !gotException); size = 1; gotException = false; try { bitKey = BitKey.Factory.makeBitKey(size); } catch (IllegalArgumentException e) { gotException = true; } assertTrue("BitKey size " + size, !gotException); size = 10; gotException = false; try { bitKey = BitKey.Factory.makeBitKey(size); } catch (IllegalArgumentException e) { gotException = true; } assertTrue("BitKey size " + size, !gotException); } /** * Test that the implementation object returned is expected type. */ public void testSizeTypes() { int size = 0; BitKey bitKey = BitKey.Factory.makeBitKey(size); assertTrue( "BitKey size " + size + " not BitKey.Small", (bitKey.getClass() == BitKey.Small.class)); size = 63; bitKey = BitKey.Factory.makeBitKey(size); assertTrue( "BitKey size " + size + " not BitKey.Small", (bitKey.getClass() == BitKey.Small.class)); size = 64; bitKey = BitKey.Factory.makeBitKey(size); assertTrue( "BitKey size " + size + " not BitKey.Mid128", (bitKey.getClass() == BitKey.Mid128.class)); size = 65; bitKey = BitKey.Factory.makeBitKey(size); assertTrue( "BitKey size " + size + " not BitKey.Mid128", (bitKey.getClass() == BitKey.Mid128.class)); size = 127; bitKey = BitKey.Factory.makeBitKey(size); assertTrue( "BitKey size " + size + " not BitKey.Mid128", (bitKey.getClass() == BitKey.Mid128.class)); size = 128; bitKey = BitKey.Factory.makeBitKey(size); assertTrue( "BitKey size " + size + " not BitKey.Big", (bitKey.getClass() == BitKey.Big.class)); size = 129; bitKey = BitKey.Factory.makeBitKey(size); assertTrue( "BitKey size " + size + " not BitKey.Big", (bitKey.getClass() == BitKey.Big.class)); size = 1280; bitKey = BitKey.Factory.makeBitKey(size); assertTrue( "BitKey size " + size + " not BitKey.Big", (bitKey.getClass() == BitKey.Big.class)); } /** * Test for equals and not equals */ public void testEquals() { int[][] positionsArray0 = { new int[] { 0, 1, 2, 3, }, new int[] { 3, 17, 33, 63 }, new int[] { 1, 2, 3, 20, 21, 33, 61, 62, 63 }, }; doTestEquals(0, 0, positionsArray0); doTestEquals(0, 64, positionsArray0); doTestEquals(64, 0, positionsArray0); doTestEquals(0, 128, positionsArray0); doTestEquals(128, 0, positionsArray0); doTestEquals(64, 128, positionsArray0); doTestEquals(128, 64, positionsArray0); int[][] positionsArray1 = { new int[] { 0, 1, 2, 3, }, new int[] { 3, 17, 33, 63 }, new int[] { 1, 2, 3, 20, 21, 33, 61, 62, 63 }, new int[] { 1, 2, 3, 20, 21, 33, 61, 62, 55, 56, 127 }, }; doTestEquals(65, 65, positionsArray1); doTestEquals(65, 128, positionsArray1); doTestEquals(128, 65, positionsArray1); doTestEquals(128, 128, positionsArray1); int[][] positionsArray2 = { new int[] { 0, 1, 2, 3, }, new int[] { 1, 2, 3, 20, 21, 33, 61, 62, 55, 56, 127, 128 }, new int[] { 1, 2, 499}, new int[] { 1, 2, 200, 300, 499}, }; doTestEquals(500, 500, positionsArray2); doTestEquals(500, 700, positionsArray2); doTestEquals(700, 500, positionsArray2); doTestEquals(700, 700, positionsArray2); } public void testHashCode() { BitKey small = BitKey.Factory.makeBitKey(10); BitKey mid = BitKey.Factory.makeBitKey(70); BitKey big255 = BitKey.Factory.makeBitKey(255); BitKey big256 = BitKey.Factory.makeBitKey(256); BitKey big257 = BitKey.Factory.makeBitKey(257); BitKey[] bitKeys = {small, mid, big255, big256, big257}; doHashCode(bitKeys); for (int i = 0; i < bitKeys.length; i++) { bitKeys[i].set(0, true); } doHashCode(bitKeys); bitKeys = new BitKey[] {mid, big255, big256, big257}; for (int i = 0; i < bitKeys.length; i++) { bitKeys[i].set(50, true); } doHashCode(bitKeys); bitKeys = new BitKey[] {big255, big256, big257}; for (int i = 0; i < bitKeys.length; i++) { bitKeys[i].set(128, true); bitKeys[i].set(50, false); } doHashCode(bitKeys); } /** * Applies hashCode, compareTo and equals to all combinations of bit keys, * including reflexive (comparing a key to itself) and symmetric * (comparing in both directions). All keys must be equal (albeit different * representations). */ private void doHashCode(BitKey[] bitKeys) { for (int i1 = 0; i1 < bitKeys.length; i1++) { BitKey bitKey1 = bitKeys[i1]; for (int i2 = 0; i2 < bitKeys.length; i2++) { BitKey bitKey2 = bitKeys[i2]; String s = "(" + i1 + ", " + i2 + ")"; assertEquals(s, bitKey1, bitKey2); assertEquals(s, bitKey1.hashCode(), bitKey2.hashCode()); assertEquals(s, 0, bitKey1.compareTo(bitKey2)); } } } /** * Test for not equals and not equals */ public void testNotEquals() { int[] positions0 = { 0, 1, 2, 3, 4 }; int[] positions1 = { 0, 1, 2, 3, }; doTestNotEquals(0, positions0, 0, positions1); doTestNotEquals(0, positions1, 0, positions0); doTestNotEquals(0, positions0, 64, positions1); doTestNotEquals(0, positions1, 64, positions0); doTestNotEquals(64, positions0, 0, positions1); doTestNotEquals(64, positions1, 0, positions0); doTestNotEquals(0, positions0, 128, positions1); doTestNotEquals(128, positions1, 0, positions0); doTestNotEquals(64, positions0, 128, positions1); doTestNotEquals(128, positions1, 64, positions0); doTestNotEquals(128, positions0, 128, positions1); doTestNotEquals(128, positions1, 128, positions0); int[] positions2 = { 0, 1, }; int[] positions3 = { 0, 1, 113 }; doTestNotEquals(0, positions2, 127, positions3); doTestNotEquals(127, positions3, 0, positions2); int[] positions4 = { 0, 1, 100, 121 }; int[] positions5 = { 0, 1, 100, 121, 200 }; doTestNotEquals(127, positions4, 300, positions5); doTestNotEquals(300, positions5, 127, positions4); int[] positions6 = { 0, 1, 100, 121, 200, }; int[] positions7 = { 0, 1, 100, 121, 130, 200, }; doTestNotEquals(200, positions6, 300, positions7); doTestNotEquals(300, positions7, 200, positions6); } /** * Test that after clear the internal values are 0. */ public void testClear() { BitKey bitKey_0 = BitKey.Factory.makeBitKey(0); BitKey bitKey_64 = BitKey.Factory.makeBitKey(64); BitKey bitKey_128 = BitKey.Factory.makeBitKey(128); int size0 = 20; int[] positions0 = { 0, 1, 2, 3, 4 }; BitKey bitKey0 = makeAndSet(size0, positions0); bitKey0.clear(); assertTrue( "BitKey 0 not equals after clear to 0", (bitKey0.equals(bitKey_0))); assertTrue( "BitKey 0 not equals after clear to 64", (bitKey0.equals(bitKey_64))); assertTrue( "BitKey 0 not equals after clear to 128", (bitKey0.equals(bitKey_128))); int size1 = 68; int[] positions1 = { 0, 1, 2, 3, 4, 45, 67 }; BitKey bitKey1 = makeAndSet(size1, positions1); bitKey1.clear(); assertTrue( "BitKey 1 not equals after clear to 0", (bitKey1.equals(bitKey_0))); assertTrue( "BitKey 1 not equals after clear to 64", (bitKey1.equals(bitKey_64))); assertTrue( "BitKey 1 not equals after clear to 128", (bitKey1.equals(bitKey_128))); int size2 = 400; int[] positions2 = { 0, 1, 2, 3, 4, 45, 67, 213, 333 }; BitKey bitKey2 = makeAndSet(size2, positions2); bitKey2.clear(); assertTrue( "BitKey 2 not equals after clear to 0", (bitKey2.equals(bitKey_0))); assertTrue( "BitKey 2 not equals after clear to 64", (bitKey2.equals(bitKey_64))); assertTrue( "BitKey 2 not equals after clear to 128", (bitKey2.equals(bitKey_128))); } public void testNewBitKeyIsTheSameAsAClearedBitKey() { BitKey bitKey = BitKey.Factory.makeBitKey(8); bitKey.set(1); assertFalse(BitKey.Factory.makeBitKey(8).equals(bitKey)); bitKey.clear(); assertEquals(BitKey.Factory.makeBitKey(8), bitKey); } public void testEmptyCopyCreatesBitKeyOfTheSameSize() { BitKey bitKey = BitKey.Factory.makeBitKey(8); assertEquals(bitKey, bitKey.emptyCopy()); } /** * This test is one BitKey is a subset of another. */ public void testIsSuperSetOf() { int size0 = 20; int[] positions0 = { 0, 2, 3, 4, 23, 30 }; BitKey bitKey0 = makeAndSet(size0, positions0); int size1 = 20; int[] positions1 = { 0, 2, 23 }; BitKey bitKey1 = makeAndSet(size1, positions1); assertTrue( "BitKey 1 not subset of 0", (bitKey0.isSuperSetOf(bitKey1))); assertTrue( "BitKey 0 is subset of 1", (!bitKey1.isSuperSetOf(bitKey0))); int size2 = 65; int[] positions2 = { 0, 1, 2, 3, 4, 23, 30, 113 }; BitKey bitKey2 = makeAndSet(size2, positions2); assertTrue( "BitKey 0 not subset of 2", (bitKey2.isSuperSetOf(bitKey0))); assertTrue( "BitKey 1 not subset of 2", (bitKey2.isSuperSetOf(bitKey1))); assertTrue( "BitKey 2 is subset of 0", (!bitKey0.isSuperSetOf(bitKey2))); assertTrue( "BitKey 2 is subset of 1", (!bitKey1.isSuperSetOf(bitKey2))); int size3 = 213; int[] positions3 = { 0, 1, 2, 3, 4, 23, 30, 113, 145, 233, 234 }; BitKey bitKey3 = makeAndSet(size3, positions3); assertTrue( "BitKey 0 not subset of 3", (bitKey3.isSuperSetOf(bitKey0))); assertTrue( "BitKey 1 not subset of 3", (bitKey3.isSuperSetOf(bitKey1))); assertTrue( "BitKey 2 not subset of 3", (bitKey3.isSuperSetOf(bitKey2))); assertTrue( "BitKey 3 is subset of 0", (!bitKey0.isSuperSetOf(bitKey3))); assertTrue( "BitKey 3 is subset of 1", (!bitKey1.isSuperSetOf(bitKey3))); assertTrue( "BitKey 3 is subset of 2", (!bitKey2.isSuperSetOf(bitKey3))); } /** * Tests the 'or' operation on BitKeys */ public void testOr() { doTestOp( new Checker() { public void check( int size0, int[] positions0, int size1, int[] positions1) { BitKey bitKey0 = makeAndSet(size0, positions0); BitKey bitKey1 = makeAndSet(size1, positions1); BitKey bitKey = bitKey0.or(bitKey1); int max = 0; for (int i = 0; i < positions0.length; i++) { max = Math.max(max, positions0[i]); } for (int i = 0; i < positions1.length; i++) { max = Math.max(max, positions1[i]); } for (int pos = 0; pos <= max; pos++) { boolean expected = contains(positions0, pos) || contains(positions1, pos); assertEquals(expected, bitKey.get(pos)); } } }); } /** * Tests the 'nor' operation on BitKeys */ public void testOrNot() { doTestOp( new Checker() { public void check( int size0, int[] positions0, int size1, int[] positions1) { BitKey bitKey0 = makeAndSet(size0, positions0); BitKey bitKey1 = makeAndSet(size1, positions1); BitKey bitKey = bitKey0.orNot(bitKey1); int max = 0; for (int i = 0; i < positions0.length; i++) { max = Math.max(max, positions0[i]); } for (int i = 0; i < positions1.length; i++) { max = Math.max(max, positions1[i]); } for (int pos = 0; pos <= max; pos++) { boolean expected = contains(positions0, pos) ^ contains(positions1, pos); assertEquals(expected, bitKey.get(pos)); } } }); } /** * Tests the 'and' operation on BitKeys */ public void testAnd() { doTestOp( new Checker() { public void check( int size0, int[] positions0, int size1, int[] positions1) { BitKey bitKey0 = makeAndSet(size0, positions0); BitKey bitKey1 = makeAndSet(size1, positions1); BitKey bitKey = bitKey0.and(bitKey1); int max = 0; for (int i = 0; i < positions0.length; i++) { max = Math.max(max, positions0[i]); } for (int i = 0; i < positions1.length; i++) { max = Math.max(max, positions1[i]); } for (int pos = 0; pos <= max; pos++) { boolean expected = contains(positions0, pos) && contains(positions1, pos); assertEquals(expected, bitKey.get(pos)); } } }); } /** * Tests the {@link BitKey#andNot(BitKey)} operation. */ public void testAndNot() { doTestOp( new Checker() { public void check( int size0, int[] positions0, int size1, int[] positions1) { BitKey bitKey0 = makeAndSet(size0, positions0); BitKey bitKey1 = makeAndSet(size1, positions1); BitKey bitKey = bitKey0.andNot(bitKey1); int max = 0; for (int i = 0; i < positions0.length; i++) { max = Math.max(max, positions0[i]); } for (int i = 0; i < positions1.length; i++) { max = Math.max(max, positions1[i]); } for (int pos = 0; pos <= max; pos++) { boolean expected = contains(positions0, pos) && !contains(positions1, pos); assertEquals(expected, bitKey.get(pos)); } } }); } /** * Tests the 'intersects' operation on BitKeys */ public void testIntersects() { doTestOp( new Checker() { public void check( int size0, int[] positions0, int size1, int[] positions1) { BitKey bitKey0 = makeAndSet(size0, positions0); BitKey bitKey1 = makeAndSet(size1, positions1); boolean result = bitKey0.intersects(bitKey1); boolean expected = false; for (int i = 0; i < positions0.length; i++) { for (int j = 0; j < positions1.length; j++) { if (positions0[i] == positions1[j]) { expected = true; } } } assertEquals(expected, result); } }); } /** * Tests the {@link BitKey#toBitSet()} method. */ public void testToBitSet() { doTestOp( new Checker() { public void check( int size0, int[] positions0, int size1, int[] positions1) { BitKey bitKey0 = makeAndSet(size0, positions0); final BitSet bitSet = bitKey0.toBitSet(); int j = 0; for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) { assertTrue(i == positions0[j++]); } assertTrue(j == positions0.length); } }); } /** * Tests the 'compareTo' operation on BitKeys */ public void testCompareTo() { doTestOp( new Checker() { public void check( int size0, int[] positions0, int size1, int[] positions1) { BitKey bitKey0 = makeAndSet(size0, positions0); BitKey bitKey1 = makeAndSet(size1, positions1); int c = bitKey0.compareTo(bitKey1); final String s0 = bitKey0.toString(); final String s1 = bitKey1.toString(); String ps0 = s0.substring("0x".length()); String ps1 = s1.substring("0x".length()); while (ps0.length() < ps1.length()) { ps0 = "0" + ps0; } while (ps1.length() < ps0.length()) { ps1 = "0" + ps1; } assertEquals(c, sign(ps0.compareTo(ps1))); assertEquals(-c, bitKey1.compareTo(bitKey0)); assertEquals(0, bitKey0.compareTo(bitKey0)); assertEquals(0, bitKey1.compareTo(bitKey1)); } }); } private static int sign(int c) { return c < 0 ? -1 : c > 0 ? 1 : 0; } private void doTestOp(final Checker checker) { int size0 = 40; int size1 = 100; int size2 = 400; int[] positions0 = { 0 }; int[] positions1 = { 1 }; checker.check(size0, positions0, size0, positions1); int[] positions2 = { 0, 1, 10, 20 }; int[] positions3 = { 1, 2, 10, 11 }; checker.check(size0, positions2, size0, positions3); int[] positions4 = { 0, 1, 10, 20 }; int[] positions5 = { 1, 2, 10, 65, 66 }; checker.check(size0, positions4, size1, positions5); checker.check(size1, positions5, size0, positions4); int[] positions6 = { 0, 1, 10, 20, 64, 65, 66 }; int[] positions7 = { 1, 2, 10, 65, 66 }; checker.check(size1, positions6, size1, positions7); int[] positions8 = { 0, 1, 10, 20 }; int[] positions9 = { 1, 2, 10, 165, 366 }; checker.check(size0, positions8, size2, positions9); checker.check(size2, positions9, size0, positions8); int[] positions10 = { 0, 1, 10, 20, 100 }; int[] positions11 = { 1, 2, 10, 165, 366 }; checker.check(size1, positions10, size2, positions11); checker.check(size2, positions11, size1, positions10); int[] positions12 = { 0, 1, 10, 20, 100, 165, 367 }; int[] positions13 = { 1, 2, 10, 165, 366 }; checker.check(size2, positions12, size2, positions13); checker.check(size2, positions13, size2, positions12); int[] positions14 = {63}; int[] positions15 = {63, 127, 191}; checker.check(size1, positions14, size1, positions14); checker.check(size2, positions15, size2, positions15); } private interface Checker { void check( int size0, int[] positions0, int size1, int[] positions1); } private static boolean contains(int[] positions, int pos) { for (int i = 0; i < positions.length; i++) { if (positions[i] == pos) { return true; } } return false; } public void testCreateFromBitSet() { final BitSet bitSet = new BitSet(72); bitSet.set(2); bitSet.set(3); bitSet.set(5); bitSet.set(11); BitKey bitKey = BitKey.Factory.makeBitKey(bitSet); assertEquals( "0x0000000000000000000000000000000000000000000000000000100000101100", bitKey.toString()); final BitSet emptyBitSet = new BitSet(77); bitKey = BitKey.Factory.makeBitKey(emptyBitSet); assertTrue(bitKey.isEmpty()); } public void testIsEmpty() { BitKey small = BitKey.Factory.makeBitKey(3); assertTrue(small.isEmpty()); small.set(2); assertFalse(small.isEmpty()); BitKey medium = BitKey.Factory.makeBitKey(66); assertTrue(medium.isEmpty()); medium.set(2); assertFalse(medium.isEmpty()); medium.set(2, false); assertTrue(medium.isEmpty()); medium.set(65); assertFalse(medium.isEmpty()); BitKey large = BitKey.Factory.makeBitKey(131); assertTrue(large.isEmpty()); large.set(2); assertFalse(large.isEmpty()); large.set(129); assertFalse(large.isEmpty()); large.set(129, false); large.set(2, false); assertTrue(large.isEmpty()); } public void testIterator() { /* printBitPositions(0); printBitPositions(1); printBitPositions(2); printBitPositions(3); printBitPositions(4); printBitPositions(5); printBitPositions(6); printBitPositions(7); printBitPositions(8); printBitPositions(9); */ // BitKey.Small int[] bitPositions = new int[] { //0, 1, 2 1 }; doTestIterator(bitPositions); bitPositions = new int[] { 2, 3, 4, 7, 14 }; doTestIterator(bitPositions); bitPositions = new int[] { 3, 6, 9, 12, 15, 24, 35, 48 }; doTestIterator(bitPositions); bitPositions = new int[] { 60, 62 }; doTestIterator(bitPositions); bitPositions = new int[] { 1, 3, 60, 63 }; doTestIterator(bitPositions); bitPositions = new int[] { 63 }; doTestIterator(bitPositions); bitPositions = new int[] { 0, 1, 62, 63 }; doTestIterator(bitPositions); // BitKey.Mid128 bitPositions = new int[] { 65 }; doTestIterator(bitPositions); bitPositions = new int[] { 1, 65 }; doTestIterator(bitPositions); bitPositions = new int[] { 1, 63, 64, 65, 66, 127 }; doTestIterator(bitPositions); bitPositions = new int[] { 127 }; doTestIterator(bitPositions); // BitKey.Big bitPositions = new int[] { 128 }; doTestIterator(bitPositions); bitPositions = new int[] { 192 }; doTestIterator(bitPositions); bitPositions = new int[] { 1, 128 }; doTestIterator(bitPositions); bitPositions = new int[] { 0, 1, 127, 193 }; doTestIterator(bitPositions); bitPositions = new int[] { 0, 1, 127, 128, 191, 192, 193 }; doTestIterator(bitPositions); bitPositions = new int[] { 0, 1, 62, 63, 64, 127, 128, 191, 192, 193 }; doTestIterator(bitPositions); bitPositions = new int[] { 567 }; doTestIterator(bitPositions); bitPositions = new int[] { }; doTestIterator(bitPositions); } private void printBitPositions(int i) { int b = (i & -i); int p = BitKey.bitPositionTable[b]; System.out.println(" i=" + i + ",b=" + b + ",p=" + p); } private void doTestIterator(int[] bitPositions) { int maxPosition = 0; for (int pos : bitPositions) { if (pos > maxPosition) { maxPosition = pos; } } BitKey bitKey = BitKey.Factory.makeBitKey(maxPosition); for (int pos : bitPositions) { bitKey.set(pos); } int index = 0; for (Integer i : bitKey) { assertEquals(i, Integer.valueOf(bitPositions[index++])); } // Check cardinality assertEquals(bitKey.cardinality(), bitPositions.length); // Check nextSetBit index = -1; final Iterator iter = bitKey.iterator(); while (iter.hasNext()) { index = bitKey.nextSetBit(index + 1); assertEquals(index, (int) iter.next()); } assertEquals(-1, bitKey.nextSetBit(index + 1)); } private void doTestEquals(int size0, int size1, int[][] positionsArray) { for (int i = 0; i < positionsArray.length; i++) { int[] positions = positionsArray[i]; BitKey bitKey0 = makeAndSet(size0, positions); BitKey bitKey1 = makeAndSet(size1, positions); assertTrue( "BitKey not equals size0=" + size0 + ", size1=" + size1 + ", i=" + i, (bitKey0.equals(bitKey1))); } } private void doTestNotEquals( int size0, int[] positions0, int size1, int[] positions1) { BitKey bitKey0 = makeAndSet(size0, positions0); BitKey bitKey1 = makeAndSet(size1, positions1); assertTrue( "BitKey not equals size0=" + size0 + ", size1=" + size1, (!bitKey0.equals(bitKey1))); } private static BitKey makeAndSet(int size, int[] positions) { BitKey bitKey = BitKey.Factory.makeBitKey(size); for (int i = 0; i < positions.length; i++) { bitKey.set(positions[i]); } return bitKey; } public void testCompareUnsigned() { assertEquals(0, BitKey.AbstractBitKey.compareUnsigned(0, 0)); assertEquals(0, BitKey.AbstractBitKey.compareUnsigned(10, 10)); assertEquals(0, BitKey.AbstractBitKey.compareUnsigned(-3, -3)); assertEquals(-1, BitKey.AbstractBitKey.compareUnsigned(0, 1)); assertEquals(1, BitKey.AbstractBitKey.compareUnsigned(1, 0)); // negative numbers are interpreted as large unsigned assertEquals(1, BitKey.AbstractBitKey.compareUnsigned(-1, 1)); assertEquals(1, BitKey.AbstractBitKey.compareUnsigned(-1, 0)); // -1 is a larger unsigned number than -2 assertEquals(1, BitKey.AbstractBitKey.compareUnsigned(-1, -2)); assertEquals(-1, BitKey.AbstractBitKey.compareUnsigned(-2, -1)); } public void testCompareUnsignedLongArrays() { // empty arrays are equal assertEquals( 0, BitKey.AbstractBitKey.compareUnsignedArrays( new long[] {}, new long[] {})); // empty array does not equal other assertEquals( -1, BitKey.AbstractBitKey.compareUnsignedArrays( new long[] {}, new long[] {1})); // empty array with left-padding assertEquals( 0, BitKey.AbstractBitKey.compareUnsignedArrays( new long[] {}, new long[] {0, 0})); assertEquals( 0, BitKey.AbstractBitKey.compareUnsignedArrays( new long[] {0}, new long[] {})); assertEquals( 0, BitKey.AbstractBitKey.compareUnsignedArrays( new long[] {0, 0}, new long[] {0, 0})); // 0x00000050000001 > 00000040000002 assertEquals( 1, BitKey.AbstractBitKey.compareUnsignedArrays( new long[] {1, 5}, new long[] {2, 4})); // 0x00000050000001 < 00000050000002 assertEquals( -1, BitKey.AbstractBitKey.compareUnsignedArrays( new long[] {1, 5}, new long[] {2, 5})); // as above, with zero padding assertEquals( -1, BitKey.AbstractBitKey.compareUnsignedArrays( new long[] {1, 5}, new long[] {2, 5, 0, 0})); assertEquals( -1, BitKey.AbstractBitKey.compareUnsignedArrays( new long[] {1, 5, 0, 0, 0}, new long[] {2, 5, 0, 0})); assertEquals( -1, BitKey.AbstractBitKey.compareUnsignedArrays( new long[] {1, 5, 0, 0, 0}, new long[] {2, 5})); // negative numbers are interpreted as large unsigned assertEquals( 1, BitKey.AbstractBitKey.compareUnsignedArrays( new long[] {1, 5}, new long[] {-2, 4})); } } // End BitKeyTest.java mondrian-3.4.1/testsrc/main/mondrian/rolap/CacheFlushTest.ref.xml0000644000175000017500000001052611735330606024733 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/rolap/FastBatchingCellReaderTest.java0000644000175000017500000026104511735330606026560 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.MondrianProperties; import mondrian.olap.MondrianServer; import mondrian.rolap.agg.*; import mondrian.server.*; import mondrian.spi.Dialect; import mondrian.test.SqlPattern; import mondrian.test.TestContext; import mondrian.util.Bug; import mondrian.util.DelegatingInvocationHandler; import java.lang.reflect.Proxy; import java.util.*; import java.util.concurrent.Future; /** * Test for FastBatchingCellReader. * * @author Thiyagu * @since 24-May-2007 */ public class FastBatchingCellReaderTest extends BatchTestCase { private Locus locus; private Execution e; private AggregationManager aggMgr; private RolapCube salesCube; @Override protected void setUp() throws Exception { super.setUp(); getTestContext().getConnection() .getCacheControl(null).flushSchemaCache(); final Statement statement = ((RolapConnection) getTestContext().getConnection()) .getInternalStatement(); e = new Execution(statement, 0); aggMgr = e.getMondrianStatement() .getMondrianConnection() .getServer().getAggregationManager(); locus = new Locus(e, "FastBatchingCellReaderTest", null); Locus.push(locus); salesCube = (RolapCube) getTestContext().getConnection().getSchemaReader() .withLocus().getCubes()[0]; } @Override protected void tearDown() throws Exception { Locus.pop(locus); // cleanup e = null; aggMgr = null; locus = null; salesCube = null; super.tearDown(); } private BatchLoader createFbcr( Boolean useGroupingSets, RolapCube cube) { Dialect dialect = cube.getStar().getSqlQueryDialect(); if (useGroupingSets != null) { dialect = dialectWithGroupingSets(dialect, useGroupingSets); } return new BatchLoader( Locus.peek(), aggMgr.cacheMgr, dialect, cube); } private Dialect dialectWithGroupingSets( final Dialect dialect, final boolean supportsGroupingSets) { return (Dialect) Proxy.newProxyInstance( Dialect.class.getClassLoader(), new Class[] {Dialect.class}, new MyDelegatingInvocationHandler(dialect, supportsGroupingSets)); } public void testMissingSubtotalBugMetricFilter() { assertQueryReturns( "With " + "Set [*NATIVE_CJ_SET] as " + "'NonEmptyCrossJoin({[Time].[Year].[1997]}," + " NonEmptyCrossJoin({[Product].[All Products].[Drink]},{[Education Level].[All Education Levels].[Bachelors Degree]}))' " + "Set [*METRIC_CJ_SET] as 'Filter([*NATIVE_CJ_SET],[Measures].[*Unit Sales_SEL~SUM] > 1000.0)' " + "Set [*METRIC_MEMBERS_Education Level] as 'Generate([*METRIC_CJ_SET], {[Education Level].CurrentMember})' " + "Member [Measures].[*Unit Sales_SEL~SUM] as '([Measures].[Unit Sales],[Time].[Time].CurrentMember,[Product].CurrentMember,[Education Level].CurrentMember)', SOLVE_ORDER=200 " + "Member [Education Level].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter([*METRIC_MEMBERS_Education Level],[Measures].[*Unit Sales_SEL~SUM] > 1000.0))', SOLVE_ORDER=-102 " + "Select " + "{[Measures].[Unit Sales]} on columns, " + "Non Empty Union(CrossJoin(Generate([*METRIC_CJ_SET], {([Time].[Time].CurrentMember,[Product].CurrentMember)}),{[Education Level].[*CTX_MEMBER_SEL~SUM]})," + " Generate([*METRIC_CJ_SET], {([Time].[Time].CurrentMember,[Product].CurrentMember,[Education Level].CurrentMember)})) on rows " + "From [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997], [Product].[Drink], [Education Level].[*CTX_MEMBER_SEL~SUM]}\n" + "{[Time].[1997], [Product].[Drink], [Education Level].[Bachelors Degree]}\n" + "Row #0: 6,423\n" + "Row #1: 6,423\n"); } public void testMissingSubtotalBugMultiLevelMetricFilter() { assertQueryReturns( "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Product],[*BASE_MEMBERS_Education Level])' " + "Set [*METRIC_CJ_SET] as 'Filter([*NATIVE_CJ_SET],[Measures].[*Store Cost_SEL~SUM] > 1000.0)' " + "Set [*BASE_MEMBERS_Product] as '{[Product].[All Products].[Drink].[Beverages],[Product].[All Products].[Food].[Baked Goods]}' " + "Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' " + "Set [*BASE_MEMBERS_Education Level] as '{[Education Level].[All Education Levels].[High School Degree],[Education Level].[All Education Levels].[Partial High School]}' " + "Set [*METRIC_MEMBERS_Education Level] as 'Generate([*METRIC_CJ_SET], {[Education Level].CurrentMember})' " + "Member [Measures].[*Store Cost_SEL~SUM] as '([Measures].[Store Cost],[Product].CurrentMember,[Education Level].CurrentMember)', SOLVE_ORDER=200 " + "Member [Product].[Drink].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter([*METRIC_MEMBERS_Product],[Product].CurrentMember.Parent = [Product].[All Products].[Drink]))', SOLVE_ORDER=-100 " + "Member [Product].[Food].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter([*METRIC_MEMBERS_Product],[Product].CurrentMember.Parent = [Product].[All Products].[Food]))', SOLVE_ORDER=-100 " + "Member [Education Level].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter([*METRIC_MEMBERS_Education Level],[Measures].[*Store Cost_SEL~SUM] > 1000.0))', SOLVE_ORDER=-101 " + "Select " + "{[Measures].[Store Cost]} on columns, " + "NonEmptyCrossJoin({[Product].[Drink].[*CTX_MEMBER_SEL~SUM],[Product].[Food].[*CTX_MEMBER_SEL~SUM]},{[Education Level].[*CTX_MEMBER_SEL~SUM]}) " + "on rows From [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Cost]}\n" + "Axis #2:\n" + "{[Product].[Drink].[*CTX_MEMBER_SEL~SUM], [Education Level].[*CTX_MEMBER_SEL~SUM]}\n" + "{[Product].[Food].[*CTX_MEMBER_SEL~SUM], [Education Level].[*CTX_MEMBER_SEL~SUM]}\n" + "Row #0: 6,535.30\n" + "Row #1: 3,860.89\n"); } public void testShouldUseGroupingFunctionOnPropertyTrueAndOnSupportedDB() { propSaver.set( MondrianProperties.instance().EnableGroupingSets, true); BatchLoader fbcr = createFbcr(true, salesCube); assertTrue(fbcr.shouldUseGroupingFunction()); } public void testShouldUseGroupingFunctionOnPropertyTrueAndOnNonSupportedDB() { propSaver.set( MondrianProperties.instance().EnableGroupingSets, true); BatchLoader fbcr = createFbcr(false, salesCube); assertFalse(fbcr.shouldUseGroupingFunction()); } public void testShouldUseGroupingFunctionOnPropertyFalseOnSupportedDB() { propSaver.set( MondrianProperties.instance().EnableGroupingSets, false); BatchLoader fbcr = createFbcr(true, salesCube); assertFalse(fbcr.shouldUseGroupingFunction()); } public void testShouldUseGroupingFunctionOnPropertyFalseOnNonSupportedDB() { propSaver.set( MondrianProperties.instance().EnableGroupingSets, false); BatchLoader fbcr = createFbcr(false, salesCube); assertFalse(fbcr.shouldUseGroupingFunction()); } public void testDoesDBSupportGroupingSets() { final Dialect dialect = getTestContext().getDialect(); FastBatchingCellReader fbcr = new FastBatchingCellReader(e, salesCube, aggMgr) { Dialect getDialect() { return dialect; } }; switch (dialect.getDatabaseProduct()) { case ORACLE: case TERADATA: case DB2: case DB2_AS400: case DB2_OLD_AS400: case GREENPLUM: assertTrue(fbcr.getDialect().supportsGroupingSets()); break; default: assertFalse(fbcr.getDialect().supportsGroupingSets()); break; } } public void testGroupBatchesForNonGroupableBatchesWithSorting() { final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch genderBatch = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "gender", "F")); BatchLoader.Batch maritalStatusBatch = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "marital_status", "M")); ArrayList batchList = new ArrayList(); batchList.add(genderBatch); batchList.add(maritalStatusBatch); List groupedBatches = BatchLoader.groupBatches(batchList); assertEquals(batchList.size(), groupedBatches.size()); assertEquals(genderBatch, groupedBatches.get(0).detailedBatch); assertEquals(maritalStatusBatch, groupedBatches.get(1).detailedBatch); } public void testGroupBatchesForNonGroupableBatchesWithConstraints() { final BatchLoader fbcr = createFbcr(null, salesCube); List compoundMembers = new ArrayList(); compoundMembers.add(new String[] {"USA", "CA"}); compoundMembers.add(new String[] {"Canada", "BC"}); CellRequestConstraint constraint = makeConstraintCountryState(compoundMembers); BatchLoader.Batch genderBatch = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "gender", "F", constraint)); BatchLoader.Batch maritalStatusBatch = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "marital_status", "M", constraint)); ArrayList batchList = new ArrayList(); batchList.add(genderBatch); batchList.add(maritalStatusBatch); List groupedBatches = BatchLoader.groupBatches(batchList); assertEquals(batchList.size(), groupedBatches.size()); assertEquals(genderBatch, groupedBatches.get(0).detailedBatch); assertEquals(maritalStatusBatch, groupedBatches.get(1).detailedBatch); } public void testGroupBatchesForGroupableBatches() { final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch genderBatch = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "gender", "F")) { boolean canBatch(BatchLoader.Batch other) { return false; } }; BatchLoader.Batch superBatch = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, new String[0], new String[0], new String[0])) { boolean canBatch(BatchLoader.Batch batch) { return true; } }; ArrayList batchList = new ArrayList(); batchList.add(genderBatch); batchList.add(superBatch); List groupedBatches = BatchLoader.groupBatches(batchList); assertEquals(1, groupedBatches.size()); assertEquals(superBatch, groupedBatches.get(0).detailedBatch); assertTrue( groupedBatches.get(0).summaryBatches.contains(genderBatch)); } public void testGroupBatchesForGroupableBatchesAndNonGroupableBatches() { final BatchLoader fbcr = createFbcr(null, salesCube); final BatchLoader.Batch group1Agg2 = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "gender", "F")) { boolean canBatch(BatchLoader.Batch batch) { return false; } }; final BatchLoader.Batch group1Agg1 = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "country", "F")) { boolean canBatch(BatchLoader.Batch batch) { return batch.equals(group1Agg2); } }; BatchLoader.Batch group1Detailed = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, new String[0], new String[0], new String[0])) { boolean canBatch(BatchLoader.Batch batch) { return batch.equals(group1Agg1); } }; final BatchLoader.Batch group2Agg1 = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "education", "F")) { boolean canBatch(BatchLoader.Batch batch) { return false; } }; BatchLoader.Batch group2Detailed = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "yearly_income", "")) { boolean canBatch(BatchLoader.Batch batch) { return batch.equals(group2Agg1); } }; ArrayList batchList = new ArrayList(); batchList.add(group1Agg1); batchList.add(group1Agg2); batchList.add(group1Detailed); batchList.add(group2Agg1); batchList.add(group2Detailed); List groupedBatches = BatchLoader.groupBatches(batchList); assertEquals(2, groupedBatches.size()); assertEquals(group1Detailed, groupedBatches.get(0).detailedBatch); assertTrue(groupedBatches.get(0).summaryBatches.contains(group1Agg1)); assertTrue(groupedBatches.get(0).summaryBatches.contains(group1Agg2)); assertEquals(group2Detailed, groupedBatches.get(1).detailedBatch); assertTrue(groupedBatches.get(1).summaryBatches.contains(group2Agg1)); } public void testGroupBatchesForTwoSetOfGroupableBatches() { String[] fieldValuesStoreType = { "Deluxe Supermarket", "Gourmet Supermarket", "HeadQuarters", "Mid-Size Grocery", "Small Grocery", "Supermarket" }; String fieldStoreType = "store_type"; String tableStore = "store"; String[] fieldValuesWarehouseCountry = {"Canada", "Mexico", "USA"}; String fieldWarehouseCountry = "warehouse_country"; String tableWarehouse = "warehouse"; final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch batch1RollupOnGender = createBatch( fbcr, new String[] {tableTime, tableStore, tableProductClass}, new String[] {fieldYear, fieldStoreType, fieldProductFamily}, new String[][] { fieldValuesYear, fieldValuesStoreType, fieldValuesProductFamily}, cubeNameSales, measureUnitSales); BatchLoader.Batch batch1RollupOnGenderAndProductDepartment = createBatch( fbcr, new String[] {tableTime, tableProductClass}, new String[] {fieldYear, fieldProductFamily}, new String[][] {fieldValuesYear, fieldValuesProductFamily}, cubeNameSales, measureUnitSales); BatchLoader.Batch batch1RollupOnStoreTypeAndProductDepartment = createBatch( fbcr, new String[] {tableTime, tableCustomer}, new String[] {fieldYear, fieldGender}, new String[][] {fieldValuesYear, fieldValuesGender}, cubeNameSales, measureUnitSales); BatchLoader.Batch batch1Detailed = createBatch( fbcr, new String[] { tableTime, tableStore, tableProductClass, tableCustomer}, new String[] { fieldYear, fieldStoreType, fieldProductFamily, fieldGender}, new String[][] { fieldValuesYear, fieldValuesStoreType, fieldValuesProductFamily, fieldValuesGender}, cubeNameSales, measureUnitSales); String warehouseCube = "Warehouse"; String measure2 = "[Measures].[Warehouse Sales]"; BatchLoader.Batch batch2RollupOnStoreType = createBatch( fbcr, new String[] { tableWarehouse, tableTime, tableProductClass }, new String[] { fieldWarehouseCountry, fieldYear, fieldProductFamily }, new String[][] { fieldValuesWarehouseCountry, fieldValuesYear, fieldValuesProductFamily}, warehouseCube, measure2); BatchLoader.Batch batch2RollupOnStoreTypeAndWareHouseCountry = createBatch( fbcr, new String[] {tableTime, tableProductClass}, new String[] {fieldYear, fieldProductFamily}, new String[][] {fieldValuesYear, fieldValuesProductFamily}, warehouseCube, measure2); BatchLoader.Batch batch2RollupOnProductFamilyAndWareHouseCountry = createBatch( fbcr, new String[] {tableTime, tableStore}, new String[] {fieldYear, fieldStoreType}, new String[][] {fieldValuesYear, fieldValuesStoreType}, warehouseCube, measure2); BatchLoader.Batch batch2Detailed = createBatch( fbcr, new String[] { tableWarehouse, tableTime, tableStore, tableProductClass}, new String[] { fieldWarehouseCountry, fieldYear, fieldStoreType, fieldProductFamily}, new String[][] { fieldValuesWarehouseCountry, fieldValuesYear, fieldValuesStoreType, fieldValuesProductFamily}, warehouseCube, measure2); List batchList = new ArrayList(); batchList.add(batch1RollupOnGender); batchList.add(batch2RollupOnStoreType); batchList.add(batch2RollupOnStoreTypeAndWareHouseCountry); batchList.add(batch2RollupOnProductFamilyAndWareHouseCountry); batchList.add(batch1RollupOnGenderAndProductDepartment); batchList.add(batch1RollupOnStoreTypeAndProductDepartment); batchList.add(batch2Detailed); batchList.add(batch1Detailed); List groupedBatches = fbcr.groupBatches(batchList); final int groupedBatchCount = groupedBatches.size(); // Until MONDRIAN-1001 is fixed, behavior is flaky due to interaction // with previous tests. if (Bug.BugMondrian1001Fixed) { if (MondrianProperties.instance().UseAggregates.get() && MondrianProperties.instance().ReadAggregates.get()) { assertEquals(4, groupedBatchCount); } else { assertEquals(2, groupedBatchCount); } } else { assertTrue(groupedBatchCount == 2 || groupedBatchCount == 4); } } public void testAddToCompositeBatchForBothBatchesNotPartOfCompositeBatch() { final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch batch1 = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "country", "F")); BatchLoader.Batch batch2 = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "gender", "F")); Map batchGroups = new HashMap< AggregationKey, BatchLoader.CompositeBatch>(); fbcr.addToCompositeBatch(batchGroups, batch1, batch2); assertEquals(1, batchGroups.size()); BatchLoader.CompositeBatch compositeBatch = batchGroups.get(batch1.batchKey); assertEquals(batch1, compositeBatch.detailedBatch); assertEquals(1, compositeBatch.summaryBatches.size()); assertTrue(compositeBatch.summaryBatches.contains(batch2)); } public void testAddToCompositeBatchForDetailedBatchAlreadyPartOfACompositeBatch() { final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch detailedBatch = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "country", "F")); BatchLoader.Batch aggBatch1 = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "gender", "F")); BatchLoader.Batch aggBatchAlreadyInComposite = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "gender", "F")); Map batchGroups = new HashMap< AggregationKey, BatchLoader.CompositeBatch>(); BatchLoader.CompositeBatch existingCompositeBatch = new BatchLoader.CompositeBatch(detailedBatch); existingCompositeBatch.add(aggBatchAlreadyInComposite); batchGroups.put(detailedBatch.batchKey, existingCompositeBatch); BatchLoader.addToCompositeBatch(batchGroups, detailedBatch, aggBatch1); assertEquals(1, batchGroups.size()); BatchLoader.CompositeBatch compositeBatch = batchGroups.get(detailedBatch.batchKey); assertEquals(detailedBatch, compositeBatch.detailedBatch); assertEquals(2, compositeBatch.summaryBatches.size()); assertTrue(compositeBatch.summaryBatches.contains(aggBatch1)); assertTrue(compositeBatch.summaryBatches.contains( aggBatchAlreadyInComposite)); } public void testAddToCompositeBatchForAggregationBatchAlreadyPartOfACompositeBatch() { final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch detailedBatch = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "country", "F")); BatchLoader.Batch aggBatchToAddToDetailedBatch = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "gender", "F")); BatchLoader.Batch aggBatchAlreadyInComposite = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "city", "F")); Map batchGroups = new HashMap< AggregationKey, BatchLoader.CompositeBatch>(); BatchLoader.CompositeBatch existingCompositeBatch = new BatchLoader.CompositeBatch(aggBatchToAddToDetailedBatch); existingCompositeBatch.add(aggBatchAlreadyInComposite); batchGroups.put( aggBatchToAddToDetailedBatch.batchKey, existingCompositeBatch); fbcr.addToCompositeBatch( batchGroups, detailedBatch, aggBatchToAddToDetailedBatch); assertEquals(1, batchGroups.size()); BatchLoader.CompositeBatch compositeBatch = batchGroups.get(detailedBatch.batchKey); assertEquals(detailedBatch, compositeBatch.detailedBatch); assertEquals(2, compositeBatch.summaryBatches.size()); assertTrue(compositeBatch.summaryBatches.contains( aggBatchToAddToDetailedBatch)); assertTrue(compositeBatch.summaryBatches.contains( aggBatchAlreadyInComposite)); } public void testAddToCompositeBatchForBothBatchAlreadyPartOfACompositeBatch() { final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch detailedBatch = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "country", "F")); BatchLoader.Batch aggBatchToAddToDetailedBatch = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "gender", "F")); BatchLoader.Batch aggBatchAlreadyInCompositeOfAgg = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "city", "F")); BatchLoader.Batch aggBatchAlreadyInCompositeOfDetail = fbcr.new Batch( createRequest( cubeNameSales, measureUnitSales, "customer", "state_province", "F")); Map batchGroups = new HashMap< AggregationKey, BatchLoader.CompositeBatch>(); BatchLoader.CompositeBatch existingAggCompositeBatch = new BatchLoader.CompositeBatch(aggBatchToAddToDetailedBatch); existingAggCompositeBatch.add(aggBatchAlreadyInCompositeOfAgg); batchGroups.put( aggBatchToAddToDetailedBatch.batchKey, existingAggCompositeBatch); BatchLoader.CompositeBatch existingCompositeBatch = new BatchLoader.CompositeBatch(detailedBatch); existingCompositeBatch.add(aggBatchAlreadyInCompositeOfDetail); batchGroups.put(detailedBatch.batchKey, existingCompositeBatch); BatchLoader.addToCompositeBatch( batchGroups, detailedBatch, aggBatchToAddToDetailedBatch); assertEquals(1, batchGroups.size()); BatchLoader.CompositeBatch compositeBatch = batchGroups.get(detailedBatch.batchKey); assertEquals(detailedBatch, compositeBatch.detailedBatch); assertEquals(3, compositeBatch.summaryBatches.size()); assertTrue(compositeBatch.summaryBatches.contains( aggBatchToAddToDetailedBatch)); assertTrue(compositeBatch.summaryBatches.contains( aggBatchAlreadyInCompositeOfAgg)); assertTrue(compositeBatch.summaryBatches.contains( aggBatchAlreadyInCompositeOfDetail)); } /** * Tests that can batch for batch with super set of contraint * column bit key and all values for additional condition. */ public void testCanBatchForSuperSet() { final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch aggregationBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment}, new String[][] { fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment}, cubeNameSales, measureUnitSales); BatchLoader.Batch detailedBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass, tableCustomer}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment, fieldGender}, new String[][] { fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment, fieldValuesGender}, cubeNameSales, measureUnitSales); assertTrue(detailedBatch.canBatch(aggregationBatch)); assertFalse(aggregationBatch.canBatch(detailedBatch)); } public void testCanBatchForBatchWithConstraint() { final BatchLoader fbcr = createFbcr(null, salesCube); List compoundMembers = new ArrayList(); compoundMembers.add(new String[] {"USA", "CA"}); compoundMembers.add(new String[] {"Canada", "BC"}); CellRequestConstraint constraint = makeConstraintCountryState(compoundMembers); BatchLoader.Batch aggregationBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment}, new String[][] { fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment}, cubeNameSales, measureUnitSales, constraint); BatchLoader.Batch detailedBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass, tableCustomer}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment, fieldGender}, new String[][] { fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment, fieldValuesGender}, cubeNameSales, measureUnitSales, constraint); assertTrue(detailedBatch.canBatch(aggregationBatch)); assertFalse(aggregationBatch.canBatch(detailedBatch)); } public void testCanBatchForBatchWithConstraint2() { final BatchLoader fbcr = createFbcr(null, salesCube); List compoundMembers1 = new ArrayList(); compoundMembers1.add(new String[] {"USA", "CA"}); compoundMembers1.add(new String[] {"Canada", "BC"}); CellRequestConstraint constraint1 = makeConstraintCountryState(compoundMembers1); // Different constraint will cause the Batch not to match. List compoundMembers2 = new ArrayList(); compoundMembers2.add(new String[] {"USA", "CA"}); compoundMembers2.add(new String[] {"USA", "OR"}); CellRequestConstraint constraint2 = makeConstraintCountryState(compoundMembers2); BatchLoader.Batch aggregationBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment}, new String[][] { fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment}, cubeNameSales, measureUnitSales, constraint1); BatchLoader.Batch detailedBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass, tableCustomer}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment, fieldGender}, new String[][] { fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment, fieldValuesGender}, cubeNameSales, measureUnitSales, constraint2); assertTrue(detailedBatch.canBatch(aggregationBatch)); assertFalse(aggregationBatch.canBatch(detailedBatch)); } public void testCanBatchForBatchWithDistinctCountInDetailedBatch() { if (!MondrianProperties.instance().UseAggregates.get() || !MondrianProperties.instance().ReadAggregates.get()) { return; } final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch aggregationBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment}, new String[][] { fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment}, cubeNameSales, measureUnitSales); BatchLoader.Batch detailedBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment}, new String[][] { fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment}, cubeNameSales, "[Measures].[Customer Count]"); assertFalse(detailedBatch.canBatch(aggregationBatch)); assertFalse(aggregationBatch.canBatch(detailedBatch)); } public void testCanBatchForBatchWithDistinctCountInAggregateBatch() { if (!MondrianProperties.instance().UseAggregates.get() || !MondrianProperties.instance().ReadAggregates.get()) { return; } final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch aggregationBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment}, new String[][] { fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment}, cubeNameSales, "[Measures].[Customer Count]"); BatchLoader.Batch detailedBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment}, new String[][] { fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment}, cubeNameSales, measureUnitSales); assertFalse(detailedBatch.canBatch(aggregationBatch)); assertFalse(aggregationBatch.canBatch(detailedBatch)); } public void testCanBatchSummaryBatchWithDetailedBatchWithDistinctCount() { if (MondrianProperties.instance().UseAggregates.get() || MondrianProperties.instance().ReadAggregates.get()) { return; } final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch aggregationBatch = createBatch( fbcr, new String[] {tableTime}, new String[] {fieldYear}, new String[][] {fieldValuesYear}, cubeNameSales, "[Measures].[Customer Count]"); BatchLoader.Batch detailedBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment}, new String[][] { fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment}, cubeNameSales, measureUnitSales); assertFalse(detailedBatch.canBatch(aggregationBatch)); assertFalse(aggregationBatch.canBatch(detailedBatch)); } /** * Test that can batch for batch with non superset of constraint * column bit key and all values for additional condition. */ public void testNonSuperSet() { final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch aggregationBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment}, new String[][] { fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment}, cubeNameSales, measureUnitSales); BatchLoader.Batch detailedBatch = createBatch( fbcr, new String[] { tableProductClass, tableProductClass, tableCustomer}, new String[] { fieldProductFamily, fieldProductDepartment, fieldGender}, new String[][] { fieldValuesProductFamily, fieldValueProductDepartment, fieldValuesGender}, cubeNameSales, measureUnitSales); assertFalse(detailedBatch.canBatch(aggregationBatch)); assertFalse(aggregationBatch.canBatch(detailedBatch)); } /** * Tests that can batch for batch with super set of constraint * column bit key and NOT all values for additional condition. */ public void testSuperSetAndNotAllValues() { final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch aggregationBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment}, new String[][] { fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment}, cubeNameSales, measureUnitSales); BatchLoader.Batch detailedBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass, tableCustomer}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment, fieldGender}, new String[][] { fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment, new String[] {"M"}}, cubeNameSales, measureUnitSales); assertFalse(detailedBatch.canBatch(aggregationBatch)); assertFalse(aggregationBatch.canBatch(detailedBatch)); } public void testCanBatchForBatchesFromSameAggregationButDifferentRollupOption() { final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch batch1 = createBatch( fbcr, new String[] {tableTime}, new String[] {fieldYear}, new String[][] {fieldValuesYear}, cubeNameSales, measureUnitSales); BatchLoader.Batch batch2 = createBatch( fbcr, new String[] {tableTime, tableTime, tableTime}, new String[] {fieldYear, "quarter", "month_of_year"}, new String[][] { fieldValuesYear, new String[] {"Q1", "Q2", "Q3", "Q4"}, new String[] { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"}}, cubeNameSales, measureUnitSales); // Until MONDRIAN-1001 is fixed, behavior is flaky due to interaction // with previous tests. final boolean batch2CanBatch1 = batch2.canBatch(batch1); final boolean batch1CanBatch2 = batch1.canBatch(batch2); if (Bug.BugMondrian1001Fixed) { if (MondrianProperties.instance().UseAggregates.get() && MondrianProperties.instance().ReadAggregates.get()) { assertFalse(batch2CanBatch1); assertFalse(batch1CanBatch2); } else { assertTrue(batch2CanBatch1); } } } /** * Tests that Can Batch For Batch With Super Set Of Constraint * Column Bit Key And Different Values For Overlapping Columns. */ public void testSuperSetDifferentValues() { final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch aggregationBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment}, new String[][] { new String[] {"1997"}, fieldValuesProductFamily, fieldValueProductDepartment}, cubeNameSales, measureUnitSales); BatchLoader.Batch detailedBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass, tableCustomer}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment, fieldGender}, new String[][] { new String[] {"1998"}, fieldValuesProductFamily, fieldValueProductDepartment, fieldValuesGender}, cubeNameSales, measureUnitSales); assertFalse(detailedBatch.canBatch(aggregationBatch)); assertFalse(aggregationBatch.canBatch(detailedBatch)); } public void testCanBatchForBatchWithDifferentAggregationTable() { final Dialect dialect = getTestContext().getDialect(); final Dialect.DatabaseProduct product = dialect.getDatabaseProduct(); switch (product) { case TERADATA: case INFOBRIGHT: case NEOVIEW: // On Teradata, Infobright and Neoview we don't create aggregate // tables, so this test will fail. return; } final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch summaryBatch = createBatch( fbcr, new String[] {tableTime}, new String[] {fieldYear}, new String[][] {fieldValuesYear}, cubeNameSales, measureUnitSales); BatchLoader.Batch detailedBatch = createBatch( fbcr, new String[] {tableTime, tableCustomer}, new String[] {fieldYear, fieldGender}, new String[][] {fieldValuesYear, fieldValuesGender}, cubeNameSales, measureUnitSales); if (MondrianProperties.instance().UseAggregates.get() && MondrianProperties.instance().ReadAggregates.get()) { assertFalse(detailedBatch.canBatch(summaryBatch)); assertFalse(summaryBatch.canBatch(detailedBatch)); } else { assertTrue(detailedBatch.canBatch(summaryBatch)); assertFalse(summaryBatch.canBatch(detailedBatch)); } } public void testCannotBatchTwoBatchesAtTheSameLevel() { final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch firstBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment}, new String[][] { fieldValuesYear, new String[] {"Food"}, fieldValueProductDepartment}, cubeNameSales, "[Measures].[Customer Count]"); BatchLoader.Batch secondBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment}, new String[][] { fieldValuesYear, new String[] {"Drink"}, fieldValueProductDepartment}, cubeNameSales, "[Measures].[Customer Count]"); assertFalse(firstBatch.canBatch(secondBatch)); assertFalse(secondBatch.canBatch(firstBatch)); } public void testCompositeBatchLoadAggregation() throws Exception { if (!getTestContext().getDialect().supportsGroupingSets()) { return; } final BatchLoader fbcr = createFbcr(null, salesCube); BatchLoader.Batch summaryBatch = createBatch( fbcr, new String[] {tableTime, tableProductClass, tableProductClass}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment}, new String[][] { fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment}, cubeNameSales, measureUnitSales); BatchLoader.Batch detailedBatch = createBatch( fbcr, new String[] { tableTime, tableProductClass, tableProductClass, tableCustomer}, new String[] { fieldYear, fieldProductFamily, fieldProductDepartment, fieldGender}, new String[][] { fieldValuesYear, fieldValuesProductFamily, fieldValueProductDepartment, fieldValuesGender}, cubeNameSales, measureUnitSales); final BatchLoader.CompositeBatch compositeBatch = new BatchLoader.CompositeBatch(detailedBatch); compositeBatch.add(summaryBatch); final List>> segmentFutures = new ArrayList>>(); MondrianServer.forConnection( getTestContext().getConnection()) .getAggregationManager().cacheMgr.execute( new SegmentCacheManager.Command() { private final Locus locus = Locus.peek(); public Void call() throws Exception { compositeBatch.load(segmentFutures); return null; } public Locus getLocus() { return locus; } }); assertEquals(1, segmentFutures.size()); assertEquals(2, segmentFutures.get(0).get().size()); // The order of the segments is not deterministic, so we need to // iterate over the segments and find a match for the batch. // If none are found, we fail. boolean found = false; for (Segment seg : segmentFutures.get(0).get().keySet()) { if (detailedBatch.getConstrainedColumnsBitKey() .equals(seg.getConstrainedColumnsBitKey())) { found = true; break; } } if (!found) { fail("No bitkey match found."); } found = false; for (Segment seg : segmentFutures.get(0).get().keySet()) { if (summaryBatch.getConstrainedColumnsBitKey() .equals(seg.getConstrainedColumnsBitKey())) { found = true; break; } } if (!found) { fail("No bitkey match found."); } } /** * Checks that in dialects that request it (e.g. LucidDB), * distinct aggregates based on SQL expressions, * e.g. count(distinct "col1" + "col2"), count(distinct query), * are loaded individually, and separately from the other aggregates. */ public void testLoadDistinctSqlMeasure() { // Some databases cannot handle scalar subqueries inside // count(distinct). final Dialect dialect = getTestContext().getDialect(); switch (dialect.getDatabaseProduct()) { case ORACLE: // Oracle gives 'feature not supported' in Express 10.2 case ACCESS: case TERADATA: // Teradata gives "Syntax error: expected something between '(' and // the 'select' keyword." in 12.0. case NEOVIEW: // Neoview gives "ERROR[4008] A subquery is not allowed inside an // aggregate function." case NETEZZA: // Netezza gives an "ERROR: Correlated Subplan expressions not // supported" case GREENPLUM: // Greenplum says 'Does not support yet that query' return; } String cube = "" + "

" + " " + " " + " " + " (select `warehouse_class`.`warehouse_class_id` AS `warehouse_class_id` from `warehouse_class` AS `warehouse_class` where `warehouse_class`.`warehouse_class_id` = `warehouse`.`warehouse_class_id` and `warehouse_class`.`description` = 'Large Owned')" + " " + " " + " " + " " + " (select `warehouse_class`.`warehouse_class_id` AS `warehouse_class_id` from `warehouse_class` AS `warehouse_class` where `warehouse_class`.`warehouse_class_id` = `warehouse`.`warehouse_class_id` and `warehouse_class`.`description` = 'Large Independent')" + " " + " " + " " + " " + " (select `warehouse_class`.`warehouse_class_id` AS `warehouse_class_id` from `warehouse_class` AS `warehouse_class` where `warehouse_class`.`warehouse_class_id` = `warehouse`.`warehouse_class_id` and `warehouse_class`.`description` = 'Large Independent')" + " " + " " + " " + " `store_id`+`warehouse_id`" + " " + " " + " `store_id`+`warehouse_id`" + " " + " " + ""; cube = cube.replaceAll("`", dialect.getQuoteIdentifierString()); if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.ORACLE) { cube = cube.replaceAll(" AS ", " "); } String query = "select " + " [Store Type].Children on rows, " + " {[Measures].[Count Distinct of Warehouses (Large Owned)]," + " [Measures].[Count Distinct of Warehouses (Large Independent)]," + " [Measures].[Count All of Warehouses (Large Independent)]," + " [Measures].[Count Distinct Store+Warehouse]," + " [Measures].[Count All Store+Warehouse]," + " [Measures].[Store Count]} on columns " + "from [Warehouse2]"; TestContext testContext = TestContext.instance().create( null, cube, null, null, null, null); String desiredResult = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Count Distinct of Warehouses (Large Owned)]}\n" + "{[Measures].[Count Distinct of Warehouses (Large Independent)]}\n" + "{[Measures].[Count All of Warehouses (Large Independent)]}\n" + "{[Measures].[Count Distinct Store+Warehouse]}\n" + "{[Measures].[Count All Store+Warehouse]}\n" + "{[Measures].[Store Count]}\n" + "Axis #2:\n" + "{[Store Type].[Deluxe Supermarket]}\n" + "{[Store Type].[Gourmet Supermarket]}\n" + "{[Store Type].[HeadQuarters]}\n" + "{[Store Type].[Mid-Size Grocery]}\n" + "{[Store Type].[Small Grocery]}\n" + "{[Store Type].[Supermarket]}\n" + "Row #0: 1\n" + "Row #0: 0\n" + "Row #0: 0\n" + "Row #0: 6\n" + "Row #0: 6\n" + "Row #0: 6\n" + "Row #1: 1\n" + "Row #1: 0\n" + "Row #1: 0\n" + "Row #1: 2\n" + "Row #1: 2\n" + "Row #1: 2\n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #3: 0\n" + "Row #3: 1\n" + "Row #3: 1\n" + "Row #3: 4\n" + "Row #3: 4\n" + "Row #3: 4\n" + "Row #4: 0\n" + "Row #4: 1\n" + "Row #4: 1\n" + "Row #4: 4\n" + "Row #4: 4\n" + "Row #4: 4\n" + "Row #5: 0\n" + "Row #5: 1\n" + "Row #5: 3\n" + "Row #5: 8\n" + "Row #5: 8\n" + "Row #5: 8\n"; testContext.assertQueryReturns(query, desiredResult); String loadCountDistinct_luciddb1 = "select " + "\"store\".\"store_type\" as \"c0\", " + "count(distinct " + "(select \"warehouse_class\".\"warehouse_class_id\" AS \"warehouse_class_id\" " + "from \"warehouse_class\" AS \"warehouse_class\" " + "where \"warehouse_class\".\"warehouse_class_id\" = \"warehouse\".\"warehouse_class_id\" and \"warehouse_class\".\"description\" = 'Large Owned')) as \"m0\" " + "from \"store\" as \"store\", \"warehouse\" as \"warehouse\" " + "where \"warehouse\".\"stores_id\" = \"store\".\"store_id\" " + "group by \"store\".\"store_type\""; String loadCountDistinct_luciddb2 = "select " + "\"store\".\"store_type\" as \"c0\", " + "count(distinct " + "(select \"warehouse_class\".\"warehouse_class_id\" AS \"warehouse_class_id\" " + "from \"warehouse_class\" AS \"warehouse_class\" " + "where \"warehouse_class\".\"warehouse_class_id\" = \"warehouse\".\"warehouse_class_id\" and \"warehouse_class\".\"description\" = 'Large Independent')) as \"m0\" " + "from \"store\" as \"store\", \"warehouse\" as \"warehouse\" " + "where \"warehouse\".\"stores_id\" = \"store\".\"store_id\" " + "group by \"store\".\"store_type\""; String loadOtherAggs_luciddb = "select " + "\"store\".\"store_type\" as \"c0\", " + "count(" + "(select \"warehouse_class\".\"warehouse_class_id\" AS \"warehouse_class_id\" " + "from \"warehouse_class\" AS \"warehouse_class\" " + "where \"warehouse_class\".\"warehouse_class_id\" = \"warehouse\".\"warehouse_class_id\" and \"warehouse_class\".\"description\" = 'Large Independent')) as \"m0\", " + "count(distinct \"store_id\"+\"warehouse_id\") as \"m1\", " + "count(\"store_id\"+\"warehouse_id\") as \"m2\", " + "count(\"warehouse\".\"stores_id\") as \"m3\" " + "from \"store\" as \"store\", \"warehouse\" as \"warehouse\" " + "where \"warehouse\".\"stores_id\" = \"store\".\"store_id\" " + "group by \"store\".\"store_type\""; // Derby splits into multiple statements. String loadCountDistinct_derby1 = "select \"store\".\"store_type\" as \"c0\", count(distinct (select \"warehouse_class\".\"warehouse_class_id\" AS \"warehouse_class_id\" from \"warehouse_class\" AS \"warehouse_class\" where \"warehouse_class\".\"warehouse_class_id\" = \"warehouse\".\"warehouse_class_id\" and \"warehouse_class\".\"description\" = 'Large Owned')) as \"m0\" from \"store\" as \"store\", \"warehouse\" as \"warehouse\" where \"warehouse\".\"stores_id\" = \"store\".\"store_id\" group by \"store\".\"store_type\""; String loadCountDistinct_derby2 = "select \"store\".\"store_type\" as \"c0\", count(distinct (select \"warehouse_class\".\"warehouse_class_id\" AS \"warehouse_class_id\" from \"warehouse_class\" AS \"warehouse_class\" where \"warehouse_class\".\"warehouse_class_id\" = \"warehouse\".\"warehouse_class_id\" and \"warehouse_class\".\"description\" = 'Large Independent')) as \"m0\" from \"store\" as \"store\", \"warehouse\" as \"warehouse\" where \"warehouse\".\"stores_id\" = \"store\".\"store_id\" group by \"store\".\"store_type\""; String loadCountDistinct_derby3 = "select \"store\".\"store_type\" as \"c0\", count(distinct \"store_id\"+\"warehouse_id\") as \"m0\" from \"store\" as \"store\", \"warehouse\" as \"warehouse\" where \"warehouse\".\"stores_id\" = \"store\".\"store_id\" group by \"store\".\"store_type\""; String loadOtherAggs_derby = "select \"store\".\"store_type\" as \"c0\", count((select \"warehouse_class\".\"warehouse_class_id\" AS \"warehouse_class_id\" from \"warehouse_class\" AS \"warehouse_class\" where \"warehouse_class\".\"warehouse_class_id\" = \"warehouse\".\"warehouse_class_id\" and \"warehouse_class\".\"description\" = 'Large Independent')) as \"m0\", count(\"store_id\"+\"warehouse_id\") as \"m1\", count(\"warehouse\".\"stores_id\") as \"m2\" from \"store\" as \"store\", \"warehouse\" as \"warehouse\" where \"warehouse\".\"stores_id\" = \"store\".\"store_id\" group by \"store\".\"store_type\""; // MySQL does it in one statement. String load_mysql = "select" + " `store`.`store_type` as `c0`," + " count(distinct (select `warehouse_class`.`warehouse_class_id` AS `warehouse_class_id` from `warehouse_class` AS `warehouse_class` where `warehouse_class`.`warehouse_class_id` = `warehouse`.`warehouse_class_id` and `warehouse_class`.`description` = 'Large Owned')) as `m0`," + " count(distinct (select `warehouse_class`.`warehouse_class_id` AS `warehouse_class_id` from `warehouse_class` AS `warehouse_class` where `warehouse_class`.`warehouse_class_id` = `warehouse`.`warehouse_class_id` and `warehouse_class`.`description` = 'Large Independent')) as `m1`," + " count((select `warehouse_class`.`warehouse_class_id` AS `warehouse_class_id` from `warehouse_class` AS `warehouse_class` where `warehouse_class`.`warehouse_class_id` = `warehouse`.`warehouse_class_id` and `warehouse_class`.`description` = 'Large Independent')) as `m2`," + " count(distinct `store_id`+`warehouse_id`) as `m3`," + " count(`store_id`+`warehouse_id`) as `m4`," + " count(`warehouse`.`stores_id`) as `m5` " + "from `store` as `store`," + " `warehouse` as `warehouse` " + "where `warehouse`.`stores_id` = `store`.`store_id` " + "group by `store`.`store_type`"; SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.LUCIDDB, loadCountDistinct_luciddb1, loadCountDistinct_luciddb1), new SqlPattern( Dialect.DatabaseProduct.LUCIDDB, loadCountDistinct_luciddb2, loadCountDistinct_luciddb2), new SqlPattern( Dialect.DatabaseProduct.LUCIDDB, loadOtherAggs_luciddb, loadOtherAggs_luciddb), new SqlPattern( Dialect.DatabaseProduct.DERBY, loadCountDistinct_derby1, loadCountDistinct_derby1), new SqlPattern( Dialect.DatabaseProduct.DERBY, loadCountDistinct_derby2, loadCountDistinct_derby2), new SqlPattern( Dialect.DatabaseProduct.DERBY, loadCountDistinct_derby3, loadCountDistinct_derby3), new SqlPattern( Dialect.DatabaseProduct.DERBY, loadOtherAggs_derby, loadOtherAggs_derby), new SqlPattern( Dialect.DatabaseProduct.MYSQL, load_mysql, load_mysql), }; assertQuerySql(testContext, query, patterns); } public void testAggregateDistinctCount() { // solve_order=1 says to aggregate [CA] and [OR] before computing their // sums assertQueryReturns( "WITH MEMBER [Time].[Time].[1997 Q1 plus Q2] AS 'AGGREGATE({[Time].[1997].[Q1], [Time].[1997].[Q2]})', solve_order=1\n" + "SELECT {[Measures].[Customer Count]} ON COLUMNS,\n" + " {[Time].[1997].[Q1], [Time].[1997].[Q2], [Time].[1997 Q1 plus Q2]} ON ROWS\n" + "FROM Sales\n" + "WHERE ([Store].[USA].[CA])", "Axis #0:\n" + "{[Store].[USA].[CA]}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997 Q1 plus Q2]}\n" + "Row #0: 1,110\n" + "Row #1: 1,173\n" + "Row #2: 1,854\n"); } /** * As {@link #testAggregateDistinctCount()}, but (a) calc member includes * members from different levels and (b) also display [unit sales]. */ public void testAggregateDistinctCount2() { assertQueryReturns( "WITH MEMBER [Time].[Time].[1997 Q1 plus July] AS\n" + " 'AGGREGATE({[Time].[1997].[Q1], [Time].[1997].[Q3].[7]})', solve_order=1\n" + "SELECT {[Measures].[Unit Sales], [Measures].[Customer Count]} ON COLUMNS,\n" + " {[Time].[1997].[Q1],\n" + " [Time].[1997].[Q2],\n" + " [Time].[1997].[Q3].[7],\n" + " [Time].[1997 Q1 plus July]} ON ROWS\n" + "FROM Sales\n" + "WHERE ([Store].[USA].[CA])", "Axis #0:\n" + "{[Store].[USA].[CA]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q3].[7]}\n" + "{[Time].[1997 Q1 plus July]}\n" + "Row #0: 16,890\n" + "Row #0: 1,110\n" + "Row #1: 18,052\n" + "Row #1: 1,173\n" + "Row #2: 5,403\n" + "Row #2: 412\n" // !!! + "Row #3: 22,293\n" // = 16,890 + 5,403 + "Row #3: 1,386\n"); // between 1,110 and 1,110 + 412 } /** * As {@link #testAggregateDistinctCount2()}, but with two calc members * simultaneously. */ public void testAggregateDistinctCount3() { assertQueryReturns( "WITH\n" + " MEMBER [Promotion Media].[TV plus Radio] AS 'AGGREGATE({[Promotion Media].[TV], [Promotion Media].[Radio]})', solve_order=1\n" + " MEMBER [Time].[Time].[1997 Q1 plus July] AS 'AGGREGATE({[Time].[1997].[Q1], [Time].[1997].[Q3].[7]})', solve_order=1\n" + "SELECT {[Promotion Media].[TV plus Radio],\n" + " [Promotion Media].[TV],\n" + " [Promotion Media].[Radio]} ON COLUMNS,\n" + " {[Time].[1997],\n" + " [Time].[1997].[Q1],\n" + " [Time].[1997 Q1 plus July]} ON ROWS\n" + "FROM Sales\n" + "WHERE [Measures].[Customer Count]", "Axis #0:\n" + "{[Measures].[Customer Count]}\n" + "Axis #1:\n" + "{[Promotion Media].[TV plus Radio]}\n" + "{[Promotion Media].[TV]}\n" + "{[Promotion Media].[Radio]}\n" + "Axis #2:\n" + "{[Time].[1997]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997 Q1 plus July]}\n" + "Row #0: 455\n" + "Row #0: 274\n" + "Row #0: 186\n" + "Row #1: 139\n" + "Row #1: 99\n" + "Row #1: 40\n" + "Row #2: 139\n" + "Row #2: 99\n" + "Row #2: 40\n"); // There are 9 cells in the result. 6 sql statements have to be issued // to fetch all of them, with each loading these cells: // (1) ([1997], [TV Plus radio]) // // (2) ([1997], [TV]) // ([1997], [radio]) // // (3) ([1997].[Q1], [TV Plus radio]) // // (4) ([1997].[Q1], [TV]) // ([1997].[Q1], [radio]) // // (5) ([1997 Q1 plus July], [TV Plus radio]) // // (6) ([1997 Q1 Plus July], [TV]) // ([1997 Q1 Plus July], [radio]) final String oracleSql = "select " + "\"time_by_day\".\"the_year\" as \"c0\", \"time_by_day\".\"quarter\" as \"c1\", " + "\"promotion\".\"media_type\" as \"c2\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from " + "\"time_by_day\" \"time_by_day\", \"sales_fact_1997\" \"sales_fact_1997\", " + "\"promotion\" \"promotion\" " + "where " + "\"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and " + "\"time_by_day\".\"the_year\" = 1997 and " + "\"time_by_day\".\"quarter\" = 'Q1' and " + "\"sales_fact_1997\".\"promotion_id\" = \"promotion\".\"promotion_id\" and " + "\"promotion\".\"media_type\" in ('Radio', 'TV') " + "group by " + "\"time_by_day\".\"the_year\", \"time_by_day\".\"quarter\", " + "\"promotion\".\"media_type\""; final String mysqlSql = "select " + "`time_by_day`.`the_year` as `c0`, `time_by_day`.`quarter` as `c1`, " + "`promotion`.`media_type` as `c2`, count(distinct `sales_fact_1997`.`customer_id`) as `m0` " + "from " + "`time_by_day` as `time_by_day`, `sales_fact_1997` as `sales_fact_1997`, " + "`promotion` as `promotion` " + "where " + "`sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and " + "`time_by_day`.`the_year` = 1997 and `time_by_day`.`quarter` = 'Q1' and `" + "sales_fact_1997`.`promotion_id` = `promotion`.`promotion_id` and " + "`promotion`.`media_type` in ('Radio', 'TV') " + "group by " + "`time_by_day`.`the_year`, `time_by_day`.`quarter`, `promotion`.`media_type`"; final String derbySql = "select " + "\"time_by_day\".\"the_year\" as \"c0\", \"time_by_day\".\"quarter\" as \"c1\", " + "\"promotion\".\"media_type\" as \"c2\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from " + "\"time_by_day\" as \"time_by_day\", \"sales_fact_1997\" as \"sales_fact_1997\", " + "\"promotion\" as \"promotion\" " + "where " + "\"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" and " + "\"time_by_day\".\"the_year\" = 1997 and \"time_by_day\".\"quarter\" = 'Q1' and " + "\"sales_fact_1997\".\"promotion_id\" = \"promotion\".\"promotion_id\" and " + "\"promotion\".\"media_type\" in ('Radio', 'TV') " + "group by " + "\"time_by_day\".\"the_year\", \"time_by_day\".\"quarter\", " + "\"promotion\".\"media_type\""; assertQuerySql( "WITH\n" + " MEMBER [Promotion Media].[TV plus Radio] AS 'AGGREGATE({[Promotion Media].[TV], [Promotion Media].[Radio]})', solve_order=1\n" + " MEMBER [Time].[Time].[1997 Q1 plus July] AS 'AGGREGATE({[Time].[1997].[Q1], [Time].[1997].[Q3].[7]})', solve_order=1\n" + "SELECT {[Promotion Media].[TV plus Radio],\n" + " [Promotion Media].[TV],\n" + " [Promotion Media].[Radio]} ON COLUMNS,\n" + " {[Time].[1997],\n" + " [Time].[1997].[Q1],\n" + " [Time].[1997 Q1 plus July]} ON ROWS\n" + "FROM Sales\n" + "WHERE [Measures].[Customer Count]", new SqlPattern[] { new SqlPattern( Dialect.DatabaseProduct.ORACLE, oracleSql, oracleSql), new SqlPattern( Dialect.DatabaseProduct.MYSQL, mysqlSql, mysqlSql), new SqlPattern( Dialect.DatabaseProduct.DERBY, derbySql, derbySql) }); } /** * Distinct count over aggregate member which contains overlapping * members. Need to count them twice for rollable measures such as * [Unit Sales], but not for distinct-count measures such as * [Customer Count]. */ public void testAggregateDistinctCount4() { // CA and USA are overlapping members final String mdxQuery = "WITH\n" + " MEMBER [Store].[CA plus USA] AS 'AGGREGATE({[Store].[USA].[CA], [Store].[USA]})', solve_order=1\n" + " MEMBER [Time].[Time].[Q1 plus July] AS 'AGGREGATE({[Time].[1997].[Q1], [Time].[1997].[Q3].[7]})', solve_order=1\n" + "SELECT {[Measures].[Customer Count], [Measures].[Unit Sales]} ON COLUMNS,\n" + " Union({[Store].[CA plus USA]} * {[Time].[Q1 plus July]}, " + " Union({[Store].[USA].[CA]} * {[Time].[Q1 plus July]}," + " Union({[Store].[USA]} * {[Time].[Q1 plus July]}," + " Union({[Store].[CA plus USA]} * {[Time].[1997].[Q1]}," + " {[Store].[CA plus USA]} * {[Time].[1997].[Q3].[7]})))) ON ROWS\n" + "FROM Sales"; String result = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[CA plus USA], [Time].[Q1 plus July]}\n" + "{[Store].[USA].[CA], [Time].[Q1 plus July]}\n" + "{[Store].[USA], [Time].[Q1 plus July]}\n" + "{[Store].[CA plus USA], [Time].[1997].[Q1]}\n" + "{[Store].[CA plus USA], [Time].[1997].[Q3].[7]}\n" + "Row #0: 3,505\n" + "Row #0: 112,347\n" + "Row #1: 1,386\n" + "Row #1: 22,293\n" + "Row #2: 3,505\n" + "Row #2: 90,054\n" + "Row #3: 2,981\n" + "Row #3: 83,181\n" + "Row #4: 1,462\n" + "Row #4: 29,166\n"; assertQueryReturns(mdxQuery, result); } /** * Fix a problem when genergating predicates for distinct count aggregate * loading and using the aggregate function in the slicer. */ public void testAggregateDistinctCount5() { String query = "With " + "Set [Products] as " + " '{[Product].[Drink], " + " [Product].[Food], " + " [Product].[Non-Consumable]}' " + "Member [Product].[Selected Products] as " + " 'Aggregate([Products])', SOLVE_ORDER=2 " + "Select " + " {[Store].[Store State].Members} on rows, " + " {[Measures].[Customer Count]} on columns " + "From [Sales] " + "Where ([Product].[Selected Products])"; String derbySql = "select \"store\".\"store_state\" as \"c0\", " + "\"time_by_day\".\"the_year\" as \"c1\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from \"store\" as \"store\", \"sales_fact_1997\" as \"sales_fact_1997\", " + "\"time_by_day\" as \"time_by_day\" " + "where \"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\" = 1997 " + "group by \"store\".\"store_state\", \"time_by_day\".\"the_year\""; String mysqlSql = "select `store`.`store_state` as `c0`, `time_by_day`.`the_year` as `c1`, " + "count(distinct `sales_fact_1997`.`customer_id`) as `m0` " + "from `store` as `store`, `sales_fact_1997` as `sales_fact_1997`, " + "`time_by_day` as `time_by_day` " + "where `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` = 1997 " + "group by `store`.`store_state`, `time_by_day`.`the_year`"; SqlPattern[] patterns = { new SqlPattern(Dialect.DatabaseProduct.DERBY, derbySql, derbySql), new SqlPattern(Dialect.DatabaseProduct.MYSQL, mysqlSql, mysqlSql)}; assertQuerySql(query, patterns); } /* * Test for multiple members on different levels within the same hierarchy. */ public void testAggregateDistinctCount6() { // CA and USA are overlapping members final String mdxQuery = "WITH " + " MEMBER [Store].[Select Region] AS " + " 'AGGREGATE({[Store].[USA].[CA], [Store].[Mexico], [Store].[Canada], [Store].[USA].[OR]})', solve_order=1\n" + " MEMBER [Time].[Time].[Select Time Period] AS " + " 'AGGREGATE({[Time].[1997].[Q1], [Time].[1997].[Q3].[7], [Time].[1997].[Q4], [Time].[1997]})', solve_order=1\n" + "SELECT {[Measures].[Customer Count], [Measures].[Unit Sales]} ON COLUMNS,\n" + " Union({[Store].[Select Region]} * {[Time].[Select Time Period]}," + " Union({[Store].[Select Region]} * {[Time].[1997].[Q1]}," + " Union({[Store].[Select Region]} * {[Time].[1997].[Q3].[7]}," + " Union({[Store].[Select Region]} * {[Time].[1997].[Q4]}," + " {[Store].[Select Region]} * {[Time].[1997]})))) " + "ON ROWS\n" + "FROM Sales"; String result = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[Select Region], [Time].[Select Time Period]}\n" + "{[Store].[Select Region], [Time].[1997].[Q1]}\n" + "{[Store].[Select Region], [Time].[1997].[Q3].[7]}\n" + "{[Store].[Select Region], [Time].[1997].[Q4]}\n" + "{[Store].[Select Region], [Time].[1997]}\n" + "Row #0: 3,753\n" + "Row #0: 229,496\n" + "Row #1: 1,877\n" + "Row #1: 36,177\n" + "Row #2: 845\n" + "Row #2: 13,123\n" + "Row #3: 2,073\n" + "Row #3: 37,789\n" + "Row #4: 3,753\n" + "Row #4: 142,407\n"; assertQueryReturns(mdxQuery, result); } /* * Test case for bug 1785406 to fix "query already contains alias" * exception. * *

Note: 1785406 is a regression from checkin 9710. Code changes made in * 9710 is no longer in use (and removed). So this bug will not occur; * however, keeping the test case here to get some coverage for a query with * a slicer. */ public void testDistinctCountBug1785406() { String query = "With \n" + "Set [*BASE_MEMBERS_Product] as {[Product].[All Products].[Food].[Deli]}\n" + "Set [*BASE_MEMBERS_Store] as {[Store].[All Stores].[USA].[WA]}\n" + "Member [Product].[*CTX_MEMBER_SEL~SUM] As Aggregate([*BASE_MEMBERS_Product])\n" + "Select\n" + "{[Measures].[Customer Count]} on columns,\n" + "NonEmptyCrossJoin([*BASE_MEMBERS_Store],{([Product].[*CTX_MEMBER_SEL~SUM])})\n" + "on rows\n" + "From [Sales]\n" + "where ([Time].[1997])"; assertQueryReturns( query, "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Store].[USA].[WA], [Product].[*CTX_MEMBER_SEL~SUM]}\n" + "Row #0: 889\n"); String mysqlSql = "select " + "`store`.`store_state` as `c0`, `time_by_day`.`the_year` as `c1`, " + "count(distinct `sales_fact_1997`.`customer_id`) as `m0` " + "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` " + "where " + "`sales_fact_1997`.`store_id` = `store`.`store_id` " + "and `store`.`store_state` = 'WA' " + "and `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_department` = 'Deli' " + "and `product_class`.`product_family` = 'Food') " + "group by `store`.`store_state`, `time_by_day`.`the_year`"; String accessSql = "select `d0` as `c0`," + " `d1` as `c1`," + " count(`m0`) as `c2` " + "from (select distinct `store`.`store_state` as `d0`," + " `time_by_day`.`the_year` as `d1`," + " `sales_fact_1997`.`customer_id` as `m0` " + "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` " + "where `sales_fact_1997`.`store_id` = `store`.`store_id` " + "and `store`.`store_state` = 'WA' " + "and `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_department` = 'Deli' " + "and `product_class`.`product_family` = 'Food')) as `dummyname` " + "group by `d0`, `d1`"; String derbySql = "select " + "\"store\".\"store_state\" as \"c0\", " + "\"time_by_day\".\"the_year\" as \"c1\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "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\" " + "where " + "\"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" " + "and \"store\".\"store_state\" = 'WA' " + "and \"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_department\" = 'Deli' " + "and \"product_class\".\"product_family\" = 'Food') " + "group by \"store\".\"store_state\", \"time_by_day\".\"the_year\""; SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.ACCESS, accessSql, accessSql), new SqlPattern(Dialect.DatabaseProduct.DERBY, derbySql, derbySql), new SqlPattern(Dialect.DatabaseProduct.MYSQL, mysqlSql, mysqlSql)}; assertQuerySql(query, patterns); } public void testDistinctCountBug1785406_2() { String query = "With " + "Member [Product].[x] as 'Aggregate({Gender.CurrentMember})'\n" + "member [Measures].[foo] as '([Product].[x],[Measures].[Customer Count])'\n" + "select Filter([Gender].members,(Not IsEmpty([Measures].[foo]))) on 0 " + "from Sales"; assertQueryReturns( query, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 266,773\n" + "Row #0: 131,558\n" + "Row #0: 135,215\n"); String mysqlSql = "select " + "`time_by_day`.`the_year` as `c0`, " + "count(distinct `sales_fact_1997`.`customer_id`) as `m0` " + "from " + "`time_by_day` as `time_by_day`, " + "`sales_fact_1997` as `sales_fact_1997` " + "where `sales_fact_1997`.`time_id` = `time_by_day`.`time_id` " + "and `time_by_day`.`the_year` = 1997 " + "group by `time_by_day`.`the_year`"; String accessSql = "select `d0` as `c0`," + " count(`m0`) as `c1` " + "from (select distinct `time_by_day`.`the_year` as `d0`," + " `sales_fact_1997`.`customer_id` as `m0` " + "from `time_by_day` as `time_by_day`, " + "`sales_fact_1997` as `sales_fact_1997` " + "where `sales_fact_1997`.`time_id` = `time_by_day`.`time_id` " + "and `time_by_day`.`the_year` = 1997) as `dummyname` group by `d0`"; String derbySql = "select " + "\"time_by_day\".\"the_year\" as \"c0\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "from " + "\"time_by_day\" as \"time_by_day\", " + "\"sales_fact_1997\" as \"sales_fact_1997\" " + "where " + "\"sales_fact_1997\".\"time_id\" = \"time_by_day\".\"time_id\" " + "and \"time_by_day\".\"the_year\" = 1997 " + "group by \"time_by_day\".\"the_year\""; SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.ACCESS, accessSql, accessSql), new SqlPattern(Dialect.DatabaseProduct.DERBY, derbySql, derbySql), new SqlPattern(Dialect.DatabaseProduct.MYSQL, mysqlSql, mysqlSql)}; assertQuerySql(query, patterns); } public void testAggregateDistinctCountInDimensionFilter() { String query = "With " + "Set [Products] as '{[Product].[All Products].[Drink], [Product].[All Products].[Food]}' " + "Set [States] as '{[Store].[All Stores].[USA].[CA], [Store].[All Stores].[USA].[OR]}' " + "Member [Product].[Selected Products] as 'Aggregate([Products])', SOLVE_ORDER=2 " + "Select " + "Filter([States], not IsEmpty([Measures].[Customer Count])) on rows, " + "{[Measures].[Customer Count]} on columns " + "From [Sales] " + "Where ([Product].[Selected Products])"; assertQueryReturns( query, "Axis #0:\n" + "{[Product].[Selected Products]}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "Row #0: 2,692\n" + "Row #1: 1,036\n"); String mysqlSql = "select " + "`store`.`store_state` as `c0`, `time_by_day`.`the_year` as `c1`, " + "count(distinct `sales_fact_1997`.`customer_id`) as `m0` " + "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` " + "where " + "`sales_fact_1997`.`store_id` = `store`.`store_id` and " + "`store`.`store_state` in ('CA', 'OR') and " + "`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` in ('Drink', 'Food') " + "group by " + "`store`.`store_state`, `time_by_day`.`the_year`"; String derbySql = "select " + "\"store\".\"store_state\" as \"c0\", \"time_by_day\".\"the_year\" as \"c1\", " + "count(distinct \"sales_fact_1997\".\"customer_id\") as \"m0\" " + "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\" " + "where " + "\"sales_fact_1997\".\"store_id\" = \"store\".\"store_id\" and " + "\"store\".\"store_state\" in ('CA', 'OR') and " + "\"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\" in ('Drink', 'Food') " + "group by " + "\"store\".\"store_state\", \"time_by_day\".\"the_year\""; SqlPattern[] patterns = { new SqlPattern(Dialect.DatabaseProduct.DERBY, derbySql, derbySql), new SqlPattern(Dialect.DatabaseProduct.MYSQL, mysqlSql, mysqlSql)}; assertQuerySql(query, patterns); } public static class MyDelegatingInvocationHandler extends DelegatingInvocationHandler { private final Dialect dialect; private final boolean supportsGroupingSets; private MyDelegatingInvocationHandler( Dialect dialect, boolean supportsGroupingSets) { this.dialect = dialect; this.supportsGroupingSets = supportsGroupingSets; } protected Object getTarget() { return dialect; } /** * Handler for * {@link mondrian.spi.Dialect#supportsGroupingSets()}. * * @return whether dialect supports GROUPING SETS syntax */ public boolean supportsGroupingSets() { return supportsGroupingSets; } } } // End FastBatchingCellReaderTest.java mondrian-3.4.1/testsrc/main/mondrian/olap/0000755000175000017500000000000011735330606020403 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/olap/HierarchyBugTest.java0000644000175000017500000000512011735330606024460 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. // // remberson, Jan 31, 2006 */ package mondrian.olap; import mondrian.test.FoodMartTestCase; public class HierarchyBugTest extends FoodMartTestCase { public HierarchyBugTest(String name) { super(name); } public HierarchyBugTest() { super(); } /* This is code that demonstrates a bug that appears when using JPivot with the current version of Mondrian. With the previous version of Mondrian (and JPivot), pre compilation Mondrian, this was not a bug (or at least Mondrian did not have a null hierarchy). Here the Time dimension is not returned in axis == 0, rather null is returned. This causes a NullPointer exception in JPivot when it tries to access the (null) hierarchy's name. If the Time hierarchy is miss named in the query string, then the parse ought to pick it up. */ public void testNoHierarchy() { String queryString = "select NON EMPTY " + "Crossjoin(Hierarchize(Union({[Time].[Time].LastSibling}, " + "[Time].[Time].LastSibling.Children)), " + "{[Measures].[Unit Sales], " + "[Measures].[Store Cost]}) ON columns, " + "NON EMPTY Hierarchize(Union({[Store].[All Stores]}, " + "[Store].[All Stores].Children)) ON rows " + "from [Sales]"; Connection conn = getConnection(); Query query = conn.parseQuery(queryString); Result result = conn.execute(query); String failStr = null; int len = query.getAxes().length; for (int i = 0; i < len; i++) { Hierarchy[] hs = query.getMdxHierarchiesOnAxis( AxisOrdinal.StandardAxisOrdinal.forLogicalOrdinal(i)); if (hs == null) { } else { for (Hierarchy h : hs) { // This should NEVER be null, but it is. if (h == null) { failStr = "Got a null Hierarchy, " + "Should be Time Hierarchy"; } } } } if (failStr != null) { fail(failStr); } } } // End HierarchyBugTest.java mondrian-3.4.1/testsrc/main/mondrian/olap/fun/0000755000175000017500000000000011735330606021173 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/olap/fun/SetFunDefTest.java0000644000175000017500000000277511735330606024534 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho and others // All Rights Reserved. // // ajogleka, 19 December, 2007 */ package mondrian.olap.fun; import mondrian.test.FoodMartTestCase; /** * Unit test for the set constructor function { ... }, * {@link SetFunDef}. * * @author ajogleka * @since 19 December, 2007 */ public class SetFunDefTest extends FoodMartTestCase { public void testSetWithMembersFromDifferentHierarchies() { assertQueryFailsInSetValidation( "with member store.x as " + "'{[Gender].[M],[Store].[USA].[CA]}' " + " SELECT store.x on 0, [measures].[customer count] on 1 from sales"); } public void testSetWith2TuplesWithDifferentHierarchies() { assertQueryFailsInSetValidation( "with member store.x as '{([Gender].[M],[Store].[All Stores].[USA].[CA])," + "([Store].[USA].[OR],[Gender].[F])}'\n" + " SELECT store.x on 0, [measures].[customer count] on 1 from sales"); } private void assertQueryFailsInSetValidation(String query) { assertQueryThrows( query, "Mondrian Error:All arguments to function '{}' " + "must have same hierarchy"); } } // End SetFunDefTest.java mondrian-3.4.1/testsrc/main/mondrian/olap/fun/NativizeSetFunDefTest.java0000644000175000017500000020765711735330606026254 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2012 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.olap.*; import mondrian.rolap.BatchTestCase; import mondrian.rolap.RolapConnection; import mondrian.server.Locus; import mondrian.spi.Dialect; import mondrian.test.SqlPattern; import mondrian.test.TestContext; /** * Unit test for the {@code NativizeSet} function. * * @author jrand * @since Oct 14, 2009 */ public class NativizeSetFunDefTest extends BatchTestCase { public void setUp() throws Exception { super.setUp(); propSaver.set( MondrianProperties.instance().EnableNonEmptyOnAllAxis, true); propSaver.set( MondrianProperties.instance().NativizeMinThreshold, 0); propSaver.set( MondrianProperties.instance().UseAggregates, false); propSaver.set( MondrianProperties.instance().ReadAggregates, false); propSaver.set( MondrianProperties.instance().EnableNativeCrossJoin, true); // SSAS-compatible naming causes ..members to be // interpreted as ..members, and that happens a // lot in this test. There is little to be gained by having this test // run for both values. When SSAS-compatible naming is the standard, we // should upgrade all the MDX. propSaver.set( MondrianProperties.instance().SsasCompatibleNaming, false); } public void tearDown() throws Exception { super.tearDown(); } public void testIsNoOpWithAggregatesTablesOn() { propSaver.set( MondrianProperties.instance().UseAggregates, true); propSaver.set( MondrianProperties.instance().UseAggregates, true); checkNotNative( "with member [gender].[agg] as" + " 'aggregate({[gender].[gender].members},[measures].[unit sales])'" + "select NativizeSet(CrossJoin( " + "{gender.gender.members, gender.agg}, " + "{[marital status].[marital status].members}" + ")) on 0 from sales"); } public void testLevelHierarchyHighCardinality() { // The cardinality for the hierarchy looks like this: // Year: 2 (level * gender cardinality:2) // Quarter: 16 (level * gender cardinality:2) // Month: 48 (level * gender cardinality:2) propSaver.set(MondrianProperties.instance().NativizeMinThreshold, 17); String mdx = "select NativizeSet(" + "CrossJoin( " + "gender.gender.members, " + "CrossJoin(" + "{ measures.[unit sales] }, " + "[Time].[Month].members" + "))) on 0" + "from sales"; checkNative(mdx); } public void testLevelHierarchyLowCardinality() { // The cardinality for the hierarchy looks like this: // Year: 2 (level * gender cardinality:2) // Quarter: 16 (level * gender cardinality:2) // Month: 48 (level * gender cardinality:2) propSaver.set(MondrianProperties.instance().NativizeMinThreshold, 50); String mdx = "select NativizeSet(" + "CrossJoin( " + "gender.gender.members, " + "CrossJoin(" + "{ measures.[unit sales] }, " + "[Time].[Month].members" + "))) on 0" + "from sales"; checkNotNative(mdx); } public void testNamedSetLowCardinality() { propSaver.set( MondrianProperties.instance().NativizeMinThreshold, Integer.MAX_VALUE); checkNotNative( "with " + "set [levelMembers] as 'crossjoin( gender.gender.members, " + "[marital status].[marital status].members) '" + "select nativizeSet([levelMembers]) on 0 " + "from [warehouse and sales]"); } public void testCrossjoinWithNamedSetLowCardinality() { propSaver.set( MondrianProperties.instance().NativizeMinThreshold, Integer.MAX_VALUE); checkNotNative( "with " + "set [genderMembers] as 'gender.gender.members'" + "set [maritalMembers] as '[marital status].[marital status].members'" + "set [levelMembers] as 'crossjoin( [genderMembers],[maritalMembers]) '" + "select nativizeSet([levelMembers]) on 0 " + "from [warehouse and sales]"); } public void testMeasureInCrossJoinWithTwoDimensions() { checkNative( "select NativizeSet(" + "CrossJoin( " + "gender.gender.members, " + "CrossJoin(" + "{ measures.[unit sales] }, " + "[marital status].[marital status].members" + "))) on 0 " + "from sales"); } public void testNativeResultLimitAtZero() { // This query will return exactly 6 rows: // {Female,Male,Agg}x{Married,Single} String mdx = "with member [gender].[agg] as" + " 'aggregate({[gender].[gender].members},[measures].[unit sales])'" + "select NativizeSet(CrossJoin( " + "{gender.gender.members, gender.agg}, " + "{[marital status].[marital status].members}" + ")) on 0 from sales"; // Set limit to zero (effectively, no limit) propSaver.set(MondrianProperties.instance().NativizeMaxResults, 0); checkNative(mdx); } public void testNativeResultLimitBeforeMerge() { // This query will return exactly 6 rows: // {Female,Male,Agg}x{Married,Single} String mdx = "with member [gender].[agg] as" + " 'aggregate({[gender].[gender].members},[measures].[unit sales])'" + "select NativizeSet(CrossJoin( " + "{gender.gender.members, gender.agg}, " + "{[marital status].[marital status].members}" + ")) on 0 from sales"; // Set limit to exact size of result propSaver.set(MondrianProperties.instance().NativizeMaxResults, 6); checkNative(mdx); try { // The native list doesn't contain the calculated members, // so it will have 4 rows. Setting the limit to 3 means // that the exception will be thrown before calculated // members are merged into the result. propSaver.set(MondrianProperties.instance().NativizeMaxResults, 3); checkNative(mdx); fail("Should have thrown ResourceLimitExceededException."); } catch (ResourceLimitExceededException expected) { // ok } } public void testNativeResultLimitDuringMerge() { // This query will return exactly 6 rows: // {Female,Male,Agg}x{Married,Single} String mdx = "with member [gender].[agg] as" + " 'aggregate({[gender].[gender].members},[measures].[unit sales])'" + "select NativizeSet(CrossJoin( " + "{gender.gender.members, gender.agg}, " + "{[marital status].[marital status].members}" + ")) on 0 from sales"; // Set limit to exact size of result propSaver.set(MondrianProperties.instance().NativizeMaxResults, 6); checkNative(mdx); try { // The native list doesn't contain the calculated members, // so setting the limit to 5 means the exception won't be // thrown until calculated members are merged into the result. propSaver.set(MondrianProperties.instance().NativizeMaxResults, 5); checkNative(mdx); fail("Should have thrown ResourceLimitExceededException."); } catch (ResourceLimitExceededException expected) { } } public void testMeasureAndDimensionInCrossJoin() { checkNotNative( // There's no crossjoin left after the measure is set aside, // so it's not even a candidate for native evaluation. // This test is here to ensure that "NativizeSet" still returns // the correct result. "select NativizeSet(" + "CrossJoin(" + "{ measures.[unit sales] }, " + "[marital status].[marital status].members" + ")) on 0" + "from sales"); } public void testDimensionAndMeasureInCrossJoin() { checkNotNative( // There's no crossjoin left after the measure is set aside, // so it's not even a candidate for native evaluation. // This test is here to ensure that "NativizeSet" still returns // the correct result. "select NativizeSet(" + "CrossJoin(" + "[marital status].[marital status].members, " + "{ measures.[unit sales] }" + ")) on 0" + "from sales"); } public void testAllByAll() { checkNotNative( // There's no crossjoin left after all members are set aside, // so it's not even a candidate for native evaluation. // This test is here to ensure that "NativizeSet" still returns // the correct result. "select NativizeSet(" + "CrossJoin(" + "{ [gender].[all gender] }, " + "{ [marital status].[all marital status] } " + ")) on 0" + "from sales"); } public void testAllByAllByAll() { checkNotNative( // There's no crossjoin left after all members are set aside, // so it's not even a candidate for native evaluation. // This test is here to ensure that "NativizeSet" still returns // the correct result. "select NativizeSet(" + "CrossJoin(" + "{ [product].[all products] }, " + "CrossJoin(" + "{ [gender].[all gender] }, " + "{ [marital status].[all marital status] } " + "))) on 0" + "from sales"); } public void testNativizeTwoAxes() { String mdx = "select " + "NativizeSet(" + "CrossJoin(" + "{ [gender].[gender].members }, " + "{ [marital status].[marital status].members } " + ")) on 0," + "NativizeSet(" + "CrossJoin(" + "{ [measures].[unit sales] }, " + "{ [Education Level].[Education Level].members } " + ")) on 1" + "from [warehouse and sales]"; // Our setUp sets threshold at zero, so should always be native // if possible. checkNative(mdx); // Set the threshold high; same mdx should no longer be natively // evaluated. propSaver.set( MondrianProperties.instance().NativizeMinThreshold, 200000); checkNotNative(mdx); } public void testCurrentMemberAsFunArg() { checkNative( "with " //////////////////////////////////////////////////////////// // Having a member of the measures dimension as a function // argument will normally disable native evaluation but // there is a special case in FunUtil.checkNativeCompatible // which allows currentmember //////////////////////////////////////////////////////////// + "member [gender].[x] " + " as 'iif (measures.currentmember is measures.[unit sales], " + " Aggregate(gender.gender.members), 101010)' " + "select " + "NativizeSet(" + "crossjoin(" + "{time.year.members}, " + "crossjoin(" + "{gender.x}," + "[marital status].[marital status].members" + "))) " + "on axis(0) " + "from [warehouse and sales]"); } public void testOnlyMeasureIsLiteral() { checkNotNative( ////////////////////////////////////////////////////////////////// // There's no base cube, so this should NOT be natively evaluated. ////////////////////////////////////////////////////////////////// "with " + "member [measures].[cog_oqp_int_t1] as '1', solve_order = 65535 " + "select NativizeSet(CrossJoin(" + " [marital status].[marital status].members, " + " [gender].[gender].members " + ")) on 1, " + "{ [measures].[cog_oqp_int_t1] } " + "on 0 " + "from [warehouse and sales]"); } public void testTwoLiteralMeasuresAndUnitAndStoreSales() { checkNative( // Should be natively evaluated because the unit sales // measure will bring in a base cube. "with " + "member [measures].[cog_oqp_int_t1] as '1', solve_order = 65535 " + "member [measures].[cog_oqp_int_t2] as '2', solve_order = 65535 " + "select " + " NativizeSet(CrossJoin(" + " [marital status].[marital status].members, " + " [gender].[gender].members " + " ))" + "on 1, " + "{ " + " { [measures].[cog_oqp_int_t1] }, " + " { [measures].[unit sales] }, " + " { [measures].[cog_oqp_int_t2] }, " + " { [measures].[store sales] } " + "} " + " on 0 " + "from [warehouse and sales]"); } public void testLiteralMeasuresWithinParentheses() { checkNative( // Should be natively evaluated because the unit sales // measure will bring in a base cube. The extra parens // around the reference to the calculated member should no // longer cause native evaluation to be abandoned. "with " + "member [measures].[cog_oqp_int_t1] as '1', solve_order = 65535 " + "member [measures].[cog_oqp_int_t2] as '2', solve_order = 65535 " + "select " + " NativizeSet(CrossJoin(" + " [marital status].[marital status].members, " + " [gender].[gender].members " + " ))" + "on 1, " + "{ " + " { ((( [measures].[cog_oqp_int_t1] ))) }, " + " { [measures].[unit sales] }, " + " { ( [measures].[cog_oqp_int_t2] ) }, " + " { [measures].[store sales] } " + "} " + " on 0 " + "from [warehouse and sales]"); } public void testIsEmptyOnMeasures() { checkNative( "with " //////////////////////////////////////////////////////// // isEmpty doesn't pose a problem for native evaluation. //////////////////////////////////////////////////////// + "member [measures].[cog_oqp_int_t1] " + " as 'iif( isEmpty( measures.[unit sales]), 1010,2020)', solve_order = 65535 " + "select " + " NativizeSet(CrossJoin(" + " [marital status].[marital status].members, " + " [gender].[gender].members " + " ))" + "on 1, " + "{ " + " { [measures].[cog_oqp_int_t1] }, " + " { [measures].[unit sales] } " + "} " + " on 0 " + "from [warehouse and sales]"); } public void testLagOnMeasures() { checkNotNative( "with " ///////////////////////////////////////////// // Lag function is NOT compatible with native. ///////////////////////////////////////////// + "member [measures].[cog_oqp_int_t1] " + " as 'measures.[store sales].lag(1)', solve_order = 65535 " + "select " + " NativizeSet(CrossJoin(" + " [marital status].[marital status].members, " + " [gender].[gender].members " + " ))" + "on 1, " + "{ " + " { [measures].[cog_oqp_int_t1] }, " + " { [measures].[unit sales] }, " + " { [measures].[store sales] } " + "} " + " on 0 " + "from [warehouse and sales]"); } public void testLagOnMeasuresWithinParentheses() { checkNotNative( "with " ///////////////////////////////////////////// // Lag function is NOT compatible with native. // Here we're making sure that the lag function // disables native eval even when buried in layers // of parentheses. ///////////////////////////////////////////// + "member [measures].[cog_oqp_int_t1] " + " as 'measures.[store sales].lag(1)', solve_order = 65535 " + "select " + " NativizeSet(CrossJoin(" + " [marital status].[marital status].members, " + " [gender].[gender].members " + " ))" + "on 1, " + "{ " + " { ((( [measures].[cog_oqp_int_t1] ))) }, " + " { [measures].[unit sales] }, " + " { [measures].[store sales] } " + "} " + " on 0 " + "from [warehouse and sales]"); } public void testRangeOfMeasures() { checkNotNative( "select " + " NativizeSet(CrossJoin(" + " [marital status].[marital status].members, " + " [gender].[gender].members " + " ))" + "on 1, " + "{ " /////////////////////////////////////////////////// // Range of measures is NOT compatible with native. /////////////////////////////////////////////////// + " measures.[unit sales] : measures.[store sales] " + "} " + " on 0 " + "from [warehouse and sales]"); } public void testOrderOnMeasures() { checkNative( "with " /////////////////////////////////////////////////// // Order function should be compatible with native. /////////////////////////////////////////////////// + "member [measures].[cog_oqp_int_t1] " + " as 'aggregate(order({measures.[store sales]}, measures.[store sales]), " + "measures.[store sales])', solve_order = 65535 " + "select " + " NativizeSet(CrossJoin(" + " [marital status].[marital status].members, " + " [gender].[gender].members " + " ))" + "on 1, " + "{ " + " measures.[cog_oqp_int_t1]," + " measures.[unit sales]" + "} " + " on 0 " + "from [warehouse and sales]"); } public void testLiteralMeasureAndUnitSalesUsingSet() { checkNative( // Should be natively evaluated because the unit sales "with " // measure will bring in a base cube. + "member [measures].[cog_oqp_int_t1] as '1', solve_order = 65535 " + "member [measures].[cog_oqp_int_t2] as '2', solve_order = 65535 " + "set [cog_oqp_int_s1] as " + " 'CrossJoin(" + " [marital status].[marital status].members, " + " [gender].[gender].members " + " )'" + "select " + " NativizeSet([cog_oqp_int_s1])" + "on 1, " + "{ " + " [measures].[cog_oqp_int_t1], " + " [measures].[unit sales], " + " [measures].[cog_oqp_int_t1], " + " [measures].[store sales] " + "} " + " on 0 " + "from [warehouse and sales]"); } public void testNoSubstitutionsArityOne() { checkNotNative( // no crossjoin, so not native "SELECT NativizeSet({Gender.F, Gender.M}) on 0 from sales"); } public void testNoSubstitutionsArityTwo() { checkNotNative( "SELECT NativizeSet(CrossJoin(" + "{Gender.F, Gender.M}, " + "{ [Marital Status].M } " + ")) on 0 from sales"); } public void testExplicitCurrentMonth() { checkNative( "SELECT NativizeSet(CrossJoin( " + " { [Time].[Month].currentmember }, " + " Gender.Gender.members )) " + "on 0 from sales"); } public void disabled_testCalculatedCurrentMonth() { checkNative( "WITH " + "SET [Current Month] AS 'tail([Time].[month].members, 1)'" + "SELECT NativizeSet(CrossJoin( " + " { [Current Month] }, " + " Gender.Gender.members )) " + "on 0 from sales"); } public void disabled_testCalculatedRelativeMonth() { checkNative( "with " + "member [gender].[cog_oqp_int_t2] as '1', solve_order = 65535 " + "select NativizeSet(" + " { { [gender].[cog_oqp_int_t2] }, " + " crossjoin( {tail([Time].[month].members, 1)}, [gender].[gender].members ) }," + " { { [gender].[cog_oqp_int_t2] }, " + " crossjoin( {tail([Time].[month].members, 1).lag(1)}, [gender].[gender].members ) }," + ") on 0 " + "from [sales]"); } public void testAcceptsAllDimensionMembersSetAsInput() { checkNotNative( // no crossjoin, so not native "SELECT NativizeSet({[Marital Status].[Marital Status].members})" + " on 0 from sales"); } public void testAcceptsCrossJoinAsInput() { checkNative( "SELECT NativizeSet( CrossJoin({ Gender.F, Gender.M }, " + "{[Marital Status].[Marital Status].members})) on 0 from sales"); } public void testRedundantEnumMembersFirst() { checkNative( // In the enumerated marital status values { M, S, S } // the second S is clearly redundant, but should be // included in the result nonetheless. The extra // level of parens aren't logically necessary, but // are included here because they require special handling. "SELECT NativizeSet( CrossJoin(" + "{ { [Marital Status].M, [Marital Status].S }, " + " { [Marital Status].S } " + "}," + "CrossJoin( " + "{ gender.gender.members }, " + "{ time.quarter.members } " + "))) on 0 from sales"); } public void testRedundantEnumMembersMiddle() { checkNative( // In the enumerated gender values { F, M, M, M } // the last two M values are redunant, but should be // included in the result nonetheless. The extra // level of parens aren't logically necessary, but // are included here because they require special handling. "SELECT NativizeSet( CrossJoin(" + "{ [Marital Status].[Marital Status].members }," + "CrossJoin( " + "{ { gender.F, gender.M , gender.M}, " + " { gender.M } " + "}, " + "{ time.quarter.members } " + "))) on 0 from sales"); } public void testRedundantEnumMembersLast() { checkNative( // In the enumerated time quarter values { Q1, Q2, Q2 } // the last two Q2 values are redunant, but should be // included in the result nonetheless. The extra // level of parens aren't logically necessary, but // are included here because they require special handling. "SELECT NativizeSet( CrossJoin(" + "{ [Marital Status].[Marital Status].members }," + "CrossJoin( " + "{ gender.gender.members }, " + "{ { time.[1997].Q1, time.[1997].Q2 }, " + " { time.[1997].Q2 } " + "} " + "))) on 0 from sales"); } public void testRedundantLevelMembersFirst() { checkNative( // The second marital status members function is clearly // redundant, but should be included in the result // nonetheless. The extra level of parens aren't logically // necessary, but are included here because they require // special handling. "SELECT NativizeSet( CrossJoin(" + "{ [Marital Status].[Marital Status].members, " + " { [Marital Status].[Marital Status].members } " + "}," + "CrossJoin( " + "{ gender.gender.members }, " + "{ time.quarter.members } " + "))) on 0 from sales"); } public void testRedundantLevelMembersMiddle() { checkNative( // The second gender members function is clearly // redundant, but should be included in the result // nonetheless. The extra level of parens aren't logically // necessary, but are included here because they require // special handling. "SELECT NativizeSet( CrossJoin(" + "{ [Marital Status].[Marital Status].members }," + "CrossJoin( " + "{ gender.gender.members, " + " { gender.gender.members } " + "}, " + "{ time.quarter.members } " + "))) on 0 from sales"); } public void testRedundantLevelMembersLast() { checkNative( // The second time.quarter members function is clearly // redundant, but should be included in the result // nonetheless. The extra level of parens aren't logically // necessary, but are included here because they require // special handling. "SELECT NativizeSet( CrossJoin(" + "{ [Marital Status].[Marital Status].members }," + "CrossJoin( " + "{ gender.gender.members }, " + "{ time.quarter.members, " + " { time.quarter.members } " + "} " + "))) on 0 from sales"); } public void testNonEmptyNestedCrossJoins() { checkNative( "SELECT " + "NativizeSet(CrossJoin(" + "{ Gender.F, Gender.M }, " + "CrossJoin(" + "{ [Marital Status].[Marital Status].members }, " + "CrossJoin(" + "{ [Store].[All Stores].[USA].[CA], [Store].[All Stores].[USA].[OR] }, " + "{ [Education Level].[Education Level].members } " + ")))" + ") on 0 from sales"); } public void testLevelMembersAndAll() { checkNative( "select NativizeSet (" + "crossjoin( " + " { gender.gender.members, gender.[all gender] }, " + " [marital status].[marital status].members " + ")) on 0 from sales"); } public void testCrossJoinArgInNestedBraces() { checkNative( "select NativizeSet (" + "crossjoin( " + " { { gender.gender.members } }, " + " [marital status].[marital status].members " + ")) on 0 from sales"); } public void testLevelMembersAndAllWhereOrderMatters() { checkNative( "select NativizeSet (" + "crossjoin( " + " { gender.gender.members, gender.[all gender] }, " + " { [marital status].S, [marital status].M } " + ")) on 0 from sales"); } public void testEnumMembersAndAll() { checkNative( "select NativizeSet (" + "crossjoin( " + " { gender.F, gender.M, gender.[all gender] }, " + " [marital status].[marital status].members " + ")) on 0 from sales"); } public void testNativizeWithASetAtTopLevel() { checkNative( "WITH" + " MEMBER [Gender].[umg1] AS " + " '([Gender].[gender agg], [Measures].[Unit Sales])', SOLVE_ORDER = 8 " + " MEMBER [Gender].[gender agg] AS" + " 'AGGREGATE({[Gender].[Gender].MEMBERS},[Measures].[Unit Sales])', SOLVE_ORDER = 8 " + " MEMBER [Marital Status].[umg2] AS " + " '([Marital Status].[marital agg], [Measures].[Unit Sales])', SOLVE_ORDER = 4 " + " MEMBER [Marital Status].[marital agg] AS " + " 'AGGREGATE({[Marital Status].[Marital Status].MEMBERS},[Measures].[Unit Sales])', SOLVE_ORDER = 4 " + " SET [s2] AS " + " 'CROSSJOIN({[Marital Status].[Marital Status].MEMBERS}, {{[Gender].[Gender].MEMBERS}, {[Gender].[umg1]}})' " + " SET [s1] AS " + " 'CROSSJOIN({[Marital Status].[umg2]}, {[Gender].DEFAULTMEMBER})' " + " SELECT " + " NativizeSet({[Measures].[Unit Sales]}) DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(0), " + " NativizeSet({[s2],[s1]}) " + " DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(1)" + " FROM [Sales] CELL PROPERTIES VALUE, FORMAT_STRING"); } public void testNativizeWithASetAtTopLevel3Levels() { checkNative( "WITH\n" + "MEMBER [Gender].[COG_OQP_INT_umg2] AS 'IIF([Measures].CURRENTMEMBER IS [Measures].[Unit Sales], " + "([Gender].[COG_OQP_INT_m5], [Measures].[Unit Sales]), " + "AGGREGATE({[Gender].[Gender].MEMBERS}))', SOLVE_ORDER = 8\n" + "MEMBER [Gender].[COG_OQP_INT_m5] AS " + "'AGGREGATE({[Gender].[Gender].MEMBERS}, [Measures].[Unit Sales])', SOLVE_ORDER = 8\n" + "MEMBER [Store Type].[COG_OQP_INT_umg1] AS " + "'IIF([Measures].CURRENTMEMBER IS [Measures].[Unit Sales], " + "([Store Type].[COG_OQP_INT_m4], [Measures].[Unit Sales]), " + "AGGREGATE({[Store Type].[Store Type].MEMBERS}))', SOLVE_ORDER = 12\n" + "MEMBER [Store Type].[COG_OQP_INT_m4] AS " + "'AGGREGATE({[Store Type].[Store Type].MEMBERS}, [Measures].[Unit Sales])', SOLVE_ORDER = 12\n" + "MEMBER [Marital Status].[COG_OQP_INT_umg3] AS " + "'IIF([Measures].CURRENTMEMBER IS [Measures].[Unit Sales], " + "([Marital Status].[COG_OQP_INT_m6], [Measures].[Unit Sales]), " + "AGGREGATE({[Marital Status].[Marital Status].MEMBERS}))', SOLVE_ORDER = 4\n" + "MEMBER [Marital Status].[COG_OQP_INT_m6] AS " + "'AGGREGATE({[Marital Status].[Marital Status].MEMBERS}, [Measures].[Unit Sales])', SOLVE_ORDER = 4\n" + "SET [COG_OQP_INT_s5] AS 'CROSSJOIN({[Marital Status].[Marital Status].MEMBERS}, {[COG_OQP_INT_s4], [COG_OQP_INT_s3]})'\n" + "SET [COG_OQP_INT_s4] AS 'CROSSJOIN({[Gender].[Gender].MEMBERS}, {{[Store Type].[Store Type].MEMBERS}, " + "{[Store Type].[COG_OQP_INT_umg1]}})'\n" + "SET [COG_OQP_INT_s3] AS 'CROSSJOIN({[Gender].[COG_OQP_INT_umg2]}, {[Store Type].DEFAULTMEMBER})'\n" + "SET [COG_OQP_INT_s2] AS 'CROSSJOIN({[Marital Status].[COG_OQP_INT_umg3]}, [COG_OQP_INT_s1])'\n" + "SET [COG_OQP_INT_s1] AS 'CROSSJOIN({[Gender].DEFAULTMEMBER}, {[Store Type].DEFAULTMEMBER})' \n" + "SELECT {[Measures].[Unit Sales]} " + "DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(0), \n" + "NativizeSet({[COG_OQP_INT_s5], [COG_OQP_INT_s2]}) " + "DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(1)\n" + "FROM [Sales] CELL PROPERTIES VALUE, FORMAT_STRING\n"); } public void testNativizeWithASetAtTopLevel2() { checkNative( "WITH" + " MEMBER [Gender].[umg1] AS " + " '([Gender].[gender agg], [Measures].[Unit Sales])', SOLVE_ORDER = 8 " + " MEMBER [Gender].[gender agg] AS" + " 'AGGREGATE({[Gender].[Gender].MEMBERS},[Measures].[Unit Sales])', SOLVE_ORDER = 8 " + " MEMBER [Marital Status].[umg2] AS " + " '([Marital Status].[marital agg], [Measures].[Unit Sales])', SOLVE_ORDER = 4 " + " MEMBER [Marital Status].[marital agg] AS " + " 'AGGREGATE({[Marital Status].[Marital Status].MEMBERS},[Measures].[Unit Sales])', SOLVE_ORDER = 4 " + " SET [s2] AS " + " 'CROSSJOIN({{[Marital Status].[Marital Status].MEMBERS},{[Marital Status].[umg2]}}, " + "{{[Gender].[Gender].MEMBERS}, {[Gender].[umg1]}})' " + " SET [s1] AS " + " 'CROSSJOIN({[Marital Status].[umg2]}, {[Gender].DEFAULTMEMBER})' " + " SELECT " + " NativizeSet({[Measures].[Unit Sales]}) " + "DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(0), " + " NativizeSet({[s2]}) " + " DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(1)" + " FROM [Sales] CELL PROPERTIES VALUE, FORMAT_STRING"); } public void testGenderMembersAndAggByMaritalStatus() { checkNative( "with member gender.agg as 'Aggregate( gender.gender.members )' " + "select NativizeSet(" + "crossjoin( " + " { gender.gender.members, gender.[agg] }, " + " [marital status].[marital status].members " + ")) on 0 from sales"); } public void testGenderAggAndMembersByMaritalStatus() { checkNative( "with member gender.agg as 'Aggregate( gender.gender.members )' " + "select NativizeSet(" + "crossjoin( " + " { gender.[agg], gender.gender.members }, " + " [marital status].[marital status].members " + ")) on 0 from sales"); } public void testGenderAggAndMembersAndAllByMaritalStatus() { checkNative( "with member gender.agg as 'Aggregate( gender.gender.members )' " + "select NativizeSet(" + "crossjoin( " + " { gender.[agg], gender.gender.members, gender.[all gender] }, " + " [marital status].[marital status].members " + ")) on 0 from sales"); } public void testMaritalStatusByGenderMembersAndAgg() { checkNative( "with member gender.agg as 'Aggregate( gender.gender.members )' " + "select NativizeSet(" + "crossjoin( " + " [marital status].[marital status].members, " + " { gender.gender.members, gender.[agg] } " + ")) on 0 from sales"); } public void testMaritalStatusByGenderAggAndMembers() { checkNative( "with member gender.agg as 'Aggregate( gender.gender.members )' " + "select NativizeSet(" + "crossjoin( " + " [marital status].[marital status].members, " + " { gender.[agg], gender.gender.members } " + ")) on 0 from sales"); } public void testAggWithEnumMembers() { checkNative( "with member gender.agg as 'Aggregate( gender.gender.members )' " + "select NativizeSet(" + "crossjoin( " + " { gender.gender.members, gender.[agg] }, " + " { [marital status].[marital status].[M], [marital status].[marital status].[S] } " + ")) on 0 from sales"); } public void testCrossjoinArgWithMultipleElementTypes() { checkNative( // Test for correct handling of a crossjoin arg that contains // a combination of element types: a members function, an // explicit enumerated value, an aggregate, and the all level. "with member [gender].agg as 'Aggregate( gender.gender.members )' " + "select NativizeSet(" + "crossjoin( " + "{ time.quarter.members }, " + "CrossJoin( " + "{ gender.gender.members, gender.F, gender.[agg], gender.[all gender] }, " + "{ [marital status].[marital status].members }" + "))) on 0 from sales"); } public void testProductFamilyMembers() { checkNative( "select non empty NativizeSet(" + "crossjoin( " + " [product].[product family].members, " + " { [gender].F } " + ")) on 0 from sales"); } public void testNestedCrossJoinWhereAllColsHaveNative() { checkNative( "with " + "member gender.agg as 'Aggregate( gender.gender.members )' " + "member [marital status].agg as 'Aggregate( [marital status].[marital status].members )' " + "select NativizeSet(" + "crossjoin( " + " { gender.[all gender], gender.gender.members, gender.[agg] }, " + " crossjoin(" + " { [marital status].[marital status].members, [marital status].[agg] }," + " [Education Level].[Education Level].members " + "))) on 0 from sales"); } public void testNestedCrossJoinWhereFirstColumnNonNative() { checkNative( "with " + "member gender.agg as 'Aggregate( gender.gender.members )' " + "member [marital status].agg as 'Aggregate( [marital status].[marital status].members )' " + "select NativizeSet(" + "crossjoin( " + " { gender.[all gender], gender.[agg] }, " + " crossjoin(" + " { [marital status].[marital status].members, [marital status].[agg] }," + " [Education Level].[Education Level].members " + "))) on 0 from sales"); } public void testNestedCrossJoinWhereMiddleColumnNonNative() { checkNative( "with " + "member gender.agg as 'Aggregate( gender.gender.members )' " + "member [marital status].agg as 'Aggregate( [marital status].[marital status].members )' " + "select NativizeSet(" + "crossjoin( " + " { [marital status].[marital status].members, [marital status].[agg] }," + " crossjoin(" + " { gender.[all gender], gender.[agg] }, " + " [Education Level].[Education Level].members " + "))) on 0 from sales"); } public void testNestedCrossJoinWhereLastColumnNonNative() { checkNative( "with " + "member gender.agg as 'Aggregate( gender.gender.members )' " + "member [marital status].agg as 'Aggregate( [marital status].[marital status].members )' " + "select NativizeSet(" + "crossjoin( " + " { [marital status].[marital status].members, [marital status].[agg] }," + " crossjoin(" + " [Education Level].[Education Level].members, " + " { gender.[all gender], gender.[agg] } " + "))) on 0 from sales"); } public void testGenderAggByMaritalStatus() { checkNotNative( // NativizeSet removes the crossjoin, so not native "with member gender.agg as 'Aggregate( gender.gender.members )' " + "select NativizeSet(" + "crossjoin( " + " { gender.[agg] }, " + " [marital status].[marital status].members " + ")) on 0 from sales"); } public void testGenderAggTwiceByMaritalStatus() { checkNotNative( // NativizeSet removes the crossjoin, so not native "with " + "member gender.agg1 as 'Aggregate( { gender.M } )' " + "member gender.agg2 as 'Aggregate( { gender.F } )' " + "select NativizeSet(" + "crossjoin( " + " { gender.[agg1], gender.[agg2] }, " + " [marital status].[marital status].members " + ")) on 0 from sales"); } public void testSameGenderAggTwiceByMaritalStatus() { checkNotNative( // NativizeSet removes the crossjoin, so not native "with " + "member gender.agg as 'Aggregate( gender.gender.members )' " + "select NativizeSet(" + "crossjoin( " + " { gender.[agg], gender.[agg] }, " + " [marital status].[marital status].members " + ")) on 0 from sales"); } public void testMaritalStatusByGenderAgg() { checkNotNative( // NativizeSet removes the crossjoin, so not native "with member gender.agg as 'Aggregate( gender.gender.members )' " + "select NativizeSet(" + "crossjoin( " + " [marital status].[marital status].members, " + " { gender.[agg] } " + ")) on 0 from sales"); } public void testMaritalStatusByTwoGenderAggs() { checkNotNative( // NativizeSet removes the crossjoin, so not native "with " + "member gender.agg1 as 'Aggregate( { gender.M } )' " + "member gender.agg2 as 'Aggregate( { gender.F } )' " + "select NativizeSet(" + "crossjoin( " + " [marital status].[marital status].members, " + " { gender.[agg1], gender.[agg2] } " + ")) on 0 from sales"); } public void testMaritalStatusBySameGenderAggTwice() { checkNotNative( // NativizeSet removes the crossjoin, so not native "with " + "member gender.agg as 'Aggregate( { gender.M } )' " + "select NativizeSet(" + "crossjoin( " + " [marital status].[marital status].members, " + " { gender.[agg], gender.[agg] } " + ")) on 0 from sales"); } public void testMultipleLevelsOfSameDimInConcatenatedJoins() { checkNotNative( // See notes for testMultipleLevelsOfSameDimInSingleArg // because the NativizeSetFunDef transforms this mdx into the // mdx in that test. "select NativizeSet( {" + "CrossJoin(" + " { [Time].[Year].members }," + " { gender.F, gender. M } )," + "CrossJoin(" + " { [Time].[Quarter].members }," + " { gender.F, gender. M } )" + "} ) on 0 from sales"); } public void testMultipleLevelsOfSameDimInSingleArg() { checkNotNative( // Although it's legal MDX, the RolapNativeSet.checkCrossJoinArg // can't deal with an arg that contains multiple .members functions. // If they were at the same level, the NativizeSetFunDef would // deal with them, but since they are at differen levels, we're // stuck. "select NativizeSet( {" + "CrossJoin(" + " { [Time].[Year].members," + " [Time].[Quarter].members }," + " { gender.F, gender. M } )" + "} ) on 0 from sales"); } public void testDoesNoHarmToPlainEnumeratedMembers() { propSaver.set( MondrianProperties.instance().EnableNonEmptyOnAllAxis, false); assertQueryIsReWritten( "SELECT NativizeSet({Gender.M,Gender.F}) on 0 from sales", "select " + "NativizeSet({[Gender].[M], [Gender].[F]}) " + "ON COLUMNS\n" + "from [Sales]\n"); } public void testDoesNoHarmToPlainDotMembers() { propSaver.set( MondrianProperties.instance().EnableNonEmptyOnAllAxis, false); assertQueryIsReWritten( "select NativizeSet({[Marital Status].[Marital Status].members}) " + "on 0 from sales", "select NativizeSet({[Marital Status].[Marital Status].Members}) " + "ON COLUMNS\n" + "from [Sales]\n"); } public void testTransformsCallToRemoveDotMembersInCrossJoin() { propSaver.set( MondrianProperties.instance().EnableNonEmptyOnAllAxis, false); assertQueryIsReWritten( "select NativizeSet(CrossJoin({Gender.M,Gender.F},{[Marital Status].[Marital Status].members})) " + "on 0 from sales", "with member [Marital Status].[_Nativized_Member_Marital Status_Marital Status_] as '[Marital Status].DefaultMember'\n" + " set [_Nativized_Set_Marital Status_Marital Status_] as " + "'{[Marital Status].[_Nativized_Member_Marital Status_Marital Status_]}'\n" + " member [Gender].[_Nativized_Sentinel_Gender_(All)_] as '101010'\n" + " member [Marital Status].[_Nativized_Sentinel_Marital Status_(All)_] as '101010'\n" + "select NativizeSet(Crossjoin({[Gender].[M], [Gender].[F]}, " + "{[_Nativized_Set_Marital Status_Marital Status_]})) ON COLUMNS\n" + "from [Sales]\n"); } public void DISABLED_testTransformsWithSeveralDimensionsNestedOnRows() { propSaver.set( MondrianProperties.instance().EnableNonEmptyOnAllAxis, false); assertQueryIsReWritten( "WITH SET [COG_OQP_INT_s4] AS 'CROSSJOIN({[Education Level].[Graduate Degree]}," + " [COG_OQP_INT_s3])'" + " SET [COG_OQP_INT_s3] AS 'CROSSJOIN({[Marital Status].[S]}, [COG_OQP_INT_s2])'" + " SET [COG_OQP_INT_s2] AS 'CROSSJOIN({[Gender].[F]}, [COG_OQP_INT_s1])'" + " SET [COG_OQP_INT_s1] AS 'CROSSJOIN({[Product].[Product Name].MEMBERS}, {[Customers].[Name].MEMBERS})' " + "SELECT {[Measures].[Unit Sales]} DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(0)," + " NativizeSet([COG_OQP_INT_s4]) DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(1) " + "FROM [Sales] CELL PROPERTIES VALUE, FORMAT_STRING", "with set [COG_OQP_INT_s4] as 'Crossjoin({[Education Level].[Graduate Degree]}, [COG_OQP_INT_s3])'\n" + " set [COG_OQP_INT_s3] as 'Crossjoin({[Marital Status].[S]}, [COG_OQP_INT_s2])'\n" + " set [COG_OQP_INT_s2] as 'Crossjoin({[Gender].[F]}, [COG_OQP_INT_s1])'\n" + " set [COG_OQP_INT_s1] as 'Crossjoin({[_Nativized_Set_Product_Product Name_]}, {[_Nativized_Set_Customers_Name_]})'\n" + " member [Customers].[_Nativized_Member_Customers_Name_] as '[Customers].DefaultMember'\n" + " set [_Nativized_Set_Customers_Name_] as '{[Customers].[_Nativized_Member_Customers_Name_]}'\n" + " member [Product].[_Nativized_Member_Product_Product Name_] as '[Product].DefaultMember'\n" + " set [_Nativized_Set_Product_Product Name_] as '{[Product].[_Nativized_Member_Product_Product Name_]}'\n" + " member [Education Level].[_Nativized_Sentinel_Education Level_(All)_] as '101010'\n" + " member [Marital Status].[_Nativized_Sentinel_Marital Status_(All)_] as '101010'\n" + " member [Gender].[_Nativized_Sentinel_Gender_(All)_] as '101010'\n" + " member [Product].[_Nativized_Sentinel_Product_(All)_] as '101010'\n" + " member [Customers].[_Nativized_Sentinel_Customers_(All)_] as '101010'\n" + "select {[Measures].[Unit Sales]} DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON COLUMNS,\n" + " NativizeSet([COG_OQP_INT_s4]) DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON ROWS\n" + "from [Sales]\n"); } public void testTransformsComplexQueryWithGenerateAndAggregate() { propSaver.set( MondrianProperties.instance().EnableNonEmptyOnAllAxis, false); assertQueryIsReWritten( "WITH MEMBER [Product].[COG_OQP_INT_umg1] AS " + "'IIF([Measures].CURRENTMEMBER IS [Measures].[Unit Sales], ([Product].[COG_OQP_INT_m2], [Measures].[Unit Sales])," + " AGGREGATE({[Product].[Product Name].MEMBERS}))', SOLVE_ORDER = 4 " + "MEMBER [Product].[COG_OQP_INT_m2] AS 'AGGREGATE({[Product].[Product Name].MEMBERS}," + " [Measures].[Unit Sales])', SOLVE_ORDER = 4 " + "SET [COG_OQP_INT_s5] AS 'CROSSJOIN({[Marital Status].[S]}, [COG_OQP_INT_s4])'" + " SET [COG_OQP_INT_s4] AS 'CROSSJOIN({[Gender].[F]}, [COG_OQP_INT_s2])'" + " SET [COG_OQP_INT_s3] AS 'CROSSJOIN({[Gender].[F]}, {[COG_OQP_INT_s2], [COG_OQP_INT_s1]})' " + "SET [COG_OQP_INT_s2] AS 'CROSSJOIN({[Product].[Product Name].MEMBERS}, {[Customers].[Name].MEMBERS})' " + "SET [COG_OQP_INT_s1] AS 'CROSSJOIN({[Product].[COG_OQP_INT_umg1]}, {[Customers].DEFAULTMEMBER})' " + "SELECT {[Measures].[Unit Sales]} DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(0)," + " NativizeSet(GENERATE({[Education Level].[Graduate Degree]}, \n" + "CROSSJOIN(HEAD({([Education Level].CURRENTMEMBER)}, IIF(COUNT([COG_OQP_INT_s5], INCLUDEEMPTY) > 0, 1, 0)), " + "GENERATE({[Marital Status].[S]}, CROSSJOIN(HEAD({([Marital Status].CURRENTMEMBER)}, " + "IIF(COUNT([COG_OQP_INT_s4], INCLUDEEMPTY) > 0, 1, 0)), [COG_OQP_INT_s3]), ALL)), ALL))" + " DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(1)" + " FROM [Sales] CELL PROPERTIES VALUE, FORMAT_STRING", "with member [Product].[COG_OQP_INT_umg1] as " + "'IIf(([Measures].CurrentMember IS [Measures].[Unit Sales]), ([Product].[COG_OQP_INT_m2], [Measures].[Unit Sales]), " + "Aggregate({[Product].[Product Name].Members}))', SOLVE_ORDER = 4\n" + " member [Product].[COG_OQP_INT_m2] as " + "'Aggregate({[Product].[Product Name].Members}, [Measures].[Unit Sales])', SOLVE_ORDER = 4\n" + " set [COG_OQP_INT_s5] as 'Crossjoin({[Marital Status].[S]}, [COG_OQP_INT_s4])'\n" + " set [COG_OQP_INT_s4] as 'Crossjoin({[Gender].[F]}, [COG_OQP_INT_s2])'\n" + " set [COG_OQP_INT_s3] as 'Crossjoin({[Gender].[F]}, {[COG_OQP_INT_s2], [COG_OQP_INT_s1]})'\n" + " set [COG_OQP_INT_s2] as 'Crossjoin({[Product].[Product Name].Members}, {[Customers].[Name].Members})'\n" + " set [COG_OQP_INT_s1] as 'Crossjoin({[Product].[COG_OQP_INT_umg1]}, {[Customers].DefaultMember})'\n" + "select {[Measures].[Unit Sales]} DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON COLUMNS,\n" + " NativizeSet(Generate({[Education Level].[Graduate Degree]}, " + "Crossjoin(Head({[Education Level].CurrentMember}, IIf((Count([COG_OQP_INT_s5], INCLUDEEMPTY) > 0), 1, 0)), " + "Generate({[Marital Status].[S]}, " + "Crossjoin(Head({[Marital Status].CurrentMember}, " + "IIf((Count([COG_OQP_INT_s4], INCLUDEEMPTY) > 0), 1, 0)), [COG_OQP_INT_s3]), ALL)), ALL)) " + "DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON ROWS\n" + "from [Sales]\n"); } public void DISABLED_testParallelCrossjoins() { checkNative( // DE2185 "select NativizeSet( {" + " CrossJoin( { [Marital Status].[Marital Status].members }, { gender.F, gender. M } )," + " CrossJoin( { [Marital Status].[Marital Status].members }, { gender.F, gender. M } )" + "} ) on 0 from sales"); } public void testMultipleHierarchySsasTrue() { propSaver.set( MondrianProperties.instance().SsasCompatibleNaming, true); propSaver.set( MondrianProperties.instance().EnableNonEmptyOnAllAxis, false); // Ssas compatible: time.[weekly].[week] // Use fresh connection -- unique names are baked in when schema is // loaded, depending the Ssas setting at that time. assertQueryIsReWritten( getTestContext().withFreshConnection(), "select nativizeSet(crossjoin(time.[week].members, { gender.m })) on 0 " + "from sales", "with member [Time].[Weekly].[_Nativized_Member_Time_Weekly_Week_] as '[Time].[Weekly].DefaultMember'\n" + " set [_Nativized_Set_Time_Weekly_Week_] as '{[Time].[Weekly].[_Nativized_Member_Time_Weekly_Week_]}'\n" + " member [Time].[_Nativized_Sentinel_Time_Year_] as '101010'\n" + " member [Gender].[_Nativized_Sentinel_Gender_(All)_] as '101010'\n" + "select NativizeSet(Crossjoin([_Nativized_Set_Time_Weekly_Week_], {[Gender].[M]})) ON COLUMNS\n" + "from [Sales]\n"); } public void testMultipleHierarchySsasFalse() { propSaver.set( MondrianProperties.instance().SsasCompatibleNaming, false); propSaver.set( MondrianProperties.instance().EnableNonEmptyOnAllAxis, false); // Ssas compatible: [time.weekly].week assertQueryIsReWritten( "select nativizeSet(crossjoin( [time.weekly].week.members, { gender.m })) on 0 " + "from sales", "with member [Time].[_Nativized_Member_Time_Weekly_Week_] as '[Time].DefaultMember'\n" + " set [_Nativized_Set_Time_Weekly_Week_] as '{[Time].[_Nativized_Member_Time_Weekly_Week_]}'\n" + " member [Time].[_Nativized_Sentinel_Time_Year_] as '101010'\n" + " member [Gender].[_Nativized_Sentinel_Gender_(All)_] as '101010'\n" + "select NativizeSet(Crossjoin([_Nativized_Set_Time_Weekly_Week_], {[Gender].[M]})) ON COLUMNS\n" + "from [Sales]\n"); } public void testComplexCrossjoinAggInMiddle() { checkNative( "WITH\n" + "\tMEMBER [Time].[Time].[COG_OQP_USR_Aggregate(Time Values)] AS " + "'IIF([Measures].CURRENTMEMBER IS [Measures].[Unit Sales], ([Time].[1997], [Measures].[Unit Sales]), ([Time].[1997]))',\n" + "\tSOLVE_ORDER = 4 MEMBER [Store Type].[COG_OQP_INT_umg1] AS " + "'IIF([Measures].CURRENTMEMBER IS [Measures].[Unit Sales], ([Store Type].[COG_OQP_INT_m2], [Measures].[Unit Sales]), " + "AGGREGATE({[Store Type].[Store Type].MEMBERS}))',\n" + "\tSOLVE_ORDER = 8 MEMBER [Store Type].[COG_OQP_INT_m2] AS " + "'AGGREGATE({[Store Type].[Store Type].MEMBERS}, [Measures].[Unit Sales])',\n" + "\tSOLVE_ORDER = 8 \n" + "SET\n" + "\t[COG_OQP_INT_s9] AS 'CROSSJOIN({[Marital Status].[Marital Status].MEMBERS}, {[COG_OQP_INT_s8], [COG_OQP_INT_s6]})' \n" + "SET\n" + "\t[COG_OQP_INT_s8] AS 'CROSSJOIN({[Store Type].[Store Type].MEMBERS}, [COG_OQP_INT_s7])' \n" + "SET\n" + "\t[COG_OQP_INT_s7] AS 'CROSSJOIN({[Promotions].[Promotions].MEMBERS}, " + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Pearl].[Pearl Imported Beer]})' \n" + "SET\n" + "\t[COG_OQP_INT_s6] AS 'CROSSJOIN({[Store Type].[COG_OQP_INT_umg1]}, [COG_OQP_INT_s1])' \n" + "SET\n" + "\t[COG_OQP_INT_s5] AS 'CROSSJOIN({[Time].[COG_OQP_USR_Aggregate(Time Values)]}, [COG_OQP_INT_s4])' \n" + "SET\n" + "\t[COG_OQP_INT_s4] AS 'CROSSJOIN({[Gender].DEFAULTMEMBER}, [COG_OQP_INT_s3])' \n" + "SET\n" + "\t[COG_OQP_INT_s3] AS 'CROSSJOIN({[Marital Status].DEFAULTMEMBER}, [COG_OQP_INT_s2])' \n" + "SET\n" + "\t[COG_OQP_INT_s2] AS 'CROSSJOIN({[Store Type].DEFAULTMEMBER}, [COG_OQP_INT_s1])' \n" + "SET\n" + "\t[COG_OQP_INT_s11] AS 'CROSSJOIN({[Gender].[Gender].MEMBERS}, [COG_OQP_INT_s10])' \n" + "SET\n" + "\t[COG_OQP_INT_s10] AS 'CROSSJOIN({[Marital Status].[Marital Status].MEMBERS}, [COG_OQP_INT_s8])' \n" + "SET\n" + "\t[COG_OQP_INT_s1] AS 'CROSSJOIN({[Promotion Name].DEFAULTMEMBER}, " + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Pearl].[Pearl Imported Beer]})' \n" + "SELECT\n" + "\t{[Measures].[Unit Sales]} DIMENSION PROPERTIES PARENT_LEVEL,\n" + "\tCHILDREN_CARDINALITY,\n" + "\tPARENT_UNIQUE_NAME ON AXIS(0),\n" + "NativizeSet(\n" + "\t{\n" + "CROSSJOIN({[Time].[1997]}, CROSSJOIN({[Gender].[Gender].MEMBERS}, [COG_OQP_INT_s9])),\n" + "\t[COG_OQP_INT_s5]}\n" + ")\n" + "ON AXIS(1) \n" + "FROM\n" + "\t[Sales] "); } public void testTopCountDoesNotGetTransformed() { assertQueryIsReWritten( "select " + " NativizeSet(Crossjoin([Gender].[Gender].members," + "TopCount({[Marital Status].[Marital Status].members},1,[Measures].[Unit Sales]))" + " ) on 0," + "{[Measures].[Unit Sales]} on 1 FROM [Sales]", "with member [Gender].[_Nativized_Member_Gender_Gender_] as '[Gender].DefaultMember'\n" + " set [_Nativized_Set_Gender_Gender_] as '{[Gender].[_Nativized_Member_Gender_Gender_]}'\n" + " member [Gender].[_Nativized_Sentinel_Gender_(All)_] as '101010'\n" + "select NON EMPTY NativizeSet(Crossjoin([_Nativized_Set_Gender_Gender_], " + "TopCount({[Marital Status].[Marital Status].Members}, 1, [Measures].[Unit Sales]))) ON COLUMNS,\n" + " NON EMPTY {[Measures].[Unit Sales]} ON ROWS\n" + "from [Sales]\n"); } public void testCrossjoinWithFilter() { assertQueryReturns( "select\n" + "NON EMPTY {[Measures].[Unit Sales]} ON COLUMNS, \n" + "NON EMPTY NativizeSet(Crossjoin({[Time].[1997]}, " + "Filter({[Gender].[Gender].Members}, ([Measures].[Unit Sales] < 131559)))) ON ROWS \n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997], [Gender].[F]}\n" + "Row #0: 131,558\n"); } public void testEvaluationIsNonNativeWhenBelowHighcardThreshoold() { propSaver.set( MondrianProperties.instance().NativizeMinThreshold, 10000); SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.ACCESS, "select `customer`.`gender` as `c0` " + "from `customer` as `customer`, `sales_fact_1997` as `sales_fact_1997` " + "where `sales_fact_1997`.`customer_id` = `customer`.`customer_id` " + "and `customer`.`marital_status` = 'S' " + "group by `customer`.`gender` order by 1 ASC", 251) }; String mdxQuery = "select non empty NativizeSet(" + "Crossjoin([Gender].[Gender].members,{[Time].[1997]})) on 0 " + "from [Warehouse and Sales] " + "where [Marital Status].[Marital Status].[S]"; assertQuerySqlOrNot( getTestContext(), mdxQuery, patterns, true, false, true); } public void testCalculatedLevelsDoNotCauseException() { String mdx = "SELECT " + " Nativizeset" + " (" + " {" + " [Store].Levels(0).MEMBERS" + " }" + " ) ON COLUMNS" + " FROM [Sales]"; checkNotNative(mdx); } public void testAxisWithArityOneIsNotNativelyEvaluated() { SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.ACCESS, "select `promotion`.`media_type` as `c0` " + "from `promotion` as `promotion`, `sales_fact_1997` as `sales_fact_1997` " + "where `sales_fact_1997`.`promotion_id` = `promotion`.`promotion_id` " + "group by `promotion`.`media_type` " + "order by Iif(`promotion`.`media_type` IS NULL, 1, 0), " + "`promotion`.`media_type` ASC", 296) }; String query = "select " + " NON EMPTY " + " NativizeSet(" + " Except(" + " {[Promotion Media].[Promotion Media].Members},\n" + " {[Promotion Media].[Bulk Mail],[Promotion Media].[All Media].[Daily Paper]}" + " )" + " ) ON COLUMNS," + " NON EMPTY " + " {[Measures].[Unit Sales]} ON ROWS " + "from [Sales] \n" + "where [Time].[1997]"; assertQuerySqlOrNot( getTestContext(), query, patterns, true, false, true); } public void testAxisWithNamedSetArityOneIsNotNativelyEvaluated() { checkNotNative( "with " + "set [COG_OQP_INT_s1] as " + "'Intersect({[Gender].[Gender].Members}, {[Gender].[Gender].[M]})' " + "select NON EMPTY " + "NativizeSet([COG_OQP_INT_s1]) ON COLUMNS " + "from [Sales]"); } public void testOneAxisHighAndOneLowGetsNativeEvaluation() { propSaver.set(MondrianProperties.instance().NativizeMinThreshold, 19); checkNative( "select NativizeSet(" + "Crossjoin([Gender].[Gender].members," + "[Marital Status].[Marital Status].members)) on 0," + "NativizeSet(" + "Crossjoin([Store].[Store State].members,[Time].[Year].members)) on 1 " + "from [Warehouse and Sales]"); } public void disabled_testAggregatesInSparseResultsGetSortedCorrectly() { propSaver.set(MondrianProperties.instance().NativizeMinThreshold, 0); checkNative( "select non empty NativizeSet(" + "Crossjoin({[Store Type].[Store Type].members,[Store Type].[all store types]}," + "{ [Promotion Media].[Media Type].members }" + ")) on 0 from sales"); } public void testLeafMembersOfParentChildDimensionAreNativelyEvaluated() { checkNative( "SELECT" + " NON EMPTY " + "NativizeSet(Crossjoin(" + "{" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Pat Chin].[Gabriel Walton]," + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Pat Chin].[Bishop Meastas]," + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Pat Chin].[Paula Duran]," + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Pat Chin].[Margaret Earley]," + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Pat Chin].[Elizabeth Horne]" + "}," + "[Store].[Store Name].members" + ")) on 0 from hr"); } public void testAggregatedCrossjoinWithZeroMembersInNativeList() { propSaver.set(MondrianProperties.instance().NativizeMinThreshold, 0); checkNative( "with" + " member [gender].[agg] as" + " 'aggregate({[gender].[gender].members},[measures].[unit sales])'" + " member [Marital Status].[agg] as" + " 'aggregate({[Marital Status].[Marital Status].members},[measures].[unit sales])'" + "select" + " non empty " + " NativizeSet(" + "Crossjoin(" + "{[Marital Status].[Marital Status].members,[Marital Status].[agg]}," + "{[Gender].[Gender].members,[gender].[agg]}" + ")) on 0 " + " from sales " + " where [Store].[Canada].[BC].[Vancouver].[Store 19]"); } public void testCardinalityQueriesOnlyExecuteOnce() { SqlPattern[] patterns = { new SqlPattern( Dialect.DatabaseProduct.ORACLE, "select count(*) as \"c0\" " + "from (select " + "distinct \"customer\".\"gender\" as \"c0\" " + "from \"customer\" \"customer\") \"init\"", 108), new SqlPattern( Dialect.DatabaseProduct.ACCESS, "select count(*) as `c0` " + "from (select " + "distinct `customer`.`gender` as `c0` " + "from `customer` as `customer`) as `init`", 108) }; String mdxQuery = "select" + " non empty" + " NativizeSet(Crossjoin(" + "[Gender].[Gender].members,[Marital Status].[Marital Status].members" + ")) on 0 from Sales"; getConnection().execute(getConnection().parseQuery(mdxQuery)); assertQuerySqlOrNot( getTestContext(), mdxQuery, patterns, true, false, false); } public void testSingleLevelDotMembersIsNativelyEvaluated() { String mdx1 = "with member [Customers].[agg] as '" + "AGGREGATE({[Customers].[name].MEMBERS}, [Measures].[Unit Sales])'" + "select non empty NativizeSet({{[Customers].[name].members}, {[Customers].[agg]}}) on 0," + "non empty NativizeSet(" + "Crossjoin({[Gender].[Gender].[M]}," + "[Measures].[Unit Sales])) on 1 " + "from Sales"; String mdx2 = "select non empty NativizeSet({[Customers].[name].members}) on 0," + "non empty NativizeSet(" + "Crossjoin({[Gender].[Gender].[M]}," + "[Measures].[Unit Sales])) on 1 " + "from Sales"; String sql = "select \"customer\".\"country\" as \"c0\", " + "\"customer\".\"state_province\" as \"c1\", " + "\"customer\".\"city\" as \"c2\", " + "\"customer\".\"customer_id\" as \"c3\", \"fname\" || ' ' || \"lname\" as \"c4\", " + "\"fname\" || ' ' || \"lname\" as \"c5\", \"customer\".\"gender\" as \"c6\", " + "\"customer\".\"marital_status\" as \"c7\", " + "\"customer\".\"education\" as \"c8\", \"customer\".\"yearly_income\" as \"c9\" " + "from \"customer\" \"customer\", \"sales_fact_1997\" \"sales_fact_1997\" " + "where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\" " + "and (\"customer\".\"gender\" = 'M') " + "group by \"customer\".\"country\", \"customer\".\"state_province\", " + "\"customer\".\"city\", \"customer\".\"customer_id\", \"fname\" || ' ' || \"lname\", " + "\"customer\".\"gender\", \"customer\".\"marital_status\", \"customer\".\"education\", " + "\"customer\".\"yearly_income\" " + "order by \"customer\".\"country\" ASC NULLS LAST, " + "\"customer\".\"state_province\" ASC NULLS LAST, \"customer\".\"city\" ASC NULLS LAST, " + "\"fname\" || ' ' || \"lname\" ASC NULLS LAST"; SqlPattern oraclePattern = new SqlPattern(Dialect.DatabaseProduct.ORACLE, sql, sql.length()); assertQuerySql(mdx1, new SqlPattern[]{oraclePattern}); assertQuerySql(mdx2, new SqlPattern[]{oraclePattern}); } // ~ ====== Helper methods ================================================= private void checkNotNative(String mdx) { final String mdx2 = removeNativize(mdx); final Result result = getTestContext().executeQuery(mdx2); checkNotNative(mdx, result); } private void checkNative(String mdx) { final String mdx2 = removeNativize(mdx); final Result result = getTestContext().executeQuery(mdx2); checkNative(mdx, result); } private static String removeNativize(String mdx) { String mdxWithoutNativize = mdx.replaceAll("(?i)NativizeSet", ""); assertFalse( "Query does use NativizeSet", mdx.equals(mdxWithoutNativize)); return mdxWithoutNativize; } private void assertQueryIsReWritten( final String query, final String expectedQuery) { assertQueryIsReWritten(getTestContext(), query, expectedQuery); } private void assertQueryIsReWritten( TestContext testContext, final String query, final String expectedQuery) { final RolapConnection connection = (RolapConnection) testContext.getConnection(); String actualOutput = Locus.execute( connection, NativizeSetFunDefTest.class.getName(), new Locus.Action() { public String execute() { return connection.parseQuery(query).toString(); } } ); if (!Util.nl.equals("\n")) { actualOutput = actualOutput.replace(Util.nl, "\n"); } assertEquals(expectedQuery, actualOutput); } } // End NativizeSetFunDefTest.java mondrian-3.4.1/testsrc/main/mondrian/olap/fun/FunctionTest.java0000644000175000017500000171117211735330606024475 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.olap.*; import mondrian.resource.MondrianResource; import mondrian.test.FoodMartTestCase; import mondrian.test.TestContext; import mondrian.udf.*; import mondrian.util.Bug; import junit.framework.Assert; import junit.framework.ComparisonFailure; import org.apache.log4j.Logger; import org.eigenbase.xom.StringEscaper; import java.io.*; import java.util.*; /** * FunctionTest tests the functions defined in * {@link BuiltinFunTable}. * * @author gjohnson */ public class FunctionTest extends FoodMartTestCase { private static final Logger LOGGER = Logger.getLogger(FunctionTest.class); private static final String months = "[Time].[1997].[Q1].[1]\n" + "[Time].[1997].[Q1].[2]\n" + "[Time].[1997].[Q1].[3]\n" + "[Time].[1997].[Q2].[4]\n" + "[Time].[1997].[Q2].[5]\n" + "[Time].[1997].[Q2].[6]\n" + "[Time].[1997].[Q3].[7]\n" + "[Time].[1997].[Q3].[8]\n" + "[Time].[1997].[Q3].[9]\n" + "[Time].[1997].[Q4].[10]\n" + "[Time].[1997].[Q4].[11]\n" + "[Time].[1997].[Q4].[12]"; private static final String quarters = "[Time].[1997].[Q1]\n" + "[Time].[1997].[Q2]\n" + "[Time].[1997].[Q3]\n" + "[Time].[1997].[Q4]"; private static final String year1997 = "[Time].[1997]"; private static final String hierarchized1997 = year1997 + "\n" + "[Time].[1997].[Q1]\n" + "[Time].[1997].[Q1].[1]\n" + "[Time].[1997].[Q1].[2]\n" + "[Time].[1997].[Q1].[3]\n" + "[Time].[1997].[Q2]\n" + "[Time].[1997].[Q2].[4]\n" + "[Time].[1997].[Q2].[5]\n" + "[Time].[1997].[Q2].[6]\n" + "[Time].[1997].[Q3]\n" + "[Time].[1997].[Q3].[7]\n" + "[Time].[1997].[Q3].[8]\n" + "[Time].[1997].[Q3].[9]\n" + "[Time].[1997].[Q4]\n" + "[Time].[1997].[Q4].[10]\n" + "[Time].[1997].[Q4].[11]\n" + "[Time].[1997].[Q4].[12]"; private static final String NullNumericExpr = " ([Measures].[Unit Sales]," + " [Customers].[All Customers].[USA].[CA].[Bellflower], " + " [Product].[All Products].[Drink].[Alcoholic Beverages]." + "[Beer and Wine].[Beer].[Good].[Good Imported Beer])"; private static final String TimeWeekly = MondrianProperties.instance().SsasCompatibleNaming.get() ? "[Time].[Weekly]" : "[Time.Weekly]"; // ~ Constructors ---------------------------------------------------------- /** * Creates a FunctionTest. */ public FunctionTest() { } /** * Creates a FuncionTest with an explicit name. * * @param s Test name */ public FunctionTest(String s) { super(s); } // ~ Methods --------------------------------------------------------------- // ~ Test methods ---------------------------------------------------------- /** * Tests that Integeer.MIN_VALUE(-2147483648) does not cause NPE. */ public void testParallelPeriodMinValue() { executeQuery( "with " + "member [measures].[foo] as " + "'([Measures].[unit sales]," + "ParallelPeriod([Time].[Quarter], -2147483648))' " + "select " + "[measures].[foo] on columns, " + "[time].[1997].children on rows " + "from [sales]"); } /** * Tests that Integeer.MIN_VALUE(-2147483648) in Lag is handled correctly. */ public void testLagMinValue() { executeQuery( "with " + "member [measures].[foo] as " + "'([Measures].[unit sales], [Time].[1997].[Q1].Lag(-2147483648))' " + "select " + "[measures].[foo] on columns, " + "[time].[1997].children on rows " + "from [sales]"); } /* * Tests that ParallelPeriod with Aggregate function works */ public void testParallelPeriodWithSlicer() { assertQueryReturns( "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Time],[*BASE_MEMBERS_Product])' " + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0], [Measures].[*FORMATTED_MEASURE_1]}' " + "Set [*BASE_MEMBERS_Time] as '{[Time].[1997].[Q2].[6]}' " + "Set [*NATIVE_MEMBERS_Time] as 'Generate([*NATIVE_CJ_SET], {[Time].[Time].CurrentMember})' " + "Set [*BASE_MEMBERS_Product] as '{[Product].[All Products].[Drink],[Product].[All Products].[Food]}' " + "Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' " + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Customer Count]', FORMAT_STRING = '#,##0', SOLVE_ORDER=400 " + "Member [Measures].[*FORMATTED_MEASURE_1] as " + "'([Measures].[Customer Count], ParallelPeriod([Time].[Quarter], 1, [Time].[Time].currentMember))', FORMAT_STRING = '#,##0', SOLVE_ORDER=-200 " + "Member [Product].[*FILTER_MEMBER] as 'Aggregate ([*NATIVE_MEMBERS_Product])', SOLVE_ORDER=-300 " + "Select " + "[*BASE_MEMBERS_Measures] on columns, Non Empty Generate([*NATIVE_CJ_SET], {([Time].[Time].CurrentMember)}) on rows " + "From [Sales] " + "Where ([Product].[*FILTER_MEMBER])", "Axis #0:\n" + "{[Product].[*FILTER_MEMBER]}\n" + "Axis #1:\n" + "{[Measures].[*FORMATTED_MEASURE_0]}\n" + "{[Measures].[*FORMATTED_MEASURE_1]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q2].[6]}\n" + "Row #0: 1,314\n" + "Row #0: 1,447\n"); } public void testParallelperiodOnLevelsString() { assertQueryReturns( "with member Measures.[Prev Unit Sales] as 'parallelperiod(Levels(\"[Time].[Month]\"))'\n" + "select {[Measures].[Unit Sales], Measures.[Prev Unit Sales]} ON COLUMNS,\n" + "[Gender].members ON ROWS\n" + "from [Sales]\n" + "where [Time].[1997].[Q2].[5]", "Axis #0:\n" + "{[Time].[1997].[Q2].[5]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Prev Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 21,081\n" + "Row #0: 20,179\n" + "Row #1: 10,536\n" + "Row #1: 9,990\n" + "Row #2: 10,545\n" + "Row #2: 10,189\n"); } public void testParallelperiodOnStrToMember() { assertQueryReturns( "with member Measures.[Prev Unit Sales] as 'parallelperiod(strToMember(\"[Time].[1997].[Q2]\"))'\n" + "select {[Measures].[Unit Sales], Measures.[Prev Unit Sales]} ON COLUMNS,\n" + "[Gender].members ON ROWS\n" + "from [Sales]\n" + "where [Time].[1997].[Q2].[5]", "Axis #0:\n" + "{[Time].[1997].[Q2].[5]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Prev Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 21,081\n" + "Row #0: 20,957\n" + "Row #1: 10,536\n" + "Row #1: 10,266\n" + "Row #2: 10,545\n" + "Row #2: 10,691\n"); assertQueryThrows( "with member Measures.[Prev Unit Sales] as 'parallelperiod(strToMember(\"[Time].[Quarter]\"))'\n" + "select {[Measures].[Unit Sales], Measures.[Prev Unit Sales]} ON COLUMNS,\n" + "[Gender].members ON ROWS\n" + "from [Sales]\n" + "where [Time].[1997].[Q2].[5]", "Cannot find MDX member '[Time].[Quarter]'. Make sure it is indeed a member and not a level or a hierarchy."); } public void testNumericLiteral() { assertExprReturns("2", "2"); if (false) { // The test is currently broken because the value 2.5 is formatted // as "2". TODO: better default format string assertExprReturns("2.5", "2.5"); } assertExprReturns("-10.0", "-10"); getTestContext().assertExprDependsOn("1.5", "{}"); } public void testStringLiteral() { // single-quoted string if (false) { // TODO: enhance parser so that you can include a quoted string // inside a WITH MEMBER clause assertExprReturns("'foobar'", "foobar"); } // double-quoted string assertExprReturns("\"foobar\"", "foobar"); // literals don't depend on any dimensions getTestContext().assertExprDependsOn("\"foobar\"", "{}"); } public void testDimensionHierarchy() { assertExprReturns("[Time].Dimension.Name", "Time"); } public void testLevelDimension() { assertExprReturns("[Time].[Year].Dimension.UniqueName", "[Time]"); } public void testMemberDimension() { assertExprReturns("[Time].[1997].[Q2].Dimension.UniqueName", "[Time]"); } public void testDimensionsNumeric() { getTestContext().assertExprDependsOn("Dimensions(2).Name", "{}"); getTestContext().assertMemberExprDependsOn( "Dimensions(3).CurrentMember", TestContext.allHiers()); assertExprReturns("Dimensions(2).Name", "Store Size in SQFT"); // bug 1426134 -- Dimensions(0) throws 'Index '0' out of bounds' assertExprReturns("Dimensions(0).Name", "Measures"); assertExprThrows("Dimensions(-1).Name", "Index '-1' out of bounds"); assertExprThrows("Dimensions(100).Name", "Index '100' out of bounds"); // Since Dimensions returns a Hierarchy, can apply CurrentMember. assertAxisReturns( "Dimensions(3).CurrentMember", "[Store Type].[All Store Types]"); } public void testDimensionsString() { getTestContext().assertExprDependsOn( "Dimensions(\"foo\").UniqueName", "{}"); getTestContext().assertMemberExprDependsOn( "Dimensions(\"foo\").CurrentMember", TestContext.allHiers()); assertExprReturns("Dimensions(\"Store\").UniqueName", "[Store]"); // Since Dimensions returns a Hierarchy, can apply Children. assertAxisReturns( "Dimensions(\"Store\").Children", "[Store].[Canada]\n" + "[Store].[Mexico]\n" + "[Store].[USA]"); } public void testDimensionsDepends() { final String expression = "Crossjoin(" + "{Dimensions(\"Measures\").CurrentMember.Hierarchy.CurrentMember}, " + "{Dimensions(\"Product\")})"; assertAxisReturns( expression, "{[Measures].[Unit Sales], [Product].[All Products]}"); getTestContext().assertSetExprDependsOn( expression, TestContext.allHiers()); } public void testTime() { assertExprReturns( "[Time].[1997].[Q1].[1].Hierarchy.UniqueName", "[Time]"); } public void testBasic9() { assertExprReturns( "[Gender].[All Gender].[F].Hierarchy.UniqueName", "[Gender]"); } public void testFirstInLevel9() { assertExprReturns( "[Education Level].[All Education Levels].[Bachelors Degree].Hierarchy.UniqueName", "[Education Level]"); } public void testHierarchyAll() { assertExprReturns( "[Gender].[All Gender].Hierarchy.UniqueName", "[Gender]"); } public void testNullMember() { // MSAS fails here, but Mondrian doesn't. assertExprReturns( "[Gender].[All Gender].Parent.Level.UniqueName", "[Gender].[(All)]"); // MSAS fails here, but Mondrian doesn't. assertExprReturns( "[Gender].[All Gender].Parent.Hierarchy.UniqueName", "[Gender]"); // MSAS fails here, but Mondrian doesn't. assertExprReturns( "[Gender].[All Gender].Parent.Dimension.UniqueName", "[Gender]"); // MSAS succeeds too assertExprReturns( "[Gender].[All Gender].Parent.Children.Count", "0"); if (isDefaultNullMemberRepresentation()) { // MSAS returns "" here. assertExprReturns( "[Gender].[All Gender].Parent.UniqueName", "[Gender].[#null]"); // MSAS returns "" here. assertExprReturns( "[Gender].[All Gender].Parent.Name", "#null"); } } /** * Tests use of NULL literal to generate a null cell value. * Testcase is from bug 1440344. */ public void testNullValue() { assertQueryReturns( "with member [Measures].[X] as 'IIF([Measures].[Store Sales]>10000,[Measures].[Store Sales],Null)'\n" + "select\n" + "{[Measures].[X]} on columns,\n" + "{[Product].[Product Department].members} on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[X]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Drink].[Dairy]}\n" + "{[Product].[Food].[Baked Goods]}\n" + "{[Product].[Food].[Baking Goods]}\n" + "{[Product].[Food].[Breakfast Foods]}\n" + "{[Product].[Food].[Canned Foods]}\n" + "{[Product].[Food].[Canned Products]}\n" + "{[Product].[Food].[Dairy]}\n" + "{[Product].[Food].[Deli]}\n" + "{[Product].[Food].[Eggs]}\n" + "{[Product].[Food].[Frozen Foods]}\n" + "{[Product].[Food].[Meat]}\n" + "{[Product].[Food].[Produce]}\n" + "{[Product].[Food].[Seafood]}\n" + "{[Product].[Food].[Snack Foods]}\n" + "{[Product].[Food].[Snacks]}\n" + "{[Product].[Food].[Starchy Foods]}\n" + "{[Product].[Non-Consumable].[Carousel]}\n" + "{[Product].[Non-Consumable].[Checkout]}\n" + "{[Product].[Non-Consumable].[Health and Hygiene]}\n" + "{[Product].[Non-Consumable].[Household]}\n" + "{[Product].[Non-Consumable].[Periodicals]}\n" + "Row #0: 14,029.08\n" + "Row #1: 27,748.53\n" + "Row #2: \n" + "Row #3: 16,455.43\n" + "Row #4: 38,670.41\n" + "Row #5: \n" + "Row #6: 39,774.34\n" + "Row #7: \n" + "Row #8: 30,508.85\n" + "Row #9: 25,318.93\n" + "Row #10: \n" + "Row #11: 55,207.50\n" + "Row #12: \n" + "Row #13: 82,248.42\n" + "Row #14: \n" + "Row #15: 67,609.82\n" + "Row #16: 14,550.05\n" + "Row #17: 11,756.07\n" + "Row #18: \n" + "Row #19: \n" + "Row #20: 32,571.86\n" + "Row #21: 60,469.89\n" + "Row #22: \n"); } public void testNullInMultiplication() { assertExprReturns("NULL*1", ""); assertExprReturns("1*NULL", ""); assertExprReturns("NULL*NULL", ""); } public void testNullInAddition() { assertExprReturns("1+NULL", "1"); assertExprReturns("NULL+1", "1"); } public void testNullInSubtraction() { assertExprReturns("1-NULL", "1"); assertExprReturns("NULL-1", "-1"); } public void testMemberLevel() { assertExprReturns( "[Time].[1997].[Q1].[1].Level.UniqueName", "[Time].[Month]"); } public void testLevelsNumeric() { assertExprReturns("[Time].[Time].Levels(2).Name", "Month"); assertExprReturns("[Time].[Time].Levels(0).Name", "Year"); assertExprReturns("[Product].Levels(0).Name", "(All)"); } public void testLevelsTooSmall() { assertExprThrows( "[Time].[Time].Levels(-1).Name", "Index '-1' out of bounds"); } public void testLevelsTooLarge() { assertExprThrows( "[Time].[Time].Levels(8).Name", "Index '8' out of bounds"); } public void testHierarchyLevelsString() { assertExprReturns( "[Time].[Time].Levels(\"Year\").UniqueName", "[Time].[Year]"); } public void testHierarchyLevelsStringFail() { assertExprThrows( "[Time].[Time].Levels(\"nonexistent\").UniqueName", "Level 'nonexistent' not found in hierarchy '[Time]'"); } public void testLevelsString() { assertExprReturns( "Levels(\"[Time].[Year]\").UniqueName", "[Time].[Year]"); } public void testLevelsStringFail() { assertExprThrows( "Levels(\"nonexistent\").UniqueName", "Level 'nonexistent' not found"); } public void testIsEmptyQuery() { String desiredResult = "Axis #0:\n" + "{[Time].[1997].[Q4].[12], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth].[Portsmouth Imported Beer], [Measures].[Foo]}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "{[Store].[USA].[WA].[Bremerton]}\n" + "{[Store].[USA].[WA].[Seattle]}\n" + "{[Store].[USA].[WA].[Spokane]}\n" + "{[Store].[USA].[WA].[Tacoma]}\n" + "{[Store].[USA].[WA].[Walla Walla]}\n" + "{[Store].[USA].[WA].[Yakima]}\n" + "Row #0: 5\n" + "Row #0: 5\n" + "Row #0: 2\n" + "Row #0: 5\n" + "Row #0: 11\n" + "Row #0: 5\n" + "Row #0: 4\n"; assertQueryReturns( "WITH MEMBER [Measures].[Foo] AS 'Iif(IsEmpty([Measures].[Unit Sales]), 5, [Measures].[Unit Sales])'\n" + "SELECT {[Store].[USA].[WA].children} on columns\n" + "FROM Sales\n" + "WHERE ([Time].[1997].[Q4].[12],\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth].[Portsmouth Imported Beer],\n" + " [Measures].[Foo])", desiredResult); assertQueryReturns( "WITH MEMBER [Measures].[Foo] AS 'Iif([Measures].[Unit Sales] IS EMPTY, 5, [Measures].[Unit Sales])'\n" + "SELECT {[Store].[USA].[WA].children} on columns\n" + "FROM Sales\n" + "WHERE ([Time].[1997].[Q4].[12],\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth].[Portsmouth Imported Beer],\n" + " [Measures].[Foo])", desiredResult); assertQueryReturns( "WITH MEMBER [Measures].[Foo] AS 'Iif([Measures].[Bar] IS EMPTY, 1, [Measures].[Bar])'\n" + "MEMBER [Measures].[Bar] AS 'CAST(\"42\" AS INTEGER)'\n" + "SELECT {[Measures].[Unit Sales], [Measures].[Foo]} on columns\n" + "FROM Sales\n" + "WHERE ([Time].[1998].[Q4].[12])", "Axis #0:\n" + "{[Time].[1998].[Q4].[12]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Foo]}\n" + "Row #0: \n" + "Row #0: 42\n"); } public void testIsEmptyWithAggregate() { assertQueryReturns( "WITH MEMBER [gender].[foo] AS 'isEmpty(Aggregate({[Gender].m}))' " + "SELECT {Gender.foo} on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[foo]}\n" + "Row #0: false\n"); } public void testIsEmpty() { assertBooleanExprReturns("[Gender].[All Gender].Parent IS NULL", true); // Any functions that return a member from parameters that // include a member and that member is NULL also give a NULL. // Not a runtime exception. assertBooleanExprReturns( "[Gender].CurrentMember.Parent.NextMember IS NULL", true); if (!Bug.BugMondrian207Fixed) { return; } // When resolving a tuple's value in the cube, if there is // at least one NULL member in the tuple should return a // NULL cell value. assertBooleanExprReturns( "IsEmpty(([Time].currentMember.Parent, [Measures].[Unit Sales]))", false); assertBooleanExprReturns( "IsEmpty(([Time].currentMember, [Measures].[Unit Sales]))", false); // EMPTY refers to a genuine cell value that exists in the cube space, // and has no NULL members in the tuple, // but has no fact data at that crossing, // so it evaluates to EMPTY as a cell value. assertBooleanExprReturns( "IsEmpty(\n" + " ([Time].[1997].[Q4].[12],\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth].[Portsmouth Imported Beer],\n" + " [Store].[All Stores].[USA].[WA].[Bellingham]))", true); assertBooleanExprReturns( "IsEmpty(\n" + " ([Time].[1997].[Q4].[11],\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth].[Portsmouth Imported Beer],\n" + " [Store].[All Stores].[USA].[WA].[Bellingham]))", false); // The empty set is neither EMPTY nor NULL. // should give 0 as a result, not NULL and not EMPTY. assertQueryReturns( "WITH SET [empty set] AS '{}'\n" + " MEMBER [Measures].[Set Size] AS 'Count([empty set])'\n" + " MEMBER [Measures].[Set Size Is Empty] AS 'CASE WHEN IsEmpty([Measures].[Set Size]) THEN 1 ELSE 0 END '\n" + "SELECT [Measures].[Set Size] on columns", ""); assertQueryReturns( "WITH SET [empty set] AS '{}'\n" + "WITH MEMBER [Measures].[Set Size] AS 'Count([empty set])'\n" + "SELECT [Measures].[Set Size] on columns", ""); // Run time errors are BAD things. They should not occur // in almost all cases. In fact there should be no // logically formed MDX that generates them. An ERROR // value in a cell though is perfectly legal - e.g. a // divide by 0. // E.g. String foo = "WITH [Measures].[Ratio This Period to Previous] as\n" + "'([Measures].[Sales],[Time].CurrentMember/([Measures].[Sales],[Time].CurrentMember.PrevMember)'\n" + "SELECT [Measures].[Ratio This Period to Previous] ON COLUMNS,\n" + "[Time].Members ON ROWS\n" + "FROM ..."; // For the [Time].[All Time] row as well as the first // year, first month etc, the PrevMember will evaluate to // NULL, the tuple will evaluate to NULL and the division // will implicitly convert the NULL to 0 and then evaluate // to an ERROR value due to a divide by 0. // This leads to another point: NULL and EMPTY values get // implicitly converted to 0 when treated as numeric // values for division and multiplication but for addition // and subtraction, NULL is treated as NULL (5+NULL yields // NULL). // I have no idea about how EMPTY works. I.e. is does // 5+EMPTY yield 5 or EMPTY or NULL or what? // E.g. String foo2 = "WITH MEMBER [Measures].[5 plus empty] AS\n" + "'5+([Product].[All Products].[Ski boots],[Geography].[All Geography].[Hawaii])'\n" + "SELECT [Measures].[5 plus empty] ON COLUMNS\n" + "FROM ..."; // Does this yield EMPTY, 5, NULL or ERROR? // Lastly, IS NULL and IS EMPTY are both legal and // distinct. <> IS {<> | NULL} and // <> IS EMPTY. // E.g. // a) [Time].CurrentMember.Parent IS [Time].[Year].[2004] // is also a perfectly legal expression and better than // [Time].CurrentMember.Parent.Name="2004". // b) ([Measures].[Sales],[Time].FirstSibling) IS EMPTY is // a legal expression. // Microsoft's site says that the EMPTY value participates in 3 value // logic e.g. TRUE AND EMPTY gives EMPTY, FALSE AND EMPTY gives FALSE. // todo: test for this } public void testQueryWithoutValidMeasure() { assertQueryReturns( "with\n" + "member measures.[without VM] as ' [measures].[unit sales] '\n" + "select {measures.[without VM] } on 0,\n" + "[Warehouse].[Country].members on 1 from [warehouse and sales]\n", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[without VM]}\n" + "Axis #2:\n" + "{[Warehouse].[Canada]}\n" + "{[Warehouse].[Mexico]}\n" + "{[Warehouse].[USA]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: \n"); } /** Tests the ValidMeasure function. */ public void testValidMeasure() { assertQueryReturns( "with\n" + "member measures.[with VM] as 'validmeasure([measures].[unit sales])'\n" + "select { measures.[with VM]} on 0,\n" + "[Warehouse].[Country].members on 1 from [warehouse and sales]\n", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[with VM]}\n" + "Axis #2:\n" + "{[Warehouse].[Canada]}\n" + "{[Warehouse].[Mexico]}\n" + "{[Warehouse].[USA]}\n" + "Row #0: 266,773\n" + "Row #1: 266,773\n" + "Row #2: 266,773\n"); } public void _testValidMeasureNonEmpty() { // Note that [with VM2] is NULL where it needs to be - and therefore // does not prevent NON EMPTY from eliminating empty rows. assertQueryReturns( "with set [Foo] as ' Crossjoin({[Time].Children}, {[Measures].[Warehouse Sales]}) '\n" + " member [Measures].[with VM] as 'ValidMeasure([Measures].[Unit Sales])'\n" + " member [Measures].[with VM2] as 'Iif(Count(Filter([Foo], not isempty([Measures].CurrentMember))) > 0, ValidMeasure([Measures].[Unit Sales]), NULL)'\n" + "select NON EMPTY Crossjoin({[Time].Children}, {[Measures].[with VM2], [Measures].[Warehouse Sales]}) ON COLUMNS,\n" + " NON EMPTY {[Warehouse].[All Warehouses].[USA].[WA].Children} ON ROWS\n" + "from [Warehouse and Sales]\n" + "where [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Light Beer]", "Axis #0:\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Light Beer]}\n" + "Axis #1:\n" + "{[Time].[1997].[Q1], [Measures].[with VM2]}\n" + "{[Time].[1997].[Q1], [Measures].[Warehouse Sales]}\n" + "{[Time].[1997].[Q2], [Measures].[with VM2]}\n" + "{[Time].[1997].[Q2], [Measures].[Warehouse Sales]}\n" + "{[Time].[1997].[Q3], [Measures].[with VM2]}\n" + "{[Time].[1997].[Q4], [Measures].[with VM2]}\n" + "Axis #2:\n" + "{[Warehouse].[USA].[WA].[Seattle]}\n" + "{[Warehouse].[USA].[WA].[Tacoma]}\n" + "{[Warehouse].[USA].[WA].[Yakima]}\n" + "Row #0: 26\n" + "Row #0: 34.793\n" + "Row #0: 25\n" + "Row #0: \n" + "Row #0: 36\n" + "Row #0: 28\n" + "Row #1: 26\n" + "Row #1: \n" + "Row #1: 25\n" + "Row #1: 64.615\n" + "Row #1: 36\n" + "Row #1: 28\n" + "Row #2: 26\n" + "Row #2: 79.657\n" + "Row #2: 25\n" + "Row #2: \n" + "Row #2: 36\n" + "Row #2: 28\n"); } public void testValidMeasureTupleHasAnotherMember() { assertQueryReturns( "with\n" + "member measures.[with VM] as 'validmeasure(([measures].[unit sales],[customers].[all customers]))'\n" + "select { measures.[with VM]} on 0,\n" + "[Warehouse].[Country].members on 1 from [warehouse and sales]\n", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[with VM]}\n" + "Axis #2:\n" + "{[Warehouse].[Canada]}\n" + "{[Warehouse].[Mexico]}\n" + "{[Warehouse].[USA]}\n" + "Row #0: 266,773\n" + "Row #1: 266,773\n" + "Row #2: 266,773\n"); } public void testValidMeasureDepends() { String s12 = TestContext.allHiersExcept("[Measures]"); getTestContext().assertExprDependsOn( "ValidMeasure([Measures].[Unit Sales])", s12); String s11 = TestContext.allHiersExcept("[Measures]", "[Time]"); getTestContext().assertExprDependsOn( "ValidMeasure(([Measures].[Unit Sales], [Time].[1997].[Q1]))", s11); String s1 = TestContext.allHiersExcept("[Measures]"); getTestContext().assertExprDependsOn( "ValidMeasure(([Measures].[Unit Sales], " + "[Time].[Time].CurrentMember.Parent))", s1); } public void testAncestor() { Member member = executeSingletonAxis( "Ancestor([Store].[USA].[CA].[Los Angeles],[Store Country])"); Assert.assertEquals("USA", member.getName()); assertAxisThrows( "Ancestor([Store].[USA].[CA].[Los Angeles],[Promotions].[Promotion Name])", "Error while executing query"); } public void testAncestorNumeric() { Member member = executeSingletonAxis( "Ancestor([Store].[USA].[CA].[Los Angeles],1)"); Assert.assertEquals("CA", member.getName()); member = executeSingletonAxis( "Ancestor([Store].[USA].[CA].[Los Angeles], 0)"); Assert.assertEquals("Los Angeles", member.getName()); final TestContext testContextRagged = getTestContext().withCube("[Sales Ragged]"); member = testContextRagged.executeSingletonAxis( "Ancestor([Store].[All Stores].[Vatican], 1)"); Assert.assertEquals("All Stores", member.getName()); member = testContextRagged.executeSingletonAxis( "Ancestor([Store].[USA].[Washington], 1)"); Assert.assertEquals("USA", member.getName()); // complicated way to say "1". member = testContextRagged.executeSingletonAxis( "Ancestor([Store].[USA].[Washington], 7 * 6 - 41)"); Assert.assertEquals("USA", member.getName()); member = testContextRagged.executeSingletonAxis( "Ancestor([Store].[All Stores].[Vatican], 2)"); Assert.assertNull("Ancestor at 2 must be null", member); member = testContextRagged.executeSingletonAxis( "Ancestor([Store].[All Stores].[Vatican], -5)"); Assert.assertNull("Ancestor at -5 must be null", member); } public void testAncestorHigher() { Member member = executeSingletonAxis( "Ancestor([Store].[USA],[Store].[Store City])"); Assert.assertNull(member); // MSOLAP returns null } public void testAncestorSameLevel() { Member member = executeSingletonAxis( "Ancestor([Store].[Canada],[Store].[Store Country])"); Assert.assertEquals("Canada", member.getName()); } public void testAncestorWrongHierarchy() { // MSOLAP gives error "Formula error - dimensions are not // valid (they do not match) - in the Ancestor function" assertAxisThrows( "Ancestor([Gender].[M],[Store].[Store Country])", "Error while executing query"); } public void testAncestorAllLevel() { Member member = executeSingletonAxis( "Ancestor([Store].[USA].[CA],[Store].Levels(0))"); Assert.assertTrue(member.isAll()); } public void testAncestorWithHiddenParent() { final TestContext testContext = getTestContext().withCube("[Sales Ragged]"); Member member = testContext.executeSingletonAxis( "Ancestor([Store].[All Stores].[Israel].[Haifa], " + "[Store].[Store Country])"); assertNotNull("Member must not be null.", member); Assert.assertEquals("Israel", member.getName()); } public void testAncestorDepends() { getTestContext().assertExprDependsOn( "Ancestor([Store].CurrentMember, [Store].[Store Country]).Name", "{[Store]}"); getTestContext().assertExprDependsOn( "Ancestor([Store].[All Stores].[USA], " + "[Store].CurrentMember.Level).Name", "{[Store]}"); getTestContext().assertExprDependsOn( "Ancestor([Store].[All Stores].[USA], " + "[Store].[Store Country]).Name", "{}"); getTestContext().assertExprDependsOn( "Ancestor([Store].CurrentMember, 2+1).Name", "{[Store]}"); } public void testOrdinal() { final TestContext testContext = getTestContext().withCube("Sales Ragged"); Cell cell = testContext.executeExprRaw( "[Store].[All Stores].[Vatican].ordinal"); assertEquals( "Vatican is at level 1.", 1, ((Number)cell.getValue()).intValue()); cell = testContext.executeExprRaw( "[Store].[All Stores].[USA].[Washington].ordinal"); assertEquals( "Washington is at level 3.", 3, ((Number) cell.getValue()).intValue()); } public void testClosingPeriodNoArgs() { getTestContext().assertMemberExprDependsOn( "ClosingPeriod()", "{[Time]}"); // MSOLAP returns [1997].[Q4], because [Time].CurrentMember = // [1997]. Member member = executeSingletonAxis("ClosingPeriod()"); Assert.assertEquals("[Time].[1997].[Q4]", member.getUniqueName()); } public void testClosingPeriodLevel() { getTestContext().assertMemberExprDependsOn( "ClosingPeriod([Time].[Year])", "{[Time]}"); getTestContext().assertMemberExprDependsOn( "([Measures].[Unit Sales], ClosingPeriod([Time].[Month]))", "{[Time]}"); Member member; member = executeSingletonAxis("ClosingPeriod([Year])"); Assert.assertEquals("[Time].[1997]", member.getUniqueName()); member = executeSingletonAxis("ClosingPeriod([Quarter])"); Assert.assertEquals("[Time].[1997].[Q4]", member.getUniqueName()); member = executeSingletonAxis("ClosingPeriod([Month])"); Assert.assertEquals("[Time].[1997].[Q4].[12]", member.getUniqueName()); assertQueryReturns( "with member [Measures].[Closing Unit Sales] as " + "'([Measures].[Unit Sales], ClosingPeriod([Time].[Month]))'\n" + "select non empty {[Measures].[Closing Unit Sales]} on columns,\n" + " {Descendants([Time].[1997])} on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Closing Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q1].[1]}\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[5]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q3].[7]}\n" + "{[Time].[1997].[Q3].[8]}\n" + "{[Time].[1997].[Q3].[9]}\n" + "{[Time].[1997].[Q4]}\n" + "{[Time].[1997].[Q4].[10]}\n" + "{[Time].[1997].[Q4].[11]}\n" + "{[Time].[1997].[Q4].[12]}\n" + "Row #0: 26,796\n" + "Row #1: 23,706\n" + "Row #2: 21,628\n" + "Row #3: 20,957\n" + "Row #4: 23,706\n" + "Row #5: 21,350\n" + "Row #6: 20,179\n" + "Row #7: 21,081\n" + "Row #8: 21,350\n" + "Row #9: 20,388\n" + "Row #10: 23,763\n" + "Row #11: 21,697\n" + "Row #12: 20,388\n" + "Row #13: 26,796\n" + "Row #14: 19,958\n" + "Row #15: 25,270\n" + "Row #16: 26,796\n"); assertQueryReturns( "with member [Measures].[Closing Unit Sales] as '([Measures].[Unit Sales], ClosingPeriod([Time].[Month]))'\n" + "select {[Measures].[Unit Sales], [Measures].[Closing Unit Sales]} on columns,\n" + " {[Time].[1997], [Time].[1997].[Q1], [Time].[1997].[Q1].[1], [Time].[1997].[Q1].[3], [Time].[1997].[Q4].[12]} on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Closing Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q1].[1]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "{[Time].[1997].[Q4].[12]}\n" + "Row #0: 266,773\n" + "Row #0: 26,796\n" + "Row #1: 66,291\n" + "Row #1: 23,706\n" + "Row #2: 21,628\n" + "Row #2: 21,628\n" + "Row #3: 23,706\n" + "Row #3: 23,706\n" + "Row #4: 26,796\n" + "Row #4: 26,796\n"); } public void testClosingPeriodLevelNotInTimeFails() { assertAxisThrows( "ClosingPeriod([Store].[Store City])", "The and arguments to ClosingPeriod must be from " + "the same hierarchy. The level was from '[Store]' but the member " + "was from '[Time]'"); } public void testClosingPeriodMember() { if (false) { // This test is mistaken. Valid forms are ClosingPeriod() // and ClosingPeriod(, ), but not // ClosingPeriod() Member member = executeSingletonAxis("ClosingPeriod([USA])"); Assert.assertEquals("WA", member.getName()); } } public void testClosingPeriodMemberLeaf() { Member member; if (false) { // This test is mistaken. Valid forms are ClosingPeriod() // and ClosingPeriod(, ), but not // ClosingPeriod() member = executeSingletonAxis( "ClosingPeriod([Time].[1997].[Q3].[8])"); Assert.assertNull(member); } else if (isDefaultNullMemberRepresentation()) { assertQueryReturns( "with member [Measures].[Foo] as ClosingPeriod().uniquename\n" + "select {[Measures].[Foo]} on columns,\n" + " {[Time].[1997],\n" + " [Time].[1997].[Q2],\n" + " [Time].[1997].[Q2].[4]} on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Axis #2:\n" + "{[Time].[1997]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "Row #0: [Time].[1997].[Q4]\n" + "Row #1: [Time].[1997].[Q2].[6]\n" + "Row #2: [Time].[#null]\n" // MSAS returns "" here. + ""); } } public void testClosingPeriod() { getTestContext().assertMemberExprDependsOn( "ClosingPeriod([Time].[Month], [Time].[Time].CurrentMember)", "{[Time]}"); String s1 = TestContext.allHiersExcept("[Measures]"); getTestContext().assertExprDependsOn( "(([Measures].[Store Sales]," + " ClosingPeriod([Time].[Month], [Time].[Time].CurrentMember)) - " + "([Measures].[Store Cost]," + " ClosingPeriod([Time].[Month], [Time].[Time].CurrentMember)))", s1); getTestContext().assertMemberExprDependsOn( "ClosingPeriod([Time].[Month], [Time].[1997].[Q3])", "{}"); assertAxisReturns( "ClosingPeriod([Time].[Year], [Time].[1997].[Q3])", ""); assertAxisReturns( "ClosingPeriod([Time].[Quarter], [Time].[1997].[Q3])", "[Time].[1997].[Q3]"); assertAxisReturns( "ClosingPeriod([Time].[Month], [Time].[1997].[Q3])", "[Time].[1997].[Q3].[9]"); assertAxisReturns( "ClosingPeriod([Time].[Quarter], [Time].[1997])", "[Time].[1997].[Q4]"); assertAxisReturns( "ClosingPeriod([Time].[Year], [Time].[1997])", "[Time].[1997]"); assertAxisReturns( "ClosingPeriod([Time].[Month], [Time].[1997])", "[Time].[1997].[Q4].[12]"); // leaf member assertAxisReturns( "ClosingPeriod([Time].[Year], [Time].[1997].[Q3].[8])", ""); assertAxisReturns( "ClosingPeriod([Time].[Quarter], [Time].[1997].[Q3].[8])", ""); assertAxisReturns( "ClosingPeriod([Time].[Month], [Time].[1997].[Q3].[8])", "[Time].[1997].[Q3].[8]"); // non-Time dimension assertAxisReturns( "ClosingPeriod([Product].[Product Name], [Product].[All Products].[Drink])", "[Product].[Drink].[Dairy].[Dairy].[Milk].[Gorilla].[Gorilla Whole Milk]"); assertAxisReturns( "ClosingPeriod([Product].[Product Family], [Product].[All Products].[Drink])", "[Product].[Drink]"); // 'all' level assertAxisReturns( "ClosingPeriod([Product].[(All)], [Product].[All Products].[Drink])", ""); // ragged getTestContext().withCube("[Sales Ragged]").assertAxisReturns( "ClosingPeriod([Store].[Store City], [Store].[All Stores].[Israel])", "[Store].[Israel].[Israel].[Tel Aviv]"); // Default member is [Time].[1997]. assertAxisReturns( "ClosingPeriod([Time].[Month])", "[Time].[1997].[Q4].[12]"); assertAxisReturns("ClosingPeriod()", "[Time].[1997].[Q4]"); TestContext testContext = getTestContext().withCube("[Sales Ragged]"); testContext.assertAxisReturns( "ClosingPeriod([Store].[Store State], [Store].[All Stores].[Israel])", ""); testContext.assertAxisThrows( "ClosingPeriod([Time].[Year], [Store].[All Stores].[Israel])", "The and arguments to ClosingPeriod must be " + "from the same hierarchy. The level was from '[Time]' but " + "the member was from '[Store]'."); } public void testClosingPeriodBelow() { Member member = executeSingletonAxis( "ClosingPeriod([Quarter],[1997].[Q3].[8])"); Assert.assertNull(member); } public void testCousin1() { Member member = executeSingletonAxis("Cousin([1997].[Q4],[1998])"); Assert.assertEquals("[Time].[1998].[Q4]", member.getUniqueName()); } public void testCousin2() { Member member = executeSingletonAxis( "Cousin([1997].[Q4].[12],[1998].[Q1])"); Assert.assertEquals("[Time].[1998].[Q1].[3]", member.getUniqueName()); } public void testCousinOverrun() { Member member = executeSingletonAxis( "Cousin([Customers].[USA].[CA].[San Jose]," + " [Customers].[USA].[OR])"); // CA has more cities than OR Assert.assertNull(member); } public void testCousinThreeDown() { Member member = executeSingletonAxis( "Cousin([Customers].[USA].[CA].[Berkeley].[Barbara Combs]," + " [Customers].[Mexico])"); // Barbara Combs is the 6th child // of the 4th child (Berkeley) // of the 1st child (CA) // of USA // Annmarie Hill is the 6th child // of the 4th child (Tixapan) // of the 1st child (DF) // of Mexico Assert.assertEquals( "[Customers].[Mexico].[DF].[Tixapan].[Annmarie Hill]", member.getUniqueName()); } public void testCousinSameLevel() { Member member = executeSingletonAxis("Cousin([Gender].[M], [Gender].[F])"); Assert.assertEquals("F", member.getName()); } public void testCousinHigherLevel() { Member member = executeSingletonAxis("Cousin([Time].[1997], [Time].[1998].[Q1])"); Assert.assertNull(member); } public void testCousinWrongHierarchy() { assertAxisThrows( "Cousin([Time].[1997], [Gender].[M])", MondrianResource.instance().CousinHierarchyMismatch.str( "[Time].[1997]", "[Gender].[M]")); } public void testParent() { getTestContext().assertMemberExprDependsOn( "[Gender].Parent", "{[Gender]}"); getTestContext().assertMemberExprDependsOn("[Gender].[M].Parent", "{}"); assertAxisReturns( "{[Store].[USA].[CA].Parent}", "[Store].[USA]"); // root member has null parent assertAxisReturns("{[Store].[All Stores].Parent}", ""); // parent of null member is null assertAxisReturns("{[Store].[All Stores].Parent.Parent}", ""); } public void testParentPC() { final TestContext testContext = getTestContext().withCube("HR"); testContext.assertAxisReturns( "[Employees].Parent", ""); testContext.assertAxisReturns( "[Employees].[Sheri Nowmer].Parent", "[Employees].[All Employees]"); testContext.assertAxisReturns( "[Employees].[Sheri Nowmer].[Derrick Whelply].Parent", "[Employees].[Sheri Nowmer]"); testContext.assertAxisReturns( "[Employees].Members.Item(3)", "[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker]"); testContext.assertAxisReturns( "[Employees].Members.Item(3).Parent", "[Employees].[Sheri Nowmer].[Derrick Whelply]"); testContext.assertAxisReturns( "[Employees].AllMembers.Item(3).Parent", "[Employees].[Sheri Nowmer].[Derrick Whelply]"); // Ascendants() applied to parent-child hierarchy accessed via // .Members testContext.assertAxisReturns( "Ascendants([Employees].Members.Item(73))", "[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Jacqueline Wyllie].[Ralph Mccoy].[Bertha Jameson].[James Bailey]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Jacqueline Wyllie].[Ralph Mccoy].[Bertha Jameson]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Jacqueline Wyllie].[Ralph Mccoy]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Jacqueline Wyllie]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply]\n" + "[Employees].[Sheri Nowmer]\n" + "[Employees].[All Employees]"); } public void testMembers() { // .members assertAxisReturns( "{[Customers].[Country].Members}", "[Customers].[Canada]\n" + "[Customers].[Mexico]\n" + "[Customers].[USA]"); // .members applied to 'all' level assertAxisReturns( "{[Customers].[(All)].Members}", "[Customers].[All Customers]"); // .members applied to measures dimension // Note -- no cube-level calculated members are present assertAxisReturns( "{[Measures].[MeasuresLevel].Members}", "[Measures].[Unit Sales]\n" + "[Measures].[Store Cost]\n" + "[Measures].[Store Sales]\n" + "[Measures].[Sales Count]\n" + "[Measures].[Customer Count]\n" + "[Measures].[Promotion Sales]"); // .members applied to Measures assertAxisReturns( "{[Measures].Members}", "[Measures].[Unit Sales]\n" + "[Measures].[Store Cost]\n" + "[Measures].[Store Sales]\n" + "[Measures].[Sales Count]\n" + "[Measures].[Customer Count]\n" + "[Measures].[Promotion Sales]"); // .members applied to a query with calc measures // Again, no calc measures are returned switch (TestContext.instance().getDialect().getDatabaseProduct()) { case INFOBRIGHT: // Skip this test on Infobright, because [Promotion Sales] is // defined wrong. break; default: assertQueryReturns( "with member [Measures].[Xxx] AS ' [Measures].[Unit Sales] '" + "select {[Measures].members} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Sales Count]}\n" + "{[Measures].[Customer Count]}\n" + "{[Measures].[Promotion Sales]}\n" + "Row #0: 266,773\n" + "Row #0: 225,627.23\n" + "Row #0: 565,238.13\n" + "Row #0: 86,837\n" + "Row #0: 5,581\n" + "Row #0: 151,211.21\n"); } // .members applied to a query with calc measures // Again, no calc measures are returned switch (TestContext.instance().getDialect().getDatabaseProduct()) { case INFOBRIGHT: // Skip this test on Infobright, because [Promotion Sales] is // defined wrong. break; default: assertQueryReturns( "with member [Measures].[Xxx] AS ' [Measures].[Unit Sales] '" + "select {[Measures].[Measures].members} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Sales Count]}\n" + "{[Measures].[Customer Count]}\n" + "{[Measures].[Promotion Sales]}\n" + "Row #0: 266,773\n" + "Row #0: 225,627.23\n" + "Row #0: 565,238.13\n" + "Row #0: 86,837\n" + "Row #0: 5,581\n" + "Row #0: 151,211.21\n"); } } public void testHierarchyMembers() { assertAxisReturns( "Head({[Time.Weekly].Members}, 10)", "[Time].[Weekly].[All Weeklys]\n" + "[Time].[Weekly].[1997]\n" + "[Time].[Weekly].[1997].[1]\n" + "[Time].[Weekly].[1997].[1].[15]\n" + "[Time].[Weekly].[1997].[1].[16]\n" + "[Time].[Weekly].[1997].[1].[17]\n" + "[Time].[Weekly].[1997].[1].[18]\n" + "[Time].[Weekly].[1997].[1].[19]\n" + "[Time].[Weekly].[1997].[1].[20]\n" + "[Time].[Weekly].[1997].[2]"); assertAxisReturns( "Tail({[Time.Weekly].Members}, 5)", "[Time].[Weekly].[1998].[51].[5]\n" + "[Time].[Weekly].[1998].[51].[29]\n" + "[Time].[Weekly].[1998].[51].[30]\n" + "[Time].[Weekly].[1998].[52]\n" + "[Time].[Weekly].[1998].[52].[6]"); } public void testAllMembers() { // .allmembers assertAxisReturns( "{[Customers].[Country].allmembers}", "[Customers].[Canada]\n" + "[Customers].[Mexico]\n" + "[Customers].[USA]"); // .allmembers applied to 'all' level assertAxisReturns( "{[Customers].[(All)].allmembers}", "[Customers].[All Customers]"); // .allmembers applied to measures dimension // Note -- cube-level calculated members ARE present assertAxisReturns( "{[Measures].[MeasuresLevel].allmembers}", "[Measures].[Unit Sales]\n" + "[Measures].[Store Cost]\n" + "[Measures].[Store Sales]\n" + "[Measures].[Sales Count]\n" + "[Measures].[Customer Count]\n" + "[Measures].[Promotion Sales]\n" + "[Measures].[Profit]\n" + "[Measures].[Profit Growth]\n" + "[Measures].[Profit last Period]"); // .allmembers applied to Measures assertAxisReturns( "{[Measures].allmembers}", "[Measures].[Unit Sales]\n" + "[Measures].[Store Cost]\n" + "[Measures].[Store Sales]\n" + "[Measures].[Sales Count]\n" + "[Measures].[Customer Count]\n" + "[Measures].[Promotion Sales]\n" + "[Measures].[Profit]\n" + "[Measures].[Profit Growth]\n" + "[Measures].[Profit last Period]"); // .allmembers applied to a query with calc measures // Calc measures are returned switch (TestContext.instance().getDialect().getDatabaseProduct()) { case INFOBRIGHT: // Skip this test on Infobright, because [Promotion Sales] is // defined wrong. break; default: assertQueryReturns( "with member [Measures].[Xxx] AS ' [Measures].[Unit Sales] '" + "select {[Measures].allmembers} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Sales Count]}\n" + "{[Measures].[Customer Count]}\n" + "{[Measures].[Promotion Sales]}\n" + "{[Measures].[Profit]}\n" + "{[Measures].[Profit Growth]}\n" + "{[Measures].[Profit last Period]}\n" + "{[Measures].[Xxx]}\n" + "Row #0: 266,773\n" + "Row #0: 225,627.23\n" + "Row #0: 565,238.13\n" + "Row #0: 86,837\n" + "Row #0: 5,581\n" + "Row #0: 151,211.21\n" + "Row #0: $339,610.90\n" + "Row #0: 0.0%\n" + "Row #0: $339,610.90\n" + "Row #0: 266,773\n"); } // Calc measure members from schema and from query switch (TestContext.instance().getDialect().getDatabaseProduct()) { case INFOBRIGHT: // Skip this test on Infobright, because [Promotion Sales] is // defined wrong. break; default: assertQueryReturns( "WITH MEMBER [Measures].[Unit to Sales ratio] as\n" + " '[Measures].[Unit Sales] / [Measures].[Store Sales]', FORMAT_STRING='0.0%' " + "SELECT {[Measures].AllMembers} ON COLUMNS," + "non empty({[Store].[Store State].Members}) ON ROWS " + "FROM Sales " + "WHERE ([1997].[Q1])", "Axis #0:\n" + "{[Time].[1997].[Q1]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Sales Count]}\n" + "{[Measures].[Customer Count]}\n" + "{[Measures].[Promotion Sales]}\n" + "{[Measures].[Profit]}\n" + "{[Measures].[Profit Growth]}\n" + "{[Measures].[Profit last Period]}\n" + "{[Measures].[Unit to Sales ratio]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 16,890\n" + "Row #0: 14,431.09\n" + "Row #0: 36,175.20\n" + "Row #0: 5,498\n" + "Row #0: 1,110\n" + "Row #0: 14,447.16\n" + "Row #0: $21,744.11\n" + "Row #0: 0.0%\n" + "Row #0: $21,744.11\n" + "Row #0: 46.7%\n" + "Row #1: 19,287\n" + "Row #1: 16,081.07\n" + "Row #1: 40,170.29\n" + "Row #1: 6,184\n" + "Row #1: 767\n" + "Row #1: 10,829.64\n" + "Row #1: $24,089.22\n" + "Row #1: 0.0%\n" + "Row #1: $24,089.22\n" + "Row #1: 48.0%\n" + "Row #2: 30,114\n" + "Row #2: 25,240.08\n" + "Row #2: 63,282.86\n" + "Row #2: 9,906\n" + "Row #2: 1,104\n" + "Row #2: 18,459.60\n" + "Row #2: $38,042.78\n" + "Row #2: 0.0%\n" + "Row #2: $38,042.78\n" + "Row #2: 47.6%\n"); } // Calc member in query and schema not seen switch (TestContext.instance().getDialect().getDatabaseProduct()) { case INFOBRIGHT: // Skip this test on Infobright, because [Promotion Sales] is // defined wrong. break; default: assertQueryReturns( "WITH MEMBER [Measures].[Unit to Sales ratio] as '[Measures].[Unit Sales] / [Measures].[Store Sales]', FORMAT_STRING='0.0%' " + "SELECT {[Measures].AllMembers} ON COLUMNS," + "non empty({[Store].[Store State].Members}) ON ROWS " + "FROM Sales " + "WHERE ([1997].[Q1])", "Axis #0:\n" + "{[Time].[1997].[Q1]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Sales Count]}\n" + "{[Measures].[Customer Count]}\n" + "{[Measures].[Promotion Sales]}\n" + "{[Measures].[Profit]}\n" + "{[Measures].[Profit Growth]}\n" + "{[Measures].[Profit last Period]}\n" + "{[Measures].[Unit to Sales ratio]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 16,890\n" + "Row #0: 14,431.09\n" + "Row #0: 36,175.20\n" + "Row #0: 5,498\n" + "Row #0: 1,110\n" + "Row #0: 14,447.16\n" + "Row #0: $21,744.11\n" + "Row #0: 0.0%\n" + "Row #0: $21,744.11\n" + "Row #0: 46.7%\n" + "Row #1: 19,287\n" + "Row #1: 16,081.07\n" + "Row #1: 40,170.29\n" + "Row #1: 6,184\n" + "Row #1: 767\n" + "Row #1: 10,829.64\n" + "Row #1: $24,089.22\n" + "Row #1: 0.0%\n" + "Row #1: $24,089.22\n" + "Row #1: 48.0%\n" + "Row #2: 30,114\n" + "Row #2: 25,240.08\n" + "Row #2: 63,282.86\n" + "Row #2: 9,906\n" + "Row #2: 1,104\n" + "Row #2: 18,459.60\n" + "Row #2: $38,042.78\n" + "Row #2: 0.0%\n" + "Row #2: $38,042.78\n" + "Row #2: 47.6%\n"); } // Calc member in query and schema not seen switch (TestContext.instance().getDialect().getDatabaseProduct()) { case INFOBRIGHT: // Skip this test on Infobright, because [Promotion Sales] is // defined wrong. break; default: assertQueryReturns( "WITH MEMBER [Measures].[Unit to Sales ratio] as '[Measures].[Unit Sales] / [Measures].[Store Sales]', FORMAT_STRING='0.0%' " + "SELECT {[Measures].Members} ON COLUMNS," + "non empty({[Store].[Store State].Members}) ON ROWS " + "FROM Sales " + "WHERE ([1997].[Q1])", "Axis #0:\n" + "{[Time].[1997].[Q1]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Sales Count]}\n" + "{[Measures].[Customer Count]}\n" + "{[Measures].[Promotion Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 16,890\n" + "Row #0: 14,431.09\n" + "Row #0: 36,175.20\n" + "Row #0: 5,498\n" + "Row #0: 1,110\n" + "Row #0: 14,447.16\n" + "Row #1: 19,287\n" + "Row #1: 16,081.07\n" + "Row #1: 40,170.29\n" + "Row #1: 6,184\n" + "Row #1: 767\n" + "Row #1: 10,829.64\n" + "Row #2: 30,114\n" + "Row #2: 25,240.08\n" + "Row #2: 63,282.86\n" + "Row #2: 9,906\n" + "Row #2: 1,104\n" + "Row #2: 18,459.60\n"); } // Calc member in dimension based on level assertQueryReturns( "WITH MEMBER [Store].[USA].[CA plus OR] AS 'AGGREGATE({[Store].[USA].[CA], [Store].[USA].[OR]})' " + "SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS," + "non empty({[Store].[Store State].AllMembers}) ON ROWS " + "FROM Sales " + "WHERE ([1997].[Q1])", "Axis #0:\n" + "{[Time].[1997].[Q1]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "{[Store].[USA].[CA plus OR]}\n" + "Row #0: 16,890\n" + "Row #0: 36,175.20\n" + "Row #1: 19,287\n" + "Row #1: 40,170.29\n" + "Row #2: 30,114\n" + "Row #2: 63,282.86\n" + "Row #3: 36,177\n" + "Row #3: 76,345.49\n"); // Calc member in dimension based on level not seen assertQueryReturns( "WITH MEMBER [Store].[USA].[CA plus OR] AS 'AGGREGATE({[Store].[USA].[CA], [Store].[USA].[OR]})' " + "SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS," + "non empty({[Store].[Store Country].AllMembers}) ON ROWS " + "FROM Sales " + "WHERE ([1997].[Q1])", "Axis #0:\n" + "{[Time].[1997].[Q1]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Store].[USA]}\n" + "Row #0: 66,291\n" + "Row #0: 139,628.35\n"); } public void testAddCalculatedMembers() { //---------------------------------------------------- // AddCalculatedMembers: Calc member in dimension based on level // included //---------------------------------------------------- assertQueryReturns( "WITH MEMBER [Store].[USA].[CA plus OR] AS 'AGGREGATE({[Store].[USA].[CA], [Store].[USA].[OR]})' " + "SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS," + "AddCalculatedMembers([Store].[USA].Children) ON ROWS " + "FROM Sales " + "WHERE ([1997].[Q1])", "Axis #0:\n" + "{[Time].[1997].[Q1]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "{[Store].[USA].[CA plus OR]}\n" + "Row #0: 16,890\n" + "Row #0: 36,175.20\n" + "Row #1: 19,287\n" + "Row #1: 40,170.29\n" + "Row #2: 30,114\n" + "Row #2: 63,282.86\n" + "Row #3: 36,177\n" + "Row #3: 76,345.49\n"); //---------------------------------------------------- //Calc member in dimension based on level included //Calc members in measures in schema included //---------------------------------------------------- assertQueryReturns( "WITH MEMBER [Store].[USA].[CA plus OR] AS 'AGGREGATE({[Store].[USA].[CA], [Store].[USA].[OR]})' " + "SELECT AddCalculatedMembers({[Measures].[Unit Sales], [Measures].[Store Sales]}) ON COLUMNS," + "AddCalculatedMembers([Store].[USA].Children) ON ROWS " + "FROM Sales " + "WHERE ([1997].[Q1])", "Axis #0:\n" + "{[Time].[1997].[Q1]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Profit]}\n" + "{[Measures].[Profit last Period]}\n" + "{[Measures].[Profit Growth]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "{[Store].[USA].[CA plus OR]}\n" + "Row #0: 16,890\n" + "Row #0: 36,175.20\n" + "Row #0: $21,744.11\n" + "Row #0: $21,744.11\n" + "Row #0: 0.0%\n" + "Row #1: 19,287\n" + "Row #1: 40,170.29\n" + "Row #1: $24,089.22\n" + "Row #1: $24,089.22\n" + "Row #1: 0.0%\n" + "Row #2: 30,114\n" + "Row #2: 63,282.86\n" + "Row #2: $38,042.78\n" + "Row #2: $38,042.78\n" + "Row #2: 0.0%\n" + "Row #3: 36,177\n" + "Row #3: 76,345.49\n" + "Row #3: $45,833.33\n" + "Row #3: $45,833.33\n" + "Row #3: 0.0%\n"); //---------------------------------------------------- //Two dimensions //---------------------------------------------------- assertQueryReturns( "SELECT AddCalculatedMembers({[Measures].[Unit Sales], [Measures].[Store Sales]}) ON COLUMNS," + "{([Store].[USA].[CA], [Gender].[F])} ON ROWS " + "FROM Sales " + "WHERE ([1997].[Q1])", "Axis #0:\n" + "{[Time].[1997].[Q1]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Profit]}\n" + "{[Measures].[Profit last Period]}\n" + "{[Measures].[Profit Growth]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA], [Gender].[F]}\n" + "Row #0: 8,218\n" + "Row #0: 17,928.37\n" + "Row #0: $10,771.98\n" + "Row #0: $10,771.98\n" + "Row #0: 0.0%\n"); //---------------------------------------------------- //Should throw more than one dimension error //---------------------------------------------------- assertAxisThrows( "AddCalculatedMembers({([Store].[USA].[CA], [Gender].[F])})", "Only single dimension members allowed in set for " + "AddCalculatedMembers"); } public void testStripCalculatedMembers() { assertAxisReturns( "StripCalculatedMembers({[Measures].AllMembers})", "[Measures].[Unit Sales]\n" + "[Measures].[Store Cost]\n" + "[Measures].[Store Sales]\n" + "[Measures].[Sales Count]\n" + "[Measures].[Customer Count]\n" + "[Measures].[Promotion Sales]"); // applied to empty set assertAxisReturns("StripCalculatedMembers({[Gender].Parent})", ""); getTestContext().assertSetExprDependsOn( "StripCalculatedMembers([Customers].CurrentMember.Children)", "{[Customers]}"); //---------------------------------------------------- //Calc members in dimension based on level stripped //Actual members in measures left alone //---------------------------------------------------- assertQueryReturns( "WITH MEMBER [Store].[USA].[CA plus OR] AS " + "'AGGREGATE({[Store].[USA].[CA], [Store].[USA].[OR]})' " + "SELECT StripCalculatedMembers({[Measures].[Unit Sales], " + "[Measures].[Store Sales]}) ON COLUMNS," + "StripCalculatedMembers(" + "AddCalculatedMembers([Store].[USA].Children)) ON ROWS " + "FROM Sales " + "WHERE ([1997].[Q1])", "Axis #0:\n" + "{[Time].[1997].[Q1]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 16,890\n" + "Row #0: 36,175.20\n" + "Row #1: 19,287\n" + "Row #1: 40,170.29\n" + "Row #2: 30,114\n" + "Row #2: 63,282.86\n"); } public void testCurrentMember() { // .CurrentMember assertAxisReturns("[Gender].CurrentMember", "[Gender].[All Gender]"); // .CurrentMember assertAxisReturns( "[Gender].Hierarchy.CurrentMember", "[Gender].[All Gender]"); // .CurrentMember // MSAS doesn't allow this, but Mondrian does: it implicitly casts // level to hierarchy. assertAxisReturns("[Store Name].CurrentMember", "[Store].[All Stores]"); } public void testCurrentMemberDepends() { getTestContext().assertMemberExprDependsOn( "[Gender].CurrentMember", "{[Gender]}"); getTestContext().assertExprDependsOn( "[Gender].[M].Dimension.Name", "{}"); // implicit call to .CurrentMember when dimension is used as a member // expression getTestContext().assertMemberExprDependsOn( "[Gender].[M].Dimension", "{[Gender]}"); getTestContext().assertMemberExprDependsOn( "[Gender].[M].Dimension.CurrentMember", "{[Gender]}"); getTestContext().assertMemberExprDependsOn( "[Gender].[M].Dimension.CurrentMember.Parent", "{[Gender]}"); // [Customers] is short for [Customers].CurrentMember, so // depends upon everything getTestContext().assertExprDependsOn( "[Customers]", TestContext.allHiers()); } public void testCurrentMemberFromSlicer() { Result result = executeQuery( "with member [Measures].[Foo] as '[Gender].CurrentMember.Name'\n" + "select {[Measures].[Foo]} on columns\n" + "from Sales where ([Gender].[F])"); Assert.assertEquals("F", result.getCell(new int[]{0}).getValue()); } public void testCurrentMemberFromDefaultMember() { Result result = executeQuery( "with member [Measures].[Foo] as" + " '[Time].[Time].CurrentMember.Name'\n" + "select {[Measures].[Foo]} on columns\n" + "from Sales"); Assert.assertEquals("1997", result.getCell(new int[]{0}).getValue()); } public void testCurrentMemberMultiHierarchy() { final String hierarchyName = MondrianProperties.instance().SsasCompatibleNaming.get() ? "Weekly" : "Time.Weekly"; final String queryString = "with member [Measures].[Foo] as\n" + " 'IIf(([Time].[Time].CurrentMember.Hierarchy.Name = \"" + hierarchyName + "\"), \n" + "[Measures].[Unit Sales], \n" + "- [Measures].[Unit Sales])'\n" + "select {[Measures].[Unit Sales], [Measures].[Foo]} ON COLUMNS,\n" + " {[Product].[Food].[Dairy]} ON ROWS\n" + "from [Sales]"; Result result = executeQuery( queryString + " where [Time].[1997]"); final int[] coords = {1, 0}; Assert.assertEquals( "-12,885", result.getCell(coords).getFormattedValue()); // As above, but context provided on rows axis as opposed to slicer. final String queryString1 = "with member [Measures].[Foo] as\n" + " 'IIf(([Time].[Time].CurrentMember.Hierarchy.Name = \"" + hierarchyName + "\"), \n" + "[Measures].[Unit Sales], \n" + "- [Measures].[Unit Sales])'\n" + "select {[Measures].[Unit Sales], [Measures].[Foo]} ON COLUMNS,"; final String queryString2 = "from [Sales]\n" + " where [Product].[Food].[Dairy] "; result = executeQuery( queryString1 + " {[Time].[1997]} ON ROWS " + queryString2); Assert.assertEquals( "-12,885", result.getCell(coords).getFormattedValue()); result = executeQuery( queryString + " where [Time.Weekly].[1997]"); Assert.assertEquals( "-12,885", result.getCell(coords).getFormattedValue()); result = executeQuery( queryString1 + " {[Time.Weekly].[1997]} ON ROWS " + queryString2); Assert.assertEquals( "-12,885", result.getCell(coords).getFormattedValue()); } public void testDefaultMember() { // [Time] has no default member and no all, so the default member is // the first member of the first level. Result result = executeQuery( "select {[Time].[Time].DefaultMember} on columns\n" + "from Sales"); Assert.assertEquals( "1997", result.getAxes()[0].getPositions().get(0).get(0).getName()); // [Time].[Weekly] has an all member and no explicit default. result = executeQuery( "select {[Time.Weekly].DefaultMember} on columns\n" + "from Sales"); Assert.assertEquals( MondrianProperties.instance().SsasCompatibleNaming.get() ? "All Weeklys" : "All Time.Weeklys", result.getAxes()[0].getPositions().get(0).get(0).getName()); final String memberUname = MondrianProperties.instance().SsasCompatibleNaming.get() ? "[Time2].[Weekly].[1997].[23]" : "[Time2.Weekly].[1997].[23]"; TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "

\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " "); // In this variant of the schema, Time2.Weekly has an explicit default // member. result = testContext.executeQuery( "select {[Time2.Weekly].DefaultMember} on columns\n" + "from Sales"); Assert.assertEquals( "23", result.getAxes()[0].getPositions().get(0).get(0).getName()); } public void testCurrentMemberFromAxis() { Result result = executeQuery( "with member [Measures].[Foo] as" + " '[Gender].CurrentMember.Name" + " || [Marital Status].CurrentMember.Name'\n" + "select {[Measures].[Foo]} on columns,\n" + " CrossJoin({[Gender].children}," + " {[Marital Status].children}) on rows\n" + "from Sales"); Assert.assertEquals("FM", result.getCell(new int[]{0, 0}).getValue()); } /** * When evaluating a calculated member, MSOLAP regards that * calculated member as the current member of that dimension, so it * cycles in this case. But I disagree; it is the previous current * member, before the calculated member was expanded. */ public void testCurrentMemberInCalcMember() { Result result = executeQuery( "with member [Measures].[Foo] as '[Measures].CurrentMember.Name'\n" + "select {[Measures].[Foo]} on columns\n" + "from Sales"); Assert.assertEquals( "Unit Sales", result.getCell(new int[]{0}).getValue()); } /** * Tests NamedSet.CurrentOrdinal combined with the Order function. */ public void testNamedSetCurrentOrdinalWithOrder() { // The .CurrentOrdinal only works correctly when named sets // are evaluated as iterables, and JDK 1.4 only supports lists. if (Util.Retrowoven) { return; } assertQueryReturns( "with set [Time Regular] as [Time].[Time].Members\n" + " set [Time Reversed] as" + " Order([Time Regular], [Time Regular].CurrentOrdinal, BDESC)\n" + "select [Time Reversed] on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1998].[Q4].[12]}\n" + "{[Time].[1998].[Q4].[11]}\n" + "{[Time].[1998].[Q4].[10]}\n" + "{[Time].[1998].[Q4]}\n" + "{[Time].[1998].[Q3].[9]}\n" + "{[Time].[1998].[Q3].[8]}\n" + "{[Time].[1998].[Q3].[7]}\n" + "{[Time].[1998].[Q3]}\n" + "{[Time].[1998].[Q2].[6]}\n" + "{[Time].[1998].[Q2].[5]}\n" + "{[Time].[1998].[Q2].[4]}\n" + "{[Time].[1998].[Q2]}\n" + "{[Time].[1998].[Q1].[3]}\n" + "{[Time].[1998].[Q1].[2]}\n" + "{[Time].[1998].[Q1].[1]}\n" + "{[Time].[1998].[Q1]}\n" + "{[Time].[1998]}\n" + "{[Time].[1997].[Q4].[12]}\n" + "{[Time].[1997].[Q4].[11]}\n" + "{[Time].[1997].[Q4].[10]}\n" + "{[Time].[1997].[Q4]}\n" + "{[Time].[1997].[Q3].[9]}\n" + "{[Time].[1997].[Q3].[8]}\n" + "{[Time].[1997].[Q3].[7]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q2].[5]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q1].[1]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 26,796\n" + "Row #0: 25,270\n" + "Row #0: 19,958\n" + "Row #0: 72,024\n" + "Row #0: 20,388\n" + "Row #0: 21,697\n" + "Row #0: 23,763\n" + "Row #0: 65,848\n" + "Row #0: 21,350\n" + "Row #0: 21,081\n" + "Row #0: 20,179\n" + "Row #0: 62,610\n" + "Row #0: 23,706\n" + "Row #0: 20,957\n" + "Row #0: 21,628\n" + "Row #0: 66,291\n" + "Row #0: 266,773\n"); } /** * Tests NamedSet.CurrentOrdinal combined with the Generate function. */ public void testNamedSetCurrentOrdinalWithGenerate() { // The .CurrentOrdinal only works correctly when named sets // are evaluated as iterables, and JDK 1.4 only supports lists. if (Util.Retrowoven) { return; } assertQueryReturns( " with set [Time Regular] as [Time].[Time].Members\n" + "set [Every Other Time] as\n" + " Generate(\n" + " [Time Regular],\n" + " {[Time].[Time].Members.Item(\n" + " [Time Regular].CurrentOrdinal * 2)})\n" + "select [Every Other Time] on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "{[Time].[1997].[Q1].[1]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q3].[7]}\n" + "{[Time].[1997].[Q3].[9]}\n" + "{[Time].[1997].[Q4].[10]}\n" + "{[Time].[1997].[Q4].[12]}\n" + "{[Time].[1998].[Q1]}\n" + "{[Time].[1998].[Q1].[2]}\n" + "{[Time].[1998].[Q2]}\n" + "{[Time].[1998].[Q2].[5]}\n" + "{[Time].[1998].[Q3]}\n" + "{[Time].[1998].[Q3].[8]}\n" + "{[Time].[1998].[Q4]}\n" + "{[Time].[1998].[Q4].[11]}\n" + "Row #0: 266,773\n" + "Row #0: 21,628\n" + "Row #0: 23,706\n" + "Row #0: 20,179\n" + "Row #0: 21,350\n" + "Row #0: 23,763\n" + "Row #0: 20,388\n" + "Row #0: 19,958\n" + "Row #0: 26,796\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n"); } public void testNamedSetCurrentOrdinalWithFilter() { // The .CurrentOrdinal only works correctly when named sets // are evaluated as iterables, and JDK 1.4 only supports lists. if (Util.Retrowoven) { return; } assertQueryReturns( "with set [Time Regular] as [Time].[Time].Members\n" + " set [Time Subset] as " + " Filter([Time Regular], [Time Regular].CurrentOrdinal = 3" + " or [Time Regular].CurrentOrdinal = 5)\n" + "select [Time Subset] on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q2]}\n" + "Row #0: 20,957\n" + "Row #0: 62,610\n"); } public void testNamedSetCurrentOrdinalWithCrossjoin() { // TODO: } public void testNamedSetCurrentOrdinalWithNonNamedSetFails() { // a named set wrapped in {...} is not a named set, so CurrentOrdinal // fails assertQueryThrows( "with set [Time Members] as [Time].Members\n" + "member [Measures].[Foo] as ' {[Time Members]}.CurrentOrdinal '\n" + "select {[Measures].[Unit Sales], [Measures].[Foo]} on 0,\n" + " {[Product].Children} on 1\n" + "from [Sales]", "Not a named set"); // as above for Current function assertQueryThrows( "with set [Time Members] as [Time].Members\n" + "member [Measures].[Foo] as ' {[Time Members]}.Current.Name '\n" + "select {[Measures].[Unit Sales], [Measures].[Foo]} on 0,\n" + " {[Product].Children} on 1\n" + "from [Sales]", "Not a named set"); // a set expression is not a named set, so CurrentOrdinal fails assertQueryThrows( "with member [Measures].[Foo] as\n" + " ' Head([Time].Members, 5).CurrentOrdinal '\n" + "select {[Measures].[Unit Sales], [Measures].[Foo]} on 0,\n" + " {[Product].Children} on 1\n" + "from [Sales]", "Not a named set"); // as above for Current function assertQueryThrows( "with member [Measures].[Foo] as\n" + " ' Crossjoin([Time].Members, [Gender].Members).Current.Name '\n" + "select {[Measures].[Unit Sales], [Measures].[Foo]} on 0,\n" + " {[Product].Children} on 1\n" + "from [Sales]", "Not a named set"); } public void testDimensionDefaultMember() { Member member = executeSingletonAxis("[Measures].DefaultMember"); Assert.assertEquals("Unit Sales", member.getName()); } public void testDrilldownLevel() { // Expect all children of USA assertAxisReturns( "DrilldownLevel({[Store].[USA]}, [Store].[Store Country])", "[Store].[USA]\n" + "[Store].[USA].[CA]\n" + "[Store].[USA].[OR]\n" + "[Store].[USA].[WA]"); // Expect same set, because [USA] is already drilled assertAxisReturns( "DrilldownLevel({[Store].[USA], [Store].[USA].[CA]}, [Store].[Store Country])", "[Store].[USA]\n" + "[Store].[USA].[CA]"); // Expect drill, because [USA] isn't already drilled. You can't // drill down on [CA] and get to [USA] assertAxisReturns( "DrilldownLevel({[Store].[USA].[CA],[Store].[USA]}, [Store].[Store Country])", "[Store].[USA].[CA]\n" + "[Store].[USA]\n" + "[Store].[USA].[CA]\n" + "[Store].[USA].[OR]\n" + "[Store].[USA].[WA]"); assertAxisReturns( "DrilldownLevel({[Store].[USA].[CA],[Store].[USA]},, 0)", "[Store].[USA].[CA]\n" + "[Store].[USA].[CA].[Alameda]\n" + "[Store].[USA].[CA].[Beverly Hills]\n" + "[Store].[USA].[CA].[Los Angeles]\n" + "[Store].[USA].[CA].[San Diego]\n" + "[Store].[USA].[CA].[San Francisco]\n" + "[Store].[USA]\n" + "[Store].[USA].[CA]\n" + "[Store].[USA].[OR]\n" + "[Store].[USA].[WA]"); assertAxisReturns( "DrilldownLevel({[Store].[USA].[CA],[Store].[USA]} * {[Gender].Members},, 0)", "{[Store].[USA].[CA], [Gender].[All Gender]}\n" + "{[Store].[USA].[CA].[Alameda], [Gender].[All Gender]}\n" + "{[Store].[USA].[CA].[Beverly Hills], [Gender].[All Gender]}\n" + "{[Store].[USA].[CA].[Los Angeles], [Gender].[All Gender]}\n" + "{[Store].[USA].[CA].[San Diego], [Gender].[All Gender]}\n" + "{[Store].[USA].[CA].[San Francisco], [Gender].[All Gender]}\n" + "{[Store].[USA].[CA], [Gender].[F]}\n" + "{[Store].[USA].[CA].[Alameda], [Gender].[F]}\n" + "{[Store].[USA].[CA].[Beverly Hills], [Gender].[F]}\n" + "{[Store].[USA].[CA].[Los Angeles], [Gender].[F]}\n" + "{[Store].[USA].[CA].[San Diego], [Gender].[F]}\n" + "{[Store].[USA].[CA].[San Francisco], [Gender].[F]}\n" + "{[Store].[USA].[CA], [Gender].[M]}\n" + "{[Store].[USA].[CA].[Alameda], [Gender].[M]}\n" + "{[Store].[USA].[CA].[Beverly Hills], [Gender].[M]}\n" + "{[Store].[USA].[CA].[Los Angeles], [Gender].[M]}\n" + "{[Store].[USA].[CA].[San Diego], [Gender].[M]}\n" + "{[Store].[USA].[CA].[San Francisco], [Gender].[M]}\n" + "{[Store].[USA], [Gender].[All Gender]}\n" + "{[Store].[USA].[CA], [Gender].[All Gender]}\n" + "{[Store].[USA].[OR], [Gender].[All Gender]}\n" + "{[Store].[USA].[WA], [Gender].[All Gender]}\n" + "{[Store].[USA], [Gender].[F]}\n" + "{[Store].[USA].[CA], [Gender].[F]}\n" + "{[Store].[USA].[OR], [Gender].[F]}\n" + "{[Store].[USA].[WA], [Gender].[F]}\n" + "{[Store].[USA], [Gender].[M]}\n" + "{[Store].[USA].[CA], [Gender].[M]}\n" + "{[Store].[USA].[OR], [Gender].[M]}\n" + "{[Store].[USA].[WA], [Gender].[M]}"); assertAxisReturns( "DrilldownLevel({[Store].[USA].[CA],[Store].[USA]} * {[Gender].Members},, 1)", "{[Store].[USA].[CA], [Gender].[All Gender]}\n" + "{[Store].[USA].[CA], [Gender].[F]}\n" + "{[Store].[USA].[CA], [Gender].[M]}\n" + "{[Store].[USA].[CA], [Gender].[F]}\n" + "{[Store].[USA].[CA], [Gender].[M]}\n" + "{[Store].[USA], [Gender].[All Gender]}\n" + "{[Store].[USA], [Gender].[F]}\n" + "{[Store].[USA], [Gender].[M]}\n" + "{[Store].[USA], [Gender].[F]}\n" + "{[Store].[USA], [Gender].[M]}"); } public void testDrilldownLevelTop() { // , , assertAxisReturns( "DrilldownLevelTop({[Store].[USA]}, 2, [Store].[Store Country])", "[Store].[USA]\n" + "[Store].[USA].[WA]\n" + "[Store].[USA].[CA]"); // similarly DrilldownLevelBottom assertAxisReturns( "DrilldownLevelBottom({[Store].[USA]}, 2, [Store].[Store Country])", "[Store].[USA]\n" + "[Store].[USA].[OR]\n" + "[Store].[USA].[CA]"); // , assertAxisReturns( "DrilldownLevelTop({[Store].[USA]}, 2)", "[Store].[USA]\n" + "[Store].[USA].[WA]\n" + "[Store].[USA].[CA]"); // greater than number of children assertAxisReturns( "DrilldownLevelTop({[Store].[USA], [Store].[Canada]}, 4)", "[Store].[USA]\n" + "[Store].[USA].[WA]\n" + "[Store].[USA].[CA]\n" + "[Store].[USA].[OR]\n" + "[Store].[Canada]\n" + "[Store].[Canada].[BC]"); // negative assertAxisReturns( "DrilldownLevelTop({[Store].[USA]}, 2 - 3)", "[Store].[USA]"); // zero assertAxisReturns( "DrilldownLevelTop({[Store].[USA]}, 2 - 2)", "[Store].[USA]"); // null assertAxisReturns( "DrilldownLevelTop({[Store].[USA]}, null)", "[Store].[USA]"); // mixed bag, no level, all expanded assertAxisReturns( "DrilldownLevelTop({[Store].[USA], " + "[Store].[USA].[CA].[San Francisco], " + "[Store].[All Stores], " + "[Store].[Canada].[BC]}, " + "2)", "[Store].[USA]\n" + "[Store].[USA].[WA]\n" + "[Store].[USA].[CA]\n" + "[Store].[USA].[CA].[San Francisco]\n" + "[Store].[USA].[CA].[San Francisco].[Store 14]\n" + "[Store].[All Stores]\n" + "[Store].[USA]\n" + "[Store].[Canada]\n" + "[Store].[Canada].[BC]\n" + "[Store].[Canada].[BC].[Vancouver]\n" + "[Store].[Canada].[BC].[Victoria]"); // mixed bag, only specified level expanded assertAxisReturns( "DrilldownLevelTop({[Store].[USA], " + "[Store].[USA].[CA].[San Francisco], " + "[Store].[All Stores], " + "[Store].[Canada].[BC]}, 2, [Store].[Store City])", "[Store].[USA]\n" + "[Store].[USA].[CA].[San Francisco]\n" + "[Store].[USA].[CA].[San Francisco].[Store 14]\n" + "[Store].[All Stores]\n" + "[Store].[Canada].[BC]"); // bad level assertAxisThrows( "DrilldownLevelTop({[Store].[USA]}, 2, [Customers].[Country])", "Level '[Customers].[Country]' not compatible with " + "member '[Store].[USA]'"); } public void testDrilldownMemberEmptyExpr() { // no level, with expression assertAxisReturns( "DrilldownLevelTop({[Store].[USA]}, 2, , [Measures].[Unit Sales])", "[Store].[USA]\n" + "[Store].[USA].[WA]\n" + "[Store].[USA].[CA]"); // reverse expression assertAxisReturns( "DrilldownLevelTop(" + "{[Store].[USA]}, 2, , - [Measures].[Unit Sales])", "[Store].[USA]\n" + "[Store].[USA].[OR]\n" + "[Store].[USA].[CA]"); } public void testDrilldownMember() { // Expect all children of USA assertAxisReturns( "DrilldownMember({[Store].[USA]}, {[Store].[USA]})", "[Store].[USA]\n" + "[Store].[USA].[CA]\n" + "[Store].[USA].[OR]\n" + "[Store].[USA].[WA]"); // Expect all children of USA.CA and USA.OR assertAxisReturns( "DrilldownMember({[Store].[USA].[CA], [Store].[USA].[OR]}, " + "{[Store].[USA].[CA], [Store].[USA].[OR], [Store].[USA].[WA]})", "[Store].[USA].[CA]\n" + "[Store].[USA].[CA].[Alameda]\n" + "[Store].[USA].[CA].[Beverly Hills]\n" + "[Store].[USA].[CA].[Los Angeles]\n" + "[Store].[USA].[CA].[San Diego]\n" + "[Store].[USA].[CA].[San Francisco]\n" + "[Store].[USA].[OR]\n" + "[Store].[USA].[OR].[Portland]\n" + "[Store].[USA].[OR].[Salem]"); // Second set is empty assertAxisReturns( "DrilldownMember({[Store].[USA]}, {})", "[Store].[USA]"); // Drill down a leaf member assertAxisReturns( "DrilldownMember({[Store].[All Stores].[USA].[CA].[San Francisco].[Store 14]}, " + "{[Store].[USA].[CA].[San Francisco].[Store 14]})", "[Store].[USA].[CA].[San Francisco].[Store 14]"); // Complex case with option recursive assertAxisReturns( "DrilldownMember({[Store].[All Stores].[USA]}, " + "{[Store].[All Stores].[USA], [Store].[All Stores].[USA].[CA], " + "[Store].[All Stores].[USA].[CA].[San Diego], [Store].[All Stores].[USA].[WA]}, " + "RECURSIVE)", "[Store].[USA]\n" + "[Store].[USA].[CA]\n" + "[Store].[USA].[CA].[Alameda]\n" + "[Store].[USA].[CA].[Beverly Hills]\n" + "[Store].[USA].[CA].[Los Angeles]\n" + "[Store].[USA].[CA].[San Diego]\n" + "[Store].[USA].[CA].[San Diego].[Store 24]\n" + "[Store].[USA].[CA].[San Francisco]\n" + "[Store].[USA].[OR]\n" + "[Store].[USA].[WA]\n" + "[Store].[USA].[WA].[Bellingham]\n" + "[Store].[USA].[WA].[Bremerton]\n" + "[Store].[USA].[WA].[Seattle]\n" + "[Store].[USA].[WA].[Spokane]\n" + "[Store].[USA].[WA].[Tacoma]\n" + "[Store].[USA].[WA].[Walla Walla]\n" + "[Store].[USA].[WA].[Yakima]"); // Sets of tuples assertAxisReturns( "DrilldownMember({([Store Type].[Supermarket], [Store].[USA])}, {[Store].[USA]})", "{[Store Type].[Supermarket], [Store].[USA]}\n" + "{[Store Type].[Supermarket], [Store].[USA].[CA]}\n" + "{[Store Type].[Supermarket], [Store].[USA].[OR]}\n" + "{[Store Type].[Supermarket], [Store].[USA].[WA]}"); } public void testFirstChildFirstInLevel() { Member member = executeSingletonAxis("[Time].[1997].[Q4].FirstChild"); Assert.assertEquals("10", member.getName()); } public void testFirstChildAll() { Member member = executeSingletonAxis("[Gender].[All Gender].FirstChild"); Assert.assertEquals("F", member.getName()); } public void testFirstChildOfChildless() { Member member = executeSingletonAxis("[Gender].[All Gender].[F].FirstChild"); Assert.assertNull(member); } public void testFirstSiblingFirstInLevel() { Member member = executeSingletonAxis("[Gender].[F].FirstSibling"); Assert.assertEquals("F", member.getName()); } public void testFirstSiblingLastInLevel() { Member member = executeSingletonAxis("[Time].[1997].[Q4].FirstSibling"); Assert.assertEquals("Q1", member.getName()); } public void testFirstSiblingAll() { Member member = executeSingletonAxis("[Gender].[All Gender].FirstSibling"); Assert.assertTrue(member.isAll()); } public void testFirstSiblingRoot() { // The [Measures] hierarchy does not have an 'all' member, so // [Unit Sales] does not have a parent. Member member = executeSingletonAxis("[Measures].[Store Sales].FirstSibling"); Assert.assertEquals("Unit Sales", member.getName()); } public void testFirstSiblingNull() { Member member = executeSingletonAxis("[Gender].[F].FirstChild.FirstSibling"); Assert.assertNull(member); } public void testLag() { Member member = executeSingletonAxis("[Time].[1997].[Q4].[12].Lag(4)"); Assert.assertEquals("8", member.getName()); } public void testLagFirstInLevel() { Member member = executeSingletonAxis("[Gender].[F].Lag(1)"); Assert.assertNull(member); } public void testLagAll() { Member member = executeSingletonAxis("[Gender].DefaultMember.Lag(2)"); Assert.assertNull(member); } public void testLagRoot() { Member member = executeSingletonAxis("[Time].[1998].Lag(1)"); Assert.assertEquals("1997", member.getName()); } public void testLagRootTooFar() { Member member = executeSingletonAxis("[Time].[1998].Lag(2)"); Assert.assertNull(member); } public void testLastChild() { Member member = executeSingletonAxis("[Gender].LastChild"); Assert.assertEquals("M", member.getName()); } public void testLastChildLastInLevel() { Member member = executeSingletonAxis("[Time].[1997].[Q4].LastChild"); Assert.assertEquals("12", member.getName()); } public void testLastChildAll() { Member member = executeSingletonAxis("[Gender].[All Gender].LastChild"); Assert.assertEquals("M", member.getName()); } public void testLastChildOfChildless() { Member member = executeSingletonAxis("[Gender].[M].LastChild"); Assert.assertNull(member); } public void testLastSibling() { Member member = executeSingletonAxis("[Gender].[F].LastSibling"); Assert.assertEquals("M", member.getName()); } public void testLastSiblingFirstInLevel() { Member member = executeSingletonAxis("[Time].[1997].[Q1].LastSibling"); Assert.assertEquals("Q4", member.getName()); } public void testLastSiblingAll() { Member member = executeSingletonAxis("[Gender].[All Gender].LastSibling"); Assert.assertTrue(member.isAll()); } public void testLastSiblingRoot() { // The [Time] hierarchy does not have an 'all' member, so // [1997], [1998] do not have parents. Member member = executeSingletonAxis("[Time].[1998].LastSibling"); Assert.assertEquals("1998", member.getName()); } public void testLastSiblingNull() { Member member = executeSingletonAxis("[Gender].[F].FirstChild.LastSibling"); Assert.assertNull(member); } public void testLead() { Member member = executeSingletonAxis("[Time].[1997].[Q2].[4].Lead(4)"); Assert.assertEquals("8", member.getName()); } public void testLeadNegative() { Member member = executeSingletonAxis("[Gender].[M].Lead(-1)"); Assert.assertEquals("F", member.getName()); } public void testLeadLastInLevel() { Member member = executeSingletonAxis("[Gender].[M].Lead(3)"); Assert.assertNull(member); } public void testLeadNull() { Member member = executeSingletonAxis("[Gender].Parent.Lead(1)"); Assert.assertNull(member); } public void testLeadZero() { Member member = executeSingletonAxis("[Gender].[F].Lead(0)"); Assert.assertEquals("F", member.getName()); } public void testBasic2() { Result result = executeQuery( "select {[Gender].[F].NextMember} ON COLUMNS from Sales"); assertEquals( "M", result.getAxes()[0].getPositions().get(0).get(0).getName()); } public void testFirstInLevel2() { Result result = executeQuery( "select {[Gender].[M].NextMember} ON COLUMNS from Sales"); assertEquals(0, result.getAxes()[0].getPositions().size()); } public void testAll2() { Result result = executeQuery("select {[Gender].PrevMember} ON COLUMNS from Sales"); // previous to [Gender].[All] is null, so no members are returned assertEquals(0, result.getAxes()[0].getPositions().size()); } public void testBasic5() { Result result = executeQuery( "select{ [Product].[All Products].[Drink].Parent} on columns " + "from Sales"); assertEquals( "All Products", result.getAxes()[0].getPositions().get(0).get(0).getName()); } public void testFirstInLevel5() { Result result = executeQuery( "select {[Time].[1997].[Q2].[4].Parent} on columns," + "{[Gender].[M]} on rows from Sales"); assertEquals( "Q2", result.getAxes()[0].getPositions().get(0).get(0).getName()); } public void testAll5() { Result result = executeQuery( "select {[Time].[1997].[Q2].Parent} on columns," + "{[Gender].[M]} on rows from Sales"); // previous to [Gender].[All] is null, so no members are returned assertEquals( "1997", result.getAxes()[0].getPositions().get(0).get(0).getName()); } public void testBasic() { Result result = executeQuery( "select {[Gender].[M].PrevMember} ON COLUMNS from Sales"); assertEquals( "F", result.getAxes()[0].getPositions().get(0).get(0).getName()); } public void testFirstInLevel() { Result result = executeQuery( "select {[Gender].[F].PrevMember} ON COLUMNS from Sales"); assertEquals(0, result.getAxes()[0].getPositions().size()); } public void testAll() { Result result = executeQuery("select {[Gender].PrevMember} ON COLUMNS from Sales"); // previous to [Gender].[All] is null, so no members are returned assertEquals(0, result.getAxes()[0].getPositions().size()); } public void testAggregateDepends() { // Depends on everything except Measures, Gender String s12 = TestContext.allHiersExcept("[Measures]", "[Gender]"); getTestContext().assertExprDependsOn( "([Measures].[Unit Sales], [Gender].[F])", s12); // Depends on everything except Customers, Measures, Gender String s13 = TestContext.allHiersExcept("[Customers]", "[Gender]"); getTestContext().assertExprDependsOn( "Aggregate([Customers].Members, ([Measures].[Unit Sales], [Gender].[F]))", s13); // Depends on everything except Customers String s11 = TestContext.allHiersExcept("[Customers]"); getTestContext().assertExprDependsOn( "Aggregate([Customers].Members)", s11); // Depends on the current member of the Product dimension, even though // [Product].[All Products] is referenced from the expression. String s1 = TestContext.allHiersExcept("[Customers]"); getTestContext().assertExprDependsOn( "Aggregate(Filter([Customers].[City].Members, (([Measures].[Unit Sales] / ([Measures].[Unit Sales], [Product].[All Products])) > 0.1)))", s1); } public void testAggregate() { assertQueryReturns( "WITH MEMBER [Store].[CA plus OR] AS 'AGGREGATE({[Store].[USA].[CA], [Store].[USA].[OR]})'\n" + "SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS,\n" + " {[Store].[USA].[CA], [Store].[USA].[OR], [Store].[CA plus OR]} ON ROWS\n" + "FROM Sales\n" + "WHERE ([1997].[Q1])", "Axis #0:\n" + "{[Time].[1997].[Q1]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[CA plus OR]}\n" + "Row #0: 16,890\n" + "Row #0: 36,175.20\n" + "Row #1: 19,287\n" + "Row #1: 40,170.29\n" + "Row #2: 36,177\n" + "Row #2: 76,345.49\n"); } public void testAggregate2() { assertQueryReturns( "WITH\n" + " Member [Time].[Time].[1st Half Sales] AS 'Aggregate({Time.[1997].[Q1], Time.[1997].[Q2]})'\n" + " Member [Time].[Time].[2nd Half Sales] AS 'Aggregate({Time.[1997].[Q3], Time.[1997].[Q4]})'\n" + " Member [Time].[Time].[Difference] AS 'Time.[2nd Half Sales] - Time.[1st Half Sales]'\n" + "SELECT\n" + " { [Store].[Store State].Members} ON COLUMNS,\n" + " { Time.[1st Half Sales], Time.[2nd Half Sales], Time.[Difference]} ON ROWS\n" + "FROM Sales\n" + "WHERE [Measures].[Store Sales]", "Axis #0:\n" + "{[Measures].[Store Sales]}\n" + "Axis #1:\n" + "{[Store].[Canada].[BC]}\n" + "{[Store].[Mexico].[DF]}\n" + "{[Store].[Mexico].[Guerrero]}\n" + "{[Store].[Mexico].[Jalisco]}\n" + "{[Store].[Mexico].[Veracruz]}\n" + "{[Store].[Mexico].[Yucatan]}\n" + "{[Store].[Mexico].[Zacatecas]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Axis #2:\n" + "{[Time].[1st Half Sales]}\n" + "{[Time].[2nd Half Sales]}\n" + "{[Time].[Difference]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 74,571.95\n" + "Row #0: 71,943.17\n" + "Row #0: 125,779.50\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: 84,595.89\n" + "Row #1: 70,333.90\n" + "Row #1: 138,013.72\n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: 10,023.94\n" + "Row #2: -1,609.27\n" + "Row #2: 12,234.22\n"); } public void testAggregateWithIIF() { assertQueryReturns( "with member store.foo as 'iif(3>1," + "aggregate({[Store].[All Stores].[USA].[OR]})," + "aggregate({[Store].[All Stores].[USA].[CA]}))' " + "select {store.foo} on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[foo]}\n" + "Row #0: 67,659\n"); } public void testAggregate2AllMembers() { assertQueryReturns( "WITH\n" + " Member [Time].[Time].[1st Half Sales] AS 'Aggregate({Time.[1997].[Q1], Time.[1997].[Q2]})'\n" + " Member [Time].[Time].[2nd Half Sales] AS 'Aggregate({Time.[1997].[Q3], Time.[1997].[Q4]})'\n" + " Member [Time].[Time].[Difference] AS 'Time.[2nd Half Sales] - Time.[1st Half Sales]'\n" + "SELECT\n" + " { [Store].[Store State].AllMembers} ON COLUMNS,\n" + " { Time.[1st Half Sales], Time.[2nd Half Sales], Time.[Difference]} ON ROWS\n" + "FROM Sales\n" + "WHERE [Measures].[Store Sales]", "Axis #0:\n" + "{[Measures].[Store Sales]}\n" + "Axis #1:\n" + "{[Store].[Canada].[BC]}\n" + "{[Store].[Mexico].[DF]}\n" + "{[Store].[Mexico].[Guerrero]}\n" + "{[Store].[Mexico].[Jalisco]}\n" + "{[Store].[Mexico].[Veracruz]}\n" + "{[Store].[Mexico].[Yucatan]}\n" + "{[Store].[Mexico].[Zacatecas]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Axis #2:\n" + "{[Time].[1st Half Sales]}\n" + "{[Time].[2nd Half Sales]}\n" + "{[Time].[Difference]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 74,571.95\n" + "Row #0: 71,943.17\n" + "Row #0: 125,779.50\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: 84,595.89\n" + "Row #1: 70,333.90\n" + "Row #1: 138,013.72\n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: 10,023.94\n" + "Row #2: -1,609.27\n" + "Row #2: 12,234.22\n"); } public void testAggregateToSimulateCompoundSlicer() { assertQueryReturns( "WITH MEMBER [Time].[Time].[1997 H1] as 'Aggregate({[Time].[1997].[Q1], [Time].[1997].[Q2]})'\n" + " MEMBER [Education Level].[College or higher] as 'Aggregate({[Education Level].[Bachelors Degree], [Education Level].[Graduate Degree]})'\n" + "SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} on columns,\n" + " {[Product].children} on rows\n" + "FROM [Sales]\n" + "WHERE ([Time].[1997 H1], [Education Level].[College or higher], [Gender].[F])", "Axis #0:\n" + "{[Time].[1997 H1], [Education Level].[College or higher], [Gender].[F]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 1,797\n" + "Row #0: 3,620.49\n" + "Row #1: 15,002\n" + "Row #1: 31,931.88\n" + "Row #2: 3,845\n" + "Row #2: 8,173.22\n"); } /** * Tests behavior where CurrentMember occurs in calculated members and * that member is a set. * *

Mosha discusses this behavior in the article * * Multiselect friendly MDX calculations. * *

Mondrian's behavior is consistent with MSAS 2K: it returns zeroes. * SSAS 2005 returns an error, which can be fixed by reformulating the * calculated members. * * @see mondrian.rolap.FastBatchingCellReaderTest#testAggregateDistinctCount() */ public void testMultiselectCalculations() { assertQueryReturns( "WITH\n" + "MEMBER [Measures].[Declining Stores Count] AS\n" + " ' Count(Filter(Descendants(Store.CurrentMember, Store.[Store Name]), [Store Sales] < ([Store Sales],Time.Time.PrevMember))) '\n" + " MEMBER \n" + " [Store].[XL_QZX] AS 'Aggregate ({ [Store].[All Stores].[USA].[WA] , [Store].[All Stores].[USA].[CA] })' \n" + "SELECT \n" + " NON EMPTY HIERARCHIZE(AddCalculatedMembers({DrillDownLevel({[Product].[All Products]})})) \n" + " DIMENSION PROPERTIES PARENT_UNIQUE_NAME ON COLUMNS \n" + "FROM [Sales] \n" + "WHERE ([Measures].[Declining Stores Count], [Time].[1998].[Q3], [Store].[XL_QZX])", "Axis #0:\n" + "{[Measures].[Declining Stores Count], [Time].[1998].[Q3], [Store].[XL_QZX]}\n" + "Axis #1:\n" + "{[Product].[All Products]}\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: .00\n" + "Row #0: .00\n" + "Row #0: .00\n" + "Row #0: .00\n"); } public void testAvg() { assertExprReturns( "AVG({[Store].[All Stores].[USA].children},[Measures].[Store Sales])", "188,412.71"); } //todo: testAvgWithNulls public void testCorrelation() { assertExprReturns( "Correlation({[Store].[All Stores].[USA].children}, [Measures].[Unit Sales], [Measures].[Store Sales]) * 1000000", "999,906"); } public void testCount() { getTestContext().assertExprDependsOn( "count(Crossjoin([Store].[All Stores].[USA].Children, {[Gender].children}), INCLUDEEMPTY)", "{[Gender]}"); String s1 = TestContext.allHiersExcept("[Store]"); getTestContext().assertExprDependsOn( "count(Crossjoin([Store].[All Stores].[USA].Children, " + "{[Gender].children}), EXCLUDEEMPTY)", s1); assertExprReturns( "count({[Promotion Media].[Media Type].members})", "14"); // applied to an empty set assertExprReturns("count({[Gender].Parent}, IncludeEmpty)", "0"); } public void testCountExcludeEmpty() { String s1 = TestContext.allHiersExcept("[Store]"); getTestContext().assertExprDependsOn( "count(Crossjoin([Store].[USA].Children, {[Gender].children}), EXCLUDEEMPTY)", s1); assertQueryReturns( "with member [Measures].[Promo Count] as \n" + " ' Count(Crossjoin({[Measures].[Unit Sales]},\n" + " {[Promotion Media].[Media Type].members}), EXCLUDEEMPTY)'\n" + "select {[Measures].[Unit Sales], [Measures].[Promo Count]} on columns,\n" + " {[Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].children} on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Promo Count]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Excellent]}\n" + "{[Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Fabulous]}\n" + "{[Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Skinner]}\n" + "{[Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Token]}\n" + "{[Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Washington]}\n" + "Row #0: 738\n" + "Row #0: 14\n" + "Row #1: 632\n" + "Row #1: 13\n" + "Row #2: 655\n" + "Row #2: 14\n" + "Row #3: 735\n" + "Row #3: 14\n" + "Row #4: 647\n" + "Row #4: 12\n"); // applied to an empty set assertExprReturns("count({[Gender].Parent}, ExcludeEmpty)", "0"); } /** * Tests that the 'null' value is regarded as empty, even if the underlying * cell has fact table rows. * *

For a fuller test case, see * {@link mondrian.xmla.XmlaCognosTest#testCognosMDXSuiteConvertedAdventureWorksToFoodMart_015()} */ public void testCountExcludeEmptyNull() { assertQueryReturns( "WITH MEMBER [Measures].[Foo] AS\n" + " Iif(" + TestContext.hierarchyName("Time", "Time") + ".CurrentMember.Name = 'Q2', 1, NULL)\n" + " MEMBER [Measures].[Bar] AS\n" + " Iif(" + TestContext.hierarchyName("Time", "Time") + ".CurrentMember.Name = 'Q2', 1, 0)\n" + " Member [Time].[Time].[CountExc] AS\n" + " Count([Time].[1997].Children, EXCLUDEEMPTY),\n" + " SOLVE_ORDER = 2\n" + " Member [Time].[Time].[CountInc] AS\n" + " Count([Time].[1997].Children, INCLUDEEMPTY),\n" + " SOLVE_ORDER = 2\n" + "SELECT {[Measures].[Foo],\n" + " [Measures].[Bar],\n" + " [Measures].[Unit Sales]} ON 0,\n" + " {[Time].[1997].Children,\n" + " [Time].[CountExc],\n" + " [Time].[CountInc]} ON 1\n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "{[Measures].[Bar]}\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q4]}\n" + "{[Time].[CountExc]}\n" + "{[Time].[CountInc]}\n" + "Row #0: \n" + "Row #0: 0\n" + "Row #0: 66,291\n" + "Row #1: 1\n" + "Row #1: 1\n" + "Row #1: 62,610\n" + "Row #2: \n" + "Row #2: 0\n" + "Row #2: 65,848\n" + "Row #3: \n" + "Row #3: 0\n" + "Row #3: 72,024\n" + "Row #4: 1\n" + "Row #4: 4\n" + "Row #4: 4\n" + "Row #5: 4\n" + "Row #5: 4\n" + "Row #5: 4\n"); } /** * Testcase for * * bug MONDRIAN-710, "Count with ExcludeEmpty throws an exception when the * cube does not have a factCountMeasure". */ public void testCountExcludeEmptyOnCubeWithNoCountFacts() { assertQueryReturns( "WITH " + " MEMBER [Measures].[count] AS '" + " COUNT([Store Type].[Store Type].MEMBERS, EXCLUDEEMPTY)'" + " SELECT " + " {[Measures].[count]} ON AXIS(0)" + " FROM [Warehouse]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[count]}\n" + "Row #0: 5\n"); } public void testCountExcludeEmptyOnVirtualCubeWithNoCountFacts() { assertQueryReturns( "WITH " + " MEMBER [Measures].[count] AS '" + " COUNT([Store].MEMBERS, EXCLUDEEMPTY)'" + " SELECT " + " {[Measures].[count]} ON AXIS(0)" + " FROM [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[count]}\n" + "Row #0: 31\n"); } //todo: testCountNull, testCountNoExp public void testCovariance() { assertExprReturns( "Covariance({[Store].[All Stores].[USA].children}, [Measures].[Unit Sales], [Measures].[Store Sales])", "1,355,761,899"); } public void testCovarianceN() { assertExprReturns( "CovarianceN({[Store].[All Stores].[USA].children}, [Measures].[Unit Sales], [Measures].[Store Sales])", "2,033,642,849"); } public void testIIfNumeric() { assertExprReturns( "IIf(([Measures].[Unit Sales],[Product].[Drink].[Alcoholic Beverages].[Beer and Wine]) > 100, 45, 32)", "45"); // Compare two members. The system needs to figure out that they are // both numeric, and use the right overloaded version of ">", otherwise // we'll get a ClassCastException at runtime. assertExprReturns( "IIf([Measures].[Unit Sales] > [Measures].[Store Sales], 45, 32)", "32"); } public void testMax() { assertExprReturns( "MAX({[Store].[All Stores].[USA].children},[Measures].[Store Sales])", "263,793.22"); } public void testMaxNegative() { // Bug 1771928, "Max() works incorrectly with negative values" assertQueryReturns( "with \n" + " member [Customers].[Neg] as '-1'\n" + " member [Customers].[Min] as 'Min({[Customers].[Neg]})'\n" + " member [Customers].[Max] as 'Max({[Customers].[Neg]})'\n" + "select {[Customers].[Neg],[Customers].[Min],[Customers].[Max]} on 0\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[Neg]}\n" + "{[Customers].[Min]}\n" + "{[Customers].[Max]}\n" + "Row #0: -1\n" + "Row #0: -1\n" + "Row #0: -1\n"); } public void testMedian() { assertExprReturns( "MEDIAN({[Store].[All Stores].[USA].children}," + "[Measures].[Store Sales])", "159,167.84"); } public void testMedian2() { assertQueryReturns( "WITH\n" + " Member [Time].[Time].[1st Half Sales] AS 'Sum({[Time].[1997].[Q1], [Time].[1997].[Q2]})'\n" + " Member [Time].[Time].[2nd Half Sales] AS 'Sum({[Time].[1997].[Q3], [Time].[1997].[Q4]})'\n" + " Member [Time].[Time].[Median] AS 'Median(Time.[Time].Members)'\n" + "SELECT\n" + " NON EMPTY { [Store].[Store Name].Members} ON COLUMNS,\n" + " { [Time].[1st Half Sales], [Time].[2nd Half Sales], [Time].[Median]} ON ROWS\n" + "FROM Sales\n" + "WHERE [Measures].[Store Sales]", "Axis #0:\n" + "{[Measures].[Store Sales]}\n" + "Axis #1:\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Store].[USA].[WA].[Bellingham].[Store 2]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "{[Store].[USA].[WA].[Walla Walla].[Store 22]}\n" + "{[Store].[USA].[WA].[Yakima].[Store 23]}\n" + "Axis #2:\n" + "{[Time].[1st Half Sales]}\n" + "{[Time].[2nd Half Sales]}\n" + "{[Time].[Median]}\n" + "Row #0: 20,801.04\n" + "Row #0: 25,421.41\n" + "Row #0: 26,275.11\n" + "Row #0: 2,074.39\n" + "Row #0: 28,519.18\n" + "Row #0: 43,423.99\n" + "Row #0: 2,140.99\n" + "Row #0: 25,502.08\n" + "Row #0: 25,293.50\n" + "Row #0: 23,265.53\n" + "Row #0: 34,926.91\n" + "Row #0: 2,159.60\n" + "Row #0: 12,490.89\n" + "Row #1: 24,949.20\n" + "Row #1: 29,123.87\n" + "Row #1: 28,156.03\n" + "Row #1: 2,366.79\n" + "Row #1: 26,539.61\n" + "Row #1: 43,794.29\n" + "Row #1: 2,598.24\n" + "Row #1: 27,394.22\n" + "Row #1: 27,350.57\n" + "Row #1: 26,368.93\n" + "Row #1: 39,917.05\n" + "Row #1: 2,546.37\n" + "Row #1: 11,838.34\n" + "Row #2: 4,577.35\n" + "Row #2: 5,211.38\n" + "Row #2: 4,722.87\n" + "Row #2: 398.24\n" + "Row #2: 5,039.50\n" + "Row #2: 7,374.59\n" + "Row #2: 410.22\n" + "Row #2: 4,924.04\n" + "Row #2: 4,569.13\n" + "Row #2: 4,511.68\n" + "Row #2: 6,630.91\n" + "Row #2: 419.51\n" + "Row #2: 2,169.48\n"); } public void testPercentile() { // same result as median assertExprReturns( "Percentile({[Store].[All Stores].[USA].children}, [Measures].[Store Sales], 50)", "159,167.84"); // same result as min assertExprReturns( "Percentile({[Store].[All Stores].[USA].children}, [Measures].[Store Sales], 0)", "142,277.07"); // same result as max assertExprReturns( "Percentile({[Store].[All Stores].[USA].children}, [Measures].[Store Sales], 100)", "263,793.22"); // varying points between value #0 (0th percentile) and value #1 (50th // percentile) of the 3 values assertExprReturns( "Percentile({[Store].[All Stores].[USA].children}, [Measures].[Store Sales], 20)", "152,411.53"); assertExprReturns( "Percentile({[Store].[All Stores].[USA].children}, [Measures].[Store Sales], 25)", "154,945.15"); assertExprReturns( "Percentile({[Store].[All Stores].[USA].children}, [Measures].[Store Sales], 30)", "157,478.76"); } public void testMin() { assertExprReturns( "MIN({[Store].[All Stores].[USA].children},[Measures].[Store Sales])", "142,277.07"); } public void testMinTuple() { assertExprReturns( "Min([Customers].[All Customers].[USA].Children, ([Measures].[Unit Sales], [Gender].[All Gender].[F]))", "33,036"); } public void testStdev() { assertExprReturns( "STDEV({[Store].[All Stores].[USA].children},[Measures].[Store Sales])", "65,825.45"); } public void testStdevP() { assertExprReturns( "STDEVP({[Store].[All Stores].[USA].children},[Measures].[Store Sales])", "53,746.26"); } public void testSumNoExp() { assertExprReturns( "SUM({[Promotion Media].[Media Type].members})", "266,773"); } public void testValue() { // VALUE is usually a cell property, not a member property. // We allow it because MS documents it as a function, .VALUE. assertExprReturns("[Measures].[Store Sales].VALUE", "565,238.13"); // Depends upon almost everything. String s1 = TestContext.allHiersExcept("[Measures]"); getTestContext().assertExprDependsOn( "[Measures].[Store Sales].VALUE", s1); // We do not allow FORMATTED_VALUE. assertExprThrows( "[Measures].[Store Sales].FORMATTED_VALUE", "MDX object '[Measures].[Store Sales].[FORMATTED_VALUE]' not found in cube 'Sales'"); assertExprReturns("[Measures].[Store Sales].NAME", "Store Sales"); // MS says that ID and KEY are standard member properties for // OLE DB for OLAP, but not for XML/A. We don't support them. assertExprThrows( "[Measures].[Store Sales].ID", "MDX object '[Measures].[Store Sales].[ID]' not found in cube 'Sales'"); // Error for KEY is slightly different than for ID. It doesn't matter // very much. // // The error is different because KEY is registered as a Mondrian // builtin property, but ID isn't. KEY cannot be evaluated in // ".KEY" syntax because there is not function defined. For // other builtin properties, such as NAME, CAPTION there is a builtin // function. assertExprThrows( "[Measures].[Store Sales].KEY", "No function matches signature '.KEY'"); assertExprReturns("[Measures].[Store Sales].CAPTION", "Store Sales"); } public void testVar() { assertExprReturns( "VAR({[Store].[All Stores].[USA].children},[Measures].[Store Sales])", "4,332,990,493.69"); } public void testVarP() { assertExprReturns( "VARP({[Store].[All Stores].[USA].children},[Measures].[Store Sales])", "2,888,660,329.13"); } /** * Tests the AS operator, that gives an expression an alias. */ public void testAs() { assertAxisReturns( "Filter([Customers].Children as t,\n" + "t.Current.Name = 'USA')", "[Customers].[USA]"); // 'AS' and the ':' operator have similar precedence, so it's worth // checking that they play nice. assertQueryReturns( "select\n" + " filter(\n" + " [Time].[1997].[Q1].[2] : [Time].[1997].[Q3].[9] as t," + " mod(t.CurrentOrdinal, 2) = 0) on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q3].[8]}\n" + "Row #0: 20,957\n" + "Row #0: 20,179\n" + "Row #0: 21,350\n" + "Row #0: 21,697\n"); // AS member fails on SSAS with "The CHILDREN function expects a member // expression for the 0 argument. A tuple set expression was used." assertQueryThrows( "select\n" + " {([Time].[1997].[Q1] as t).Children, \n" + " t.Parent } on 0 \n" + "from [Sales]", "No function matches signature '.Children'"); // Set of members. OK. assertQueryReturns( "select Measures.[Unit Sales] on 0, \n" + " {[Time].[1997].Children as t, \n" + " Descendants(t, [Time].[Month])} on 1 \n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q4]}\n" + "{[Time].[1997].[Q1].[1]}\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[5]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q3].[7]}\n" + "{[Time].[1997].[Q3].[8]}\n" + "{[Time].[1997].[Q3].[9]}\n" + "{[Time].[1997].[Q4].[10]}\n" + "{[Time].[1997].[Q4].[11]}\n" + "{[Time].[1997].[Q4].[12]}\n" + "Row #0: 66,291\n" + "Row #1: 62,610\n" + "Row #2: 65,848\n" + "Row #3: 72,024\n" + "Row #4: 21,628\n" + "Row #5: 20,957\n" + "Row #6: 23,706\n" + "Row #7: 20,179\n" + "Row #8: 21,081\n" + "Row #9: 21,350\n" + "Row #10: 23,763\n" + "Row #11: 21,697\n" + "Row #12: 20,388\n" + "Row #13: 19,958\n" + "Row #14: 25,270\n" + "Row #15: 26,796\n"); // Alias a member. Implicitly becomes set. OK. assertQueryReturns( "select Measures.[Unit Sales] on 0,\n" + " {[Time].[1997] as t,\n" + " Descendants(t, [Time].[Month])} on 1\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997]}\n" + "{[Time].[1997].[Q1].[1]}\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[5]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q3].[7]}\n" + "{[Time].[1997].[Q3].[8]}\n" + "{[Time].[1997].[Q3].[9]}\n" + "{[Time].[1997].[Q4].[10]}\n" + "{[Time].[1997].[Q4].[11]}\n" + "{[Time].[1997].[Q4].[12]}\n" + "Row #0: 266,773\n" + "Row #1: 21,628\n" + "Row #2: 20,957\n" + "Row #3: 23,706\n" + "Row #4: 20,179\n" + "Row #5: 21,081\n" + "Row #6: 21,350\n" + "Row #7: 23,763\n" + "Row #8: 21,697\n" + "Row #9: 20,388\n" + "Row #10: 19,958\n" + "Row #11: 25,270\n" + "Row #12: 26,796\n"); // Alias a tuple. Implicitly becomes set. The error confirms that the // named set's type is a set of tuples. SSAS gives error "Descendants // function expects a member or set ..." assertQueryThrows( "select Measures.[Unit Sales] on 0,\n" + " {([Time].[1997], [Customers].[USA].[CA]) as t,\n" + " Descendants(t, [Time].[Month])} on 1\n" + "from [Sales]", "Argument to Descendants function must be a member or set of members, not a set of tuples"); } public void testAs2() { // Named set and alias with same name (t) and a second alias (t2). // Reference to t from within descendants resolves to alias, of type // [Time], because it is nearer. final String result = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales], [Gender].[F]}\n" + "{[Measures].[Unit Sales], [Gender].[M]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q4]}\n" + "{[Time].[1997].[Q1].[1]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q4].[11]}\n" + "Row #0: 32,910\n" + "Row #0: 33,381\n" + "Row #1: 30,992\n" + "Row #1: 31,618\n" + "Row #2: 32,599\n" + "Row #2: 33,249\n" + "Row #3: 35,057\n" + "Row #3: 36,967\n" + "Row #4: 10,932\n" + "Row #4: 10,696\n" + "Row #5: 10,466\n" + "Row #5: 10,884\n" + "Row #6: 12,320\n" + "Row #6: 12,950\n"; assertQueryReturns( "with set t as [Gender].Children\n" + "select\n" + " Measures.[Unit Sales] * t on 0,\n" + " {\n" + " [Time].[1997].Children as t,\n" + " Filter(\n" + " Descendants(t, [Time].[Month]) as t2,\n" + " Mod(t2.CurrentOrdinal, 5) = 0)\n" + " } on 1\n" + "from [Sales]", result); // Two aliases with same name. OK. assertQueryReturns( "select\n" + " Measures.[Unit Sales] * [Gender].Children as t on 0,\n" + " {[Time].[1997].Children as t,\n" + " Filter(\n" + " Descendants(t, [Time].[Month]) as t2,\n" + " Mod(t2.CurrentOrdinal, 5) = 0)\n" + " } on 1\n" + "from [Sales]", result); // Bug MONDRIAN-648 causes 'AS' to have lower precedence than '*'. if (Bug.BugMondrian648Fixed) { // Note that 'as' has higher precedence than '*'. assertQueryReturns( "select\n" + " Measures.[Unit Sales] * [Gender].Members as t on 0,\n" + " {t} on 1\n" + "from [Sales]", "xxxxx"); } // Reference to hierarchy on other axis. // On SSAS 2005, finds t, and gives error, // "The Gender hierarchy already appears in the Axis0 axis." // On Mondrian, cannot find t. FIXME. assertQueryThrows( "select\n" + " Measures.[Unit Sales] * ([Gender].Members as t) on 0,\n" + " {t} on 1\n" + "from [Sales]", "MDX object '[t]' not found in cube 'Sales'"); // As above, with parentheses. Tuple valued. // On SSAS 2005, finds t, and gives error, // "The Measures hierarchy already appears in the Axis0 axis." // On Mondrian, cannot find t. FIXME. assertQueryThrows( "select\n" + " (Measures.[Unit Sales] * [Gender].Members) as t on 0,\n" + " {t} on 1\n" + "from [Sales]", "MDX object '[t]' not found in cube 'Sales'"); // Calculated set, CurrentMember assertQueryReturns( "select Measures.[Unit Sales] on 0,\n" + " filter(\n" + " (Time.Month.Members * Gender.Members) as s,\n" + " (s.Current.Item(0).Parent, [Marital Status].[S], [Gender].[F]) > 17000) on 1\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q4].[10], [Gender].[All Gender]}\n" + "{[Time].[1997].[Q4].[10], [Gender].[F]}\n" + "{[Time].[1997].[Q4].[10], [Gender].[M]}\n" + "{[Time].[1997].[Q4].[11], [Gender].[All Gender]}\n" + "{[Time].[1997].[Q4].[11], [Gender].[F]}\n" + "{[Time].[1997].[Q4].[11], [Gender].[M]}\n" + "{[Time].[1997].[Q4].[12], [Gender].[All Gender]}\n" + "{[Time].[1997].[Q4].[12], [Gender].[F]}\n" + "{[Time].[1997].[Q4].[12], [Gender].[M]}\n" + "Row #0: 19,958\n" + "Row #1: 9,506\n" + "Row #2: 10,452\n" + "Row #3: 25,270\n" + "Row #4: 12,320\n" + "Row #5: 12,950\n" + "Row #6: 26,796\n" + "Row #7: 13,231\n" + "Row #8: 13,565\n"); // As above, but don't override [Gender] in filter condition. Note that // the filter condition is evaluated in the context created by the // filter set. So, only items with [All Gender] pass the filter. assertQueryReturns( "select Measures.[Unit Sales] on 0,\n" + " filter(\n" + " (Time.Month.Members * Gender.Members) as s,\n" + " (s.Current.Item(0).Parent, [Marital Status].[S]) > 35000) on 1\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q4].[10], [Gender].[All Gender]}\n" + "{[Time].[1997].[Q4].[11], [Gender].[All Gender]}\n" + "{[Time].[1997].[Q4].[12], [Gender].[All Gender]}\n" + "Row #0: 19,958\n" + "Row #1: 25,270\n" + "Row #2: 26,796\n"); // Multiple definitions of alias within same axis assertQueryReturns( "select Measures.[Unit Sales] on 0,\n" + " generate(\n" + " [Marital Status].Children as s,\n" + " filter(\n" + " (Time.Month.Members * Gender.Members) as s,\n" + " (s.Current.Item(0).Parent, [Marital Status].[S], [Gender].[F]) > 17000),\n" + " ALL) on 1\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q4].[10], [Gender].[All Gender]}\n" + "{[Time].[1997].[Q4].[10], [Gender].[F]}\n" + "{[Time].[1997].[Q4].[10], [Gender].[M]}\n" + "{[Time].[1997].[Q4].[11], [Gender].[All Gender]}\n" + "{[Time].[1997].[Q4].[11], [Gender].[F]}\n" + "{[Time].[1997].[Q4].[11], [Gender].[M]}\n" + "{[Time].[1997].[Q4].[12], [Gender].[All Gender]}\n" + "{[Time].[1997].[Q4].[12], [Gender].[F]}\n" + "{[Time].[1997].[Q4].[12], [Gender].[M]}\n" + "{[Time].[1997].[Q4].[10], [Gender].[All Gender]}\n" + "{[Time].[1997].[Q4].[10], [Gender].[F]}\n" + "{[Time].[1997].[Q4].[10], [Gender].[M]}\n" + "{[Time].[1997].[Q4].[11], [Gender].[All Gender]}\n" + "{[Time].[1997].[Q4].[11], [Gender].[F]}\n" + "{[Time].[1997].[Q4].[11], [Gender].[M]}\n" + "{[Time].[1997].[Q4].[12], [Gender].[All Gender]}\n" + "{[Time].[1997].[Q4].[12], [Gender].[F]}\n" + "{[Time].[1997].[Q4].[12], [Gender].[M]}\n" + "Row #0: 19,958\n" + "Row #1: 9,506\n" + "Row #2: 10,452\n" + "Row #3: 25,270\n" + "Row #4: 12,320\n" + "Row #5: 12,950\n" + "Row #6: 26,796\n" + "Row #7: 13,231\n" + "Row #8: 13,565\n" + "Row #9: 19,958\n" + "Row #10: 9,506\n" + "Row #11: 10,452\n" + "Row #12: 25,270\n" + "Row #13: 12,320\n" + "Row #14: 12,950\n" + "Row #15: 26,796\n" + "Row #16: 13,231\n" + "Row #17: 13,565\n"); // Multiple definitions of alias within same axis. // // On SSAS 2005, gives error, "The CURRENT function cannot be called in // current context because the 'x' set is not in scope". SSAS 2005 gives // same error even if set does not exist. assertQueryThrows( "with member Measures.Foo as 'x.Current.Name'\n" + "select\n" + " {Measures.[Unit Sales], Measures.Foo} on 0,\n" + " generate(\n" + " [Marital Status].\n" + " Children as x,\n" + " filter(\n" + " Gender.Members as x,\n" + " (x.Current, [Marital Status].[S]) > 50000),\n" + " ALL) on 1\n" + "from [Sales]", "MDX object '[x]' not found in cube 'Sales'"); // As above, but set is not out of scope; it does not exist; but error // should be the same. assertQueryThrows( "with member Measures.Foo as 'z.Current.Name'\n" + "select\n" + " {Measures.[Unit Sales], Measures.Foo} on 0,\n" + " generate(\n" + " [Marital Status].\n" + " Children as s,\n" + " filter(\n" + " Gender.Members as s,\n" + " (s.Current, [Marital Status].[S]) > 50000),\n" + " ALL) on 1\n" + "from [Sales]", "MDX object '[z]' not found in cube 'Sales'"); // 'set AS string' is invalid assertQueryThrows( "select Measures.[Unit Sales] on 0,\n" + " filter(\n" + " (Time.Month.Members * Gender.Members) as 'foo',\n" + " (s.Current.Item(0).Parent, [Marital Status].[S]) > 50000) on 1\n" + "from [Sales]", "Syntax error at line 3, column 46, token ''foo''"); // 'set AS numeric' is invalid assertQueryThrows( "select Measures.[Unit Sales] on 0,\n" + " filter(\n" + " (Time.Month.Members * Gender.Members) as 1234,\n" + " (s.Current.Item(0).Parent, [Marital Status].[S]) > 50000) on 1\n" + "from [Sales]", "Syntax error at line 3, column 46, token '1234'"); // 'numeric AS identifier' is invalid assertQueryThrows( "select Measures.[Unit Sales] on 0,\n" + " filter(\n" + " 123 * 456 as s,\n" + " (s.Current.Item(0).Parent, [Marital Status].[S]) > 50000) on 1\n" + "from [Sales]", "No function matches signature ' AS '"); } public void testAscendants() { assertAxisReturns( "Ascendants([Store].[USA].[CA])", "[Store].[USA].[CA]\n" + "[Store].[USA]\n" + "[Store].[All Stores]"); } public void testAscendantsAll() { assertAxisReturns( "Ascendants([Store].DefaultMember)", "[Store].[All Stores]"); } public void testAscendantsNull() { assertAxisReturns( "Ascendants([Gender].[F].PrevMember)", ""); } public void testBottomCount() { assertAxisReturns( "BottomCount({[Promotion Media].[Media Type].members}, 2, [Measures].[Unit Sales])", "[Promotion Media].[Radio]\n" + "[Promotion Media].[Sunday Paper, Radio, TV]"); } //todo: test unordered public void testBottomPercent() { assertAxisReturns( "BottomPercent(Filter({[Store].[All Stores].[USA].[CA].Children, [Store].[All Stores].[USA].[OR].Children, [Store].[All Stores].[USA].[WA].Children}, ([Measures].[Unit Sales] > 0.0)), 100.0, [Measures].[Store Sales])", "[Store].[USA].[CA].[San Francisco]\n" + "[Store].[USA].[WA].[Walla Walla]\n" + "[Store].[USA].[WA].[Bellingham]\n" + "[Store].[USA].[WA].[Yakima]\n" + "[Store].[USA].[CA].[Beverly Hills]\n" + "[Store].[USA].[WA].[Spokane]\n" + "[Store].[USA].[WA].[Seattle]\n" + "[Store].[USA].[WA].[Bremerton]\n" + "[Store].[USA].[CA].[San Diego]\n" + "[Store].[USA].[CA].[Los Angeles]\n" + "[Store].[USA].[OR].[Portland]\n" + "[Store].[USA].[WA].[Tacoma]\n" + "[Store].[USA].[OR].[Salem]"); assertAxisReturns( "BottomPercent({[Promotion Media].[Media Type].members}, 1, [Measures].[Unit Sales])", "[Promotion Media].[Radio]\n" + "[Promotion Media].[Sunday Paper, Radio, TV]"); } //todo: test precision public void testBottomSum() { assertAxisReturns( "BottomSum({[Promotion Media].[Media Type].members}, 5000, [Measures].[Unit Sales])", "[Promotion Media].[Radio]\n" + "[Promotion Media].[Sunday Paper, Radio, TV]"); } public void testExceptEmpty() { // If left is empty, result is empty. assertAxisReturns( "Except(Filter([Gender].Members, 1=0), {[Gender].[M]})", ""); // If right is empty, result is left. assertAxisReturns( "Except({[Gender].[M]}, Filter([Gender].Members, 1=0))", "[Gender].[M]"); } /** * Tests that Except() successfully removes crossjoined tuples * from the axis results. Previously, this would fail by returning * all tuples in the first argument to Except. bug 1439627 */ public void testExceptCrossjoin() { assertAxisReturns( "Except(CROSSJOIN({[Promotion Media].[All Media]},\n" + " [Product].[All Products].Children),\n" + " CROSSJOIN({[Promotion Media].[All Media]},\n" + " {[Product].[All Products].[Drink]}))", "{[Promotion Media].[All Media], [Product].[Food]}\n" + "{[Promotion Media].[All Media], [Product].[Non-Consumable]}"); } public void testExtract() { assertAxisReturns( "Extract(\n" + "Crossjoin({[Gender].[F], [Gender].[M]},\n" + " {[Marital Status].Members}),\n" + "[Gender])", "[Gender].[F]\n" + "[Gender].[M]"); // Extract() with no dimensions is not valid assertAxisThrows( "Extract(Crossjoin({[Gender].[F], [Gender].[M]}, {[Marital Status].Members}))", "No function matches signature 'Extract()'"); // Extract applied to non-constant dimension should fail assertAxisThrows( "Extract(Crossjoin([Gender].Members, [Store].Children), [Store].Hierarchy.Dimension)", "not a constant hierarchy: [Store].Hierarchy.Dimension"); // Extract applied to non-constant hierarchy should fail assertAxisThrows( "Extract(Crossjoin([Gender].Members, [Store].Children), [Store].Hierarchy)", "not a constant hierarchy: [Store].Hierarchy"); // Extract applied to set of members is OK (if silly). Duplicates are // removed, as always. assertAxisReturns( "Extract({[Gender].[M], [Gender].Members}, [Gender])", "[Gender].[M]\n" + "[Gender].[All Gender]\n" + "[Gender].[F]"); // Extract of hierarchy not in set fails assertAxisThrows( "Extract(Crossjoin([Gender].Members, [Store].Children), [Marital Status])", "hierarchy [Marital Status] is not a hierarchy of the expression Crossjoin([Gender].Members, [Store].Children)"); // Extract applied to empty set returns empty set assertAxisReturns( "Extract(Crossjoin({[Gender].Parent}, [Store].Children), [Store])", ""); // Extract applied to asymmetric set assertAxisReturns( "Extract(\n" + "{([Gender].[M], [Marital Status].[M]),\n" + " ([Gender].[F], [Marital Status].[M]),\n" + " ([Gender].[M], [Marital Status].[S])},\n" + "[Gender])", "[Gender].[M]\n" + "[Gender].[F]"); // Extract applied to asymmetric set (other side) assertAxisReturns( "Extract(\n" + "{([Gender].[M], [Marital Status].[M]),\n" + " ([Gender].[F], [Marital Status].[M]),\n" + " ([Gender].[M], [Marital Status].[S])},\n" + "[Marital Status])", "[Marital Status].[M]\n" + "[Marital Status].[S]"); // Extract more than one hierarchy assertAxisReturns( "Extract(\n" + "[Gender].Children * [Marital Status].Children * [Time].[1997].Children * [Store].[USA].Children,\n" + "[Time], [Marital Status])", "{[Time].[1997].[Q1], [Marital Status].[M]}\n" + "{[Time].[1997].[Q2], [Marital Status].[M]}\n" + "{[Time].[1997].[Q3], [Marital Status].[M]}\n" + "{[Time].[1997].[Q4], [Marital Status].[M]}\n" + "{[Time].[1997].[Q1], [Marital Status].[S]}\n" + "{[Time].[1997].[Q2], [Marital Status].[S]}\n" + "{[Time].[1997].[Q3], [Marital Status].[S]}\n" + "{[Time].[1997].[Q4], [Marital Status].[S]}"); // Extract duplicate hierarchies fails assertAxisThrows( "Extract(\n" + "{([Gender].[M], [Marital Status].[M]),\n" + " ([Gender].[F], [Marital Status].[M]),\n" + " ([Gender].[M], [Marital Status].[S])},\n" + "[Gender], [Gender])", "hierarchy [Gender] is extracted more than once"); } /** * Tests that TopPercent() operates succesfully on a * axis of crossjoined tuples. previously, this would * fail with a ClassCastException in FunUtil.java. bug 1440306 */ public void testTopPercentCrossjoin() { assertAxisReturns( "{TopPercent(Crossjoin([Product].[Product Department].members,\n" + "[Time].[1997].children),10,[Measures].[Store Sales])}", "{[Product].[Food].[Produce], [Time].[1997].[Q4]}\n" + "{[Product].[Food].[Produce], [Time].[1997].[Q1]}\n" + "{[Product].[Food].[Produce], [Time].[1997].[Q3]}"); } public void testCrossjoinNested() { assertAxisReturns( " CrossJoin(\n" + " CrossJoin(\n" + " [Gender].members,\n" + " [Marital Status].members),\n" + " {[Store], [Store].children})", "{[Gender].[All Gender], [Marital Status].[All Marital Status], [Store].[All Stores]}\n" + "{[Gender].[All Gender], [Marital Status].[All Marital Status], [Store].[Canada]}\n" + "{[Gender].[All Gender], [Marital Status].[All Marital Status], [Store].[Mexico]}\n" + "{[Gender].[All Gender], [Marital Status].[All Marital Status], [Store].[USA]}\n" + "{[Gender].[All Gender], [Marital Status].[M], [Store].[All Stores]}\n" + "{[Gender].[All Gender], [Marital Status].[M], [Store].[Canada]}\n" + "{[Gender].[All Gender], [Marital Status].[M], [Store].[Mexico]}\n" + "{[Gender].[All Gender], [Marital Status].[M], [Store].[USA]}\n" + "{[Gender].[All Gender], [Marital Status].[S], [Store].[All Stores]}\n" + "{[Gender].[All Gender], [Marital Status].[S], [Store].[Canada]}\n" + "{[Gender].[All Gender], [Marital Status].[S], [Store].[Mexico]}\n" + "{[Gender].[All Gender], [Marital Status].[S], [Store].[USA]}\n" + "{[Gender].[F], [Marital Status].[All Marital Status], [Store].[All Stores]}\n" + "{[Gender].[F], [Marital Status].[All Marital Status], [Store].[Canada]}\n" + "{[Gender].[F], [Marital Status].[All Marital Status], [Store].[Mexico]}\n" + "{[Gender].[F], [Marital Status].[All Marital Status], [Store].[USA]}\n" + "{[Gender].[F], [Marital Status].[M], [Store].[All Stores]}\n" + "{[Gender].[F], [Marital Status].[M], [Store].[Canada]}\n" + "{[Gender].[F], [Marital Status].[M], [Store].[Mexico]}\n" + "{[Gender].[F], [Marital Status].[M], [Store].[USA]}\n" + "{[Gender].[F], [Marital Status].[S], [Store].[All Stores]}\n" + "{[Gender].[F], [Marital Status].[S], [Store].[Canada]}\n" + "{[Gender].[F], [Marital Status].[S], [Store].[Mexico]}\n" + "{[Gender].[F], [Marital Status].[S], [Store].[USA]}\n" + "{[Gender].[M], [Marital Status].[All Marital Status], [Store].[All Stores]}\n" + "{[Gender].[M], [Marital Status].[All Marital Status], [Store].[Canada]}\n" + "{[Gender].[M], [Marital Status].[All Marital Status], [Store].[Mexico]}\n" + "{[Gender].[M], [Marital Status].[All Marital Status], [Store].[USA]}\n" + "{[Gender].[M], [Marital Status].[M], [Store].[All Stores]}\n" + "{[Gender].[M], [Marital Status].[M], [Store].[Canada]}\n" + "{[Gender].[M], [Marital Status].[M], [Store].[Mexico]}\n" + "{[Gender].[M], [Marital Status].[M], [Store].[USA]}\n" + "{[Gender].[M], [Marital Status].[S], [Store].[All Stores]}\n" + "{[Gender].[M], [Marital Status].[S], [Store].[Canada]}\n" + "{[Gender].[M], [Marital Status].[S], [Store].[Mexico]}\n" + "{[Gender].[M], [Marital Status].[S], [Store].[USA]}"); } public void testCrossjoinSingletonTuples() { assertAxisReturns( "CrossJoin({([Gender].[M])}, {([Marital Status].[S])})", "{[Gender].[M], [Marital Status].[S]}"); } public void testCrossjoinSingletonTuplesNested() { assertAxisReturns( "CrossJoin({([Gender].[M])}, CrossJoin({([Marital Status].[S])}, [Store].children))", "{[Gender].[M], [Marital Status].[S], [Store].[Canada]}\n" + "{[Gender].[M], [Marital Status].[S], [Store].[Mexico]}\n" + "{[Gender].[M], [Marital Status].[S], [Store].[USA]}"); } public void testCrossjoinAsterisk() { assertAxisReturns( "{[Gender].[M]} * {[Marital Status].[S]}", "{[Gender].[M], [Marital Status].[S]}"); } public void testCrossjoinAsteriskTuple() { assertQueryReturns( "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NON EMPTY [Store].[All Stores] " + " * ([Product].[All Products], [Gender]) " + " * [Customers].[All Customers] ON ROWS " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[All Stores], [Product].[All Products], [Gender].[All Gender], [Customers].[All Customers]}\n" + "Row #0: 266,773\n"); } public void testCrossjoinAsteriskAssoc() { assertAxisReturns( "Order({[Gender].Children} * {[Marital Status].Children} * {[Time].[1997].[Q2].Children}," + "[Measures].[Unit Sales])", "{[Gender].[F], [Marital Status].[M], [Time].[1997].[Q2].[4]}\n" + "{[Gender].[F], [Marital Status].[M], [Time].[1997].[Q2].[6]}\n" + "{[Gender].[F], [Marital Status].[M], [Time].[1997].[Q2].[5]}\n" + "{[Gender].[F], [Marital Status].[S], [Time].[1997].[Q2].[4]}\n" + "{[Gender].[F], [Marital Status].[S], [Time].[1997].[Q2].[5]}\n" + "{[Gender].[F], [Marital Status].[S], [Time].[1997].[Q2].[6]}\n" + "{[Gender].[M], [Marital Status].[M], [Time].[1997].[Q2].[4]}\n" + "{[Gender].[M], [Marital Status].[M], [Time].[1997].[Q2].[5]}\n" + "{[Gender].[M], [Marital Status].[M], [Time].[1997].[Q2].[6]}\n" + "{[Gender].[M], [Marital Status].[S], [Time].[1997].[Q2].[6]}\n" + "{[Gender].[M], [Marital Status].[S], [Time].[1997].[Q2].[4]}\n" + "{[Gender].[M], [Marital Status].[S], [Time].[1997].[Q2].[5]}"); } public void testCrossjoinAsteriskInsideBraces() { assertAxisReturns( "{[Gender].[M] * [Marital Status].[S] * [Time].[1997].[Q2].Children}", "{[Gender].[M], [Marital Status].[S], [Time].[1997].[Q2].[4]}\n" + "{[Gender].[M], [Marital Status].[S], [Time].[1997].[Q2].[5]}\n" + "{[Gender].[M], [Marital Status].[S], [Time].[1997].[Q2].[6]}"); } public void testCrossJoinAsteriskQuery() { assertQueryReturns( "SELECT {[Measures].members * [1997].children} ON COLUMNS,\n" + " {[Store].[USA].children * [Position].[All Position].children} DIMENSION PROPERTIES [Store].[Store SQFT] ON ROWS\n" + "FROM [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Org Salary], [Time].[1997].[Q1]}\n" + "{[Measures].[Org Salary], [Time].[1997].[Q2]}\n" + "{[Measures].[Org Salary], [Time].[1997].[Q3]}\n" + "{[Measures].[Org Salary], [Time].[1997].[Q4]}\n" + "{[Measures].[Count], [Time].[1997].[Q1]}\n" + "{[Measures].[Count], [Time].[1997].[Q2]}\n" + "{[Measures].[Count], [Time].[1997].[Q3]}\n" + "{[Measures].[Count], [Time].[1997].[Q4]}\n" + "{[Measures].[Number of Employees], [Time].[1997].[Q1]}\n" + "{[Measures].[Number of Employees], [Time].[1997].[Q2]}\n" + "{[Measures].[Number of Employees], [Time].[1997].[Q3]}\n" + "{[Measures].[Number of Employees], [Time].[1997].[Q4]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA], [Position].[Middle Management]}\n" + "{[Store].[USA].[CA], [Position].[Senior Management]}\n" + "{[Store].[USA].[CA], [Position].[Store Full Time Staf]}\n" + "{[Store].[USA].[CA], [Position].[Store Management]}\n" + "{[Store].[USA].[CA], [Position].[Store Temp Staff]}\n" + "{[Store].[USA].[OR], [Position].[Middle Management]}\n" + "{[Store].[USA].[OR], [Position].[Senior Management]}\n" + "{[Store].[USA].[OR], [Position].[Store Full Time Staf]}\n" + "{[Store].[USA].[OR], [Position].[Store Management]}\n" + "{[Store].[USA].[OR], [Position].[Store Temp Staff]}\n" + "{[Store].[USA].[WA], [Position].[Middle Management]}\n" + "{[Store].[USA].[WA], [Position].[Senior Management]}\n" + "{[Store].[USA].[WA], [Position].[Store Full Time Staf]}\n" + "{[Store].[USA].[WA], [Position].[Store Management]}\n" + "{[Store].[USA].[WA], [Position].[Store Temp Staff]}\n" + "Row #0: $275.40\n" + "Row #0: $275.40\n" + "Row #0: $275.40\n" + "Row #0: $275.40\n" + "Row #0: 27\n" + "Row #0: 27\n" + "Row #0: 27\n" + "Row #0: 27\n" + "Row #0: 9\n" + "Row #0: 9\n" + "Row #0: 9\n" + "Row #0: 9\n" + "Row #1: $837.00\n" + "Row #1: $837.00\n" + "Row #1: $837.00\n" + "Row #1: $837.00\n" + "Row #1: 24\n" + "Row #1: 24\n" + "Row #1: 24\n" + "Row #1: 24\n" + "Row #1: 8\n" + "Row #1: 8\n" + "Row #1: 8\n" + "Row #1: 8\n" + "Row #2: $1,728.45\n" + "Row #2: $1,727.02\n" + "Row #2: $1,727.72\n" + "Row #2: $1,726.55\n" + "Row #2: 357\n" + "Row #2: 357\n" + "Row #2: 357\n" + "Row #2: 357\n" + "Row #2: 119\n" + "Row #2: 119\n" + "Row #2: 119\n" + "Row #2: 119\n" + "Row #3: $473.04\n" + "Row #3: $473.04\n" + "Row #3: $473.04\n" + "Row #3: $473.04\n" + "Row #3: 51\n" + "Row #3: 51\n" + "Row #3: 51\n" + "Row #3: 51\n" + "Row #3: 17\n" + "Row #3: 17\n" + "Row #3: 17\n" + "Row #3: 17\n" + "Row #4: $401.35\n" + "Row #4: $405.73\n" + "Row #4: $400.61\n" + "Row #4: $402.31\n" + "Row #4: 120\n" + "Row #4: 120\n" + "Row #4: 120\n" + "Row #4: 120\n" + "Row #4: 40\n" + "Row #4: 40\n" + "Row #4: 40\n" + "Row #4: 40\n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #7: $1,343.62\n" + "Row #7: $1,342.61\n" + "Row #7: $1,342.57\n" + "Row #7: $1,343.65\n" + "Row #7: 279\n" + "Row #7: 279\n" + "Row #7: 279\n" + "Row #7: 279\n" + "Row #7: 93\n" + "Row #7: 93\n" + "Row #7: 93\n" + "Row #7: 93\n" + "Row #8: $286.74\n" + "Row #8: $286.74\n" + "Row #8: $286.74\n" + "Row #8: $286.74\n" + "Row #8: 30\n" + "Row #8: 30\n" + "Row #8: 30\n" + "Row #8: 30\n" + "Row #8: 10\n" + "Row #8: 10\n" + "Row #8: 10\n" + "Row #8: 10\n" + "Row #9: $333.20\n" + "Row #9: $332.65\n" + "Row #9: $331.28\n" + "Row #9: $332.43\n" + "Row #9: 99\n" + "Row #9: 99\n" + "Row #9: 99\n" + "Row #9: 99\n" + "Row #9: 33\n" + "Row #9: 33\n" + "Row #9: 33\n" + "Row #9: 33\n" + "Row #10: \n" + "Row #10: \n" + "Row #10: \n" + "Row #10: \n" + "Row #10: \n" + "Row #10: \n" + "Row #10: \n" + "Row #10: \n" + "Row #10: \n" + "Row #10: \n" + "Row #10: \n" + "Row #10: \n" + "Row #11: \n" + "Row #11: \n" + "Row #11: \n" + "Row #11: \n" + "Row #11: \n" + "Row #11: \n" + "Row #11: \n" + "Row #11: \n" + "Row #11: \n" + "Row #11: \n" + "Row #11: \n" + "Row #11: \n" + "Row #12: $2,768.60\n" + "Row #12: $2,769.18\n" + "Row #12: $2,766.78\n" + "Row #12: $2,769.50\n" + "Row #12: 579\n" + "Row #12: 579\n" + "Row #12: 579\n" + "Row #12: 579\n" + "Row #12: 193\n" + "Row #12: 193\n" + "Row #12: 193\n" + "Row #12: 193\n" + "Row #13: $736.29\n" + "Row #13: $736.29\n" + "Row #13: $736.29\n" + "Row #13: $736.29\n" + "Row #13: 81\n" + "Row #13: 81\n" + "Row #13: 81\n" + "Row #13: 81\n" + "Row #13: 27\n" + "Row #13: 27\n" + "Row #13: 27\n" + "Row #13: 27\n" + "Row #14: $674.70\n" + "Row #14: $674.54\n" + "Row #14: $676.26\n" + "Row #14: $676.48\n" + "Row #14: 201\n" + "Row #14: 201\n" + "Row #14: 201\n" + "Row #14: 201\n" + "Row #14: 67\n" + "Row #14: 67\n" + "Row #14: 67\n" + "Row #14: 67\n"); } /** * Testcase for bug 1889745, "StackOverflowError while resolving * crossjoin". The problem occurs when a calculated member that references * itself is referenced in a crossjoin. */ public void testCrossjoinResolve() { assertQueryReturns( "with\n" + "member [Measures].[Filtered Unit Sales] as\n" + " 'IIf((([Measures].[Unit Sales] > 50000.0)\n" + " OR ([Product].CurrentMember.Level.UniqueName <>\n" + " \"[Product].[Product Family]\")),\n" + " IIf(((Count([Product].CurrentMember.Children) = 0.0)),\n" + " [Measures].[Unit Sales],\n" + " Sum([Product].CurrentMember.Children,\n" + " [Measures].[Filtered Unit Sales])),\n" + " NULL)'\n" + "select NON EMPTY {crossjoin({[Measures].[Filtered Unit Sales]},\n" + "{[Gender].[M], [Gender].[F]})} ON COLUMNS,\n" + "NON EMPTY {[Product].[All Products]} ON ROWS\n" + "from [Sales]\n" + "where [Time].[1997]", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Filtered Unit Sales], [Gender].[M]}\n" + "{[Measures].[Filtered Unit Sales], [Gender].[F]}\n" + "Axis #2:\n" + "{[Product].[All Products]}\n" + "Row #0: 97,126\n" + "Row #0: 94,814\n"); } /** * Test case for bug 1911832, "Exception converting immutable list to array * in JDK 1.5". */ public void testCrossjoinOrder() { assertQueryReturns( "WITH\n" + "\n" + "SET [S1] AS 'CROSSJOIN({[Time].[1997]}, {[Gender].[Gender].MEMBERS})'\n" + "\n" + "SELECT CROSSJOIN(ORDER([S1], [Measures].[Unit Sales], BDESC),\n" + "{[Measures].[Unit Sales]}) ON AXIS(0)\n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997], [Gender].[M], [Measures].[Unit Sales]}\n" + "{[Time].[1997], [Gender].[F], [Measures].[Unit Sales]}\n" + "Row #0: 135,215\n" + "Row #0: 131,558\n"); } public void testCrossjoinDupHierarchyFails() { assertQueryThrows( "select [Measures].[Unit Sales] ON COLUMNS,\n" + " CrossJoin({[Time].[Quarter].[Q1]}, {[Time].[Month].[5]}) ON ROWS\n" + "from [Sales]", "Tuple contains more than one member of hierarchy '[Time]'."); // now with Item, for kicks assertQueryThrows( "select [Measures].[Unit Sales] ON COLUMNS,\n" + " CrossJoin({[Time].[Quarter].[Q1]}, {[Time].[Month].[5]}).Item(0) ON ROWS\n" + "from [Sales]", "Tuple contains more than one member of hierarchy '[Time]'."); // same query using explicit tuple assertQueryThrows( "select [Measures].[Unit Sales] ON COLUMNS,\n" + " ([Time].[Quarter].[Q1], [Time].[Month].[5]) ON ROWS\n" + "from [Sales]", "Tuple contains more than one member of hierarchy '[Time]'."); } /** * Tests cases of different hierarchies in the same dimension. * (Compare to {@link #testCrossjoinDupHierarchyFails()}). Not an error. */ public void testCrossjoinDupDimensionOk() { final String expectedResult = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1], [Time].[Weekly].[1997].[10]}\n" + "Row #0: 4,395\n"; final String timeWeekly = TestContext.hierarchyName("Time", "Weekly"); assertQueryReturns( "select [Measures].[Unit Sales] ON COLUMNS,\n" + " CrossJoin({[Time].[Quarter].[Q1]}, {" + timeWeekly + ".[1997].[10]}) ON ROWS\n" + "from [Sales]", expectedResult); // now with Item, for kicks assertQueryReturns( "select [Measures].[Unit Sales] ON COLUMNS,\n" + " CrossJoin({[Time].[Quarter].[Q1]}, {" + timeWeekly + ".[1997].[10]}).Item(0) ON ROWS\n" + "from [Sales]", expectedResult); // same query using explicit tuple assertQueryReturns( "select [Measures].[Unit Sales] ON COLUMNS,\n" + " ([Time].[Quarter].[Q1], " + timeWeekly + ".[1997].[10]) ON ROWS\n" + "from [Sales]", expectedResult); } public void testDescendantsM() { assertAxisReturns( "Descendants([Time].[1997].[Q1])", "[Time].[1997].[Q1]\n" + "[Time].[1997].[Q1].[1]\n" + "[Time].[1997].[Q1].[2]\n" + "[Time].[1997].[Q1].[3]"); } public void testDescendantsDepends() { getTestContext().assertSetExprDependsOn( "Descendants([Time].[Time].CurrentMember)", "{[Time]}"); } public void testDescendantsML() { assertAxisReturns( "Descendants([Time].[1997], [Time].[Month])", months); } public void testDescendantsMLSelf() { assertAxisReturns( "Descendants([Time].[1997], [Time].[Quarter], SELF)", quarters); } public void testDescendantsMLLeaves() { assertAxisReturns( "Descendants([Time].[1997], [Time].[Year], LEAVES)", ""); assertAxisReturns( "Descendants([Time].[1997], [Time].[Quarter], LEAVES)", ""); assertAxisReturns( "Descendants([Time].[1997], [Time].[Month], LEAVES)", months); assertAxisReturns( "Descendants([Gender], [Gender].[Gender], leaves)", "[Gender].[F]\n" + "[Gender].[M]"); } public void testDescendantsMLLeavesRagged() { // no cities are at leaf level final TestContext raggedContext = getTestContext().withCube("[Sales Ragged]"); raggedContext.assertAxisReturns( "Descendants([Store].[Israel], [Store].[Store City], leaves)", ""); // all cities are leaves raggedContext.assertAxisReturns( "Descendants([Geography].[Israel], [Geography].[City], leaves)", "[Geography].[Israel].[Israel].[Haifa]\n" + "[Geography].[Israel].[Israel].[Tel Aviv]"); // No state is a leaf (not even Israel, which is both a country and a // a state, or Vatican, with is a country/state/city) raggedContext.assertAxisReturns( "Descendants([Geography], [Geography].[State], leaves)", ""); // The Vatican is a nation with no children (they're all celibate, // you know). raggedContext.assertAxisReturns( "Descendants([Geography], [Geography].[Country], leaves)", "[Geography].[Vatican]"); } public void testDescendantsMNLeaves() { // leaves at depth 0 returns the member itself assertAxisReturns( "Descendants([Time].[1997].[Q2].[4], 0, Leaves)", "[Time].[1997].[Q2].[4]"); // leaves at depth > 0 returns the member itself assertAxisReturns( "Descendants([Time].[1997].[Q2].[4], 100, Leaves)", "[Time].[1997].[Q2].[4]"); // leaves at depth < 0 returns all descendants assertAxisReturns( "Descendants([Time].[1997].[Q2], -1, Leaves)", "[Time].[1997].[Q2].[4]\n" + "[Time].[1997].[Q2].[5]\n" + "[Time].[1997].[Q2].[6]"); // leaves at depth 0 returns the member itself assertAxisReturns( "Descendants([Time].[1997].[Q2], 0, Leaves)", "[Time].[1997].[Q2].[4]\n" + "[Time].[1997].[Q2].[5]\n" + "[Time].[1997].[Q2].[6]"); assertAxisReturns( "Descendants([Time].[1997].[Q2], 3, Leaves)", "[Time].[1997].[Q2].[4]\n" + "[Time].[1997].[Q2].[5]\n" + "[Time].[1997].[Q2].[6]"); } public void testDescendantsMLSelfBefore() { assertAxisReturns( "Descendants([Time].[1997], [Time].[Quarter], SELF_AND_BEFORE)", year1997 + "\n" + quarters); } public void testDescendantsMLSelfBeforeAfter() { assertAxisReturns( "Descendants([Time].[1997], [Time].[Quarter], SELF_BEFORE_AFTER)", hierarchized1997); } public void testDescendantsMLBefore() { assertAxisReturns( "Descendants([Time].[1997], [Time].[Quarter], BEFORE)", year1997); } public void testDescendantsMLBeforeAfter() { assertAxisReturns( "Descendants([Time].[1997], [Time].[Quarter], BEFORE_AND_AFTER)", year1997 + "\n" + months); } public void testDescendantsMLAfter() { assertAxisReturns( "Descendants([Time].[1997], [Time].[Quarter], AFTER)", months); } public void testDescendantsMLAfterEnd() { assertAxisReturns( "Descendants([Time].[1997], [Time].[Month], AFTER)", ""); } public void testDescendantsM0() { assertAxisReturns( "Descendants([Time].[1997], 0)", year1997); } public void testDescendantsM2() { assertAxisReturns( "Descendants([Time].[1997], 2)", months); } public void testDescendantsM2Self() { assertAxisReturns( "Descendants([Time].[1997], 2, Self)", months); } public void testDescendantsM2Leaves() { assertAxisReturns( "Descendants([Time].[1997], 2, Leaves)", months); } public void testDescendantsMFarLeaves() { assertAxisReturns( "Descendants([Time].[1997], 10000, Leaves)", months); } public void testDescendantsMEmptyLeaves() { assertAxisReturns( "Descendants([Time].[1997], , Leaves)", months); } public void testDescendantsMEmptyLeavesFail() { assertAxisThrows( "Descendants([Time].[1997],)", "No function matches signature 'Descendants(, )"); } public void testDescendantsMEmptyLeavesFail2() { assertAxisThrows( "Descendants([Time].[1997], , AFTER)", "depth must be specified unless DESC_FLAG is LEAVES"); } public void testDescendantsMFarSelf() { assertAxisReturns( "Descendants([Time].[1997], 10000, Self)", ""); } public void testDescendantsMNY() { assertAxisReturns( "Descendants([Time].[1997], 1, BEFORE_AND_AFTER)", year1997 + "\n" + months); } public void testDescendants2ndHier() { assertAxisReturns( "Descendants([Time.Weekly].[1997].[10], [Time.Weekly].[Day])", "[Time].[Weekly].[1997].[10].[1]\n" + "[Time].[Weekly].[1997].[10].[23]\n" + "[Time].[Weekly].[1997].[10].[24]\n" + "[Time].[Weekly].[1997].[10].[25]\n" + "[Time].[Weekly].[1997].[10].[26]\n" + "[Time].[Weekly].[1997].[10].[27]\n" + "[Time].[Weekly].[1997].[10].[28]"); } public void testDescendantsParentChild() { getTestContext().withCube("HR").assertAxisReturns( "Descendants([Employees], 2)", "[Employees].[Sheri Nowmer].[Derrick Whelply]\n" + "[Employees].[Sheri Nowmer].[Michael Spence]\n" + "[Employees].[Sheri Nowmer].[Maya Gutierrez]\n" + "[Employees].[Sheri Nowmer].[Roberta Damstra]\n" + "[Employees].[Sheri Nowmer].[Rebecca Kanagaki]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz]\n" + "[Employees].[Sheri Nowmer].[Donna Arnold]"); } public void testDescendantsParentChildBefore() { getTestContext().withCube("HR").assertAxisReturns( "Descendants([Employees], 2, BEFORE)", "[Employees].[All Employees]\n" + "[Employees].[Sheri Nowmer]"); } public void testDescendantsParentChildLeaves() { final TestContext testContext = getTestContext().withCube("HR"); if (Bug.avoidSlowTestOnLucidDB(testContext.getDialect())) { return; } // leaves, restricted by level testContext.assertAxisReturns( "Descendants([Employees].[All Employees].[Sheri Nowmer].[Michael Spence], [Employees].[Employee Id], LEAVES)", "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[John Brooks]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Todd Logan]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Joshua Several]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[James Thomas]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Robert Vessa]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Bronson Jacobs]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Rebecca Barley]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Emilio Alvaro]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Becky Waters]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[A. Joyce Jarvis]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Ruby Sue Styles]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Lisa Roy]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Ingrid Burkhardt]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Todd Whitney]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Barbara Wisnewski]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Karren Burkhardt]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[John Long]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Edwin Olenzek]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Jessie Valerio]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Robert Ahlering]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Megan Burke]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Mary Sandidge].[Karel Bates]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[James Tran]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Shelley Crow]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Anne Sims]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Clarence Tatman]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Jan Nelsen]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Jeanie Glenn]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Peggy Smith]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Tish Duff]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Anita Lucero]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Stephen Burton]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Amy Consentino]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Stacie Mcanich]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Mary Browning]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Alexandra Wellington]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Cory Bacugalupi]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Stacy Rizzi]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Mike White]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Marty Simpson]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Robert Jones]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Raul Casts]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Bridget Browqett]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Monk Skonnard].[Kay Kartz]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Jeanette Cole]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Phyllis Huntsman]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Hannah Arakawa]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Wathalee Steuber]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Pamela Cox]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Helen Lutes]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Linda Ecoffey]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Katherine Swint]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Dianne Slattengren]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Ronald Heymsfield]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Steven Whitehead]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[William Sotelo]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Beth Stanley]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Jill Markwood]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Mildred Valentine]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Suzann Reams]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Audrey Wold]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Susan French]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Trish Pederson]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Eric Renn]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Elizabeth Catalano]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Christopher Beck].[Eric Coleman]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Catherine Abel]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Emilo Miller]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Daniel Wolter].[Michael John Troyer].[Hazel Walker]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Dianne Collins].[Lawrence Hurkett].[Sara Pettengill].[Linda Blasingame]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Dianne Collins].[Lawrence Hurkett].[Sara Pettengill].[Jackie Blackwell]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Dianne Collins].[Lawrence Hurkett].[Sara Pettengill].[John Ortiz]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Dianne Collins].[Lawrence Hurkett].[Sara Pettengill].[Stacey Tearpak]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Dianne Collins].[Lawrence Hurkett].[Sara Pettengill].[Fannye Weber]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Dianne Collins].[Lawrence Hurkett].[Sara Pettengill].[Diane Kabbes]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Dianne Collins].[Lawrence Hurkett].[Sara Pettengill].[Brenda Heaney]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Dianne Collins].[Lawrence Hurkett].[Sara Pettengill].[Judith Karavites]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Dianne Collins].[Lawrence Hurkett].[Jauna Elson]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Dianne Collins].[Lawrence Hurkett].[Nancy Hirota]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Dianne Collins].[Lawrence Hurkett].[Marie Moya]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Dianne Collins].[Lawrence Hurkett].[Nicky Chesnut]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Dianne Collins].[Lawrence Hurkett].[Karen Hall]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Dianne Collins].[Lawrence Hurkett].[Greg Narberes]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Dianne Collins].[Lawrence Hurkett].[Anna Townsend]\n" + "[Employees].[Sheri Nowmer].[Michael Spence].[Dianne Collins].[Lawrence Hurkett].[Carol Ann Rockne]"); // leaves, restricted by depth testContext.assertAxisReturns( "Descendants([Employees], 1, LEAVES)", ""); testContext.assertAxisReturns( "Descendants([Employees], 2, LEAVES)", "[Employees].[Sheri Nowmer].[Roberta Damstra].[Jennifer Cooper]\n" + "[Employees].[Sheri Nowmer].[Roberta Damstra].[Peggy Petty]\n" + "[Employees].[Sheri Nowmer].[Roberta Damstra].[Jessica Olguin]\n" + "[Employees].[Sheri Nowmer].[Roberta Damstra].[Phyllis Burchett]\n" + "[Employees].[Sheri Nowmer].[Rebecca Kanagaki].[Juanita Sharp]\n" + "[Employees].[Sheri Nowmer].[Rebecca Kanagaki].[Sandra Brunner]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz].[Ernest Staton]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz].[Rose Sims]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz].[Lauretta De Carlo]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz].[Mary Williams]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz].[Terri Burke]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz].[Audrey Osborn]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz].[Brian Binai]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz].[Concepcion Lozada]\n" + "[Employees].[Sheri Nowmer].[Donna Arnold].[Howard Bechard]\n" + "[Employees].[Sheri Nowmer].[Donna Arnold].[Doris Carter]"); testContext.assertAxisReturns( "Descendants([Employees], 3, LEAVES)", "[Employees].[Sheri Nowmer].[Roberta Damstra].[Jennifer Cooper]\n" + "[Employees].[Sheri Nowmer].[Roberta Damstra].[Peggy Petty]\n" + "[Employees].[Sheri Nowmer].[Roberta Damstra].[Jessica Olguin]\n" + "[Employees].[Sheri Nowmer].[Roberta Damstra].[Phyllis Burchett]\n" + "[Employees].[Sheri Nowmer].[Rebecca Kanagaki].[Juanita Sharp]\n" + "[Employees].[Sheri Nowmer].[Rebecca Kanagaki].[Sandra Brunner]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz].[Ernest Staton]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz].[Rose Sims]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz].[Lauretta De Carlo]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz].[Mary Williams]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz].[Terri Burke]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz].[Audrey Osborn]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz].[Brian Binai]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz].[Concepcion Lozada]\n" + "[Employees].[Sheri Nowmer].[Donna Arnold].[Howard Bechard]\n" + "[Employees].[Sheri Nowmer].[Donna Arnold].[Doris Carter]"); // note that depth is RELATIVE to the starting member testContext.assertAxisReturns( "Descendants([Employees].[Sheri Nowmer].[Roberta Damstra], 1, LEAVES)", "[Employees].[Sheri Nowmer].[Roberta Damstra].[Jennifer Cooper]\n" + "[Employees].[Sheri Nowmer].[Roberta Damstra].[Peggy Petty]\n" + "[Employees].[Sheri Nowmer].[Roberta Damstra].[Jessica Olguin]\n" + "[Employees].[Sheri Nowmer].[Roberta Damstra].[Phyllis Burchett]"); // Howard Bechard is a leaf member -- appears even at depth 0 testContext.assertAxisReturns( "Descendants([Employees].[All Employees].[Sheri Nowmer].[Donna Arnold].[Howard Bechard], 0, LEAVES)", "[Employees].[Sheri Nowmer].[Donna Arnold].[Howard Bechard]"); testContext.assertAxisReturns( "Descendants([Employees].[All Employees].[Sheri Nowmer].[Donna Arnold].[Howard Bechard], 1, LEAVES)", "[Employees].[Sheri Nowmer].[Donna Arnold].[Howard Bechard]"); testContext.assertExprReturns( "Count(Descendants([Employees], 2, LEAVES))", "16"); testContext.assertExprReturns( "Count(Descendants([Employees], 3, LEAVES))", "16"); testContext.assertExprReturns( "Count(Descendants([Employees], 4, LEAVES))", "63"); testContext.assertExprReturns( "Count(Descendants([Employees], 999, LEAVES))", "1,044"); // Negative depth acts like +infinity (per MSAS). Run the test several // times because we had a non-deterministic bug here. for (int i = 0; i < 100; ++i) { testContext.assertExprReturns( "Count(Descendants([Employees], -1, LEAVES))", "1,044"); } } public void testDescendantsSBA() { assertAxisReturns( "Descendants([Time].[1997], 1, SELF_BEFORE_AFTER)", hierarchized1997); } public void testDescendantsSet() { assertAxisReturns( "Descendants({[Time].[1997].[Q4], [Time].[1997].[Q2]}, 1)", "[Time].[1997].[Q4].[10]\n" + "[Time].[1997].[Q4].[11]\n" + "[Time].[1997].[Q4].[12]\n" + "[Time].[1997].[Q2].[4]\n" + "[Time].[1997].[Q2].[5]\n" + "[Time].[1997].[Q2].[6]"); assertAxisReturns( "Descendants({[Time].[1997]}, [Time].[Month], LEAVES)", months); } public void testDescendantsSetEmpty() { assertAxisThrows( "Descendants({}, 1)", "Cannot deduce type of set"); assertAxisReturns( "Descendants(Filter({[Time].[Time].Members}, 1=0), 1)", ""); } public void testRange() { assertAxisReturns( "[Time].[1997].[Q1].[2] : [Time].[1997].[Q2].[5]", "[Time].[1997].[Q1].[2]\n" + "[Time].[1997].[Q1].[3]\n" + "[Time].[1997].[Q2].[4]\n" + "[Time].[1997].[Q2].[5]"); // not parents // testcase for bug XXXXX: braces required assertQueryReturns( "with set [Set1] as '[Product].[Drink]:[Product].[Food]' \n" + "\n" + "select [Set1] on columns, {[Measures].defaultMember} on rows \n" + "\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 24,597\n" + "Row #0: 191,940\n"); } /** * tests that a null passed in returns an empty set in range function */ public void testNullRange() { assertAxisReturns( "[Time].[1997].[Q1].[2] : NULL", //[Time].[1997].[Q2].[5] ""); // Empty Set } /** * tests that an exception is thrown if both parameters in a range function * are null. */ public void testTwoNullRange() { assertAxisThrows( "NULL : NULL", "Mondrian Error:Failed to parse query 'select {NULL : NULL} on columns from Sales'"); } /** * Large dimensions use a different member reader, therefore need to * be tested separately. */ public void testRangeLarge() { assertAxisReturns( "[Customers].[USA].[CA].[San Francisco] : [Customers].[USA].[WA].[Bellingham]", "[Customers].[USA].[CA].[San Francisco]\n" + "[Customers].[USA].[CA].[San Gabriel]\n" + "[Customers].[USA].[CA].[San Jose]\n" + "[Customers].[USA].[CA].[Santa Cruz]\n" + "[Customers].[USA].[CA].[Santa Monica]\n" + "[Customers].[USA].[CA].[Spring Valley]\n" + "[Customers].[USA].[CA].[Torrance]\n" + "[Customers].[USA].[CA].[West Covina]\n" + "[Customers].[USA].[CA].[Woodland Hills]\n" + "[Customers].[USA].[OR].[Albany]\n" + "[Customers].[USA].[OR].[Beaverton]\n" + "[Customers].[USA].[OR].[Corvallis]\n" + "[Customers].[USA].[OR].[Lake Oswego]\n" + "[Customers].[USA].[OR].[Lebanon]\n" + "[Customers].[USA].[OR].[Milwaukie]\n" + "[Customers].[USA].[OR].[Oregon City]\n" + "[Customers].[USA].[OR].[Portland]\n" + "[Customers].[USA].[OR].[Salem]\n" + "[Customers].[USA].[OR].[W. Linn]\n" + "[Customers].[USA].[OR].[Woodburn]\n" + "[Customers].[USA].[WA].[Anacortes]\n" + "[Customers].[USA].[WA].[Ballard]\n" + "[Customers].[USA].[WA].[Bellingham]"); } public void testRangeStartEqualsEnd() { assertAxisReturns( "[Time].[1997].[Q3].[7] : [Time].[1997].[Q3].[7]", "[Time].[1997].[Q3].[7]"); } public void testRangeStartEqualsEndLarge() { assertAxisReturns( "[Customers].[USA].[CA] : [Customers].[USA].[CA]", "[Customers].[USA].[CA]"); } public void testRangeEndBeforeStart() { assertAxisReturns( "[Time].[1997].[Q3].[7] : [Time].[1997].[Q2].[5]", "[Time].[1997].[Q2].[5]\n" + "[Time].[1997].[Q2].[6]\n" + "[Time].[1997].[Q3].[7]"); // same as if reversed } public void testRangeEndBeforeStartLarge() { assertAxisReturns( "[Customers].[USA].[WA] : [Customers].[USA].[CA]", "[Customers].[USA].[CA]\n" + "[Customers].[USA].[OR]\n" + "[Customers].[USA].[WA]"); } public void testRangeBetweenDifferentLevelsIsError() { assertAxisThrows( "[Time].[1997].[Q2] : [Time].[1997].[Q2].[5]", "Members must belong to the same level"); } public void testRangeBoundedByAll() { assertAxisReturns( "[Gender] : [Gender]", "[Gender].[All Gender]"); } public void testRangeBoundedByAllLarge() { assertAxisReturns( "[Customers].DefaultMember : [Customers]", "[Customers].[All Customers]"); } public void testRangeBoundedByNull() { assertAxisReturns( "[Gender].[F] : [Gender].[M].NextMember", ""); } public void testRangeBoundedByNullLarge() { assertAxisReturns( "[Customers].PrevMember : [Customers].[USA].[OR]", ""); } public void testSetContainingLevelFails() { assertAxisThrows( "[Store].[Store City]", "No function matches signature '{}'"); } public void testBug715177() { assertQueryReturns( "WITH MEMBER [Product].[Non-Consumable].[Other] AS\n" + " 'Sum(Except( [Product].[Product Department].Members,\n" + " TopCount([Product].[Product Department].Members, 3)),\n" + " Measures.[Unit Sales])'\n" + "SELECT\n" + " { [Measures].[Unit Sales] } ON COLUMNS,\n" + " { TopCount([Product].[Product Department].Members,3),\n" + " [Product].[Non-Consumable].[Other] } ON ROWS\n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Drink].[Dairy]}\n" + "{[Product].[Non-Consumable].[Other]}\n" + "Row #0: 6,838\n" + "Row #1: 13,573\n" + "Row #2: 4,186\n" + "Row #3: 242,176\n"); } public void testBug714707() { // Same issue as bug 715177 -- "children" returns immutable // list, which set operator must make mutable. assertAxisReturns( "{[Store].[USA].[CA].children, [Store].[USA]}", "[Store].[USA].[CA].[Alameda]\n" + "[Store].[USA].[CA].[Beverly Hills]\n" + "[Store].[USA].[CA].[Los Angeles]\n" + "[Store].[USA].[CA].[San Diego]\n" + "[Store].[USA].[CA].[San Francisco]\n" + "[Store].[USA]"); } public void testBug715177c() { assertAxisReturns( "Order(TopCount({[Store].[USA].[CA].children}," + " [Measures].[Unit Sales], 2), [Measures].[Unit Sales])", "[Store].[USA].[CA].[Alameda]\n" + "[Store].[USA].[CA].[San Francisco]\n" + "[Store].[USA].[CA].[Beverly Hills]\n" + "[Store].[USA].[CA].[San Diego]\n" + "[Store].[USA].[CA].[Los Angeles]"); } public void testFormatFixed() { assertExprReturns( "Format(12.2, \"#,##0.00\")", "12.20"); } public void testFormatVariable() { assertExprReturns( "Format(1234.5, \"#,#\" || \"#0.00\")", "1,234.50"); } public void testFormatMember() { assertExprReturns( "Format([Store].[USA].[CA], \"#,#\" || \"#0.00\")", "74,748.00"); } public void testIIf() { assertExprReturns( "IIf(([Measures].[Unit Sales],[Product].[Drink].[Alcoholic Beverages].[Beer and Wine]) > 100, \"Yes\",\"No\")", "Yes"); } public void testIIfWithNullAndNumber() { assertExprReturns( "IIf(([Measures].[Unit Sales],[Product].[Drink].[Alcoholic Beverages].[Beer and Wine]) > 100, null,20)", ""); assertExprReturns( "IIf(([Measures].[Unit Sales],[Product].[Drink].[Alcoholic Beverages].[Beer and Wine]) > 100, 20,null)", "20"); } public void testIIfWithStringAndNull() { assertExprReturns( "IIf(([Measures].[Unit Sales],[Product].[Drink].[Alcoholic Beverages].[Beer and Wine]) > 100, null,\"foo\")", ""); assertExprReturns( "IIf(([Measures].[Unit Sales],[Product].[Drink].[Alcoholic Beverages].[Beer and Wine]) > 100, \"foo\",null)", "foo"); } public void testIsEmptyWithNull() { assertExprReturns( "iif (isempty(null), \"is empty\", \"not is empty\")", "is empty"); assertExprReturns("iif (isempty(null), 1, 2)", "1"); } public void testIIfMember() { assertAxisReturns( "IIf(1 > 2,[Store].[USA],[Store].[Canada].[BC])", "[Store].[Canada].[BC]"); } public void testIIfLevel() { assertExprReturns( "IIf(1 > 2, [Store].[Store Country],[Store].[Store City]).Name", "Store City"); } public void testIIfHierarchy() { assertExprReturns( "IIf(1 > 2, [Time], [Store]).Name", "Store"); // Call Iif(, , ). Argument #3, the // hierarchy [Time.Weekly] is implicitly converted to // the dimension [Time] to match argument #2 which is a dimension. assertExprReturns( "IIf(1 > 2, [Time], [Time.Weekly]).Name", "Time"); } public void testIIfDimension() { assertExprReturns( "IIf(1 > 2, [Store], [Time]).Name", "Time"); } public void testIIfSet() { assertAxisReturns( "IIf(1 > 2, {[Store].[USA], [Store].[USA].[CA]}, {[Store].[Mexico], [Store].[USA].[OR]})", "[Store].[Mexico]\n" + "[Store].[USA].[OR]"); } public void testDimensionCaption() { assertExprReturns("[Time].[1997].Dimension.Caption", "Time"); } public void testHierarchyCaption() { assertExprReturns("[Time].[1997].Hierarchy.Caption", "Time"); } public void testLevelCaption() { assertExprReturns("[Time].[1997].Level.Caption", "Year"); } public void testMemberCaption() { assertExprReturns("[Time].[1997].Caption", "1997"); } public void testDimensionName() { assertExprReturns("[Time].[1997].Dimension.Name", "Time"); } public void testHierarchyName() { assertExprReturns("[Time].[1997].Hierarchy.Name", "Time"); } public void testLevelName() { assertExprReturns("[Time].[1997].Level.Name", "Year"); } public void testMemberName() { assertExprReturns("[Time].[1997].Name", "1997"); // dimension name assertExprReturns("[Store].Name", "Store"); // member name assertExprReturns("[Store].DefaultMember.Name", "All Stores"); if (isDefaultNullMemberRepresentation()) { // name of null member assertExprReturns("[Store].Parent.Name", "#null"); } } public void testDimensionUniqueName() { assertExprReturns( "[Gender].DefaultMember.Dimension.UniqueName", "[Gender]"); } public void testHierarchyUniqueName() { assertExprReturns( "[Gender].DefaultMember.Hierarchy.UniqueName", "[Gender]"); } public void testLevelUniqueName() { assertExprReturns( "[Gender].DefaultMember.Level.UniqueName", "[Gender].[(All)]"); } public void testMemberUniqueName() { assertExprReturns( "[Gender].DefaultMember.UniqueName", "[Gender].[All Gender]"); } public void testMemberUniqueNameOfNull() { if (isDefaultNullMemberRepresentation()) { assertExprReturns( "[Measures].[Unit Sales].FirstChild.UniqueName", "[Measures].[#null]"); // MSOLAP gives "" here } } public void testCoalesceEmptyDepends() { getTestContext().assertExprDependsOn( "coalesceempty([Time].[1997], [Gender].[M])", TestContext.allHiers()); String s1 = TestContext.allHiersExcept("[Measures]", "[Time]"); getTestContext().assertExprDependsOn( "coalesceempty(([Measures].[Unit Sales], [Time].[1997])," + " ([Measures].[Store Sales], [Time].[1997].[Q2]))", s1); } public void testCoalesceEmpty() { // [DF] is all null and [WA] has numbers for 1997 but not for 1998. Result result = executeQuery( "with\n" + " member Measures.[Coal1] as 'coalesceempty(([Time].[1997], Measures.[Store Sales]), ([Time].[1998], Measures.[Store Sales]))'\n" + " member Measures.[Coal2] as 'coalesceempty(([Time].[1997], Measures.[Unit Sales]), ([Time].[1998], Measures.[Unit Sales]))'\n" + "select \n" + " {Measures.[Coal1], Measures.[Coal2]} on columns,\n" + " {[Store].[All Stores].[Mexico].[DF], [Store].[All Stores].[USA].[WA]} on rows\n" + "from \n" + " [Sales]"); checkDataResults( new Double[][]{ new Double[]{null, null}, new Double[]{new Double(263793.22), new Double(124366)} }, result, 0.001); result = executeQuery( "with\n" + " member Measures.[Sales Per Customer] as 'Measures.[Sales Count] / Measures.[Customer Count]'\n" + " member Measures.[Coal] as 'coalesceempty(([Measures].[Sales Per Customer], [Store].[All Stores].[Mexico].[DF]),\n" + " Measures.[Sales Per Customer])'\n" + "select \n" + " {Measures.[Sales Per Customer], Measures.[Coal]} on columns,\n" + " {[Store].[All Stores].[Mexico].[DF], [Store].[All Stores].[USA].[WA]} on rows\n" + "from \n" + " [Sales]\n" + "where\n" + " ([Time].[1997].[Q2])"); checkDataResults( new Double[][]{ new Double[]{null, null}, new Double[]{new Double(8.963), new Double(8.963)} }, result, 0.001); result = executeQuery( "with\n" + " member Measures.[Sales Per Customer] as 'Measures.[Sales Count] / Measures.[Customer Count]'\n" + " member Measures.[Coal] as 'coalesceempty(([Measures].[Sales Per Customer], [Store].[All Stores].[Mexico].[DF]),\n" + " ([Measures].[Sales Per Customer], [Store].[All Stores].[Mexico].[DF]),\n" + " ([Measures].[Sales Per Customer], [Store].[All Stores].[Mexico].[DF]),\n" + " ([Measures].[Sales Per Customer], [Store].[All Stores].[Mexico].[DF]),\n" + " ([Measures].[Sales Per Customer], [Store].[All Stores].[Mexico].[DF]),\n" + " ([Measures].[Sales Per Customer], [Store].[All Stores].[Mexico].[DF]),\n" + " ([Measures].[Sales Per Customer], [Store].[All Stores].[Mexico].[DF]),\n" + " ([Measures].[Sales Per Customer], [Store].[All Stores].[Mexico].[DF]),\n" + " Measures.[Sales Per Customer])'\n" + "select \n" + " {Measures.[Sales Per Customer], Measures.[Coal]} on columns,\n" + " {[Store].[All Stores].[Mexico].[DF], [Store].[All Stores].[USA].[WA]} on rows\n" + "from \n" + " [Sales]\n" + "where\n" + " ([Time].[1997].[Q2])"); checkDataResults( new Double[][]{ new Double[]{null, null}, new Double[]{new Double(8.963), new Double(8.963)} }, result, 0.001); } public void testBrokenContextBug() { Result result = executeQuery( "with\n" + " member Measures.[Sales Per Customer] as 'Measures.[Sales Count] / Measures.[Customer Count]'\n" + " member Measures.[Coal] as 'coalesceempty(([Measures].[Sales Per Customer], [Store].[All Stores].[Mexico].[DF]),\n" + " Measures.[Sales Per Customer])'\n" + "select \n" + " {Measures.[Coal]} on columns,\n" + " {[Store].[All Stores].[USA].[WA]} on rows\n" + "from \n" + " [Sales]\n" + "where\n" + " ([Time].[1997].[Q2])"); checkDataResults(new Double[][]{{new Double(8.963)}}, result, 0.001); } /** * Tests the function <Set>.Item(<Integer>). */ public void testSetItemInt() { assertAxisReturns( "{[Customers].[All Customers].[USA].[OR].[Lebanon].[Mary Frances Christian]}.Item(0)", "[Customers].[USA].[OR].[Lebanon].[Mary Frances Christian]"); assertAxisReturns( "{[Customers].[All Customers].[USA]," + "[Customers].[All Customers].[USA].[WA]," + "[Customers].[All Customers].[USA].[CA]," + "[Customers].[All Customers].[USA].[OR].[Lebanon].[Mary Frances Christian]}.Item(2)", "[Customers].[USA].[CA]"); assertAxisReturns( "{[Customers].[All Customers].[USA]," + "[Customers].[All Customers].[USA].[WA]," + "[Customers].[All Customers].[USA].[CA]," + "[Customers].[All Customers].[USA].[OR].[Lebanon].[Mary Frances Christian]}.Item(100 / 50 - 1)", "[Customers].[USA].[WA]"); assertAxisReturns( "{([Time].[1997].[Q1].[1], [Customers].[All Customers].[USA])," + "([Time].[1997].[Q1].[2], [Customers].[All Customers].[USA].[WA])," + "([Time].[1997].[Q1].[3], [Customers].[All Customers].[USA].[CA])," + "([Time].[1997].[Q2].[4], [Customers].[All Customers].[USA].[OR].[Lebanon].[Mary Frances Christian])}" + ".Item(100 / 50 - 1)", "{[Time].[1997].[Q1].[2], [Customers].[USA].[WA]}"); // given index out of bounds, item returns null assertAxisReturns( "{[Customers].[All Customers].[USA]," + "[Customers].[All Customers].[USA].[WA]," + "[Customers].[All Customers].[USA].[CA]," + "[Customers].[All Customers].[USA].[OR].[Lebanon].[Mary Frances Christian]}.Item(-1)", ""); // given index out of bounds, item returns null assertAxisReturns( "{[Customers].[All Customers].[USA]," + "[Customers].[All Customers].[USA].[WA]," + "[Customers].[All Customers].[USA].[CA]," + "[Customers].[All Customers].[USA].[OR].[Lebanon].[Mary Frances Christian]}.Item(4)", ""); } /** * Tests the function <Set>.Item(<String> [,...]). */ public void testSetItemString() { assertAxisReturns( "{[Gender].[M], [Gender].[F]}.Item(\"M\")", "[Gender].[M]"); assertAxisReturns( "{CrossJoin([Gender].Members, [Marital Status].Members)}.Item(\"M\", \"S\")", "{[Gender].[M], [Marital Status].[S]}"); // MSAS fails with "duplicate dimensions across (independent) axes". // (That's a bug in MSAS.) assertAxisReturns( "{CrossJoin([Gender].Members, [Marital Status].Members)}.Item(\"M\", \"M\")", "{[Gender].[M], [Marital Status].[M]}"); // None found. assertAxisReturns( "{[Gender].[M], [Gender].[F]}.Item(\"X\")", ""); assertAxisReturns( "{CrossJoin([Gender].Members, [Marital Status].Members)}.Item(\"M\", \"F\")", ""); assertAxisReturns( "CrossJoin([Gender].Members, [Marital Status].Members).Item(\"S\", \"M\")", ""); assertAxisThrows( "CrossJoin([Gender].Members, [Marital Status].Members).Item(\"M\")", "Argument count does not match set's cardinality 2"); } public void testTuple() { assertExprReturns( "([Gender].[M], " + "[Time].[Time].Children.Item(2), " + "[Measures].[Unit Sales])", "33,249"); // Calc calls MemberValue with 3 args -- more efficient than // constructing a tuple. assertExprCompilesTo( "([Gender].[M], [Time].[Time].Children.Item(2), [Measures].[Unit Sales])", "MemberArrayValueCalc(name=MemberArrayValueCalc, class=class mondrian.calc.impl.MemberArrayValueCalc, type=SCALAR, resultStyle=VALUE)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Gender].[M])\n" + " Item(name=Item, class=class mondrian.olap.fun.SetItemFunDef$5, type=MemberType, resultStyle=VALUE)\n" + " Children(name=Children, class=class mondrian.olap.fun.BuiltinFunTable$22$1, type=SetType>, resultStyle=LIST)\n" + " CurrentMemberFixed(hierarchy=[Time], name=CurrentMemberFixed, class=class mondrian.olap.fun.HierarchyCurrentMemberFunDef$FixedCalcImpl, type=MemberType, resultStyle=VALUE)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=DecimalType(0), resultStyle=VALUE_NOT_NULL, value=2)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Measures].[Unit Sales])\n"); } /** * Tests whether the tuple operator can be applied to arguments of various * types. See bug 1491699 * "ClassCastException in mondrian.calc.impl.GenericCalc.evaluat". */ public void testTupleArgTypes() { // can coerce dimensions (if they have a unique hierarchy) and // hierarchies to members assertExprReturns( "([Gender], [Time].[Time])", "266,773"); // can coerce hierarchy to member assertExprReturns( "([Gender].[M], " + TimeWeekly + ")", "135,215"); // cannot coerce level to member assertAxisThrows( "{([Gender].[M], [Store].[Store City])}", "No function matches signature '(, )'"); // coerce args (hierarchy, member, member, dimension) assertAxisReturns( "{([Time.Weekly], [Measures].[Store Sales], [Marital Status].[M], [Promotion Media])}", "{[Time].[Weekly].[All Weeklys], [Measures].[Store Sales], [Marital Status].[M], [Promotion Media].[All Media]}"); // usage of different hierarchies in the [Time] dimension assertAxisReturns( "{([Time.Weekly], [Measures].[Store Sales], [Marital Status].[M], [Time].[Time])}", "{[Time].[Weekly].[All Weeklys], [Measures].[Store Sales], [Marital Status].[M], [Time].[1997]}"); // two usages of the [Time].[Weekly] hierarchy if (MondrianProperties.instance().SsasCompatibleNaming.get()) { assertAxisThrows( "{([Time].[Weekly], [Measures].[Store Sales], [Marital Status].[M], [Time].[Weekly])}", "Tuple contains more than one member of hierarchy '[Time].[Weekly]'."); } else { assertAxisThrows( "{([Time.Weekly], [Measures].[Store Sales], [Marital Status].[M], [Time.Weekly])}", "Tuple contains more than one member of hierarchy '[Time.Weekly]'."); } // cannot coerce integer to member assertAxisThrows( "{([Gender].[M], 123)}", "No function matches signature '(, )'"); } public void testTupleItem() { assertAxisReturns( "([Time].[1997].[Q1].[1], [Customers].[All Customers].[USA].[OR], [Gender].[All Gender].[M]).item(2)", "[Gender].[M]"); assertAxisReturns( "([Time].[1997].[Q1].[1], [Customers].[All Customers].[USA].[OR], [Gender].[All Gender].[M]).item(1)", "[Customers].[USA].[OR]"); assertAxisReturns( "{[Time].[1997].[Q1].[1]}.item(0)", "[Time].[1997].[Q1].[1]"); assertAxisReturns( "{[Time].[1997].[Q1].[1]}.Item(0).Item(0)", "[Time].[1997].[Q1].[1]"); // given out of bounds index, item returns null assertAxisReturns( "([Time].[1997].[Q1].[1], [Customers].[All Customers].[USA].[OR], [Gender].[All Gender].[M]).item(-1)", ""); // given out of bounds index, item returns null assertAxisReturns( "([Time].[1997].[Q1].[1], [Customers].[All Customers].[USA].[OR], [Gender].[All Gender].[M]).item(500)", ""); // empty set assertExprReturns( "Filter([Gender].members, 1 = 0).Item(0)", ""); // empty set of unknown type assertExprReturns( "{}.Item(3)", ""); // past end of set assertExprReturns( "{[Gender].members}.Item(4)", ""); // negative index assertExprReturns( "{[Gender].members}.Item(-50)", ""); } public void testTupleAppliedToUnknownHierarchy() { // manifestation of bug 1735821 assertQueryReturns( "with \n" + "member [Product].[Test] as '([Product].[Food],Dimensions(0).defaultMember)' \n" + "select \n" + "{[Product].[Test], [Product].[Food]} on columns, \n" + "{[Measures].[Store Sales]} on rows \n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Test]}\n" + "{[Product].[Food]}\n" + "Axis #2:\n" + "{[Measures].[Store Sales]}\n" + "Row #0: 191,940.00\n" + "Row #0: 409,035.59\n"); } public void testTupleDepends() { getTestContext().assertMemberExprDependsOn( "([Store].[USA], [Gender].[F])", "{}"); getTestContext().assertMemberExprDependsOn( "([Store].[USA], [Gender])", "{[Gender]}"); // in a scalar context, the expression depends on everything except // the explicitly stated dimensions getTestContext().assertExprDependsOn( "([Store].[USA], [Gender])", TestContext.allHiersExcept("[Store]")); // The result should be all dims except [Gender], but there's a small // bug in MemberValueCalc.dependsOn where we escalate 'might depend' to // 'depends' and we return that it depends on all dimensions. getTestContext().assertExprDependsOn( "(Dimensions('Store').CurrentMember, [Gender].[F])", TestContext.allHiers()); } public void testItemNull() { // In the following queries, MSAS returns 'Formula error - object type // is not valid - in an base class. An error occurred during // attempt to get cell value'. This is because in MSAS, Item is a COM // function, and COM doesn't like null pointers. // // Mondrian represents null members as actual objects, so its behavior // is different. // MSAS returns error here. assertExprReturns( "Filter([Gender].members, 1 = 0).Item(0).Dimension.Name", "Gender"); // MSAS returns error here. assertExprReturns( "Filter([Gender].members, 1 = 0).Item(0).Parent", ""); assertExprReturns( "(Filter([Store].members, 0 = 0).Item(0).Item(0)," + "Filter([Store].members, 0 = 0).Item(0).Item(0))", "266,773"); if (isDefaultNullMemberRepresentation()) { // MSAS returns error here. assertExprReturns( "Filter([Gender].members, 1 = 0).Item(0).Name", "#null"); } } public void testTupleNull() { // if a tuple contains any null members, it evaluates to null assertQueryReturns( "select {[Measures].[Unit Sales]} on columns,\n" + " { ([Gender].[M], [Store]),\n" + " ([Gender].[F], [Store].parent),\n" + " ([Gender].parent, [Store])} on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[M], [Store].[All Stores]}\n" + "Row #0: 135,215\n"); // the set function eliminates tuples which are wholly or partially // null assertAxisReturns( "([Gender].parent, [Marital Status]),\n" // part null + " ([Gender].[M], [Marital Status].parent),\n" // part null + " ([Gender].parent, [Marital Status].parent),\n" // wholly null + " ([Gender].[M], [Marital Status])", // not null "{[Gender].[M], [Marital Status].[All Marital Status]}"); if (isDefaultNullMemberRepresentation()) { // The tuple constructor returns a null tuple if one of its // arguments is null -- and the Item function returns null if the // tuple is null. assertExprReturns( "([Gender].parent, [Marital Status]).Item(0).Name", "#null"); assertExprReturns( "([Gender].parent, [Marital Status]).Item(1).Name", "#null"); } } private void checkDataResults( Double[][] expected, Result result, final double tolerance) { int[] coords = new int[2]; for (int row = 0; row < expected.length; row++) { coords[1] = row; for (int col = 0; col < expected[0].length; col++) { coords[0] = col; Cell cell = result.getCell(coords); final Double expectedValue = expected[row][col]; if (expectedValue == null) { assertTrue("Expected null value", cell.isNull()); } else if (cell.isNull()) { fail( "Cell at (" + row + ", " + col + ") was null, but was expecting " + expectedValue); } else { assertEquals( "Incorrect value returned at (" + row + ", " + col + ")", expectedValue, ((Number) cell.getValue()).doubleValue(), tolerance); } } } } public void testLevelMemberExpressions() { // Should return Beverly Hills in California. assertAxisReturns( "[Store].[Store City].[Beverly Hills]", "[Store].[USA].[CA].[Beverly Hills]"); // There are two months named "1" in the time dimension: one // for 1997 and one for 1998. . should return // the first one. assertAxisReturns("[Time].[Month].[1]", "[Time].[1997].[Q1].[1]"); // Shouldn't be able to find a member named "Q1" on the month level. assertAxisThrows( "[Time].[Month].[Q1]", "MDX object '[Time].[Month].[Q1]' not found in cube"); } public void testCaseTestMatch() { assertExprReturns( "CASE WHEN 1=0 THEN \"first\" WHEN 1=1 THEN \"second\" WHEN 1=2 THEN \"third\" ELSE \"fourth\" END", "second"); } public void testCaseTestMatchElse() { assertExprReturns( "CASE WHEN 1=0 THEN \"first\" ELSE \"fourth\" END", "fourth"); } public void testCaseTestMatchNoElse() { assertExprReturns( "CASE WHEN 1=0 THEN \"first\" END", ""); } /** * Testcase for bug 1799391, "Case Test function throws class cast * exception" */ public void testCaseTestReturnsMemberBug1799391() { assertQueryReturns( "WITH\n" + " MEMBER [Product].[CaseTest] AS\n" + " 'CASE\n" + " WHEN [Gender].CurrentMember IS [Gender].[M] THEN [Gender].[F]\n" + " ELSE [Gender].[F]\n" + " END'\n" + " \n" + "SELECT {[Product].[CaseTest]} ON 0, {[Gender].[M]} ON 1 FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[CaseTest]}\n" + "Axis #2:\n" + "{[Gender].[M]}\n" + "Row #0: 131,558\n"); assertAxisReturns( "CASE WHEN 1+1 = 2 THEN [Gender].[F] ELSE [Gender].[F].Parent END", "[Gender].[F]"); // try case match for good measure assertAxisReturns( "CASE 1 WHEN 2 THEN [Gender].[F] ELSE [Gender].[F].Parent END", "[Gender].[All Gender]"); } public void testCaseMatch() { assertExprReturns( "CASE 2 WHEN 1 THEN \"first\" WHEN 2 THEN \"second\" WHEN 3 THEN \"third\" ELSE \"fourth\" END", "second"); } public void testCaseMatchElse() { assertExprReturns( "CASE 7 WHEN 1 THEN \"first\" ELSE \"fourth\" END", "fourth"); } public void testCaseMatchNoElse() { assertExprReturns( "CASE 8 WHEN 0 THEN \"first\" END", ""); } public void testCaseTypeMismatch() { // type mismatch between case and else assertAxisThrows( "CASE 1 WHEN 1 THEN 2 ELSE \"foo\" END", "No function matches signature"); // type mismatch between case and case assertAxisThrows( "CASE 1 WHEN 1 THEN 2 WHEN 2 THEN \"foo\" ELSE 3 END", "No function matches signature"); // type mismatch between value and case assertAxisThrows( "CASE 1 WHEN \"foo\" THEN 2 ELSE 3 END", "No function matches signature"); // non-boolean condition assertAxisThrows( "CASE WHEN 1 = 2 THEN 3 WHEN 4 THEN 5 ELSE 6 END", "No function matches signature"); } /** * Testcase for * * bug MONDRIAN-853, "When using CASE WHEN in a CalculatedMember values are * not returned the way expected". */ public void testCaseTuple() { // The case in the bug, simplified. With the bug, returns a member array // "[Lmondrian.olap.Member;@151b0a5". Type deduction should realize // that the result is a scalar, therefore a tuple (represented by a // member array) needs to be evaluated to a scalar. I think that if we // get the type deduction right, the MDX exp compiler will handle the // rest. if (false) assertExprReturns( "case 1 when 0 then 1.5\n" + " else ([Gender].[M], [Measures].[Unit Sales]) end", "135,215"); // "case when" variant always worked assertExprReturns( "case when 1=0 then 1.5\n" + " else ([Gender].[M], [Measures].[Unit Sales]) end", "135,215"); // case 2: cannot deduce type (tuple x) vs. (tuple y). Should be able // to deduce that the result type is tuple-type, // member-type>. if (false) assertExprReturns( "case when 1=0 then ([Gender].[M], [Measures].[Store Sales])\n" + " else ([Gender].[M], [Measures].[Unit Sales]) end", "xxx"); // case 3: mixture of member & tuple. Should be able to deduce that // result type is an expression. if (false) assertExprReturns( "case when 1=0 then ([Measures].[Store Sales])\n" + " else ([Gender].[M], [Measures].[Unit Sales]) end", "xxx"); } public void testPropertiesExpr() { assertExprReturns( "[Store].[USA].[CA].[Beverly Hills].[Store 6].Properties(\"Store Type\")", "Gourmet Supermarket"); } /** * Tests that non-existent property throws an error. * */ public void testPropertiesNonExistent() { assertExprThrows( "[Store].[USA].[CA].[Beverly Hills].[Store 6].Properties(\"Foo\")", "Property 'Foo' is not valid for"); } public void testPropertiesFilter() { Result result = executeQuery( "SELECT { [Store Sales] } ON COLUMNS,\n" + " TOPCOUNT(Filter( [Store].[Store Name].Members,\n" + " [Store].CurrentMember.Properties(\"Store Type\") = \"Supermarket\"),\n" + " 10, [Store Sales]) ON ROWS\n" + "FROM [Sales]"); Assert.assertEquals(8, result.getAxes()[1].getPositions().size()); } public void testPropertyInCalculatedMember() { Result result = executeQuery( "WITH MEMBER [Measures].[Store Sales per Sqft]\n" + "AS '[Measures].[Store Sales] / " + " [Store].CurrentMember.Properties(\"Store Sqft\")'\n" + "SELECT \n" + " {[Measures].[Unit Sales], [Measures].[Store Sales per Sqft]} ON COLUMNS,\n" + " {[Store].[Store Name].members} ON ROWS\n" + "FROM Sales"); Member member; Cell cell; member = result.getAxes()[1].getPositions().get(18).get(0); Assert.assertEquals( "[Store].[USA].[WA].[Bellingham].[Store 2]", member.getUniqueName()); cell = result.getCell(new int[]{0, 18}); Assert.assertEquals("2,237", cell.getFormattedValue()); cell = result.getCell(new int[]{1, 18}); Assert.assertEquals(".17", cell.getFormattedValue()); member = result.getAxes()[1].getPositions().get(3).get(0); Assert.assertEquals( "[Store].[Mexico].[DF].[San Andres].[Store 21]", member.getUniqueName()); cell = result.getCell(new int[]{0, 3}); Assert.assertEquals("", cell.getFormattedValue()); cell = result.getCell(new int[]{1, 3}); Assert.assertEquals("", cell.getFormattedValue()); } public void testOpeningPeriod() { assertAxisReturns( "OpeningPeriod([Time].[Month], [Time].[1997].[Q3])", "[Time].[1997].[Q3].[7]"); assertAxisReturns( "OpeningPeriod([Time].[Quarter], [Time].[1997])", "[Time].[1997].[Q1]"); assertAxisReturns( "OpeningPeriod([Time].[Year], [Time].[1997])", "[Time].[1997]"); assertAxisReturns( "OpeningPeriod([Time].[Month], [Time].[1997])", "[Time].[1997].[Q1].[1]"); assertAxisReturns( "OpeningPeriod([Product].[Product Name], [Product].[All Products].[Drink])", "[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Imported Beer]"); getTestContext().withCube("[Sales Ragged]").assertAxisReturns( "OpeningPeriod([Store].[Store City], [Store].[All Stores].[Israel])", "[Store].[Israel].[Israel].[Haifa]"); getTestContext().withCube("[Sales Ragged]").assertAxisReturns( "OpeningPeriod([Store].[Store State], [Store].[All Stores].[Israel])", ""); // Default member is [Time].[1997]. assertAxisReturns( "OpeningPeriod([Time].[Month])", "[Time].[1997].[Q1].[1]"); assertAxisReturns("OpeningPeriod()", "[Time].[1997].[Q1]"); TestContext testContext = getTestContext().withCube("[Sales Ragged]"); testContext.assertAxisThrows( "OpeningPeriod([Time].[Year], [Store].[All Stores].[Israel])", "The and arguments to OpeningPeriod must be " + "from the same hierarchy. The level was from '[Time]' but " + "the member was from '[Store]'."); assertAxisThrows( "OpeningPeriod([Store].[Store City])", "The and arguments to OpeningPeriod must be " + "from the same hierarchy. The level was from '[Store]' but " + "the member was from '[Time]'."); } /** * This tests new NULL functionality exception throwing * */ public void testOpeningPeriodNull() { assertAxisThrows( "OpeningPeriod([Time].[Month], NULL)", "Mondrian Error:Failed to parse query 'select {OpeningPeriod([Time].[Month], NULL)} on columns from Sales'"); } public void testLastPeriods() { assertAxisReturns( "LastPeriods(0, [Time].[1998])", ""); assertAxisReturns( "LastPeriods(1, [Time].[1998])", "[Time].[1998]"); assertAxisReturns( "LastPeriods(-1, [Time].[1998])", "[Time].[1998]"); assertAxisReturns( "LastPeriods(2, [Time].[1998])", "[Time].[1997]\n" + "[Time].[1998]"); assertAxisReturns( "LastPeriods(-2, [Time].[1997])", "[Time].[1997]\n" + "[Time].[1998]"); assertAxisReturns( "LastPeriods(5000, [Time].[1998])", "[Time].[1997]\n" + "[Time].[1998]"); assertAxisReturns( "LastPeriods(-5000, [Time].[1997])", "[Time].[1997]\n" + "[Time].[1998]"); assertAxisReturns( "LastPeriods(2, [Time].[1998].[Q2])", "[Time].[1998].[Q1]\n" + "[Time].[1998].[Q2]"); assertAxisReturns( "LastPeriods(4, [Time].[1998].[Q2])", "[Time].[1997].[Q3]\n" + "[Time].[1997].[Q4]\n" + "[Time].[1998].[Q1]\n" + "[Time].[1998].[Q2]"); assertAxisReturns( "LastPeriods(-2, [Time].[1997].[Q2])", "[Time].[1997].[Q2]\n" + "[Time].[1997].[Q3]"); assertAxisReturns( "LastPeriods(-4, [Time].[1997].[Q2])", "[Time].[1997].[Q2]\n" + "[Time].[1997].[Q3]\n" + "[Time].[1997].[Q4]\n" + "[Time].[1998].[Q1]"); assertAxisReturns( "LastPeriods(5000, [Time].[1998].[Q2])", "[Time].[1997].[Q1]\n" + "[Time].[1997].[Q2]\n" + "[Time].[1997].[Q3]\n" + "[Time].[1997].[Q4]\n" + "[Time].[1998].[Q1]\n" + "[Time].[1998].[Q2]"); assertAxisReturns( "LastPeriods(-5000, [Time].[1998].[Q2])", "[Time].[1998].[Q2]\n" + "[Time].[1998].[Q3]\n" + "[Time].[1998].[Q4]"); assertAxisReturns( "LastPeriods(2, [Time].[1998].[Q2].[5])", "[Time].[1998].[Q2].[4]\n" + "[Time].[1998].[Q2].[5]"); assertAxisReturns( "LastPeriods(12, [Time].[1998].[Q2].[5])", "[Time].[1997].[Q2].[6]\n" + "[Time].[1997].[Q3].[7]\n" + "[Time].[1997].[Q3].[8]\n" + "[Time].[1997].[Q3].[9]\n" + "[Time].[1997].[Q4].[10]\n" + "[Time].[1997].[Q4].[11]\n" + "[Time].[1997].[Q4].[12]\n" + "[Time].[1998].[Q1].[1]\n" + "[Time].[1998].[Q1].[2]\n" + "[Time].[1998].[Q1].[3]\n" + "[Time].[1998].[Q2].[4]\n" + "[Time].[1998].[Q2].[5]"); assertAxisReturns( "LastPeriods(-2, [Time].[1998].[Q2].[4])", "[Time].[1998].[Q2].[4]\n" + "[Time].[1998].[Q2].[5]"); assertAxisReturns( "LastPeriods(-12, [Time].[1997].[Q2].[6])", "[Time].[1997].[Q2].[6]\n" + "[Time].[1997].[Q3].[7]\n" + "[Time].[1997].[Q3].[8]\n" + "[Time].[1997].[Q3].[9]\n" + "[Time].[1997].[Q4].[10]\n" + "[Time].[1997].[Q4].[11]\n" + "[Time].[1997].[Q4].[12]\n" + "[Time].[1998].[Q1].[1]\n" + "[Time].[1998].[Q1].[2]\n" + "[Time].[1998].[Q1].[3]\n" + "[Time].[1998].[Q2].[4]\n" + "[Time].[1998].[Q2].[5]"); assertAxisReturns( "LastPeriods(2, [Gender].[M])", "[Gender].[F]\n" + "[Gender].[M]"); assertAxisReturns( "LastPeriods(-2, [Gender].[F])", "[Gender].[F]\n" + "[Gender].[M]"); assertAxisReturns( "LastPeriods(2, [Gender])", "[Gender].[All Gender]"); assertAxisReturns( "LastPeriods(2, [Gender].Parent)", ""); } public void testParallelPeriod() { assertAxisReturns( "parallelperiod([Time].[Quarter], 1, [Time].[1998].[Q1])", "[Time].[1997].[Q4]"); assertAxisReturns( "parallelperiod([Time].[Quarter], -1, [Time].[1997].[Q1])", "[Time].[1997].[Q2]"); assertAxisReturns( "parallelperiod([Time].[Year], 1, [Time].[1998].[Q1])", "[Time].[1997].[Q1]"); assertAxisReturns( "parallelperiod([Time].[Year], 1, [Time].[1998].[Q1].[1])", "[Time].[1997].[Q1].[1]"); // No args, therefore finds parallel period to [Time].[1997], which // would be [Time].[1996], except that that doesn't exist, so null. assertAxisReturns("ParallelPeriod()", ""); // Parallel period to [Time].[1997], which would be [Time].[1996], // except that that doesn't exist, so null. assertAxisReturns( "ParallelPeriod([Time].[Year], 1, [Time].[1997])", ""); // one parameter, level 2 above member if (isDefaultNullMemberRepresentation()) { assertQueryReturns( "WITH MEMBER [Measures].[Foo] AS \n" + " ' ParallelPeriod([Time].[Year]).UniqueName '\n" + "SELECT {[Measures].[Foo]} ON COLUMNS\n" + "FROM [Sales]\n" + "WHERE [Time].[1997].[Q3].[8]", "Axis #0:\n" + "{[Time].[1997].[Q3].[8]}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: [Time].[#null]\n"); } // one parameter, level 1 above member assertQueryReturns( "WITH MEMBER [Measures].[Foo] AS \n" + " ' ParallelPeriod([Time].[Quarter]).UniqueName '\n" + "SELECT {[Measures].[Foo]} ON COLUMNS\n" + "FROM [Sales]\n" + "WHERE [Time].[1997].[Q3].[8]", "Axis #0:\n" + "{[Time].[1997].[Q3].[8]}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: [Time].[1997].[Q2].[5]\n"); // one parameter, level same as member assertQueryReturns( "WITH MEMBER [Measures].[Foo] AS \n" + " ' ParallelPeriod([Time].[Month]).UniqueName '\n" + "SELECT {[Measures].[Foo]} ON COLUMNS\n" + "FROM [Sales]\n" + "WHERE [Time].[1997].[Q3].[8]", "Axis #0:\n" + "{[Time].[1997].[Q3].[8]}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: [Time].[1997].[Q3].[7]\n"); // one parameter, level below member if (isDefaultNullMemberRepresentation()) { assertQueryReturns( "WITH MEMBER [Measures].[Foo] AS \n" + " ' ParallelPeriod([Time].[Month]).UniqueName '\n" + "SELECT {[Measures].[Foo]} ON COLUMNS\n" + "FROM [Sales]\n" + "WHERE [Time].[1997].[Q3]", "Axis #0:\n" + "{[Time].[1997].[Q3]}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: [Time].[#null]\n"); } } public void _testParallelPeriodThrowsException() { assertQueryThrows( "select {parallelperiod([Time].[Year], 1)} on columns " + "from [Sales] where ([Time].[1998].[Q1].[2])", "This should say something about Time appearing on two different axes (slicer an columns)"); } public void testParallelPeriodDepends() { getTestContext().assertMemberExprDependsOn( "ParallelPeriod([Time].[Quarter], 2.0)", "{[Time]}"); getTestContext().assertMemberExprDependsOn( "ParallelPeriod([Time].[Quarter], 2.0, [Time].[1997].[Q3])", "{}"); getTestContext().assertMemberExprDependsOn( "ParallelPeriod()", "{[Time]}"); getTestContext().assertMemberExprDependsOn( "ParallelPeriod([Product].[Food])", "{[Product]}"); // [Gender].[M] is used here as a numeric expression! // The numeric expression DOES depend upon [Product]. // The expression as a whole depends upon everything except [Gender]. String s1 = TestContext.allHiersExcept("[Gender]"); getTestContext().assertMemberExprDependsOn( "ParallelPeriod([Product].[Product Family], [Gender].[M], [Product].[Food])", s1); // As above String s11 = TestContext.allHiersExcept("[Gender]"); getTestContext().assertMemberExprDependsOn( "ParallelPeriod([Product].[Product Family], [Gender].[M])", s11); getTestContext().assertSetExprDependsOn( "parallelperiod([Time].[Time].CurrentMember)", "{[Time]}"); } public void testParallelPeriodLevelLag() { assertQueryReturns( "with member [Measures].[Prev Unit Sales] as " + " '([Measures].[Unit Sales], parallelperiod([Time].[Quarter], 2))' " + "select " + " crossjoin({[Measures].[Unit Sales], [Measures].[Prev Unit Sales]}, {[Marital Status].[All Marital Status].children}) on columns, " + " {[Time].[1997].[Q3]} on rows " + "from " + " [Sales] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales], [Marital Status].[M]}\n" + "{[Measures].[Unit Sales], [Marital Status].[S]}\n" + "{[Measures].[Prev Unit Sales], [Marital Status].[M]}\n" + "{[Measures].[Prev Unit Sales], [Marital Status].[S]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q3]}\n" + "Row #0: 32,815\n" + "Row #0: 33,033\n" + "Row #0: 33,101\n" + "Row #0: 33,190\n"); } public void testParallelPeriodLevel() { assertQueryReturns( "with " + " member [Measures].[Prev Unit Sales] as " + " '([Measures].[Unit Sales], parallelperiod([Time].[Quarter]))' " + "select " + " crossjoin({[Measures].[Unit Sales], [Measures].[Prev Unit Sales]}, {[Marital Status].[All Marital Status].[M]}) on columns, " + " {[Time].[1997].[Q3].[8]} on rows " + "from " + " [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales], [Marital Status].[M]}\n" + "{[Measures].[Prev Unit Sales], [Marital Status].[M]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q3].[8]}\n" + "Row #0: 10,957\n" + "Row #0: 10,280\n"); } public void testPlus() { getTestContext().assertExprDependsOn("1 + 2", "{}"); String s1 = TestContext.allHiersExcept("[Measures]", "[Gender]"); getTestContext().assertExprDependsOn( "([Measures].[Unit Sales], [Gender].[F]) + 2", s1); assertExprReturns("1+2", "3"); assertExprReturns("5 + " + NullNumericExpr, "5"); // 5 + null --> 5 assertExprReturns(NullNumericExpr + " + " + NullNumericExpr, ""); assertExprReturns(NullNumericExpr + " + 0", "0"); } public void testMinus() { assertExprReturns("1-3", "-2"); assertExprReturns("5 - " + NullNumericExpr, "5"); // 5 - null --> 5 assertExprReturns(NullNumericExpr + " - - 2", "2"); assertExprReturns(NullNumericExpr + " - " + NullNumericExpr, ""); } public void testMinus_bug1234759() { assertQueryReturns( "WITH MEMBER [Customers].[USAMinusMexico]\n" + "AS '([Customers].[All Customers].[USA] - [Customers].[All Customers].[Mexico])'\n" + "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + "{[Customers].[All Customers].[USA], [Customers].[All Customers].[Mexico],\n" + "[Customers].[USAMinusMexico]} ON ROWS\n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Customers].[USA]}\n" + "{[Customers].[Mexico]}\n" + "{[Customers].[USAMinusMexico]}\n" + "Row #0: 266,773\n" + "Row #1: \n" + "Row #2: 266,773\n" // with bug 1234759, this was null + ""); } public void testMinusAssociativity() { // right-associative would give 11-(7-5) = 9, which is wrong assertExprReturns("11-7-5", "-1"); } public void testMultiply() { assertExprReturns("4*7", "28"); assertExprReturns("5 * " + NullNumericExpr, ""); // 5 * null --> null assertExprReturns(NullNumericExpr + " * - 2", ""); assertExprReturns(NullNumericExpr + " - " + NullNumericExpr, ""); } public void testMultiplyPrecedence() { assertExprReturns("3 + 4 * 5 + 6", "29"); assertExprReturns("5 * 24 / 4 * 2", "60"); assertExprReturns("48 / 4 / 2", "6"); } /** * Bug 774807 caused expressions to be mistaken for the crossjoin * operator. */ public void testMultiplyBug774807() { final String desiredResult = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[All Stores]}\n" + "Axis #2:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[A]}\n" + "Row #0: 565,238.13\n" + "Row #1: 319,494,143,605.90\n"; assertQueryReturns( "WITH MEMBER [Measures].[A] AS\n" + " '([Measures].[Store Sales] * [Measures].[Store Sales])'\n" + "SELECT {[Store]} ON COLUMNS,\n" + " {[Measures].[Store Sales], [Measures].[A]} ON ROWS\n" + "FROM Sales", desiredResult); // as above, no parentheses assertQueryReturns( "WITH MEMBER [Measures].[A] AS\n" + " '[Measures].[Store Sales] * [Measures].[Store Sales]'\n" + "SELECT {[Store]} ON COLUMNS,\n" + " {[Measures].[Store Sales], [Measures].[A]} ON ROWS\n" + "FROM Sales", desiredResult); // as above, plus 0 assertQueryReturns( "WITH MEMBER [Measures].[A] AS\n" + " '[Measures].[Store Sales] * [Measures].[Store Sales] + 0'\n" + "SELECT {[Store]} ON COLUMNS,\n" + " {[Measures].[Store Sales], [Measures].[A]} ON ROWS\n" + "FROM Sales", desiredResult); } public void testDivide() { assertExprReturns("10 / 5", "2"); assertExprReturns(NullNumericExpr + " / - 2", ""); assertExprReturns(NullNumericExpr + " / " + NullNumericExpr, ""); boolean origNullDenominatorProducesNull = MondrianProperties.instance().NullDenominatorProducesNull.get(); try { // default behavior MondrianProperties.instance().NullDenominatorProducesNull.set( false); assertExprReturns("-2 / " + NullNumericExpr, "Infinity"); assertExprReturns("0 / 0", "NaN"); assertExprReturns("-3 / (2 - 2)", "-Infinity"); assertExprReturns("NULL/1", ""); assertExprReturns("NULL/NULL", ""); assertExprReturns("1/NULL", "Infinity"); // when NullOrZeroDenominatorProducesNull is set to true MondrianProperties.instance().NullDenominatorProducesNull.set(true); assertExprReturns("-2 / " + NullNumericExpr, ""); assertExprReturns("0 / 0", "NaN"); assertExprReturns("-3 / (2 - 2)", "-Infinity"); assertExprReturns("NULL/1", ""); assertExprReturns("NULL/NULL", ""); assertExprReturns("1/NULL", ""); } finally { MondrianProperties.instance().NullDenominatorProducesNull.set( origNullDenominatorProducesNull); } } public void testDividePrecedence() { assertExprReturns("24 / 4 / 2 * 10 - -1", "31"); } public void testMod() { // the following tests are consistent with excel xp assertExprReturns("mod(11, 3)", "2"); assertExprReturns("mod(-12, 3)", "0"); // can handle non-ints, using the formula MOD(n, d) = n - d * INT(n / d) assertExprReturns("mod(7.2, 3)", 1.2, 0.0001); assertExprReturns("mod(7.2, 3.2)", .8, 0.0001); assertExprReturns("mod(7.2, -3.2)", -2.4, 0.0001); // per Excel doc "sign of result is same as divisor" assertExprReturns("mod(3, 2)", "1"); assertExprReturns("mod(-3, 2)", "1"); assertExprReturns("mod(3, -2)", "-1"); assertExprReturns("mod(-3, -2)", "-1"); assertExprThrows( "mod(4, 0)", "java.lang.ArithmeticException: / by zero"); assertExprThrows( "mod(0, 0)", "java.lang.ArithmeticException: / by zero"); } public void testUnaryMinus() { assertExprReturns("-3", "-3"); } public void testUnaryMinusMember() { assertExprReturns( "- ([Measures].[Unit Sales],[Gender].[F])", "-131,558"); } public void testUnaryMinusPrecedence() { assertExprReturns("1 - -10.5 * 2 -3", "19"); } public void testNegativeZero() { assertExprReturns("-0.0", "0"); } public void testNegativeZero1() { assertExprReturns("-(0.0)", "0"); } public void testNegativeZeroSubtract() { assertExprReturns("-0.0 - 0.0", "0"); } public void testNegativeZeroMultiply() { assertExprReturns("-1 * 0", "0"); } public void testNegativeZeroDivide() { assertExprReturns("-0.0 / 2", "0"); } public void testString() { // The String(Integer,Char) function requires us to implicitly cast a // string to a char. assertQueryReturns( "with member measures.x as 'String(3, \"yahoo\")'\n" + "select measures.x on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[x]}\n" + "Row #0: yyy\n"); // String is converted to char by taking first character assertExprReturns("String(3, \"yahoo\")", "yyy"); // SSAS agrees // Integer is converted to char by converting to string and taking first // character if (Bug.Ssas2005Compatible) { // SSAS2005 can implicitly convert an integer (32) to a string, and // then to a char by taking the first character. Mondrian requires // an explicit cast. assertExprReturns("String(3, 32)", "333"); assertExprReturns("String(8, -5)", "--------"); } else { assertExprReturns("String(3, Cast(32 as string))", "333"); assertExprReturns("String(8, Cast(-5 as string))", "--------"); } // Error if length<0 assertExprReturns("String(0, 'x')", ""); // SSAS agrees assertExprThrows( "String(-1, 'x')", "NegativeArraySizeException"); // SSAS agrees assertExprThrows( "String(-200, 'x')", "NegativeArraySizeException"); // SSAS agrees } public void testStringConcat() { assertExprReturns( " \"foo\" || \"bar\" ", "foobar"); } public void testStringConcat2() { assertExprReturns( " \"foo\" || [Gender].[M].Name || \"\" ", "fooM"); } public void testAnd() { assertBooleanExprReturns(" 1=1 AND 2=2 ", true); } public void testAnd2() { assertBooleanExprReturns(" 1=1 AND 2=0 ", false); } public void testOr() { assertBooleanExprReturns(" 1=0 OR 2=0 ", false); } public void testOr2() { assertBooleanExprReturns(" 1=0 OR 0=0 ", true); } public void testOrAssociativity1() { // Would give 'false' if OR were stronger than AND (wrong!) assertBooleanExprReturns(" 1=1 AND 1=0 OR 1=1 ", true); } public void testOrAssociativity2() { // Would give 'false' if OR were stronger than AND (wrong!) assertBooleanExprReturns(" 1=1 OR 1=0 AND 1=1 ", true); } public void testOrAssociativity3() { assertBooleanExprReturns(" (1=0 OR 1=1) AND 1=1 ", true); } public void testXor() { assertBooleanExprReturns(" 1=1 XOR 2=2 ", false); } public void testXorAssociativity() { // Would give 'false' if XOR were stronger than AND (wrong!) assertBooleanExprReturns(" 1 = 1 AND 1 = 1 XOR 1 = 0 ", true); } public void testNonEmptyCrossJoin() { // NonEmptyCrossJoin needs to evaluate measures to find out whether // cells are empty, so it implicitly depends upon all dimensions. String s1 = TestContext.allHiersExcept("[Store]"); getTestContext().assertSetExprDependsOn( "NonEmptyCrossJoin([Store].[USA].Children, [Gender].Children)", s1); assertAxisReturns( "NonEmptyCrossJoin(" + "[Customers].[All Customers].[USA].[CA].Children, " + "[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].Children)", "{[Customers].[USA].[CA].[Bellflower], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Light Beer]}\n" + "{[Customers].[USA].[CA].[Downey], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Imported Beer]}\n" + "{[Customers].[USA].[CA].[Glendale], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Imported Beer]}\n" + "{[Customers].[USA].[CA].[Glendale], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Light Beer]}\n" + "{[Customers].[USA].[CA].[Grossmont], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Light Beer]}\n" + "{[Customers].[USA].[CA].[Imperial Beach], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Light Beer]}\n" + "{[Customers].[USA].[CA].[La Jolla], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Imported Beer]}\n" + "{[Customers].[USA].[CA].[Lincoln Acres], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Imported Beer]}\n" + "{[Customers].[USA].[CA].[Lincoln Acres], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Light Beer]}\n" + "{[Customers].[USA].[CA].[Long Beach], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Light Beer]}\n" + "{[Customers].[USA].[CA].[Los Angeles], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Imported Beer]}\n" + "{[Customers].[USA].[CA].[Newport Beach], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Imported Beer]}\n" + "{[Customers].[USA].[CA].[Pomona], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Imported Beer]}\n" + "{[Customers].[USA].[CA].[Pomona], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Light Beer]}\n" + "{[Customers].[USA].[CA].[San Gabriel], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Light Beer]}\n" + "{[Customers].[USA].[CA].[West Covina], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Imported Beer]}\n" + "{[Customers].[USA].[CA].[West Covina], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Light Beer]}\n" + "{[Customers].[USA].[CA].[Woodland Hills], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Imported Beer]}"); // empty set assertAxisReturns( "NonEmptyCrossJoin({Gender.Parent}, {Store.Parent})", ""); assertAxisReturns( "NonEmptyCrossJoin({Store.Parent}, Gender.Children)", ""); assertAxisReturns("NonEmptyCrossJoin(Store.Members, {})", ""); // same dimension twice // todo: should throw if (false) { assertAxisThrows( "NonEmptyCrossJoin({Store.[USA]}, {Store.[USA].[CA]})", "xxx"); } } public void testNot() { assertBooleanExprReturns(" NOT 1=1 ", false); } public void testNotNot() { assertBooleanExprReturns(" NOT NOT 1=1 ", true); } public void testNotAssociativity() { assertBooleanExprReturns(" 1=1 AND NOT 1=1 OR NOT 1=1 AND 1=1 ", false); } public void testIsNull() { assertBooleanExprReturns(" Store.[All Stores] IS NULL ", false); assertBooleanExprReturns(" Store.[All Stores].parent IS NULL ", true); } public void testIsMember() { assertBooleanExprReturns( " Store.[USA].parent IS Store.[All Stores]", true); assertBooleanExprReturns( " [Store].[USA].[CA].parent IS [Store].[Mexico]", false); } public void testIsString() { assertExprThrows( " [Store].[USA].Name IS \"USA\" ", "No function matches signature ' IS '"); } public void testIsNumeric() { assertExprThrows( " [Store].[USA].Level.Ordinal IS 25 ", "No function matches signature ' IS '"); } public void testIsTuple() { assertBooleanExprReturns( " (Store.[USA], Gender.[M]) IS (Store.[USA], Gender.[M])", true); assertBooleanExprReturns( " (Store.[USA], Gender.[M]) IS (Gender.[M], Store.[USA])", true); assertBooleanExprReturns( " (Store.[USA], Gender.[M]) IS (Gender.[M], Store.[USA]) " + "OR [Gender] IS NULL", true); assertBooleanExprReturns( " (Store.[USA], Gender.[M]) IS (Gender.[M], Store.[USA]) " + "AND [Gender] IS NULL", false); assertBooleanExprReturns( " (Store.[USA], Gender.[M]) IS (Store.[USA], Gender.[F])", false); assertBooleanExprReturns( " (Store.[USA], Gender.[M]) IS (Store.[USA])", false); assertBooleanExprReturns( " (Store.[USA], Gender.[M]) IS Store.[USA]", false); } public void testIsLevel() { assertBooleanExprReturns( " Store.[USA].level IS Store.[Store Country] ", true); assertBooleanExprReturns( " Store.[USA].[CA].level IS Store.[Store Country] ", false); } public void testIsHierarchy() { assertBooleanExprReturns( " Store.[USA].hierarchy IS Store.[Mexico].hierarchy ", true); assertBooleanExprReturns( " Store.[USA].hierarchy IS Gender.[M].hierarchy ", false); } public void testIsDimension() { assertBooleanExprReturns(" Store.[USA].dimension IS Store ", true); assertBooleanExprReturns(" Gender.[M].dimension IS Store ", false); } public void testStringEquals() { assertBooleanExprReturns(" \"foo\" = \"bar\" ", false); } public void testStringEqualsAssociativity() { assertBooleanExprReturns(" \"foo\" = \"fo\" || \"o\" ", true); } public void testStringEqualsEmpty() { assertBooleanExprReturns(" \"\" = \"\" ", true); } public void testEq() { assertBooleanExprReturns(" 1.0 = 1 ", true); assertBooleanExprReturns( "[Product].CurrentMember.Level.Ordinal = 2.0", false); checkNullOp("="); } public void testStringNe() { assertBooleanExprReturns(" \"foo\" <> \"bar\" ", true); } public void testNe() { assertBooleanExprReturns(" 2 <> 1.0 + 1.0 ", false); checkNullOp("<>"); } public void testNeInfinity() { // Infinity does not equal itself assertBooleanExprReturns("(1 / 0) <> (1 / 0)", false); } public void testLt() { assertBooleanExprReturns(" 2 < 1.0 + 1.0 ", false); checkNullOp("<"); } public void testLe() { assertBooleanExprReturns(" 2 <= 1.0 + 1.0 ", true); checkNullOp("<="); } public void testGt() { assertBooleanExprReturns(" 2 > 1.0 + 1.0 ", false); checkNullOp(">"); } public void testGe() { assertBooleanExprReturns(" 2 > 1.0 + 1.0 ", false); checkNullOp(">="); } private void checkNullOp(final String op) { assertBooleanExprReturns(" 0 " + op + " " + NullNumericExpr, false); assertBooleanExprReturns(NullNumericExpr + " " + op + " 0", false); assertBooleanExprReturns( NullNumericExpr + " " + op + " " + NullNumericExpr, false); } public void testDistinctTwoMembers() { getTestContext().withCube("HR").assertAxisReturns( "Distinct({[Employees].[All Employees].[Sheri Nowmer].[Donna Arnold]," + "[Employees].[Sheri Nowmer].[Donna Arnold]})", "[Employees].[Sheri Nowmer].[Donna Arnold]"); } public void testDistinctThreeMembers() { getTestContext().withCube("HR").assertAxisReturns( "Distinct({[Employees].[All Employees].[Sheri Nowmer].[Donna Arnold]," + "[Employees].[All Employees].[Sheri Nowmer].[Darren Stanz]," + "[Employees].[All Employees].[Sheri Nowmer].[Donna Arnold]})", "[Employees].[Sheri Nowmer].[Donna Arnold]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz]"); } public void testDistinctFourMembers() { getTestContext().withCube("HR").assertAxisReturns( "Distinct({[Employees].[All Employees].[Sheri Nowmer].[Donna Arnold]," + "[Employees].[All Employees].[Sheri Nowmer].[Darren Stanz]," + "[Employees].[All Employees].[Sheri Nowmer].[Donna Arnold]," + "[Employees].[All Employees].[Sheri Nowmer].[Darren Stanz]})", "[Employees].[Sheri Nowmer].[Donna Arnold]\n" + "[Employees].[Sheri Nowmer].[Darren Stanz]"); } public void testDistinctTwoTuples() { getTestContext().assertAxisReturns( "Distinct({([Time].[1997],[Store].[All Stores].[Mexico]), " + "([Time].[1997], [Store].[All Stores].[Mexico])})", "{[Time].[1997], [Store].[Mexico]}"); } public void testDistinctSomeTuples() { getTestContext().assertAxisReturns( "Distinct({([Time].[1997],[Store].[All Stores].[Mexico]), " + "crossjoin({[Time].[1997]},{[Store].[All Stores].children})})", "{[Time].[1997], [Store].[Mexico]}\n" + "{[Time].[1997], [Store].[Canada]}\n" + "{[Time].[1997], [Store].[USA]}"); } /** * Make sure that slicer is in force when expression is applied * on axis, E.g. select filter([Customers].members, [Unit Sales] > 100) * from sales where ([Time].[1998]) */ public void testFilterWithSlicer() { Result result = executeQuery( "select {[Measures].[Unit Sales]} on columns,\n" + " filter([Customers].[USA].children,\n" + " [Measures].[Unit Sales] > 20000) on rows\n" + "from Sales\n" + "where ([Time].[1997].[Q1])"); Axis rows = result.getAxes()[1]; // if slicer were ignored, there would be 3 rows Assert.assertEquals(1, rows.getPositions().size()); Cell cell = result.getCell(new int[]{0, 0}); Assert.assertEquals("30,114", cell.getFormattedValue()); } public void testFilterCompound() { Result result = executeQuery( "select {[Measures].[Unit Sales]} on columns,\n" + " Filter(\n" + " CrossJoin(\n" + " [Gender].Children,\n" + " [Customers].[USA].Children),\n" + " [Measures].[Unit Sales] > 9500) on rows\n" + "from Sales\n" + "where ([Time].[1997].[Q1])"); List rows = result.getAxes()[1].getPositions(); Assert.assertEquals(3, rows.size()); Assert.assertEquals("F", rows.get(0).get(0).getName()); Assert.assertEquals("WA", rows.get(0).get(1).getName()); Assert.assertEquals("M", rows.get(1).get(0).getName()); Assert.assertEquals("OR", rows.get(1).get(1).getName()); Assert.assertEquals("M", rows.get(2).get(0).getName()); Assert.assertEquals("WA", rows.get(2).get(1).getName()); } public void testGenerateDepends() { getTestContext().assertSetExprDependsOn( "Generate([Product].CurrentMember.Children, Crossjoin({[Product].CurrentMember}, Crossjoin([Store].[Store State].Members, [Store Type].Members)), ALL)", "{[Product]}"); getTestContext().assertSetExprDependsOn( "Generate([Product].[All Products].Children, Crossjoin({[Product].CurrentMember}, Crossjoin([Store].[Store State].Members, [Store Type].Members)), ALL)", "{}"); getTestContext().assertSetExprDependsOn( "Generate({[Store].[USA], [Store].[USA].[CA]}, {[Store].CurrentMember.Children})", "{}"); getTestContext().assertSetExprDependsOn( "Generate({[Store].[USA], [Store].[USA].[CA]}, {[Gender].CurrentMember})", "{[Gender]}"); getTestContext().assertSetExprDependsOn( "Generate({[Store].[USA], [Store].[USA].[CA]}, {[Gender].[M]})", "{}"); } public void testGenerate() { assertAxisReturns( "Generate({[Store].[USA], [Store].[USA].[CA]}, {[Store].CurrentMember.Children})", "[Store].[USA].[CA]\n" + "[Store].[USA].[OR]\n" + "[Store].[USA].[WA]\n" + "[Store].[USA].[CA].[Alameda]\n" + "[Store].[USA].[CA].[Beverly Hills]\n" + "[Store].[USA].[CA].[Los Angeles]\n" + "[Store].[USA].[CA].[San Diego]\n" + "[Store].[USA].[CA].[San Francisco]"); } public void testGenerateNonSet() { // SSAS implicitly converts arg #2 to a set assertAxisReturns( "Generate({[Store].[USA], [Store].[USA].[CA]}, [Store].PrevMember, ALL)", "[Store].[Mexico]\n" + "[Store].[Mexico].[Zacatecas]"); // SSAS implicitly converts arg #1 to a set assertAxisReturns( "Generate([Store].[USA], [Store].PrevMember, ALL)", "[Store].[Mexico]"); } public void testGenerateAll() { assertAxisReturns( "Generate({[Store].[USA].[CA], [Store].[USA].[OR].[Portland]}," + " Ascendants([Store].CurrentMember)," + " ALL)", "[Store].[USA].[CA]\n" + "[Store].[USA]\n" + "[Store].[All Stores]\n" + "[Store].[USA].[OR].[Portland]\n" + "[Store].[USA].[OR]\n" + "[Store].[USA]\n" + "[Store].[All Stores]"); } public void testGenerateUnique() { assertAxisReturns( "Generate({[Store].[USA].[CA], [Store].[USA].[OR].[Portland]}," + " Ascendants([Store].CurrentMember))", "[Store].[USA].[CA]\n" + "[Store].[USA]\n" + "[Store].[All Stores]\n" + "[Store].[USA].[OR].[Portland]\n" + "[Store].[USA].[OR]"); } public void testGenerateUniqueTuple() { assertAxisReturns( "Generate({([Store].[USA].[CA],[Product].[All Products]), " + "([Store].[USA].[CA],[Product].[All Products])}," + "{([Store].CurrentMember, [Product].CurrentMember)})", "{[Store].[USA].[CA], [Product].[All Products]}"); } public void testGenerateCrossJoin() { // Note that the different regions have different Top 2. assertAxisReturns( "Generate({[Store].[USA].[CA], [Store].[USA].[CA].[San Francisco]},\n" + " CrossJoin({[Store].CurrentMember},\n" + " TopCount([Product].[Brand Name].members, \n" + " 2,\n" + " [Measures].[Unit Sales])))", "{[Store].[USA].[CA], [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos]}\n" + "{[Store].[USA].[CA], [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale]}\n" + "{[Store].[USA].[CA].[San Francisco], [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony]}\n" + "{[Store].[USA].[CA].[San Francisco], [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top]}"); } public void testGenerateString() { assertExprReturns( "Generate({Time.[1997], Time.[1998]}," + " Time.[Time].CurrentMember.Name)", "19971998"); assertExprReturns( "Generate({Time.[1997], Time.[1998]}," + " Time.[Time].CurrentMember.Name, \" and \")", "1997 and 1998"); } public void testHead() { assertAxisReturns( "Head([Store].Children, 2)", "[Store].[Canada]\n" + "[Store].[Mexico]"); } public void testHeadNegative() { assertAxisReturns( "Head([Store].Children, 2 - 3)", ""); } public void testHeadDefault() { assertAxisReturns( "Head([Store].Children)", "[Store].[Canada]"); } public void testHeadOvershoot() { assertAxisReturns( "Head([Store].Children, 2 + 2)", "[Store].[Canada]\n" + "[Store].[Mexico]\n" + "[Store].[USA]"); } public void testHeadEmpty() { assertAxisReturns( "Head([Gender].[F].Children, 2)", ""); assertAxisReturns( "Head([Gender].[F].Children)", ""); } /** * Test case for bug 2488492, "Union between calc mem and head function * throws exception" */ public void testHeadBug() { assertQueryReturns( "SELECT\n" + " UNION(\n" + " {([Customers].CURRENTMEMBER)},\n" + " HEAD(\n" + " {([Customers].CURRENTMEMBER)},\n" + " IIF(\n" + " COUNT(\n" + " FILTER(\n" + " DESCENDANTS(\n" + " [Customers].CURRENTMEMBER,\n" + " [Customers].[Country]),\n" + " [Measures].[Unit Sales] >= 66),\n" + " INCLUDEEMPTY)> 0,\n" + " 1,\n" + " 0)),\n" + " ALL)\n" + " ON AXIS(0)\n" + "FROM\n" + " [Sales]\n", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[All Customers]}\n" + "{[Customers].[All Customers]}\n" + "Row #0: 266,773\n" + "Row #0: 266,773\n"); assertQueryReturns( "WITH\n" + " MEMBER\n" + " [Customers].[COG_OQP_INT_t2]AS '1',\n" + " SOLVE_ORDER = 65535\n" + "SELECT\n" + " UNION(\n" + " {([Customers].[COG_OQP_INT_t2])},\n" + " HEAD(\n" + " {([Customers].CURRENTMEMBER)},\n" + " IIF(\n" + " COUNT(\n" + " FILTER(\n" + " DESCENDANTS(\n" + " [Customers].CURRENTMEMBER,\n" + " [Customers].[Country]),\n" + " [Measures].[Unit Sales]>= 66),\n" + " INCLUDEEMPTY)> 0,\n" + " 1,\n" + " 0)),\n" + " ALL)\n" + " ON AXIS(0)\n" + "FROM\n" + " [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[COG_OQP_INT_t2]}\n" + "{[Customers].[All Customers]}\n" + "Row #0: 1\n" + "Row #0: 266,773\n"); // More minimal test case. Also demonstrates similar problem with Tail. assertAxisReturns( "Union(\n" + " Union(\n" + " Tail([Customers].[USA].[CA].Children, 2),\n" + " Head([Customers].[USA].[WA].Children, 2),\n" + " ALL),\n" + " Tail([Customers].[USA].[OR].Children, 2)," + " ALL)", "[Customers].[USA].[CA].[West Covina]\n" + "[Customers].[USA].[CA].[Woodland Hills]\n" + "[Customers].[USA].[WA].[Anacortes]\n" + "[Customers].[USA].[WA].[Ballard]\n" + "[Customers].[USA].[OR].[W. Linn]\n" + "[Customers].[USA].[OR].[Woodburn]"); } public void testHierarchize() { assertAxisReturns( "Hierarchize(\n" + " {[Product].[All Products], " + " [Product].[Food],\n" + " [Product].[Drink],\n" + " [Product].[Non-Consumable],\n" + " [Product].[Food].[Eggs],\n" + " [Product].[Drink].[Dairy]})", "[Product].[All Products]\n" + "[Product].[Drink]\n" + "[Product].[Drink].[Dairy]\n" + "[Product].[Food]\n" + "[Product].[Food].[Eggs]\n" + "[Product].[Non-Consumable]"); } public void testHierarchizePost() { assertAxisReturns( "Hierarchize(\n" + " {[Product].[All Products], " + " [Product].[Food],\n" + " [Product].[Food].[Eggs],\n" + " [Product].[Drink].[Dairy]},\n" + " POST)", "[Product].[Drink].[Dairy]\n" + "[Product].[Food].[Eggs]\n" + "[Product].[Food]\n" + "[Product].[All Products]"); } public void testHierarchizePC() { getTestContext().withCube("HR").assertAxisReturns( "Hierarchize(\n" + " { Subset([Employees].Members, 90, 10),\n" + " Head([Employees].Members, 5) })", "[Employees].[All Employees]\n" + "[Employees].[Sheri Nowmer]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Shauna Wyro]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Cheryl Thorton].[Leopoldo Renfro]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Cheryl Thorton].[Donna Brockett]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Cheryl Thorton].[Laurie Anderson]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Cheryl Thorton].[Louis Gomez]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Cheryl Thorton].[Melvin Glass]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Cheryl Thorton].[Kristin Cohen]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Cheryl Thorton].[Susan Kharman]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Cheryl Thorton].[Gordon Kirschner]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Cheryl Thorton].[Geneva Kouba]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Cheryl Thorton].[Tricia Clark]"); } public void testHierarchizeCrossJoinPre() { assertAxisReturns( "Hierarchize(\n" + " CrossJoin(\n" + " {[Product].[All Products], " + " [Product].[Food],\n" + " [Product].[Food].[Eggs],\n" + " [Product].[Drink].[Dairy]},\n" + " [Gender].MEMBERS),\n" + " PRE)", "{[Product].[All Products], [Gender].[All Gender]}\n" + "{[Product].[All Products], [Gender].[F]}\n" + "{[Product].[All Products], [Gender].[M]}\n" + "{[Product].[Drink].[Dairy], [Gender].[All Gender]}\n" + "{[Product].[Drink].[Dairy], [Gender].[F]}\n" + "{[Product].[Drink].[Dairy], [Gender].[M]}\n" + "{[Product].[Food], [Gender].[All Gender]}\n" + "{[Product].[Food], [Gender].[F]}\n" + "{[Product].[Food], [Gender].[M]}\n" + "{[Product].[Food].[Eggs], [Gender].[All Gender]}\n" + "{[Product].[Food].[Eggs], [Gender].[F]}\n" + "{[Product].[Food].[Eggs], [Gender].[M]}"); } public void testHierarchizeCrossJoinPost() { assertAxisReturns( "Hierarchize(\n" + " CrossJoin(\n" + " {[Product].[All Products], " + " [Product].[Food],\n" + " [Product].[Food].[Eggs],\n" + " [Product].[Drink].[Dairy]},\n" + " [Gender].MEMBERS),\n" + " POST)", "{[Product].[Drink].[Dairy], [Gender].[F]}\n" + "{[Product].[Drink].[Dairy], [Gender].[M]}\n" + "{[Product].[Drink].[Dairy], [Gender].[All Gender]}\n" + "{[Product].[Food].[Eggs], [Gender].[F]}\n" + "{[Product].[Food].[Eggs], [Gender].[M]}\n" + "{[Product].[Food].[Eggs], [Gender].[All Gender]}\n" + "{[Product].[Food], [Gender].[F]}\n" + "{[Product].[Food], [Gender].[M]}\n" + "{[Product].[Food], [Gender].[All Gender]}\n" + "{[Product].[All Products], [Gender].[F]}\n" + "{[Product].[All Products], [Gender].[M]}\n" + "{[Product].[All Products], [Gender].[All Gender]}"); } /** * Tests that the Hierarchize function works correctly when applied to * a level whose ordering is determined by an 'ordinal' property. * TODO: fix this test (bug 1220787) * * WG: Note that this is disabled right now due to its impact on other * tests later on within the test suite, specifically XMLA tests that * return a list of cubes. We could run this test after XMLA, or clear * out the cache to solve this. */ public void testHierarchizeOrdinal() { TestContext context = getTestContext().withCube("[Sales_Hierarchize]"); final Connection connection = context.getConnection(); connection.getSchema().createCube( "\n" + "

\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + "\n" + " \n" + ""); // The [Time_Alphabetical] is ordered alphabetically by month context.assertAxisReturns( "Hierarchize([Time_Alphabetical].members)", "[Time_Alphabetical].[1997]\n" + "[Time_Alphabetical].[1997].[Q1]\n" + "[Time_Alphabetical].[1997].[Q1].[2]\n" + "[Time_Alphabetical].[1997].[Q1].[1]\n" + "[Time_Alphabetical].[1997].[Q1].[3]\n" + "[Time_Alphabetical].[1997].[Q2]\n" + "[Time_Alphabetical].[1997].[Q2].[4]\n" + "[Time_Alphabetical].[1997].[Q2].[6]\n" + "[Time_Alphabetical].[1997].[Q2].[5]\n" + "[Time_Alphabetical].[1997].[Q3]\n" + "[Time_Alphabetical].[1997].[Q3].[8]\n" + "[Time_Alphabetical].[1997].[Q3].[7]\n" + "[Time_Alphabetical].[1997].[Q3].[9]\n" + "[Time_Alphabetical].[1997].[Q4]\n" + "[Time_Alphabetical].[1997].[Q4].[12]\n" + "[Time_Alphabetical].[1997].[Q4].[11]\n" + "[Time_Alphabetical].[1997].[Q4].[10]\n" + "[Time_Alphabetical].[1998]\n" + "[Time_Alphabetical].[1998].[Q1]\n" + "[Time_Alphabetical].[1998].[Q1].[2]\n" + "[Time_Alphabetical].[1998].[Q1].[1]\n" + "[Time_Alphabetical].[1998].[Q1].[3]\n" + "[Time_Alphabetical].[1998].[Q2]\n" + "[Time_Alphabetical].[1998].[Q2].[4]\n" + "[Time_Alphabetical].[1998].[Q2].[6]\n" + "[Time_Alphabetical].[1998].[Q2].[5]\n" + "[Time_Alphabetical].[1998].[Q3]\n" + "[Time_Alphabetical].[1998].[Q3].[8]\n" + "[Time_Alphabetical].[1998].[Q3].[7]\n" + "[Time_Alphabetical].[1998].[Q3].[9]\n" + "[Time_Alphabetical].[1998].[Q4]\n" + "[Time_Alphabetical].[1998].[Q4].[12]\n" + "[Time_Alphabetical].[1998].[Q4].[11]\n" + "[Time_Alphabetical].[1998].[Q4].[10]"); // The [Month_Alphabetical] is a single-level hierarchy ordered // alphabetically by month. context.assertAxisReturns( "Hierarchize([Month_Alphabetical].members)", "[Month_Alphabetical].[4]\n" + "[Month_Alphabetical].[8]\n" + "[Month_Alphabetical].[12]\n" + "[Month_Alphabetical].[2]\n" + "[Month_Alphabetical].[1]\n" + "[Month_Alphabetical].[7]\n" + "[Month_Alphabetical].[6]\n" + "[Month_Alphabetical].[3]\n" + "[Month_Alphabetical].[5]\n" + "[Month_Alphabetical].[11]\n" + "[Month_Alphabetical].[10]\n" + "[Month_Alphabetical].[9]"); // clear the cache so that future tests don't fail that expect a // specific set of cubes TestContext.instance().flushSchemaCache(); } public void testIntersectAll() { // Note: duplicates retained from left, not from right; and order is // preserved. assertAxisReturns( "Intersect({[Time].[1997].[Q2], [Time].[1997], [Time].[1997].[Q1], [Time].[1997].[Q2]}, " + "{[Time].[1998], [Time].[1997], [Time].[1997].[Q2], [Time].[1997]}, " + "ALL)", "[Time].[1997].[Q2]\n" + "[Time].[1997]\n" + "[Time].[1997].[Q2]"); } public void testIntersect() { // Duplicates not preserved. Output in order that first duplicate // occurred. assertAxisReturns( "Intersect(\n" + " {[Time].[1997].[Q2], [Time].[1997], [Time].[1997].[Q1], [Time].[1997].[Q2]}, " + "{[Time].[1998], [Time].[1997], [Time].[1997].[Q2], [Time].[1997]})", "[Time].[1997].[Q2]\n" + "[Time].[1997]"); } public void testIntersectTuples() { assertAxisReturns( "Intersect(\n" + " {([Time].[1997].[Q2], [Gender].[M]),\n" + " ([Time].[1997], [Gender].[F]),\n" + " ([Time].[1997].[Q1], [Gender].[M]),\n" + " ([Time].[1997].[Q2], [Gender].[M])},\n" + " {([Time].[1998], [Gender].[F]),\n" + " ([Time].[1997], [Gender].[F]),\n" + " ([Time].[1997].[Q2], [Gender].[M]),\n" + " ([Time].[1997], [Gender])})", "{[Time].[1997].[Q2], [Gender].[M]}\n" + "{[Time].[1997], [Gender].[F]}"); } public void testIntersectRightEmpty() { assertAxisReturns( "Intersect({[Time].[1997]}, {})", ""); } public void testIntersectLeftEmpty() { assertAxisReturns( "Intersect({}, {[Store].[USA].[CA]})", ""); } public void testOrderDepends() { // Order(, ) depends upon everything // depends upon, except the dimensions of . // Depends upon everything EXCEPT [Product], [Measures], // [Marital Status], [Gender]. String s11 = TestContext.allHiersExcept( "[Product]", "[Measures]", "[Marital Status]", "[Gender]"); getTestContext().assertSetExprDependsOn( "Order(" + " Crossjoin([Gender].MEMBERS, [Product].MEMBERS)," + " ([Measures].[Unit Sales], [Marital Status].[S])," + " ASC)", s11); // Depends upon everything EXCEPT [Product], [Measures], // [Marital Status]. Does depend upon [Gender]. String s12 = TestContext.allHiersExcept( "[Product]", "[Measures]", "[Marital Status]"); getTestContext().assertSetExprDependsOn( "Order(" + " Crossjoin({[Gender].CurrentMember}, [Product].MEMBERS)," + " ([Measures].[Unit Sales], [Marital Status].[S])," + " ASC)", s12); // Depends upon everything except [Measures]. String s13 = TestContext.allHiersExcept("[Measures]"); getTestContext().assertSetExprDependsOn( "Order(" + " Crossjoin(" + " [Gender].CurrentMember.Children, " + " [Marital Status].CurrentMember.Children), " + " [Measures].[Unit Sales], " + " BDESC)", s13); String s1 = TestContext.allHiersExcept( "[Measures]", "[Store]", "[Product]", "[Time]"); getTestContext().assertSetExprDependsOn( " Order(\n" + " CrossJoin(\n" + " {[Product].[All Products].[Food].[Eggs],\n" + " [Product].[All Products].[Food].[Seafood],\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages]},\n" + " {[Store].[USA].[WA].[Seattle],\n" + " [Store].[USA].[CA],\n" + " [Store].[USA].[OR]}),\n" + " ([Time].[1997].[Q1], [Measures].[Unit Sales]),\n" + " ASC)", s1); } public void testOrderCalc() { if (Util.Retrowoven) { // If retrowoven, we don't use Iterable, so plans are different. return; } // [Measures].[Unit Sales] is a constant member, so it is evaluated in // a ContextCalc. assertAxisCompilesTo( "order([Product].children, [Measures].[Unit Sales])", "ContextCalc(name=ContextCalc, class=class mondrian.olap.fun.OrderFunDef$ContextCalc, type=SetType>, resultStyle=MUTABLE_LIST)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Measures].[Unit Sales])\n" + " CalcImpl(name=CalcImpl, class=class mondrian.olap.fun.OrderFunDef$CalcImpl, type=SetType>, resultStyle=MUTABLE_LIST, direction=ASC)\n" + " Children(name=Children, class=class mondrian.olap.fun.BuiltinFunTable$22$1, type=SetType>, resultStyle=LIST)\n" + " CurrentMemberFixed(hierarchy=[Product], name=CurrentMemberFixed, class=class mondrian.olap.fun.HierarchyCurrentMemberFunDef$FixedCalcImpl, type=MemberType, resultStyle=VALUE)\n" + " ValueCalc(name=ValueCalc, class=class mondrian.calc.impl.ValueCalc, type=SCALAR, resultStyle=VALUE)\n"); // [Time].[1997] is constant, and is evaluated in a ContextCalc. // [Product].Parent is variable, and is evaluated inside the loop. assertAxisCompilesTo( "order([Product].children," + " ([Time].[1997], [Product].CurrentMember.Parent))", "ContextCalc(name=ContextCalc, class=class mondrian.olap.fun.OrderFunDef$ContextCalc, type=SetType>, resultStyle=MUTABLE_LIST)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Time].[1997])\n" + " CalcImpl(name=CalcImpl, class=class mondrian.olap.fun.OrderFunDef$CalcImpl, type=SetType>, resultStyle=MUTABLE_LIST, direction=ASC)\n" + " Children(name=Children, class=class mondrian.olap.fun.BuiltinFunTable$22$1, type=SetType>, resultStyle=LIST)\n" + " CurrentMemberFixed(hierarchy=[Product], name=CurrentMemberFixed, class=class mondrian.olap.fun.HierarchyCurrentMemberFunDef$FixedCalcImpl, type=MemberType, resultStyle=VALUE)\n" + " MemberValueCalc(name=MemberValueCalc, class=class mondrian.calc.impl.MemberValueCalc, type=SCALAR, resultStyle=VALUE)\n" + " Parent(name=Parent, class=class mondrian.olap.fun.BuiltinFunTable$15$1, type=MemberType, resultStyle=VALUE)\n" + " CurrentMemberFixed(hierarchy=[Product], name=CurrentMemberFixed, class=class mondrian.olap.fun.HierarchyCurrentMemberFunDef$FixedCalcImpl, type=MemberType, resultStyle=VALUE)\n"); // No ContextCalc this time. All members are non-variable. assertAxisCompilesTo( "order([Product].children, [Product].CurrentMember.Parent)", "CalcImpl(name=CalcImpl, class=class mondrian.olap.fun.OrderFunDef$CalcImpl, type=SetType>, resultStyle=MUTABLE_LIST, direction=ASC)\n" + " Children(name=Children, class=class mondrian.olap.fun.BuiltinFunTable$22$1, type=SetType>, resultStyle=LIST)\n" + " CurrentMemberFixed(hierarchy=[Product], name=CurrentMemberFixed, class=class mondrian.olap.fun.HierarchyCurrentMemberFunDef$FixedCalcImpl, type=MemberType, resultStyle=VALUE)\n" + " MemberValueCalc(name=MemberValueCalc, class=class mondrian.calc.impl.MemberValueCalc, type=SCALAR, resultStyle=VALUE)\n" + " Parent(name=Parent, class=class mondrian.olap.fun.BuiltinFunTable$15$1, type=MemberType, resultStyle=VALUE)\n" + " CurrentMemberFixed(hierarchy=[Product], name=CurrentMemberFixed, class=class mondrian.olap.fun.HierarchyCurrentMemberFunDef$FixedCalcImpl, type=MemberType, resultStyle=VALUE)\n"); // List expression is dependent on one of the constant calcs. It cannot // be pulled up, so [Gender].[M] is not in the ContextCalc. // Note that there is no CopyListCalc - because Filter creates its own // mutable copy. // Under JDK 1.4, needs an extra converter from list to iterator, // because JDK 1.4 doesn't support the ITERABLE result style. assertAxisCompilesTo( "order(filter([Product].children, [Measures].[Unit Sales] > 1000), " + "([Gender].[M], [Measures].[Store Sales]))", Util.Retrowoven ? "" + "ContextCalc(name=ContextCalc, class=class mondrian.olap.fun.OrderFunDef$ContextCalc, type=SetType>, resultStyle=MUTABLE_LIST)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Measures].[Store Sales])\n" + " MemberCalcImpl(name=MemberCalcImpl, class=class mondrian.olap.fun.OrderFunDef$MemberCalcImpl, type=SetType>, resultStyle=MUTABLE_LIST, direction=ASC)\n" + " MemberListIterCalc(name=MemberListIterCalc, class=class mondrian.calc.impl.AbstractExpCompiler$MemberListIterCalc, type=SetType>, resultStyle=ITERABLE)\n" + " ImmutableMemberListCalc(name=ImmutableMemberListCalc, class=class mondrian.olap.fun.FilterFunDef$ImmutableMemberListCalc, type=SetType>, resultStyle=MUTABLE_LIST)\n" + " Children(name=Children, class=class mondrian.olap.fun.BuiltinFunTable$22$1, type=SetType>, resultStyle=LIST)\n" + " CurrentMemberFixed(hierarchy=[Product], name=CurrentMemberFixed, class=class mondrian.olap.fun.HierarchyCurrentMemberFunDef$FixedCalcImpl, type=MemberType, resultStyle=VALUE)\n" + " >(name=>, class=class mondrian.olap.fun.BuiltinFunTable$63$1, type=BOOLEAN, resultStyle=VALUE)\n" + " MemberValueCalc(name=MemberValueCalc, class=class mondrian.calc.impl.MemberValueCalc, type=SCALAR, resultStyle=VALUE)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Measures].[Unit Sales])\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=NUMERIC, resultStyle=VALUE_NOT_NULL, value=1000.0)\n" + " MemberValueCalc(name=MemberValueCalc, class=class mondrian.calc.impl.MemberValueCalc, type=SCALAR, resultStyle=VALUE)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Gender].[M])\n" : "" + "ContextCalc(name=ContextCalc, class=class mondrian.olap.fun.OrderFunDef$ContextCalc, type=SetType>, resultStyle=MUTABLE_LIST)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Measures].[Store Sales])\n" + " CalcImpl(name=CalcImpl, class=class mondrian.olap.fun.OrderFunDef$CalcImpl, type=SetType>, resultStyle=MUTABLE_LIST, direction=ASC)\n" + " ImmutableIterCalc(name=ImmutableIterCalc, class=class mondrian.olap.fun.FilterFunDef$ImmutableIterCalc, type=SetType>, resultStyle=ITERABLE)\n" + " Children(name=Children, class=class mondrian.olap.fun.BuiltinFunTable$22$1, type=SetType>, resultStyle=LIST)\n" + " CurrentMemberFixed(hierarchy=[Product], name=CurrentMemberFixed, class=class mondrian.olap.fun.HierarchyCurrentMemberFunDef$FixedCalcImpl, type=MemberType, resultStyle=VALUE)\n" + " >(name=>, class=class mondrian.olap.fun.BuiltinFunTable$63$1, type=BOOLEAN, resultStyle=VALUE)\n" + " MemberValueCalc(name=MemberValueCalc, class=class mondrian.calc.impl.MemberValueCalc, type=SCALAR, resultStyle=VALUE)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Measures].[Unit Sales])\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=NUMERIC, resultStyle=VALUE_NOT_NULL, value=1000.0)\n" + " MemberValueCalc(name=MemberValueCalc, class=class mondrian.calc.impl.MemberValueCalc, type=SCALAR, resultStyle=VALUE)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Gender].[M])\n"); } /** * Verifies that the order function works with a defined member. * See this forum post for additional information: * http://forums.pentaho.com/showthread.php?p=179473#post179473 */ public void testOrderWithMember() { assertQueryReturns( "with member [Measures].[Product Name Length] as " + "'LEN([Product].CurrentMember.Name)'\n" + "select {[Measures].[Product Name Length]} ON COLUMNS,\n" + "Order([Product].[All Products].Children, " + "[Measures].[Product Name Length], BASC) ON ROWS\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Product Name Length]}\n" + "Axis #2:\n" + "{[Product].[Food]}\n" + "{[Product].[Drink]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 4\n" + "Row #1: 5\n" + "Row #2: 14\n"); } /** * test case for bug # 1797159, Potential MDX Order Non Empty Problem * */ public void testOrderNonEmpty() { assertQueryReturns( "select NON EMPTY [Gender].Members ON COLUMNS,\n" + "NON EMPTY Order([Product].[All Products].[Drink].Children,\n" + "[Gender].[All Gender].[F], ASC) ON ROWS\n" + "from [Sales]\n" + "where ([Customers].[All Customers].[USA].[CA].[San Francisco],\n" + " [Time].[1997])", "Axis #0:\n" + "{[Customers].[USA].[CA].[San Francisco], [Time].[1997]}\n" + "Axis #1:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "Row #0: 2\n" + "Row #0: \n" + "Row #0: 2\n" + "Row #1: 4\n" + "Row #1: 2\n" + "Row #1: 2\n"); } public void testOrder() { assertQueryReturns( "select {[Measures].[Unit Sales]} on columns,\n" + " order({\n" + " [Product].[All Products].[Drink],\n" + " [Product].[All Products].[Drink].[Beverages],\n" + " [Product].[All Products].[Drink].[Dairy],\n" + " [Product].[All Products].[Food],\n" + " [Product].[All Products].[Food].[Baked Goods],\n" + " [Product].[All Products].[Food].[Eggs],\n" + " [Product].[All Products]},\n" + " [Measures].[Unit Sales]) on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[All Products]}\n" + "{[Product].[Drink]}\n" + "{[Product].[Drink].[Dairy]}\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Food]}\n" + "{[Product].[Food].[Eggs]}\n" + "{[Product].[Food].[Baked Goods]}\n" + "Row #0: 266,773\n" + "Row #1: 24,597\n" + "Row #2: 4,186\n" + "Row #3: 13,573\n" + "Row #4: 191,940\n" + "Row #5: 4,132\n" + "Row #6: 7,870\n"); } public void testOrderParentsMissing() { // Paradoxically, [Alcoholic Beverages] comes before // [Eggs] even though it has a larger value, because // its parent [Drink] has a smaller value than [Food]. assertQueryReturns( "select {[Measures].[Unit Sales]} on columns," + " order({\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages],\n" + " [Product].[All Products].[Food].[Eggs]},\n" + " [Measures].[Unit Sales], ASC) on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "{[Product].[Food].[Eggs]}\n" + "Row #0: 6,838\n" + "Row #1: 4,132\n"); } public void testOrderCrossJoinBreak() { assertQueryReturns( "select {[Measures].[Unit Sales]} on columns,\n" + " Order(\n" + " CrossJoin(\n" + " [Gender].children,\n" + " [Marital Status].children),\n" + " [Measures].[Unit Sales],\n" + " BDESC) on rows\n" + "from Sales\n" + "where [Time].[1997].[Q1]", "Axis #0:\n" + "{[Time].[1997].[Q1]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[M], [Marital Status].[S]}\n" + "{[Gender].[F], [Marital Status].[M]}\n" + "{[Gender].[M], [Marital Status].[M]}\n" + "{[Gender].[F], [Marital Status].[S]}\n" + "Row #0: 17,070\n" + "Row #1: 16,790\n" + "Row #2: 16,311\n" + "Row #3: 16,120\n"); } public void testOrderCrossJoin() { // Note: // 1. [Alcoholic Beverages] collates before [Eggs] and // [Seafood] because its parent, [Drink], is less // than [Food] // 2. [Seattle] generally sorts after [CA] and [OR] // because invisible parent [WA] is greater. assertQueryReturns( "select CrossJoin(\n" + " {[Time].[1997],\n" + " [Time].[1997].[Q1]},\n" + " {[Measures].[Unit Sales]}) on columns,\n" + " Order(\n" + " CrossJoin(\n" + " {[Product].[All Products].[Food].[Eggs],\n" + " [Product].[All Products].[Food].[Seafood],\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages]},\n" + " {[Store].[USA].[WA].[Seattle],\n" + " [Store].[USA].[CA],\n" + " [Store].[USA].[OR]}),\n" + " ([Time].[1997].[Q1], [Measures].[Unit Sales]),\n" + " ASC) on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997], [Measures].[Unit Sales]}\n" + "{[Time].[1997].[Q1], [Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Alcoholic Beverages], [Store].[USA].[OR]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Store].[USA].[CA]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Store].[USA].[WA].[Seattle]}\n" + "{[Product].[Food].[Seafood], [Store].[USA].[CA]}\n" + "{[Product].[Food].[Seafood], [Store].[USA].[OR]}\n" + "{[Product].[Food].[Seafood], [Store].[USA].[WA].[Seattle]}\n" + "{[Product].[Food].[Eggs], [Store].[USA].[CA]}\n" + "{[Product].[Food].[Eggs], [Store].[USA].[OR]}\n" + "{[Product].[Food].[Eggs], [Store].[USA].[WA].[Seattle]}\n" + "Row #0: 1,680\n" + "Row #0: 393\n" + "Row #1: 1,936\n" + "Row #1: 431\n" + "Row #2: 635\n" + "Row #2: 142\n" + "Row #3: 441\n" + "Row #3: 91\n" + "Row #4: 451\n" + "Row #4: 107\n" + "Row #5: 217\n" + "Row #5: 44\n" + "Row #6: 1,116\n" + "Row #6: 240\n" + "Row #7: 1,119\n" + "Row #7: 251\n" + "Row #8: 373\n" + "Row #8: 57\n"); } public void testOrderHierarchicalDesc() { assertAxisReturns( "Order(\n" + " {[Product].[All Products], " + " [Product].[Food],\n" + " [Product].[Drink],\n" + " [Product].[Non-Consumable],\n" + " [Product].[Food].[Eggs],\n" + " [Product].[Drink].[Dairy]},\n" + " [Measures].[Unit Sales],\n" + " DESC)", "[Product].[All Products]\n" + "[Product].[Food]\n" + "[Product].[Food].[Eggs]\n" + "[Product].[Non-Consumable]\n" + "[Product].[Drink]\n" + "[Product].[Drink].[Dairy]"); } public void testOrderCrossJoinDesc() { assertAxisReturns( "Order(\n" + " CrossJoin(\n" + " {[Gender].[M], [Gender].[F]},\n" + " {[Product].[All Products], " + " [Product].[Food],\n" + " [Product].[Drink],\n" + " [Product].[Non-Consumable],\n" + " [Product].[Food].[Eggs],\n" + " [Product].[Drink].[Dairy]}),\n" + " [Measures].[Unit Sales],\n" + " DESC)", "{[Gender].[M], [Product].[All Products]}\n" + "{[Gender].[M], [Product].[Food]}\n" + "{[Gender].[M], [Product].[Food].[Eggs]}\n" + "{[Gender].[M], [Product].[Non-Consumable]}\n" + "{[Gender].[M], [Product].[Drink]}\n" + "{[Gender].[M], [Product].[Drink].[Dairy]}\n" + "{[Gender].[F], [Product].[All Products]}\n" + "{[Gender].[F], [Product].[Food]}\n" + "{[Gender].[F], [Product].[Food].[Eggs]}\n" + "{[Gender].[F], [Product].[Non-Consumable]}\n" + "{[Gender].[F], [Product].[Drink]}\n" + "{[Gender].[F], [Product].[Drink].[Dairy]}"); } public void testOrderBug656802() { // Note: // 1. [Alcoholic Beverages] collates before [Eggs] and // [Seafood] because its parent, [Drink], is less // than [Food] // 2. [Seattle] generally sorts after [CA] and [OR] // because invisible parent [WA] is greater. assertQueryReturns( "select {[Measures].[Unit Sales], [Measures].[Store Cost], [Measures].[Store Sales]} ON columns, \n" + "Order(\n" + " ToggleDrillState(\n" + " {([Promotion Media].[All Media], [Product].[All Products])},\n" + " {[Product].[All Products]}), \n" + " [Measures].[Unit Sales], DESC) ON rows \n" + "from [Sales] where ([Time].[1997])", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Promotion Media].[All Media], [Product].[All Products]}\n" + "{[Promotion Media].[All Media], [Product].[Food]}\n" + "{[Promotion Media].[All Media], [Product].[Non-Consumable]}\n" + "{[Promotion Media].[All Media], [Product].[Drink]}\n" + "Row #0: 266,773\n" + "Row #0: 225,627.23\n" + "Row #0: 565,238.13\n" + "Row #1: 191,940\n" + "Row #1: 163,270.72\n" + "Row #1: 409,035.59\n" + "Row #2: 50,236\n" + "Row #2: 42,879.28\n" + "Row #2: 107,366.33\n" + "Row #3: 24,597\n" + "Row #3: 19,477.23\n" + "Row #3: 48,836.21\n"); } public void testOrderBug712702_Simplified() { assertQueryReturns( "SELECT Order({[Time].[Year].members}, [Measures].[Unit Sales]) on columns\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1998]}\n" + "{[Time].[1997]}\n" + "Row #0: \n" + "Row #0: 266,773\n"); } public void testOrderBug712702_Original() { assertQueryReturns( "with member [Measures].[Average Unit Sales] as 'Avg(Descendants([Time].[Time].CurrentMember, [Time].[Month]), \n" + "[Measures].[Unit Sales])' \n" + "member [Measures].[Max Unit Sales] as 'Max(Descendants([Time].[Time].CurrentMember, [Time].[Month]), [Measures].[Unit Sales])' \n" + "select {[Measures].[Average Unit Sales], [Measures].[Max Unit Sales], [Measures].[Unit Sales]} ON columns, \n" + " NON EMPTY Order(\n" + " Crossjoin(\n" + " {[Store].[USA].[OR].[Portland],\n" + " [Store].[USA].[OR].[Salem],\n" + " [Store].[USA].[OR].[Salem].[Store 13],\n" + " [Store].[USA].[CA].[San Francisco],\n" + " [Store].[USA].[CA].[San Diego],\n" + " [Store].[USA].[CA].[Beverly Hills],\n" + " [Store].[USA].[CA].[Los Angeles],\n" + " [Store].[USA].[WA].[Walla Walla],\n" + " [Store].[USA].[WA].[Bellingham],\n" + " [Store].[USA].[WA].[Yakima],\n" + " [Store].[USA].[WA].[Spokane],\n" + " [Store].[USA].[WA].[Seattle], \n" + " [Store].[USA].[WA].[Bremerton],\n" + " [Store].[USA].[WA].[Tacoma]},\n" + " [Time].[Year].Members), \n" + " [Measures].[Average Unit Sales], ASC) ON rows\n" + "from [Sales] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Average Unit Sales]}\n" + "{[Measures].[Max Unit Sales]}\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[OR].[Portland], [Time].[1997]}\n" + "{[Store].[USA].[OR].[Salem], [Time].[1997]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13], [Time].[1997]}\n" + "{[Store].[USA].[CA].[San Francisco], [Time].[1997]}\n" + "{[Store].[USA].[CA].[Beverly Hills], [Time].[1997]}\n" + "{[Store].[USA].[CA].[San Diego], [Time].[1997]}\n" + "{[Store].[USA].[CA].[Los Angeles], [Time].[1997]}\n" + "{[Store].[USA].[WA].[Walla Walla], [Time].[1997]}\n" + "{[Store].[USA].[WA].[Bellingham], [Time].[1997]}\n" + "{[Store].[USA].[WA].[Yakima], [Time].[1997]}\n" + "{[Store].[USA].[WA].[Spokane], [Time].[1997]}\n" + "{[Store].[USA].[WA].[Bremerton], [Time].[1997]}\n" + "{[Store].[USA].[WA].[Seattle], [Time].[1997]}\n" + "{[Store].[USA].[WA].[Tacoma], [Time].[1997]}\n" + "Row #0: 2,173\n" + "Row #0: 2,933\n" + "Row #0: 26,079\n" + "Row #1: 3,465\n" + "Row #1: 5,891\n" + "Row #1: 41,580\n" + "Row #2: 3,465\n" + "Row #2: 5,891\n" + "Row #2: 41,580\n" + "Row #3: 176\n" + "Row #3: 222\n" + "Row #3: 2,117\n" + "Row #4: 1,778\n" + "Row #4: 2,545\n" + "Row #4: 21,333\n" + "Row #5: 2,136\n" + "Row #5: 2,686\n" + "Row #5: 25,635\n" + "Row #6: 2,139\n" + "Row #6: 2,669\n" + "Row #6: 25,663\n" + "Row #7: 184\n" + "Row #7: 301\n" + "Row #7: 2,203\n" + "Row #8: 186\n" + "Row #8: 275\n" + "Row #8: 2,237\n" + "Row #9: 958\n" + "Row #9: 1,163\n" + "Row #9: 11,491\n" + "Row #10: 1,966\n" + "Row #10: 2,634\n" + "Row #10: 23,591\n" + "Row #11: 2,048\n" + "Row #11: 2,623\n" + "Row #11: 24,576\n" + "Row #12: 2,084\n" + "Row #12: 2,304\n" + "Row #12: 25,011\n" + "Row #13: 2,938\n" + "Row #13: 3,818\n" + "Row #13: 35,257\n"); } public void testOrderEmpty() { assertQueryReturns( "select \n" + " Order(" + " {}," + " [Customers].currentMember, BDESC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n"); } public void testOrderOne() { assertQueryReturns( "select \n" + " Order(" + " {[Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]}," + " [Customers].currentMember, BDESC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "Row #0: 75\n"); } public void testOrderKeyEmpty() { assertQueryReturns( "select \n" + " Order(" + " {}," + " [Customers].currentMember.OrderKey, BDESC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n"); } public void testOrderKeyOne() { assertQueryReturns( "select \n" + " Order(" + " {[Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]}," + " [Customers].currentMember.OrderKey, BDESC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "Row #0: 75\n"); } public void testOrderDesc() { // based on olap4j's OlapTest.testSortDimension assertQueryReturns( "SELECT\n" + "{[Measures].[Store Sales]} ON COLUMNS,\n" + "{Order(\n" + " {{[Product].[Drink], [Product].[Drink].Children}},\n" + " [Product].CurrentMember.Name,\n" + " DESC)} ON ROWS\n" + "FROM [Sales]\n" + "WHERE {[Time].[1997].[Q3].[7]}", "Axis #0:\n" + "{[Time].[1997].[Q3].[7]}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Drink].[Dairy]}\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "Row #0: 4,409.58\n" + "Row #1: 629.69\n" + "Row #2: 2,477.02\n" + "Row #3: 1,302.87\n"); } public void testOrderMemberMemberValueExpNew() { propSaver.set( MondrianProperties.instance().CompareSiblingsByOrderKey, true); // Use a fresh connection to make sure bad member ordinals haven't // been assigned by previous tests. final TestContext context = getTestContext().withFreshConnection(); try { context.assertQueryReturns( "select \n" + " Order(" + " {[Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]," + " [Customers].[All Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}," + " [Customers].currentMember.OrderKey, BDESC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}\n" + "{[Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "Row #0: 33\n" + "Row #0: 75\n"); } finally { if (context != null) { context.close(); } } } public void testOrderMemberMemberValueExpNew1() { // sort by default measure propSaver.set( MondrianProperties.instance().CompareSiblingsByOrderKey, true); // Use a fresh connection to make sure bad member ordinals haven't // been assigned by previous tests. final TestContext context = getTestContext().withFreshConnection(); try { context.assertQueryReturns( "select \n" + " Order(" + " {[Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]," + " [Customers].[All Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}," + " [Customers].currentMember, BDESC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "{[Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}\n" + "Row #0: 75\n" + "Row #0: 33\n"); } finally { context.close(); } } public void testOrderMemberDefaultFlag1() { // flags not specified default to ASC - sort by default measure assertQueryReturns( "with \n" + " Member [Measures].[Zero] as '0' \n" + "select \n" + " Order(" + " {[Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]," + " [Customers].[All Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}," + " [Customers].currentMember.OrderKey) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}\n" + "{[Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "Row #0: 33\n" + "Row #0: 75\n"); } public void testOrderMemberDefaultFlag2() { // flags not specified default to ASC assertQueryReturns( "with \n" + " Member [Measures].[Zero] as '0' \n" + "select \n" + " Order(" + " {[Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]," + " [Customers].[All Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}," + " [Measures].[Store Cost]) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "{[Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}\n" + "Row #0: 75\n" + "Row #0: 33\n"); } public void testOrderMemberMemberValueExpHierarchy() { // Santa Monica and Woodland Hills both don't have orderkey // members are sorted by the order of their keys assertQueryReturns( "select \n" + " Order(" + " {[Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]," + " [Customers].[All Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}," + " [Customers].currentMember.OrderKey, DESC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "{[Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}\n" + "Row #0: 75\n" + "Row #0: 33\n"); } public void testOrderMemberMultiKeysMemberValueExp1() { // sort by unit sales and then customer id (Adeline = 6442, Abe = 570) assertQueryReturns( "select \n" + " Order(" + " {[Customers].[USA].[WA].[Issaquah].[Abe Tramel]," + " [Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]," + " [Customers].[All Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}," + " [Measures].[Unit Sales], BDESC, [Customers].currentMember.OrderKey, BDESC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "{[Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}\n" + "{[Customers].[USA].[WA].[Issaquah].[Abe Tramel]}\n" + "Row #0: 75\n" + "Row #0: 33\n" + "Row #0: 33\n"); } public void testOrderMemberMultiKeysMemberValueExp2() { propSaver.set( MondrianProperties.instance().CompareSiblingsByOrderKey, true); // Use a fresh connection to make sure bad member ordinals haven't // been assigned by previous tests. final TestContext context = getTestContext().withFreshConnection(); try { context.assertQueryReturns( "select \n" + " Order(" + " {[Customers].[USA].[WA].[Issaquah].[Abe Tramel]," + " [Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]," + " [Customers].[All Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}," + " [Customers].currentMember.Parent.Parent.OrderKey, BASC, [Customers].currentMember.OrderKey, BDESC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}\n" + "{[Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "{[Customers].[USA].[WA].[Issaquah].[Abe Tramel]}\n" + "Row #0: 33\n" + "Row #0: 75\n" + "Row #0: 33\n"); } finally { context.close(); } } public void testOrderMemberMultiKeysMemberValueExpDepends() { // should preserve order of Abe and Adeline (note second key is [Time]) assertQueryReturns( "select \n" + " Order(" + " {[Customers].[USA].[WA].[Issaquah].[Abe Tramel]," + " [Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]," + " [Customers].[All Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}," + " [Measures].[Unit Sales], BDESC, [Time].[Time].currentMember, BDESC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "{[Customers].[USA].[WA].[Issaquah].[Abe Tramel]}\n" + "{[Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}\n" + "Row #0: 75\n" + "Row #0: 33\n" + "Row #0: 33\n"); } public void testOrderTupleSingleKeysNew() { propSaver.set( MondrianProperties.instance().CompareSiblingsByOrderKey, true); // Use a fresh connection to make sure bad member ordinals haven't // been assigned by previous tests. final TestContext context = getTestContext().withFreshConnection(); try { context.assertQueryReturns( "with \n" + " set [NECJ] as \n" + " 'NonEmptyCrossJoin( \n" + " {[Customers].[USA].[WA].[Issaquah].[Abe Tramel]," + " [Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]," + " [Customers].[All Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}," + " {[Store].[USA].[WA].[Seattle],\n" + " [Store].[USA].[CA],\n" + " [Store].[USA].[OR]})'\n" + "select \n" + " Order([NECJ], [Customers].currentMember.OrderKey, BDESC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[CA].[Santa Monica].[Adeline Chun], [Store].[USA].[CA]}\n" + "{[Customers].[USA].[CA].[Woodland Hills].[Abel Young], [Store].[USA].[CA]}\n" + "{[Customers].[USA].[WA].[Issaquah].[Abe Tramel], [Store].[USA].[WA].[Seattle]}\n" + "Row #0: 33\n" + "Row #0: 75\n" + "Row #0: 33\n"); } finally { context.close(); } } public void testOrderTupleSingleKeysNew1() { propSaver.set( MondrianProperties.instance().CompareSiblingsByOrderKey, true); // Use a fresh connection to make sure bad member ordinals haven't // been assigned by previous tests. final TestContext context = getTestContext().withFreshConnection(); try { context.assertQueryReturns( "with \n" + " set [NECJ] as \n" + " 'NonEmptyCrossJoin( \n" + " {[Customers].[USA].[WA].[Issaquah].[Abe Tramel]," + " [Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]," + " [Customers].[All Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}," + " {[Store].[USA].[WA].[Seattle],\n" + " [Store].[USA].[CA],\n" + " [Store].[USA].[OR]})'\n" + "select \n" + " Order([NECJ], [Store].currentMember.OrderKey, DESC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[WA].[Issaquah].[Abe Tramel], [Store].[USA].[WA].[Seattle]}\n" + "{[Customers].[USA].[CA].[Woodland Hills].[Abel Young], [Store].[USA].[CA]}\n" + "{[Customers].[USA].[CA].[Santa Monica].[Adeline Chun], [Store].[USA].[CA]}\n" + "Row #0: 33\n" + "Row #0: 75\n" + "Row #0: 33\n"); } finally { context.close(); } } public void testOrderTupleMultiKeys1() { assertQueryReturns( "with \n" + " set [NECJ] as \n" + " 'NonEmptyCrossJoin( \n" + " {[Store].[USA].[CA],\n" + " [Store].[USA].[WA]},\n" + " {[Customers].[USA].[WA].[Issaquah].[Abe Tramel]," + " [Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]," + " [Customers].[All Customers].[USA].[CA].[Santa Monica].[Adeline Chun]})' \n" + "select \n" + " Order([NECJ], [Store].currentMember.OrderKey, BDESC, [Measures].[Unit Sales], BDESC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA], [Customers].[USA].[WA].[Issaquah].[Abe Tramel]}\n" + "{[Store].[USA].[CA], [Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "{[Store].[USA].[CA], [Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}\n" + "Row #0: 33\n" + "Row #0: 75\n" + "Row #0: 33\n"); } public void testOrderTupleMultiKeys2() { assertQueryReturns( "with \n" + " set [NECJ] as \n" + " 'NonEmptyCrossJoin( \n" + " {[Store].[USA].[CA],\n" + " [Store].[USA].[WA]},\n" + " {[Customers].[USA].[WA].[Issaquah].[Abe Tramel]," + " [Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]," + " [Customers].[All Customers].[USA].[CA].[Santa Monica].[Adeline Chun]})' \n" + "select \n" + " Order([NECJ], [Measures].[Unit Sales], BDESC, Ancestor([Customers].currentMember, [Customers].[Name]).OrderKey, BDESC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA], [Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "{[Store].[USA].[CA], [Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}\n" + "{[Store].[USA].[WA], [Customers].[USA].[WA].[Issaquah].[Abe Tramel]}\n" + "Row #0: 75\n" + "Row #0: 33\n" + "Row #0: 33\n"); } public void testOrderTupleMultiKeys3() { // WA unit sales is greater than CA unit sales // Santa Monica unit sales (2660) is greater that Woodland hills (2516) assertQueryReturns( "with \n" + " set [NECJ] as \n" + " 'NonEmptyCrossJoin( \n" + " {[Store].[USA].[CA],\n" + " [Store].[USA].[WA]},\n" + " {[Customers].[USA].[WA].[Issaquah].[Abe Tramel]," + " [Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]," + " [Customers].[All Customers].[USA].[CA].[Santa Monica].[Adeline Chun]})' \n" + "select \n" + " Order([NECJ], [Measures].[Unit Sales], DESC, Ancestor([Customers].currentMember, [Customers].[Name]), BDESC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA], [Customers].[USA].[WA].[Issaquah].[Abe Tramel]}\n" + "{[Store].[USA].[CA], [Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}\n" + "{[Store].[USA].[CA], [Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "Row #0: 33\n" + "Row #0: 33\n" + "Row #0: 75\n"); } public void testOrderTupleMultiKeyswithVCube() { // WA unit sales is greater than CA unit sales propSaver.set( MondrianProperties.instance().CompareSiblingsByOrderKey, true); // Use a fresh connection to make sure bad member ordinals haven't // been assigned by previous tests. // a non-sense cube just to test ordering by order key TestContext context = TestContext.instance().create( null, null, "\n" + "\n" + "\n" + "\n" + "", null, null, null); context.assertQueryReturns( "with \n" + " set [CJ] as \n" + " 'CrossJoin( \n" + " {[Position].[Store Management].children},\n" + " {[Customers].[USA].[WA].[Issaquah].[Abe Tramel]," + " [Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]," + " [Customers].[All Customers].[USA].[CA].[Santa Monica].[Adeline Chun]})' \n" + "select \n" + " [Measures].[Org Salary] on columns, \n" + " Order([CJ], [Position].currentMember.OrderKey, BASC, Ancestor([Customers].currentMember, [Customers].[Name]).OrderKey, BDESC) \n" + "on rows \n" + "from [Sales vs HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Org Salary]}\n" + "Axis #2:\n" + "{[Position].[Store Management].[Store Manager], [Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}\n" + "{[Position].[Store Management].[Store Manager], [Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "{[Position].[Store Management].[Store Manager], [Customers].[USA].[WA].[Issaquah].[Abe Tramel]}\n" + "{[Position].[Store Management].[Store Assistant Manager], [Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}\n" + "{[Position].[Store Management].[Store Assistant Manager], [Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "{[Position].[Store Management].[Store Assistant Manager], [Customers].[USA].[WA].[Issaquah].[Abe Tramel]}\n" + "{[Position].[Store Management].[Store Shift Supervisor], [Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}\n" + "{[Position].[Store Management].[Store Shift Supervisor], [Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "{[Position].[Store Management].[Store Shift Supervisor], [Customers].[USA].[WA].[Issaquah].[Abe Tramel]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: \n" + "Row #3: \n" + "Row #4: \n" + "Row #5: \n" + "Row #6: \n" + "Row #7: \n" + "Row #8: \n"); } public void testOrderConstant1() { //sort by customerId (Abel = 7851, Adeline = 6442, Abe = 570) assertQueryReturns( "select \n" + " Order(" + " {[Customers].[USA].[WA].[Issaquah].[Abe Tramel]," + " [Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]," + " [Customers].[All Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}," + " [Customers].[USA].OrderKey, BDESC, [Customers].currentMember.OrderKey, BASC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[WA].[Issaquah].[Abe Tramel]}\n" + "{[Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}\n" + "{[Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "Row #0: 33\n" + "Row #0: 33\n" + "Row #0: 75\n"); } public void testOrderDiffrentDim() { assertQueryReturns( "select \n" + " Order(" + " {[Customers].[USA].[WA].[Issaquah].[Abe Tramel]," + " [Customers].[All Customers].[USA].[CA].[Woodland Hills].[Abel Young]," + " [Customers].[All Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}," + " [Product].currentMember.OrderKey, BDESC, [Gender].currentMember.OrderKey, BDESC) \n" + "on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[WA].[Issaquah].[Abe Tramel]}\n" + "{[Customers].[USA].[CA].[Woodland Hills].[Abel Young]}\n" + "{[Customers].[USA].[CA].[Santa Monica].[Adeline Chun]}\n" + "Row #0: 33\n" + "Row #0: 75\n" + "Row #0: 33\n"); } public void testUnorder() { assertAxisReturns( "Unorder([Gender].members)", "[Gender].[All Gender]\n" + "[Gender].[F]\n" + "[Gender].[M]"); assertAxisReturns( "Unorder(Order([Gender].members, -[Measures].[Unit Sales]))", "[Gender].[All Gender]\n" + "[Gender].[M]\n" + "[Gender].[F]"); assertAxisReturns( "Unorder(Crossjoin([Gender].members, [Marital Status].Children))", "{[Gender].[All Gender], [Marital Status].[M]}\n" + "{[Gender].[All Gender], [Marital Status].[S]}\n" + "{[Gender].[F], [Marital Status].[M]}\n" + "{[Gender].[F], [Marital Status].[S]}\n" + "{[Gender].[M], [Marital Status].[M]}\n" + "{[Gender].[M], [Marital Status].[S]}"); // implicitly convert member to set assertAxisReturns( "Unorder([Gender].[M])", "[Gender].[M]"); assertAxisThrows( "Unorder(1 + 3)", "No function matches signature 'Unorder()'"); assertAxisThrows( "Unorder([Gender].[M], 1 + 3)", "No function matches signature 'Unorder(, )'"); assertQueryReturns( "select {[Measures].[Store Sales], [Measures].[Unit Sales]} on 0,\n" + " Unorder([Gender].Members) on 1\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 565,238.13\n" + "Row #0: 266,773\n" + "Row #1: 280,226.21\n" + "Row #1: 131,558\n" + "Row #2: 285,011.92\n" + "Row #2: 135,215\n"); } public void testSiblingsA() { assertAxisReturns( "{[Time].[1997].Siblings}", "[Time].[1997]\n" + "[Time].[1998]"); } public void testSiblingsB() { assertAxisReturns( "{[Store].Siblings}", "[Store].[All Stores]"); } public void testSiblingsC() { assertAxisReturns( "{[Store].[USA].[CA].Siblings}", "[Store].[USA].[CA]\n" + "[Store].[USA].[OR]\n" + "[Store].[USA].[WA]"); } public void testSiblingsD() { // The null member has no siblings -- not even itself assertAxisReturns("{[Gender].Parent.Siblings}", ""); assertExprReturns( "count ([Gender].parent.siblings, includeempty)", "0"); } public void testSubset() { assertAxisReturns( "Subset([Promotion Media].Children, 7, 2)", "[Promotion Media].[Product Attachment]\n" + "[Promotion Media].[Radio]"); } public void testSubsetNegativeCount() { assertAxisReturns( "Subset([Promotion Media].Children, 3, -1)", ""); } public void testSubsetNegativeStart() { assertAxisReturns( "Subset([Promotion Media].Children, -2, 4)", ""); } public void testSubsetDefault() { assertAxisReturns( "Subset([Promotion Media].Children, 11)", "[Promotion Media].[Sunday Paper, Radio]\n" + "[Promotion Media].[Sunday Paper, Radio, TV]\n" + "[Promotion Media].[TV]"); } public void testSubsetOvershoot() { assertAxisReturns( "Subset([Promotion Media].Children, 15)", ""); } public void testSubsetEmpty() { assertAxisReturns( "Subset([Gender].[F].Children, 1)", ""); assertAxisReturns( "Subset([Gender].[F].Children, 1, 3)", ""); } public void testTail() { assertAxisReturns( "Tail([Store].Children, 2)", "[Store].[Mexico]\n" + "[Store].[USA]"); } public void testTailNegative() { assertAxisReturns( "Tail([Store].Children, 2 - 3)", ""); } public void testTailDefault() { assertAxisReturns( "Tail([Store].Children)", "[Store].[USA]"); } public void testTailOvershoot() { assertAxisReturns( "Tail([Store].Children, 2 + 2)", "[Store].[Canada]\n" + "[Store].[Mexico]\n" + "[Store].[USA]"); } public void testTailEmpty() { assertAxisReturns( "Tail([Gender].[F].Children, 2)", ""); assertAxisReturns( "Tail([Gender].[F].Children)", ""); } public void testToggleDrillState() { assertAxisReturns( "ToggleDrillState({[Customers].[USA],[Customers].[Canada]}," + "{[Customers].[USA],[Customers].[USA].[CA]})", "[Customers].[USA]\n" + "[Customers].[USA].[CA]\n" + "[Customers].[USA].[OR]\n" + "[Customers].[USA].[WA]\n" + "[Customers].[Canada]"); } public void testToggleDrillState2() { assertAxisReturns( "ToggleDrillState([Product].[Product Department].members, " + "{[Product].[All Products].[Food].[Snack Foods]})", "[Product].[Drink].[Alcoholic Beverages]\n" + "[Product].[Drink].[Beverages]\n" + "[Product].[Drink].[Dairy]\n" + "[Product].[Food].[Baked Goods]\n" + "[Product].[Food].[Baking Goods]\n" + "[Product].[Food].[Breakfast Foods]\n" + "[Product].[Food].[Canned Foods]\n" + "[Product].[Food].[Canned Products]\n" + "[Product].[Food].[Dairy]\n" + "[Product].[Food].[Deli]\n" + "[Product].[Food].[Eggs]\n" + "[Product].[Food].[Frozen Foods]\n" + "[Product].[Food].[Meat]\n" + "[Product].[Food].[Produce]\n" + "[Product].[Food].[Seafood]\n" + "[Product].[Food].[Snack Foods]\n" + "[Product].[Food].[Snack Foods].[Snack Foods]\n" + "[Product].[Food].[Snacks]\n" + "[Product].[Food].[Starchy Foods]\n" + "[Product].[Non-Consumable].[Carousel]\n" + "[Product].[Non-Consumable].[Checkout]\n" + "[Product].[Non-Consumable].[Health and Hygiene]\n" + "[Product].[Non-Consumable].[Household]\n" + "[Product].[Non-Consumable].[Periodicals]"); } public void testToggleDrillState3() { assertAxisReturns( "ToggleDrillState(" + "{[Time].[1997].[Q1]," + " [Time].[1997].[Q2]," + " [Time].[1997].[Q2].[4]," + " [Time].[1997].[Q2].[6]," + " [Time].[1997].[Q3]}," + "{[Time].[1997].[Q2]})", "[Time].[1997].[Q1]\n" + "[Time].[1997].[Q2]\n" + "[Time].[1997].[Q3]"); } // bug 634860 public void testToggleDrillStateTuple() { assertAxisReturns( "ToggleDrillState(\n" + "{([Store].[USA].[CA]," + " [Product].[All Products].[Drink].[Alcoholic Beverages]),\n" + " ([Store].[USA]," + " [Product].[All Products].[Drink])},\n" + "{[Store].[All stores].[USA].[CA]})", "{[Store].[USA].[CA], [Product].[Drink].[Alcoholic Beverages]}\n" + "{[Store].[USA].[CA].[Alameda], [Product].[Drink].[Alcoholic Beverages]}\n" + "{[Store].[USA].[CA].[Beverly Hills], [Product].[Drink].[Alcoholic Beverages]}\n" + "{[Store].[USA].[CA].[Los Angeles], [Product].[Drink].[Alcoholic Beverages]}\n" + "{[Store].[USA].[CA].[San Diego], [Product].[Drink].[Alcoholic Beverages]}\n" + "{[Store].[USA].[CA].[San Francisco], [Product].[Drink].[Alcoholic Beverages]}\n" + "{[Store].[USA], [Product].[Drink]}"); } public void testToggleDrillStateRecursive() { // We expect this to fail. assertQueryThrows( "Select \n" + " ToggleDrillState(\n" + " {[Store].[USA]}, \n" + " {[Store].[USA]}, recursive) on Axis(0) \n" + "from [Sales]\n", "'RECURSIVE' is not supported in ToggleDrillState."); } public void testTopCount() { assertAxisReturns( "TopCount({[Promotion Media].[Media Type].members}, 2, [Measures].[Unit Sales])", "[Promotion Media].[No Media]\n" + "[Promotion Media].[Daily Paper, Radio, TV]"); } public void testTopCountTuple() { assertAxisReturns( "TopCount([Customers].[Name].members,2,(Time.[1997].[Q1],[Measures].[Store Sales]))", "[Customers].[USA].[WA].[Spokane].[Grace McLaughlin]\n" + "[Customers].[USA].[WA].[Spokane].[Matt Bellah]"); } public void testTopCountEmpty() { assertAxisReturns( "TopCount(Filter({[Promotion Media].[Media Type].members}, 1=0), 2, [Measures].[Unit Sales])", ""); } public void testTopCountDepends() { checkTopBottomCountPercentDepends("TopCount"); checkTopBottomCountPercentDepends("TopPercent"); checkTopBottomCountPercentDepends("TopSum"); checkTopBottomCountPercentDepends("BottomCount"); checkTopBottomCountPercentDepends("BottomPercent"); checkTopBottomCountPercentDepends("BottomSum"); } private void checkTopBottomCountPercentDepends(String fun) { String s1 = TestContext.allHiersExcept("[Measures]", "[Promotion Media]"); getTestContext().assertSetExprDependsOn( fun + "({[Promotion Media].[Media Type].members}, " + "2, [Measures].[Unit Sales])", s1); if (fun.endsWith("Count")) { getTestContext().assertSetExprDependsOn( fun + "({[Promotion Media].[Media Type].members}, 2)", "{}"); } } /** * Tests TopCount applied to a large result set. * *

Before optimizing (see FunUtil.partialSort), on a 2-core 32-bit 2.4GHz * machine, the 1st query took 14.5 secs, the 2nd query took 5.0 secs. * After optimizing, who knows? */ public void testTopCountHuge() { // TODO convert printfs to trace final String query = "SELECT [Measures].[Store Sales] ON 0,\n" + "TopCount([Time].[Month].members * " + "[Customers].[Name].members, 3, [Measures].[Store Sales]) ON 1\n" + "FROM [Sales]"; final String desiredResult = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1].[3], [Customers].[USA].[WA].[Spokane].[George Todero]}\n" + "{[Time].[1997].[Q3].[7], [Customers].[USA].[WA].[Spokane].[James Horvat]}\n" + "{[Time].[1997].[Q4].[11], [Customers].[USA].[WA].[Olympia].[Charles Stanley]}\n" + "Row #0: 234.83\n" + "Row #1: 199.46\n" + "Row #2: 191.90\n"; long now = System.currentTimeMillis(); assertQueryReturns(query, desiredResult); LOGGER.info("first query took " + (System.currentTimeMillis() - now)); now = System.currentTimeMillis(); assertQueryReturns(query, desiredResult); LOGGER.info("second query took " + (System.currentTimeMillis() - now)); } public void testTopPercent() { assertAxisReturns( "TopPercent({[Promotion Media].[Media Type].members}, 70, [Measures].[Unit Sales])", "[Promotion Media].[No Media]"); } //todo: test precision public void testTopSum() { assertAxisReturns( "TopSum({[Promotion Media].[Media Type].members}, 200000, [Measures].[Unit Sales])", "[Promotion Media].[No Media]\n" + "[Promotion Media].[Daily Paper, Radio, TV]"); } public void testTopSumEmpty() { assertAxisReturns( "TopSum(Filter({[Promotion Media].[Media Type].members}, 1=0), " + "200000, [Measures].[Unit Sales])", ""); } public void testUnionAll() { assertAxisReturns( "Union({[Gender].[M]}, {[Gender].[F]}, ALL)", "[Gender].[M]\n" + "[Gender].[F]"); // order is preserved } public void testUnionAllTuple() { // With the bug, the last 8 rows are repeated. assertQueryReturns( "with \n" + "set [Set1] as 'Crossjoin({[Time].[1997].[Q1]:[Time].[1997].[Q4]},{[Store].[USA].[CA]:[Store].[USA].[OR]})'\n" + "set [Set2] as 'Crossjoin({[Time].[1997].[Q2]:[Time].[1997].[Q3]},{[Store].[Mexico].[DF]:[Store].[Mexico].[Veracruz]})'\n" + "select \n" + "{[Measures].[Unit Sales]} ON COLUMNS,\n" + "Union([Set1], [Set2], ALL) ON ROWS\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1], [Store].[USA].[CA]}\n" + "{[Time].[1997].[Q1], [Store].[USA].[OR]}\n" + "{[Time].[1997].[Q2], [Store].[USA].[CA]}\n" + "{[Time].[1997].[Q2], [Store].[USA].[OR]}\n" + "{[Time].[1997].[Q3], [Store].[USA].[CA]}\n" + "{[Time].[1997].[Q3], [Store].[USA].[OR]}\n" + "{[Time].[1997].[Q4], [Store].[USA].[CA]}\n" + "{[Time].[1997].[Q4], [Store].[USA].[OR]}\n" + "{[Time].[1997].[Q2], [Store].[Mexico].[DF]}\n" + "{[Time].[1997].[Q2], [Store].[Mexico].[Guerrero]}\n" + "{[Time].[1997].[Q2], [Store].[Mexico].[Jalisco]}\n" + "{[Time].[1997].[Q2], [Store].[Mexico].[Veracruz]}\n" + "{[Time].[1997].[Q3], [Store].[Mexico].[DF]}\n" + "{[Time].[1997].[Q3], [Store].[Mexico].[Guerrero]}\n" + "{[Time].[1997].[Q3], [Store].[Mexico].[Jalisco]}\n" + "{[Time].[1997].[Q3], [Store].[Mexico].[Veracruz]}\n" + "Row #0: 16,890\n" + "Row #1: 19,287\n" + "Row #2: 18,052\n" + "Row #3: 15,079\n" + "Row #4: 18,370\n" + "Row #5: 16,940\n" + "Row #6: 21,436\n" + "Row #7: 16,353\n" + "Row #8: \n" + "Row #9: \n" + "Row #10: \n" + "Row #11: \n" + "Row #12: \n" + "Row #13: \n" + "Row #14: \n" + "Row #15: \n"); } public void testUnion() { assertAxisReturns( "Union({[Store].[USA], [Store].[USA], [Store].[USA].[OR]}, " + "{[Store].[USA].[CA], [Store].[USA]})", "[Store].[USA]\n" + "[Store].[USA].[OR]\n" + "[Store].[USA].[CA]"); } public void testUnionEmptyBoth() { assertAxisReturns( "Union({}, {})", ""); } public void testUnionEmptyRight() { assertAxisReturns( "Union({[Gender].[M]}, {})", "[Gender].[M]"); } public void testUnionTuple() { assertAxisReturns( "Union({" + " ([Gender].[M], [Marital Status].[S])," + " ([Gender].[F], [Marital Status].[S])" + "}, {" + " ([Gender].[M], [Marital Status].[M])," + " ([Gender].[M], [Marital Status].[S])" + "})", "{[Gender].[M], [Marital Status].[S]}\n" + "{[Gender].[F], [Marital Status].[S]}\n" + "{[Gender].[M], [Marital Status].[M]}"); } public void testUnionTupleDistinct() { assertAxisReturns( "Union({" + " ([Gender].[M], [Marital Status].[S])," + " ([Gender].[F], [Marital Status].[S])" + "}, {" + " ([Gender].[M], [Marital Status].[M])," + " ([Gender].[M], [Marital Status].[S])" + "}, Distinct)", "{[Gender].[M], [Marital Status].[S]}\n" + "{[Gender].[F], [Marital Status].[S]}\n" + "{[Gender].[M], [Marital Status].[M]}"); } public void testUnionQuery() { Result result = executeQuery( "select {[Measures].[Unit Sales], " + "[Measures].[Store Cost], " + "[Measures].[Store Sales]} on columns,\n" + " Hierarchize(\n" + " Union(\n" + " Crossjoin(\n" + " Crossjoin([Gender].[All Gender].children,\n" + " [Marital Status].[All Marital Status].children),\n" + " Crossjoin([Customers].[All Customers].children,\n" + " [Product].[All Products].children) ),\n" + " Crossjoin({([Gender].[All Gender].[M], [Marital Status].[All Marital Status].[M])},\n" + " Crossjoin(\n" + " [Customers].[All Customers].[USA].children,\n" + " [Product].[All Products].children) ) )) on rows\n" + "from Sales where ([Time].[1997])"); final Axis rowsAxis = result.getAxes()[1]; Assert.assertEquals(45, rowsAxis.getPositions().size()); } public void testItemMember() { assertExprReturns( "Descendants([Time].[1997], [Time].[Month]).Item(1).Item(0).UniqueName", "[Time].[1997].[Q1].[2]"); // Access beyond the list yields the Null member. if (isDefaultNullMemberRepresentation()) { assertExprReturns( "[Time].[1997].Children.Item(6).UniqueName", "[Time].[#null]"); assertExprReturns( "[Time].[1997].Children.Item(-1).UniqueName", "[Time].[#null]"); } } public void testItemTuple() { assertExprReturns( "CrossJoin([Gender].[All Gender].children, " + "[Time].[1997].[Q2].children).Item(0).Item(1).UniqueName", "[Time].[1997].[Q2].[4]"); } public void testStrToMember() { assertExprReturns( "StrToMember(\"[Time].[1997].[Q2].[4]\").Name", "4"); } public void testStrToMemberUniqueName() { assertExprReturns( "StrToMember(\"[Store].[USA].[CA]\").Name", "CA"); } public void testStrToMemberFullyQualifiedName() { assertExprReturns( "StrToMember(\"[Store].[All Stores].[USA].[CA]\").Name", "CA"); } public void testStrToMemberNull() { // SSAS 2005 gives "#Error An MDX expression was expected. An empty // expression was specified." assertExprThrows( "StrToMember(null).Name", "An MDX expression was expected. An empty expression was specified"); assertExprThrows( "StrToSet(null, [Gender]).Count", "An MDX expression was expected. An empty expression was specified"); assertExprThrows( "StrToTuple(null, [Gender]).Name", "An MDX expression was expected. An empty expression was specified"); } /** * Testcase for * * bug MONDRIAN-560, "StrToMember function doesn't use IgnoreInvalidMembers * option". */ public void testStrToMemberIgnoreInvalidMembers() { final MondrianProperties properties = MondrianProperties.instance(); propSaver.set(properties.IgnoreInvalidMembersDuringQuery, true); // [Product].[Drugs] is invalid, becomes null member, and is dropped // from list assertQueryReturns( "select \n" + " {[Product].[Food],\n" + " StrToMember(\"[Product].[Drugs]\")} on columns,\n" + " {[Measures].[Unit Sales]} on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Food]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 191,940\n"); // Hierarchy is inferred from leading edge assertExprReturns( "StrToMember(\"[Marital Status].[Separated]\").Hierarchy.Name", "Marital Status"); // Null member is returned assertExprReturns( "StrToMember(\"[Marital Status].[Separated]\").Name", "#null"); // Use longest valid prefix, so get [Time].[Weekly] rather than just // [Time]. final String timeWeekly = TestContext.hierarchyName("Time", "Weekly"); assertExprReturns( "StrToMember(\"" + timeWeekly + ".[1996].[Q1]\").Hierarchy.UniqueName", timeWeekly); // If hierarchy is invalid, throw an error even though // IgnoreInvalidMembersDuringQuery is set. assertExprThrows( "StrToMember(\"[Unknown Hierarchy].[Invalid].[Member]\").Name", "MDX object '[Unknown Hierarchy].[Invalid].[Member]' not found in cube 'Sales'"); assertExprThrows( "StrToMember(\"[Unknown Hierarchy].[Invalid]\").Name", "MDX object '[Unknown Hierarchy].[Invalid]' not found in cube 'Sales'"); assertExprThrows( "StrToMember(\"[Unknown Hierarchy]\").Name", "MDX object '[Unknown Hierarchy]' not found in cube 'Sales'"); assertAxisThrows( "StrToMember(\"\")", "MDX object '' not found in cube 'Sales'"); propSaver.set(properties.IgnoreInvalidMembersDuringQuery, false); assertQueryThrows( "select \n" + " {[Product].[Food],\n" + " StrToMember(\"[Product].[Drugs]\")} on columns,\n" + " {[Measures].[Unit Sales]} on rows\n" + "from [Sales]", "Member '[Product].[Drugs]' not found"); assertExprThrows( "StrToMember(\"[Marital Status].[Separated]\").Hierarchy.Name", "Member '[Marital Status].[Separated]' not found"); } public void testStrToTuple() { // single dimension yields member assertAxisReturns( "{StrToTuple(\"[Time].[1997].[Q2]\", [Time])}", "[Time].[1997].[Q2]"); // multiple dimensions yield tuple assertAxisReturns( "{StrToTuple(\"([Gender].[F], [Time].[1997].[Q2])\", [Gender], [Time])}", "{[Gender].[F], [Time].[1997].[Q2]}"); // todo: test for garbage at end of string } public void testStrToTupleIgnoreInvalidMembers() { final MondrianProperties properties = MondrianProperties.instance(); propSaver.set(properties.IgnoreInvalidMembersDuringQuery, true); // If any member is invalid, the whole tuple is null. assertAxisReturns( "StrToTuple(\"([Gender].[M], [Marital Status].[Separated])\"," + " [Gender], [Marital Status])", ""); } public void testStrToTupleDuHierarchiesFails() { assertAxisThrows( "{StrToTuple(\"([Gender].[F], [Time].[1997].[Q2], [Gender].[M])\", [Gender], [Time], [Gender])}", "Tuple contains more than one member of hierarchy '[Gender]'."); } public void testStrToTupleDupHierInSameDimensions() { assertAxisThrows( "{StrToTuple(" + "\"([Gender].[F], " + "[Time].[1997].[Q2], " + "[Time].[Weekly].[1997].[10])\"," + " [Gender], " + TestContext.hierarchyName("Time", "Weekly") + ", [Gender])}", "Tuple contains more than one member of hierarchy '[Gender]'."); } public void testStrToTupleDepends() { getTestContext().assertMemberExprDependsOn( "StrToTuple(\"[Time].[1997].[Q2]\", [Time])", "{}"); // converted to scalar, depends set is larger getTestContext().assertExprDependsOn( "StrToTuple(\"[Time].[1997].[Q2]\", [Time])", TestContext.allHiersExcept("[Time]")); getTestContext().assertMemberExprDependsOn( "StrToTuple(\"[Time].[1997].[Q2], [Gender].[F]\", [Time], [Gender])", "{}"); getTestContext().assertExprDependsOn( "StrToTuple(\"[Time].[1997].[Q2], [Gender].[F]\", [Time], [Gender])", TestContext.allHiersExcept("[Time]", "[Gender]")); } public void testStrToSet() { // TODO: handle text after '}' // TODO: handle string which ends too soon // TODO: handle spaces before first '{' // TODO: test spaces before unbracketed names, // e.g. "{Gender. M, Gender. F }". assertAxisReturns( "StrToSet(" + " \"{[Gender].[F], [Gender].[M]}\"," + " [Gender])", "[Gender].[F]\n" + "[Gender].[M]"); assertAxisThrows( "StrToSet(" + " \"{[Gender].[F], [Time].[1997]}\"," + " [Gender])", "member is of wrong hierarchy"); // whitespace ok assertAxisReturns( "StrToSet(" + " \" { [Gender] . [F] ,[Gender].[M] } \"," + " [Gender])", "[Gender].[F]\n" + "[Gender].[M]"); // tuples assertAxisReturns( "StrToSet(" + "\"" + "{" + " ([Gender].[F], [Time].[1997].[Q2]), " + " ([Gender].[M], [Time].[1997])" + "}" + "\"," + " [Gender]," + " [Time])", "{[Gender].[F], [Time].[1997].[Q2]}\n" + "{[Gender].[M], [Time].[1997]}"); // matches unique name assertAxisReturns( "StrToSet(" + "\"" + "{" + " [Store].[USA].[CA], " + " [Store].[All Stores].[USA].OR," + " [Store].[All Stores]. [USA] . [WA]" + "}" + "\"," + " [Store])", "[Store].[USA].[CA]\n" + "[Store].[USA].[OR]\n" + "[Store].[USA].[WA]"); } public void testStrToSetDupDimensionsFails() { assertAxisThrows( "StrToSet(" + "\"" + "{" + " ([Gender].[F], [Time].[1997].[Q2], [Gender].[F]), " + " ([Gender].[M], [Time].[1997], [Gender].[F])" + "}" + "\"," + " [Gender]," + " [Time]," + " [Gender])", "Tuple contains more than one member of hierarchy '[Gender]'."); } public void testStrToSetIgnoreInvalidMembers() { final MondrianProperties properties = MondrianProperties.instance(); propSaver.set(properties.IgnoreInvalidMembersDuringQuery, true); assertAxisReturns( "StrToSet(" + "\"" + "{" + " [Product].[Food]," + " [Product].[Food].[You wouldn't like]," + " [Product].[Drink].[You would like]," + " [Product].[Drink].[Dairy]" + "}" + "\"," + " [Product])", "[Product].[Food]\n" + "[Product].[Drink].[Dairy]"); assertAxisReturns( "StrToSet(" + "\"" + "{" + " ([Gender].[M], [Product].[Food])," + " ([Gender].[F], [Product].[Food].[You wouldn't like])," + " ([Gender].[M], [Product].[Drink].[You would like])," + " ([Gender].[F], [Product].[Drink].[Dairy])" + "}" + "\"," + " [Gender], [Product])", "{[Gender].[M], [Product].[Food]}\n" + "{[Gender].[F], [Product].[Drink].[Dairy]}"); } public void testYtd() { assertAxisReturns( "Ytd()", "[Time].[1997]"); assertAxisReturns( "Ytd([Time].[1997].[Q3])", "[Time].[1997].[Q1]\n" + "[Time].[1997].[Q2]\n" + "[Time].[1997].[Q3]"); assertAxisReturns( "Ytd([Time].[1997].[Q2].[4])", "[Time].[1997].[Q1].[1]\n" + "[Time].[1997].[Q1].[2]\n" + "[Time].[1997].[Q1].[3]\n" + "[Time].[1997].[Q2].[4]"); assertAxisThrows( "Ytd([Store])", "Argument to function 'Ytd' must belong to Time hierarchy"); getTestContext().assertSetExprDependsOn( "Ytd()", "{[Time], " + TimeWeekly + "}"); getTestContext().assertSetExprDependsOn( "Ytd([Time].[1997].[Q2])", "{}"); } /** * Testcase for * * bug MONDRIAN-458, "error deducing type of Ytd/Qtd/Mtd functions within * Generate". */ public void testGeneratePlusXtd() { assertAxisReturns( "generate(\n" + " {[Time].[1997].[Q1].[2], [Time].[1997].[Q3].[7]},\n" + " {Ytd( [Time].[Time].currentMember)})", "[Time].[1997].[Q1].[1]\n" + "[Time].[1997].[Q1].[2]\n" + "[Time].[1997].[Q1].[3]\n" + "[Time].[1997].[Q2].[4]\n" + "[Time].[1997].[Q2].[5]\n" + "[Time].[1997].[Q2].[6]\n" + "[Time].[1997].[Q3].[7]"); assertAxisReturns( "generate(\n" + " {[Time].[1997].[Q1].[2], [Time].[1997].[Q3].[7]},\n" + " {Ytd( [Time].[Time].currentMember)}, ALL)", "[Time].[1997].[Q1].[1]\n" + "[Time].[1997].[Q1].[2]\n" + "[Time].[1997].[Q1].[1]\n" + "[Time].[1997].[Q1].[2]\n" + "[Time].[1997].[Q1].[3]\n" + "[Time].[1997].[Q2].[4]\n" + "[Time].[1997].[Q2].[5]\n" + "[Time].[1997].[Q2].[6]\n" + "[Time].[1997].[Q3].[7]"); assertExprReturns( "count(generate({[Time].[1997].[Q4].[11]}," + " {Qtd( [Time].[Time].currentMember)}))", 2, 0); assertExprReturns( "count(generate({[Time].[1997].[Q4].[11]}," + " {Mtd( [Time].[Time].currentMember)}))", 1, 0); } public void testQtd() { // zero args assertQueryReturns( "with member [Measures].[Foo] as ' SetToStr(Qtd()) '\n" + "select {[Measures].[Foo]} on columns\n" + "from [Sales]\n" + "where [Time].[1997].[Q2].[5]", "Axis #0:\n" + "{[Time].[1997].[Q2].[5]}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: {[Time].[1997].[Q2].[4], [Time].[1997].[Q2].[5]}\n"); // one arg, a month assertAxisReturns( "Qtd([Time].[1997].[Q2].[5])", "[Time].[1997].[Q2].[4]\n" + "[Time].[1997].[Q2].[5]"); // one arg, a quarter assertAxisReturns( "Qtd([Time].[1997].[Q2])", "[Time].[1997].[Q2]"); // one arg, a year assertAxisReturns( "Qtd([Time].[1997])", ""); assertAxisThrows( "Qtd([Store])", "Argument to function 'Qtd' must belong to Time hierarchy"); } public void testMtd() { // zero args assertQueryReturns( "with member [Measures].[Foo] as ' SetToStr(Mtd()) '\n" + "select {[Measures].[Foo]} on columns\n" + "from [Sales]\n" + "where [Time].[1997].[Q2].[5]", "Axis #0:\n" + "{[Time].[1997].[Q2].[5]}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: {[Time].[1997].[Q2].[5]}\n"); // one arg, a month assertAxisReturns( "Mtd([Time].[1997].[Q2].[5])", "[Time].[1997].[Q2].[5]"); // one arg, a quarter assertAxisReturns( "Mtd([Time].[1997].[Q2])", ""); // one arg, a year assertAxisReturns( "Mtd([Time].[1997])", ""); assertAxisThrows( "Mtd([Store])", "Argument to function 'Mtd' must belong to Time hierarchy"); } public void testPeriodsToDate() { getTestContext().assertSetExprDependsOn("PeriodsToDate()", "{[Time]}"); getTestContext().assertSetExprDependsOn( "PeriodsToDate([Time].[Year])", "{[Time]}"); getTestContext().assertSetExprDependsOn( "PeriodsToDate([Time].[Year], [Time].[1997].[Q2].[5])", "{}"); // two args assertAxisReturns( "PeriodsToDate([Time].[Quarter], [Time].[1997].[Q2].[5])", "[Time].[1997].[Q2].[4]\n" + "[Time].[1997].[Q2].[5]"); // equivalent to above assertAxisReturns( "TopCount(" + " Descendants(" + " Ancestor(" + " [Time].[1997].[Q2].[5], [Time].[Quarter])," + " [Time].[1997].[Q2].[5].Level)," + " 1).Item(0) : [Time].[1997].[Q2].[5]", "[Time].[1997].[Q2].[4]\n" + "[Time].[1997].[Q2].[5]"); // one arg assertQueryReturns( "with member [Measures].[Foo] as ' SetToStr(PeriodsToDate([Time].[Quarter])) '\n" + "select {[Measures].[Foo]} on columns\n" + "from [Sales]\n" + "where [Time].[1997].[Q2].[5]", "Axis #0:\n" + "{[Time].[1997].[Q2].[5]}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: {[Time].[1997].[Q2].[4], [Time].[1997].[Q2].[5]}\n"); // zero args assertQueryReturns( "with member [Measures].[Foo] as ' SetToStr(PeriodsToDate()) '\n" + "select {[Measures].[Foo]} on columns\n" + "from [Sales]\n" + "where [Time].[1997].[Q2].[5]", "Axis #0:\n" + "{[Time].[1997].[Q2].[5]}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: {[Time].[1997].[Q2].[4], [Time].[1997].[Q2].[5]}\n"); // zero args, evaluated at a member which is at the top level. // The default level is the level above the current member -- so // choosing a member at the highest level might trip up the // implementation. assertQueryReturns( "with member [Measures].[Foo] as ' SetToStr(PeriodsToDate()) '\n" + "select {[Measures].[Foo]} on columns\n" + "from [Sales]\n" + "where [Time].[1997]", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: {}\n"); // Testcase for bug 1598379, which caused NPE because the args[0].type // knew its dimension but not its hierarchy. assertQueryReturns( "with member [Measures].[Position] as\n" + " 'Sum(" + "PeriodsToDate([Time].[Time].Levels(0)," + " [Time].[Time].CurrentMember), " + "[Measures].[Store Sales])'\n" + "select {[Time].[1997],\n" + " [Time].[1997].[Q1],\n" + " [Time].[1997].[Q1].[1],\n" + " [Time].[1997].[Q1].[2],\n" + " [Time].[1997].[Q1].[3]} ON COLUMNS,\n" + "{[Measures].[Store Sales], [Measures].[Position] } ON ROWS\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q1].[1]}\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "Axis #2:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Position]}\n" + "Row #0: 565,238.13\n" + "Row #0: 139,628.35\n" + "Row #0: 45,539.69\n" + "Row #0: 44,058.79\n" + "Row #0: 50,029.87\n" + "Row #1: 565,238.13\n" + "Row #1: 139,628.35\n" + "Row #1: 45,539.69\n" + "Row #1: 89,598.48\n" + "Row #1: 139,628.35\n"); assertQueryReturns( "select\n" + "{[Measures].[Unit Sales]} on columns,\n" + "periodstodate(\n" + " [Product].[Product Category],\n" + " [Product].[Food].[Baked Goods].[Bread].[Muffins]) on rows\n" + "from [Sales]\n" + "", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Baked Goods].[Bread].[Bagels]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Muffins]}\n" + "Row #0: 815\n" + "Row #1: 3,497\n" + ""); // TODO: enable if (false) { assertExprThrows( "Sum(PeriodsToDate([Time.Weekly].[Year], [Time].CurrentMember), [Measures].[Unit Sales])", "wrong dimension"); } } public void testSetToStr() { assertExprReturns( "SetToStr([Time].[Time].children)", "{[Time].[1997].[Q1], [Time].[1997].[Q2], [Time].[1997].[Q3], [Time].[1997].[Q4]}"); // Now, applied to tuples assertExprReturns( "SetToStr({CrossJoin([Marital Status].children, {[Gender].[M]})})", "{" + "([Marital Status].[M], [Gender].[M]), " + "([Marital Status].[S], [Gender].[M])" + "}"); } public void testTupleToStr() { // Applied to a dimension (which becomes a member) assertExprReturns( "TupleToStr([Product])", "[Product].[All Products]"); // Applied to a dimension (invalid because has no default hierarchy) if (MondrianProperties.instance().SsasCompatibleNaming.get()) { assertExprThrows( "TupleToStr([Time])", "The 'Time' dimension contains more than one hierarchy, " + "therefore the hierarchy must be explicitly specified."); } else { assertExprReturns( "TupleToStr([Time])", "[Time].[1997]"); } // Applied to a hierarchy assertExprReturns( "TupleToStr([Time].[Time])", "[Time].[1997]"); // Applied to a member assertExprReturns( "TupleToStr([Store].[USA].[OR])", "[Store].[USA].[OR]"); // Applied to a member (extra set of parens) assertExprReturns( "TupleToStr(([Store].[USA].[OR]))", "[Store].[USA].[OR]"); // Now, applied to a tuple assertExprReturns( "TupleToStr(([Marital Status], [Gender].[M]))", "([Marital Status].[All Marital Status], [Gender].[M])"); // Applied to a tuple containing a null member assertExprReturns( "TupleToStr(([Marital Status], [Gender].Parent))", ""); // Applied to a null member assertExprReturns( "TupleToStr([Marital Status].Parent)", ""); } /** * Executes a scalar expression, and asserts that the result is as * expected. For example, assertExprReturns("1 + 2", "3") * should succeed. */ public void assertExprReturns(String expr, String expected) { String actual = executeExpr(expr); assertEquals(expected, actual); } /** * Executes a scalar expression, and asserts that the result is within * delta of the expected result. * * @param expr MDX scalar expression * @param expected Expected value * @param delta Maximum allowed deviation from expected value */ public void assertExprReturns( String expr, double expected, double delta) { Object value = getTestContext().executeExprRaw(expr).getValue(); try { double actual = ((Number) value).doubleValue(); if (Double.isNaN(expected) && Double.isNaN(actual)) { return; } Assert.assertEquals( null, expected, actual, delta); } catch (ClassCastException ex) { String msg = "Actual value \"" + value + "\" is not a number."; throw new ComparisonFailure( msg, Double.toString(expected), String.valueOf(value)); } } /** * Compiles a scalar expression, and asserts that the program looks as * expected. */ public void assertExprCompilesTo( String expr, String expectedCalc) { final String actualCalc = getTestContext().compileExpression(expr, true); final int expDeps = MondrianProperties.instance().TestExpDependencies.get(); if (expDeps > 0) { // Don't bother checking the compiled output if we are also // testing dependencies. The compiled code will have extra // 'DependencyTestingCalc' instances embedded in it. return; } TestContext.assertEqualsVerbose(expectedCalc, actualCalc); } /** * Compiles a set expression, and asserts that the program looks as * expected. */ public void assertAxisCompilesTo( String expr, String expectedCalc) { final String actualCalc = getTestContext().compileExpression(expr, false); final int expDeps = MondrianProperties.instance().TestExpDependencies.get(); if (expDeps > 0) { // Don't bother checking the compiled output if we are also // testing dependencies. The compiled code will have extra // 'DependencyTestingCalc' instances embedded in it. return; } TestContext.assertEqualsVerbose(expectedCalc, actualCalc); } /** * Tests the Rank(member, set) MDX function. */ public void testRank() { // Member within set assertExprReturns( "Rank([Store].[USA].[CA], " + "{[Store].[USA].[OR]," + " [Store].[USA].[CA]," + " [Store].[USA]})", "2"); // Member not in set assertExprReturns( "Rank([Store].[USA].[WA], " + "{[Store].[USA].[OR]," + " [Store].[USA].[CA]," + " [Store].[USA]})", "0"); // Member not in empty set assertExprReturns( "Rank([Store].[USA].[WA], {})", "0"); // Null member not in set returns null. assertExprReturns( "Rank([Store].Parent, " + "{[Store].[USA].[OR]," + " [Store].[USA].[CA]," + " [Store].[USA]})", ""); // Null member in empty set. (MSAS returns an error "Formula error - // dimension count is not valid - in the Rank function" but I think // null is the correct behavior.) assertExprReturns( "Rank([Gender].Parent, {})", ""); // Member occurs twice in set -- pick first assertExprReturns( "Rank([Store].[USA].[WA], \n" + "{[Store].[USA].[WA]," + " [Store].[USA].[CA]," + " [Store].[USA]," + " [Store].[USA].[WA]})", "1"); // Tuple not in set assertExprReturns( "Rank(([Gender].[F], [Marital Status].[M]), \n" + "{([Gender].[F], [Marital Status].[S]),\n" + " ([Gender].[M], [Marital Status].[S]),\n" + " ([Gender].[M], [Marital Status].[M])})", "0"); // Tuple in set assertExprReturns( "Rank(([Gender].[F], [Marital Status].[M]), \n" + "{([Gender].[F], [Marital Status].[S]),\n" + " ([Gender].[M], [Marital Status].[S]),\n" + " ([Gender].[F], [Marital Status].[M])})", "3"); // Tuple not in empty set assertExprReturns( "Rank(([Gender].[F], [Marital Status].[M]), \n" + "{})", "0"); // Partially null tuple in set, returns null assertExprReturns( "Rank(([Gender].[F], [Marital Status].Parent), \n" + "{([Gender].[F], [Marital Status].[S]),\n" + " ([Gender].[M], [Marital Status].[S]),\n" + " ([Gender].[F], [Marital Status].[M])})", ""); } public void testRankWithExpr() { // Note that [Good] and [Top Measure] have the same [Unit Sales] // value (5), but [Good] ranks 1 and [Top Measure] ranks 2. Even though // they are sorted descending on unit sales, they remain in their // natural order (member name) because MDX sorts are stable. assertQueryReturns( "with member [Measures].[Sibling Rank] as ' Rank([Product].CurrentMember, [Product].CurrentMember.Siblings) '\n" + " member [Measures].[Sales Rank] as ' Rank([Product].CurrentMember, Order([Product].Parent.Children, [Measures].[Unit Sales], DESC)) '\n" + " member [Measures].[Sales Rank2] as ' Rank([Product].CurrentMember, [Product].Parent.Children, [Measures].[Unit Sales]) '\n" + "select {[Measures].[Unit Sales], [Measures].[Sales Rank], [Measures].[Sales Rank2]} on columns,\n" + " {[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].children} on rows\n" + "from [Sales]\n" + "WHERE ([Store].[USA].[OR].[Portland].[Store 11], [Time].[1997].[Q2].[6])", "Axis #0:\n" + "{[Store].[USA].[OR].[Portland].[Store 11], [Time].[1997].[Q2].[6]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Rank]}\n" + "{[Measures].[Sales Rank2]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Pearl]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Top Measure]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Walrus]}\n" + "Row #0: 5\n" + "Row #0: 1\n" + "Row #0: 1\n" + "Row #1: \n" + "Row #1: 5\n" + "Row #1: 5\n" + "Row #2: 3\n" + "Row #2: 3\n" + "Row #2: 3\n" + "Row #3: 5\n" + "Row #3: 2\n" + "Row #3: 1\n" + "Row #4: 3\n" + "Row #4: 4\n" + "Row #4: 3\n"); } public void testRankMembersWithTiedExpr() { assertQueryReturns( "with " + " Set [Beers] as {[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].children} " + " member [Measures].[Sales Rank] as ' Rank([Product].CurrentMember, [Beers], [Measures].[Unit Sales]) '\n" + "select {[Measures].[Unit Sales], [Measures].[Sales Rank]} on columns,\n" + " Generate([Beers], {[Product].CurrentMember}) on rows\n" + "from [Sales]\n" + "WHERE ([Store].[USA].[OR].[Portland].[Store 11], [Time].[1997].[Q2].[6])", "Axis #0:\n" + "{[Store].[USA].[OR].[Portland].[Store 11], [Time].[1997].[Q2].[6]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Rank]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Pearl]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Top Measure]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Walrus]}\n" + "Row #0: 5\n" + "Row #0: 1\n" + "Row #1: \n" + "Row #1: 5\n" + "Row #2: 3\n" + "Row #2: 3\n" + "Row #3: 5\n" + "Row #3: 1\n" + "Row #4: 3\n" + "Row #4: 3\n"); } public void testRankTuplesWithTiedExpr() { assertQueryReturns( "with " + " Set [Beers for Store] as 'NonEmptyCrossJoin(" + "[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].children, " + "{[Store].[USA].[OR].[Portland].[Store 11]})' " + " member [Measures].[Sales Rank] as ' Rank(([Product].CurrentMember,[Store].CurrentMember), [Beers for Store], [Measures].[Unit Sales]) '\n" + "select {[Measures].[Unit Sales], [Measures].[Sales Rank]} on columns,\n" + " Generate([Beers for Store], {([Product].CurrentMember, [Store].CurrentMember)}) on rows\n" + "from [Sales]\n" + "WHERE ([Time].[1997].[Q2].[6])", "Axis #0:\n" + "{[Time].[1997].[Q2].[6]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Rank]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good], [Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth], [Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Top Measure], [Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Walrus], [Store].[USA].[OR].[Portland].[Store 11]}\n" + "Row #0: 5\n" + "Row #0: 1\n" + "Row #1: 3\n" + "Row #1: 3\n" + "Row #2: 5\n" + "Row #2: 1\n" + "Row #3: 3\n" + "Row #3: 3\n"); } public void testRankWithExpr2() { // Data: Unit Sales // All gender 266,733 // F 131,558 // M 135,215 assertExprReturns( "Rank([Gender].[All Gender]," + " {[Gender].Members}," + " [Measures].[Unit Sales])", "1"); assertExprReturns( "Rank([Gender].[F]," + " {[Gender].Members}," + " [Measures].[Unit Sales])", "3"); assertExprReturns( "Rank([Gender].[M]," + " {[Gender].Members}," + " [Measures].[Unit Sales])", "2"); // Null member. Expression evaluates to null, therefore value does // not appear in the list of values, therefore the rank is null. assertExprReturns( "Rank([Gender].[All Gender].Parent," + " {[Gender].Members}," + " [Measures].[Unit Sales])", ""); // Empty set. Value would appear after all elements in the empty set, // therefore rank is 1. // Note that SSAS gives error 'The first argument to the Rank function, // a tuple expression, should reference the same hierachies as the // second argument, a set expression'. I think that's because it can't // deduce a type for '{}'. SSAS's problem, not Mondrian's. :) assertExprReturns( "Rank([Gender].[M]," + " {}," + " [Measures].[Unit Sales])", "1"); // As above, but SSAS can type-check this. assertExprReturns( "Rank([Gender].[M]," + " Filter(Gender.Members, 1 = 0)," + " [Measures].[Unit Sales])", "1"); // Member is not in set assertExprReturns( "Rank([Gender].[M]," + " {[Gender].[All Gender], [Gender].[F]})", "0"); // Even though M is not in the set, its value lies between [All Gender] // and [F]. assertExprReturns( "Rank([Gender].[M]," + " {[Gender].[All Gender], [Gender].[F]}," + " [Measures].[Unit Sales])", "2"); // Expr evaluates to null for some values of set. assertExprReturns( "Rank([Product].[Non-Consumable].[Household]," + " {[Product].[Food], [Product].[All Products], [Product].[Drink].[Dairy]}," + " [Product].CurrentMember.Parent)", "2"); // Expr evaluates to null for all values in the set. assertExprReturns( "Rank([Gender].[M]," + " {[Gender].[All Gender], [Gender].[F]}," + " [Marital Status].[All Marital Status].Parent)", "1"); } /** * Tests the 3-arg version of the RANK function with a value * which returns null within a set of nulls. */ public void testRankWithNulls() { assertQueryReturns( "with member [Measures].[X] as " + "'iif([Measures].[Store Sales]=777," + "[Measures].[Store Sales],Null)'\n" + "member [Measures].[Y] as 'Rank([Gender].[M]," + "{[Measures].[X],[Measures].[X],[Measures].[X]}," + " [Marital Status].[All Marital Status].Parent)'" + "select {[Measures].[Y]} on columns from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Y]}\n" + "Row #0: 1\n"); } /** * Tests a RANK function which is so large that we need to use caching * in order to execute it efficiently. */ public void testRankHuge() { // If caching is disabled, don't even try -- it will take too long. if (!MondrianProperties.instance().EnableExpCache.get()) { return; } checkRankHuge( "WITH \n" + " MEMBER [Measures].[Rank among products] \n" + " AS ' Rank([Product].CurrentMember, " + " Order([Product].members, " + " [Measures].[Unit Sales], BDESC)) '\n" + "SELECT CrossJoin(\n" + " [Gender].members,\n" + " {[Measures].[Unit Sales],\n" + " [Measures].[Rank among products]}) ON COLUMNS,\n" // + " {[Product], [Product].[All Products].[Non-Consumable]. // [Periodicals].[Magazines].[Sports Magazines].[Robust]. // [Robust Monthly Sports Magazine]} ON ROWS\n" + " {[Product].members} ON ROWS\n" + "FROM [Sales]", false); } /** * As {@link #testRankHuge()}, but for the 3-argument form of the * RANK function. * *

Disabled by jhyde, 2006/2/14. Bug 1431316 logged. */ public void _testRank3Huge() { // If caching is disabled, don't even try -- it will take too long. if (!MondrianProperties.instance().EnableExpCache.get()) { return; } checkRankHuge( "WITH \n" + " MEMBER [Measures].[Rank among products] \n" + " AS ' Rank([Product].CurrentMember, [Product].members, [Measures].[Unit Sales]) '\n" + "SELECT CrossJoin(\n" + " [Gender].members,\n" + " {[Measures].[Unit Sales],\n" + " [Measures].[Rank among products]}) ON COLUMNS,\n" + " {[Product]," + " [Product].[All Products].[Non-Consumable].[Periodicals]" + ".[Magazines].[Sports Magazines].[Robust]" + ".[Robust Monthly Sports Magazine]} ON ROWS\n" // + " {[Product].members} ON ROWS\n" + "FROM [Sales]", true); } private void checkRankHuge(String query, boolean rank3) { final Result result = getTestContext().executeQuery(query); final Axis[] axes = result.getAxes(); final Axis rowsAxis = axes[1]; final int rowCount = rowsAxis.getPositions().size(); assertEquals(2256, rowCount); // [All Products], [All Gender], [Rank] Cell cell = result.getCell(new int[] {1, 0}); assertEquals("1", cell.getFormattedValue()); // [Robust Monthly Sports Magazine] Member member = rowsAxis.getPositions().get(rowCount - 1).get(0); assertEquals("Robust Monthly Sports Magazine", member.getName()); // [Robust Monthly Sports Magazine], [All Gender], [Rank] cell = result.getCell(new int[] {0, rowCount - 1}); assertEquals("152", cell.getFormattedValue()); cell = result.getCell(new int[] {1, rowCount - 1}); assertEquals(rank3 ? "1,854" : "1,871", cell.getFormattedValue()); // [Robust Monthly Sports Magazine], [Gender].[F], [Rank] cell = result.getCell(new int[] {2, rowCount - 1}); assertEquals("90", cell.getFormattedValue()); cell = result.getCell(new int[] {3, rowCount - 1}); assertEquals(rank3 ? "1,119" : "1,150", cell.getFormattedValue()); // [Robust Monthly Sports Magazine], [Gender].[M], [Rank] cell = result.getCell(new int[] {4, rowCount - 1}); assertEquals("62", cell.getFormattedValue()); cell = result.getCell(new int[] {5, rowCount - 1}); assertEquals(rank3 ? "2,131" : "2,147", cell.getFormattedValue()); } public void testLinRegPointQuarter() { assertQueryReturns( "WITH MEMBER [Measures].[Test] as \n" + " 'LinRegPoint(\n" + " Rank(Time.[Time].CurrentMember, Time.[Time].CurrentMember.Level.Members),\n" + " Descendants([Time].[1997], [Time].[Quarter]), \n" + "[Measures].[Store Sales], \n" + " Rank(Time.[Time].CurrentMember, Time.[Time].CurrentMember.Level.Members))' \n" + "SELECT \n" + "{[Measures].[Test],[Measures].[Store Sales]} ON ROWS, \n" + "{[Time].[1997].Children} ON COLUMNS \n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q4]}\n" + "Axis #2:\n" + "{[Measures].[Test]}\n" + "{[Measures].[Store Sales]}\n" + "Row #0: 134,299.22\n" + "Row #0: 138,972.76\n" + "Row #0: 143,646.30\n" + "Row #0: 148,319.85\n" + "Row #1: 139,628.35\n" + "Row #1: 132,666.27\n" + "Row #1: 140,271.89\n" + "Row #1: 152,671.62\n"); } /** * Tests all of the linear regression functions, as suggested by * a Microsoft knowledge * base article. */ public void _testLinRegAll() { // We have not implemented the LastPeriods function, so we use // [Time].CurrentMember.Lag(9) : [Time].CurrentMember // is equivalent to // LastPeriods(10) assertQueryReturns( "WITH MEMBER \n" + "[Measures].[Intercept] AS \n" + " 'LinRegIntercept([Time].CurrentMember.Lag(10) : [Time].CurrentMember, [Measures].[Unit Sales], [Measures].[Store Sales])' \n" + "MEMBER [Measures].[Regression Slope] AS\n" + " 'LinRegSlope([Time].CurrentMember.Lag(9) : [Time].CurrentMember,[Measures].[Unit Sales],[Measures].[Store Sales]) '\n" + "MEMBER [Measures].[Predict] AS\n" + " 'LinRegPoint([Measures].[Unit Sales],[Time].CurrentMember.Lag(9) : [Time].CurrentMember,[Measures].[Unit Sales],[Measures].[Store Sales])',\n" + " FORMAT_STRING = 'Standard' \n" + "MEMBER [Measures].[Predict Formula] AS\n" + " '([Measures].[Regression Slope] * [Measures].[Unit Sales]) + [Measures].[Intercept]',\n" + " FORMAT_STRING='Standard'\n" + "MEMBER [Measures].[Good Fit] AS\n" + " 'LinRegR2([Time].CurrentMember.Lag(9) : [Time].CurrentMember, [Measures].[Unit Sales],[Measures].[Store Sales])',\n" + " FORMAT_STRING='#,#.00'\n" + "MEMBER [Measures].[Variance] AS\n" + " 'LinRegVariance([Time].CurrentMember.Lag(9) : [Time].CurrentMember,[Measures].[Unit Sales],[Measures].[Store Sales])'\n" + "SELECT \n" + " {[Measures].[Store Sales], \n" + " [Measures].[Intercept], \n" + " [Measures].[Regression Slope], \n" + " [Measures].[Predict], \n" + " [Measures].[Predict Formula], \n" + " [Measures].[Good Fit], \n" + " [Measures].[Variance] } ON COLUMNS, \n" + " Descendants([Time].[1997], [Time].[Month]) ON ROWS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Intercept]}\n" + "{[Measures].[Regression Slope]}\n" + "{[Measures].[Predict]}\n" + "{[Measures].[Predict Formula]}\n" + "{[Measures].[Good Fit]}\n" + "{[Measures].[Variance]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1].[1]}\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[5]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q3].[7]}\n" + "{[Time].[1997].[Q3].[8]}\n" + "{[Time].[1997].[Q3].[9]}\n" + "{[Time].[1997].[Q4].[10]}\n" + "{[Time].[1997].[Q4].[11]}\n" + "{[Time].[1997].[Q4].[12]}\n" + "Row #0: 45,539.69\n" + "Row #0: 68711.40\n" + "Row #0: -1.033\n" + "Row #0: 46,350.26\n" + "Row #0: 46.350.26\n" + "Row #0: -1.#INF\n" + "Row #0: 5.17E-08\n" + "...\n" + "Row #11: 15343.67\n"); } public void testLinRegPointMonth() { assertQueryReturns( "WITH MEMBER \n" + "[Measures].[Test] as \n" + " 'LinRegPoint(\n" + " Rank(Time.[Time].CurrentMember, Time.[Time].CurrentMember.Level.Members),\n" + " Descendants([Time].[1997], [Time].[Month]), \n" + " [Measures].[Store Sales], \n" + " Rank(Time.[Time].CurrentMember, Time.[Time].CurrentMember.Level.Members)\n" + " )' \n" + "SELECT \n" + " {[Measures].[Test],[Measures].[Store Sales]} ON ROWS, \n" + " Descendants([Time].[1997], [Time].[Month]) ON COLUMNS \n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997].[Q1].[1]}\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[5]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q3].[7]}\n" + "{[Time].[1997].[Q3].[8]}\n" + "{[Time].[1997].[Q3].[9]}\n" + "{[Time].[1997].[Q4].[10]}\n" + "{[Time].[1997].[Q4].[11]}\n" + "{[Time].[1997].[Q4].[12]}\n" + "Axis #2:\n" + "{[Measures].[Test]}\n" + "{[Measures].[Store Sales]}\n" + "Row #0: 43,824.36\n" + "Row #0: 44,420.51\n" + "Row #0: 45,016.66\n" + "Row #0: 45,612.81\n" + "Row #0: 46,208.95\n" + "Row #0: 46,805.10\n" + "Row #0: 47,401.25\n" + "Row #0: 47,997.40\n" + "Row #0: 48,593.55\n" + "Row #0: 49,189.70\n" + "Row #0: 49,785.85\n" + "Row #0: 50,382.00\n" + "Row #1: 45,539.69\n" + "Row #1: 44,058.79\n" + "Row #1: 50,029.87\n" + "Row #1: 42,878.25\n" + "Row #1: 44,456.29\n" + "Row #1: 45,331.73\n" + "Row #1: 50,246.88\n" + "Row #1: 46,199.04\n" + "Row #1: 43,825.97\n" + "Row #1: 42,342.27\n" + "Row #1: 53,363.71\n" + "Row #1: 56,965.64\n"); } public void testLinRegIntercept() { assertExprReturns( "LinRegIntercept([Time].[Month].members," + " [Measures].[Unit Sales], [Measures].[Store Sales])", -126.65, 0.50); /* -1#IND missing data */ /* 1#INF division by zero */ /* The following table shows query return values from using different FORMAT_STRING's in an expression involving 'division by zero' (tested on Intel platforms): +===========================+=====================+ | Format Strings | Query Return Values | +===========================+=====================+ | FORMAT_STRING=" | 1.#INF | +===========================+=====================+ | FORMAT_STRING='Standard' | 1.#J | +===========================+=====================+ | FORMAT_STRING='Fixed' | 1.#J | +===========================+=====================+ | FORMAT_STRING='Percent' | 1#I.NF% | +===========================+=====================+ | FORMAT_STRING='Scientific'| 1.JE+00 | +===========================+=====================+ */ // Mondrian can not return "missing data" value -1.#IND // empty set if (false) { assertExprReturns( "LinRegIntercept({[Time].Parent}," + " [Measures].[Unit Sales], [Measures].[Store Sales])", "-1.#IND"); // MSAS returns -1.#IND (whatever that means) } // first expr constant if (false) { assertExprReturns( "LinRegIntercept([Time].[Month].members," + " 7, [Measures].[Store Sales])", "$7.00"); } // format does not add '$' assertExprReturns( "LinRegIntercept([Time].[Month].members," + " 7, [Measures].[Store Sales])", 7.00, 0.01); // Mondrian can not return "missing data" value -1.#IND // second expr constant if (false) { assertExprReturns( "LinRegIntercept([Time].[Month].members," + " [Measures].[Unit Sales], 4)", "-1.#IND"); // MSAS returns -1.#IND (whatever that means) } } public void testLinRegSlope() { assertExprReturns( "LinRegSlope([Time].[Month].members," + " [Measures].[Unit Sales], [Measures].[Store Sales])", 0.4746, 0.50); // Mondrian can not return "missing data" value -1.#IND // empty set if (false) { assertExprReturns( "LinRegSlope({[Time].Parent}," + " [Measures].[Unit Sales], [Measures].[Store Sales])", "-1.#IND"); // MSAS returns -1.#IND (whatever that means) } // first expr constant if (false) { assertExprReturns( "LinRegSlope([Time].[Month].members," + " 7, [Measures].[Store Sales])", "$7.00"); } // ^^^^ // copy and paste error assertExprReturns( "LinRegSlope([Time].[Month].members," + " 7, [Measures].[Store Sales])", 0.00, 0.01); // Mondrian can not return "missing data" value -1.#IND // second expr constant if (false) { assertExprReturns( "LinRegSlope([Time].[Month].members," + " [Measures].[Unit Sales], 4)", "-1.#IND"); // MSAS returns -1.#IND (whatever that means) } } public void testLinRegPoint() { // NOTE: mdx does not parse if (false) { assertExprReturns( "LinRegPoint([Measures].[Unit Sales]," + " [Time].CurrentMember[Time].[Month].members," + " [Measures].[Unit Sales], [Measures].[Store Sales])", "0.4746"); } // Mondrian can not return "missing data" value -1.#IND // empty set if (false) { assertExprReturns( "LinRegPoint([Measures].[Unit Sales]," + " {[Time].Parent}," + " [Measures].[Unit Sales], [Measures].[Store Sales])", "-1.#IND"); // MSAS returns -1.#IND (whatever that means) } // Expected value is wrong // zeroth expr constant if (false) { assertExprReturns( "LinRegPoint(-1," + " [Time].[Month].members," + " 7, [Measures].[Store Sales])", "-127.124"); } // first expr constant if (false) { assertExprReturns( "LinRegPoint([Measures].[Unit Sales]," + " [Time].[Month].members," + " 7, [Measures].[Store Sales])", "$7.00"); } // format does not add '$' assertExprReturns( "LinRegPoint([Measures].[Unit Sales]," + " [Time].[Month].members," + " 7, [Measures].[Store Sales])", 7.00, 0.01); // Mondrian can not return "missing data" value -1.#IND // second expr constant if (false) { assertExprReturns( "LinRegPoint([Measures].[Unit Sales]," + " [Time].[Month].members," + " [Measures].[Unit Sales], 4)", "-1.#IND"); // MSAS returns -1.#IND (whatever that means) } } public void _testLinRegR2() { // Why would R2 equal the slope if (false) { assertExprReturns( "LinRegR2([Time].[Month].members," + " [Measures].[Unit Sales], [Measures].[Store Sales])", "0.4746"); } // Mondrian can not return "missing data" value -1.#IND // empty set if (false) { assertExprReturns( "LinRegR2({[Time].Parent}," + " [Measures].[Unit Sales], [Measures].[Store Sales])", "-1.#IND"); // MSAS returns -1.#IND (whatever that means) } // first expr constant assertExprReturns( "LinRegR2([Time].[Month].members," + " 7, [Measures].[Store Sales])", "$7.00"); // Mondrian can not return "missing data" value -1.#IND // second expr constant if (false) { assertExprReturns( "LinRegR2([Time].[Month].members," + " [Measures].[Unit Sales], 4)", "-1.#IND"); // MSAS returns -1.#IND (whatever that means) } } public void _testLinRegVariance() { assertExprReturns( "LinRegVariance([Time].[Month].members," + " [Measures].[Unit Sales], [Measures].[Store Sales])", "0.4746"); // empty set assertExprReturns( "LinRegVariance({[Time].Parent}," + " [Measures].[Unit Sales], [Measures].[Store Sales])", "-1.#IND"); // MSAS returns -1.#IND (whatever that means) // first expr constant assertExprReturns( "LinRegVariance([Time].[Month].members," + " 7, [Measures].[Store Sales])", "$7.00"); // second expr constant assertExprReturns( "LinRegVariance([Time].[Month].members," + " [Measures].[Unit Sales], 4)", "-1.#IND"); // MSAS returns -1.#IND (whatever that means) } public void testVisualTotalsBasic() { assertQueryReturns( "select {[Measures].[Unit Sales]} on columns, " + "{VisualTotals(" + " {[Product].[All Products].[Food].[Baked Goods].[Bread]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Bagels]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Muffins]}," + " \"**Subtotal - *\")} on rows " + "from [Sales]", // note that Subtotal - Bread only includes 2 displayed children "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Baked Goods].[*Subtotal - Bread]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Bagels]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Muffins]}\n" + "Row #0: 4,312\n" + "Row #1: 815\n" + "Row #2: 3,497\n"); } public void testVisualTotalsConsecutively() { assertQueryReturns( "select {[Measures].[Unit Sales]} on columns, " + "{VisualTotals(" + " {[Product].[All Products].[Food].[Baked Goods].[Bread]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Bagels]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Bagels]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Bagels].[Colony]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Bagels]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Muffins]}," + " \"**Subtotal - *\")} on rows " + "from [Sales]", // Note that [Bagels] occurs 3 times, but only once does it // become a subtotal. Note that the subtotal does not include // the following [Bagels] member. "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Baked Goods].[*Subtotal - Bread]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Bagels]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[*Subtotal - Bagels]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Bagels].[Colony]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Bagels]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Muffins]}\n" + "Row #0: 5,290\n" + "Row #1: 815\n" + "Row #2: 163\n" + "Row #3: 163\n" + "Row #4: 815\n" + "Row #5: 3,497\n"); } public void testVisualTotalsNoPattern() { assertAxisReturns( "VisualTotals(" + " {[Product].[All Products].[Food].[Baked Goods].[Bread]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Bagels]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Muffins]})", // Note that the [Bread] visual member is just called [Bread]. "[Product].[Food].[Baked Goods].[Bread]\n" + "[Product].[Food].[Baked Goods].[Bread].[Bagels]\n" + "[Product].[Food].[Baked Goods].[Bread].[Muffins]"); } public void testVisualTotalsWithFilter() { assertQueryReturns( "select {[Measures].[Unit Sales]} on columns, " + "{Filter(" + " VisualTotals(" + " {[Product].[All Products].[Food].[Baked Goods].[Bread]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Bagels]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Muffins]}," + " \"**Subtotal - *\")," + "[Measures].[Unit Sales] > 3400)} on rows " + "from [Sales]", // Note that [*Subtotal - Bread] still contains the // contribution of [Bagels] 815, which was filtered out. "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Baked Goods].[*Subtotal - Bread]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Muffins]}\n" + "Row #0: 4,312\n" + "Row #1: 3,497\n"); } public void testVisualTotalsNested() { assertQueryReturns( "select {[Measures].[Unit Sales]} on columns, " + "{VisualTotals(" + " Filter(" + " VisualTotals(" + " {[Product].[All Products].[Food].[Baked Goods].[Bread]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Bagels]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Muffins]}," + " \"**Subtotal - *\")," + " [Measures].[Unit Sales] > 3400)," + " \"Second total - *\")} on rows " + "from [Sales]", // Yields the same -- no extra total. "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Baked Goods].[*Subtotal - Bread]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Muffins]}\n" + "Row #0: 4,312\n" + "Row #1: 3,497\n"); } public void testVisualTotalsFilterInside() { assertQueryReturns( "select {[Measures].[Unit Sales]} on columns, " + "{VisualTotals(" + " Filter(" + " {[Product].[All Products].[Food].[Baked Goods].[Bread]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Bagels]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Muffins]}," + " [Measures].[Unit Sales] > 3400)," + " \"**Subtotal - *\")} on rows " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Baked Goods].[*Subtotal - Bread]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Muffins]}\n" + "Row #0: 3,497\n" + "Row #1: 3,497\n"); } public void testVisualTotalsOutOfOrder() { assertQueryReturns( "select {[Measures].[Unit Sales]} on columns, " + "{VisualTotals(" + " {[Product].[All Products].[Food].[Baked Goods].[Bread].[Bagels]," + " [Product].[All Products].[Food].[Baked Goods].[Bread]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Muffins]}," + " \"**Subtotal - *\")} on rows " + "from [Sales]", // Note that [*Subtotal - Bread] 3497 does not include 815 for // bagels. "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Baked Goods].[Bread].[Bagels]}\n" + "{[Product].[Food].[Baked Goods].[*Subtotal - Bread]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Muffins]}\n" + "Row #0: 815\n" + "Row #1: 3,497\n" + "Row #2: 3,497\n"); } public void testVisualTotalsGrandparentsAndOutOfOrder() { assertQueryReturns( "select {[Measures].[Unit Sales]} on columns, " + "{VisualTotals(" + " {[Product].[All Products].[Food]," + " [Product].[All Products].[Food].[Baked Goods].[Bread]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Bagels]," + " [Product].[All Products].[Food].[Frozen Foods].[Breakfast Foods]," + " [Product].[All Products].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[Golden]," + " [Product].[All Products].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[Big Time]," + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Muffins]}," + " \"**Subtotal - *\")} on rows " + "from [Sales]", // Note: // [*Subtotal - Food] = 4513 = 815 + 311 + 3497 // [*Subtotal - Bread] = 815, does not include muffins // [*Subtotal - Breakfast Foods] = 311 = 110 + 201, includes // grandchildren "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[*Subtotal - Food]}\n" + "{[Product].[Food].[Baked Goods].[*Subtotal - Bread]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Bagels]}\n" + "{[Product].[Food].[Frozen Foods].[*Subtotal - Breakfast Foods]}\n" + "{[Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[Golden]}\n" + "{[Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[Big Time]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Muffins]}\n" + "Row #0: 4,623\n" + "Row #1: 815\n" + "Row #2: 815\n" + "Row #3: 311\n" + "Row #4: 110\n" + "Row #5: 201\n" + "Row #6: 3,497\n"); } public void testVisualTotalsCrossjoin() { assertAxisThrows( "VisualTotals(Crossjoin([Gender].Members, [Store].children))", "Argument to 'VisualTotals' function must be a set of members; got set of tuples."); } /** * Test case for bug * MONDRIAN-615, * "VisualTotals doesn't work for the all member". */ public void testVisualTotalsAll() { final String query = "SELECT \n" + " {[Measures].[Unit Sales]} ON 0, \n" + " VisualTotals(\n" + " {[Customers].[All Customers],\n" + " [Customers].[USA],\n" + " [Customers].[USA].[CA],\n" + " [Customers].[USA].[OR]}) ON 1\n" + "FROM [Sales]"; assertQueryReturns( query, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Customers].[All Customers]}\n" + "{[Customers].[USA]}\n" + "{[Customers].[USA].[CA]}\n" + "{[Customers].[USA].[OR]}\n" + "Row #0: 142,407\n" + "Row #1: 142,407\n" + "Row #2: 74,748\n" + "Row #3: 67,659\n"); // Check captions final Result result = getTestContext().executeQuery(query); final List positionList = result.getAxes()[1].getPositions(); assertEquals("All Customers", positionList.get(0).get(0).getCaption()); assertEquals("USA", positionList.get(1).get(0).getCaption()); assertEquals("CA", positionList.get(2).get(0).getCaption()); } /** * Test case involving a named set and query pivoted. Suggested in * MONDRIAN-615, * "VisualTotals doesn't work for the all member". */ public void testVisualTotalsWithNamedSetAndPivot() { assertQueryReturns( "WITH SET [CA_OR] AS\n" + " VisualTotals(\n" + " {[Customers].[All Customers],\n" + " [Customers].[USA],\n" + " [Customers].[USA].[CA],\n" + " [Customers].[USA].[OR]})\n" + "SELECT \n" + " Drilldownlevel({[Time].[1997]}) ON 0, \n" + " [CA_OR] ON 1 \n" + "FROM [Sales] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q4]}\n" + "Axis #2:\n" + "{[Customers].[All Customers]}\n" + "{[Customers].[USA]}\n" + "{[Customers].[USA].[CA]}\n" + "{[Customers].[USA].[OR]}\n" + "Row #0: 142,407\n" + "Row #0: 36,177\n" + "Row #0: 33,131\n" + "Row #0: 35,310\n" + "Row #0: 37,789\n" + "Row #1: 142,407\n" + "Row #1: 36,177\n" + "Row #1: 33,131\n" + "Row #1: 35,310\n" + "Row #1: 37,789\n" + "Row #2: 74,748\n" + "Row #2: 16,890\n" + "Row #2: 18,052\n" + "Row #2: 18,370\n" + "Row #2: 21,436\n" + "Row #3: 67,659\n" + "Row #3: 19,287\n" + "Row #3: 15,079\n" + "Row #3: 16,940\n" + "Row #3: 16,353\n"); // same query, swap axes assertQueryReturns( "WITH SET [CA_OR] AS\n" + " VisualTotals(\n" + " {[Customers].[All Customers],\n" + " [Customers].[USA],\n" + " [Customers].[USA].[CA],\n" + " [Customers].[USA].[OR]})\n" + "SELECT \n" + " [CA_OR] ON 0,\n" + " Drilldownlevel({[Time].[1997]}) ON 1\n" + "FROM [Sales] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[All Customers]}\n" + "{[Customers].[USA]}\n" + "{[Customers].[USA].[CA]}\n" + "{[Customers].[USA].[OR]}\n" + "Axis #2:\n" + "{[Time].[1997]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q4]}\n" + "Row #0: 142,407\n" + "Row #0: 142,407\n" + "Row #0: 74,748\n" + "Row #0: 67,659\n" + "Row #1: 36,177\n" + "Row #1: 36,177\n" + "Row #1: 16,890\n" + "Row #1: 19,287\n" + "Row #2: 33,131\n" + "Row #2: 33,131\n" + "Row #2: 18,052\n" + "Row #2: 15,079\n" + "Row #3: 35,310\n" + "Row #3: 35,310\n" + "Row #3: 18,370\n" + "Row #3: 16,940\n" + "Row #4: 37,789\n" + "Row #4: 37,789\n" + "Row #4: 21,436\n" + "Row #4: 16,353\n"); } /** * Tests that members generated by VisualTotals have correct identity. * *

Testcase for * bug MONDRIAN-295, "Query generated by Excel 2007 gives incorrect * results". */ public void testVisualTotalsIntersect() { assertQueryReturns( "WITH\n" + "SET [XL_Row_Dim_0] AS 'VisualTotals(Distinct(Hierarchize({Ascendants([Customers].[All Customers].[USA]), Descendants([Customers].[All Customers].[USA])})))' \n" + "SELECT \n" + "NON EMPTY Hierarchize({[Time].[Year].members}) ON COLUMNS , \n" + "NON EMPTY Hierarchize(Intersect({DrilldownLevel({[Customers].[All Customers]})}, [XL_Row_Dim_0])) ON ROWS \n" + "FROM [Sales] \n" + "WHERE ([Measures].[Store Sales])", "Axis #0:\n" + "{[Measures].[Store Sales]}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "Axis #2:\n" + "{[Customers].[All Customers]}\n" + "{[Customers].[USA]}\n" + "Row #0: 565,238.13\n" + "Row #1: 565,238.13\n"); } /** *

Testcase for * bug MONDRIAN-668, "Intersect should return any VisualTotals members in * right-hand set". */ public void testVisualTotalsWithNamedSetAndPivotSameAxis() { assertQueryReturns( "WITH SET [XL_Row_Dim_0] AS\n" + " VisualTotals(\n" + " Distinct(\n" + " Hierarchize(\n" + " {Ascendants([Store].[USA].[CA]),\n" + " Descendants([Store].[USA].[CA])})))\n" + "select NON EMPTY \n" + " Hierarchize(\n" + " Intersect(\n" + " {DrilldownLevel({[Store].[USA]})},\n" + " [XL_Row_Dim_0])) ON COLUMNS\n" + "from [Sales] " + "where [Measures].[Sales count]\n", "Axis #0:\n" + "{[Measures].[Sales Count]}\n" + "Axis #1:\n" + "{[Store].[USA]}\n" + "{[Store].[USA].[CA]}\n" + "Row #0: 24,442\n" + "Row #0: 24,442\n"); // now with tuples assertQueryReturns( "WITH SET [XL_Row_Dim_0] AS\n" + " VisualTotals(\n" + " Distinct(\n" + " Hierarchize(\n" + " {Ascendants([Store].[USA].[CA]),\n" + " Descendants([Store].[USA].[CA])})))\n" + "select NON EMPTY \n" + " Hierarchize(\n" + " Intersect(\n" + " [Marital Status].[M]\n" + " * {DrilldownLevel({[Store].[USA]})}\n" + " * [Gender].[F],\n" + " [Marital Status].[M]\n" + " * [XL_Row_Dim_0]\n" + " * [Gender].[F])) ON COLUMNS\n" + "from [Sales] " + "where [Measures].[Sales count]\n", "Axis #0:\n" + "{[Measures].[Sales Count]}\n" + "Axis #1:\n" + "{[Marital Status].[M], [Store].[USA], [Gender].[F]}\n" + "{[Marital Status].[M], [Store].[USA].[CA], [Gender].[F]}\n" + "Row #0: 6,054\n" + "Row #0: 6,054\n"); } /** *

Testcase for * bug MONDRIAN-682, "VisualTotals + Distinct-count measure gives wrong * results". */ public void testVisualTotalsDistinctCountMeasure() { // distinct measure assertQueryReturns( "WITH SET [XL_Row_Dim_0] AS\n" + " VisualTotals(\n" + " Distinct(\n" + " Hierarchize(\n" + " {Ascendants([Store].[USA].[CA]),\n" + " Descendants([Store].[USA].[CA])})))\n" + "select NON EMPTY \n" + " Hierarchize(\n" + " Intersect(\n" + " {DrilldownLevel({[Store].[All Stores]})},\n" + " [XL_Row_Dim_0])) ON COLUMNS\n" + "from [HR] " + "where [Measures].[Number of Employees]\n", "Axis #0:\n" + "{[Measures].[Number of Employees]}\n" + "Axis #1:\n" + "{[Store].[All Stores]}\n" + "{[Store].[USA]}\n" + "Row #0: 193\n" + "Row #0: 193\n"); // distinct measure assertQueryReturns( "WITH SET [XL_Row_Dim_0] AS\n" + " VisualTotals(\n" + " Distinct(\n" + " Hierarchize(\n" + " {Ascendants([Store].[USA].[CA].[Beverly Hills]),\n" + " Descendants([Store].[USA].[CA].[Beverly Hills]),\n" + " Ascendants([Store].[USA].[CA].[Los Angeles]),\n" + " Descendants([Store].[USA].[CA].[Los Angeles])})))" + "select NON EMPTY \n" + " Hierarchize(\n" + " Intersect(\n" + " {DrilldownLevel({[Store].[All Stores]})},\n" + " [XL_Row_Dim_0])) ON COLUMNS\n" + "from [HR] " + "where [Measures].[Number of Employees]\n", "Axis #0:\n" + "{[Measures].[Number of Employees]}\n" + "Axis #1:\n" + "{[Store].[All Stores]}\n" + "{[Store].[USA]}\n" + "Row #0: 110\n" + "Row #0: 110\n"); // distinct measure on columns assertQueryReturns( "WITH SET [XL_Row_Dim_0] AS\n" + " VisualTotals(\n" + " Distinct(\n" + " Hierarchize(\n" + " {Ascendants([Store].[USA].[CA]),\n" + " Descendants([Store].[USA].[CA])})))\n" + "select {[Measures].[Count], [Measures].[Number of Employees]} on COLUMNS," + " NON EMPTY \n" + " Hierarchize(\n" + " Intersect(\n" + " {DrilldownLevel({[Store].[All Stores]})},\n" + " [XL_Row_Dim_0])) ON ROWS\n" + "from [HR] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Count]}\n" + "{[Measures].[Number of Employees]}\n" + "Axis #2:\n" + "{[Store].[All Stores]}\n" + "{[Store].[USA]}\n" + "Row #0: 2,316\n" + "Row #0: 193\n" + "Row #1: 2,316\n" + "Row #1: 193\n"); // distinct measure with tuples assertQueryReturns( "WITH SET [XL_Row_Dim_0] AS\n" + " VisualTotals(\n" + " Distinct(\n" + " Hierarchize(\n" + " {Ascendants([Store].[USA].[CA]),\n" + " Descendants([Store].[USA].[CA])})))\n" + "select NON EMPTY \n" + " Hierarchize(\n" + " Intersect(\n" + " [Marital Status].[M]\n" + " * {DrilldownLevel({[Store].[USA]})}\n" + " * [Gender].[F],\n" + " [Marital Status].[M]\n" + " * [XL_Row_Dim_0]\n" + " * [Gender].[F])) ON COLUMNS\n" + "from [Sales] " + "where [Measures].[Customer count]\n", "Axis #0:\n" + "{[Measures].[Customer Count]}\n" + "Axis #1:\n" + "{[Marital Status].[M], [Store].[USA], [Gender].[F]}\n" + "{[Marital Status].[M], [Store].[USA].[CA], [Gender].[F]}\n" + "Row #0: 654\n" + "Row #0: 654\n"); } /** *

Testcase for * bug MONDRIAN-761, "VisualTotalMember cannot be cast to * RolapCubeMember". */ public void testVisualTotalsClassCast() { assertQueryReturns( "WITH SET [XL_Row_Dim_0] AS\n" + " VisualTotals(\n" + " Distinct(\n" + " Hierarchize(\n" + " {Ascendants([Store].[USA].[WA].[Yakima]), \n" + " Descendants([Store].[USA].[WA].[Yakima]), \n" + " Ascendants([Store].[USA].[WA].[Walla Walla]), \n" + " Descendants([Store].[USA].[WA].[Walla Walla]), \n" + " Ascendants([Store].[USA].[WA].[Tacoma]), \n" + " Descendants([Store].[USA].[WA].[Tacoma]), \n" + " Ascendants([Store].[USA].[WA].[Spokane]), \n" + " Descendants([Store].[USA].[WA].[Spokane]), \n" + " Ascendants([Store].[USA].[WA].[Seattle]), \n" + " Descendants([Store].[USA].[WA].[Seattle]), \n" + " Ascendants([Store].[USA].[WA].[Bremerton]), \n" + " Descendants([Store].[USA].[WA].[Bremerton]), \n" + " Ascendants([Store].[USA].[OR]), \n" + " Descendants([Store].[USA].[OR])}))) \n" + " SELECT NON EMPTY \n" + " Hierarchize(\n" + " Intersect(\n" + " DrilldownMember(\n" + " {{DrilldownMember(\n" + " {{DrilldownMember(\n" + " {{DrilldownLevel(\n" + " {[Store].[All Stores]})}},\n" + " {[Store].[USA]})}},\n" + " {[Store].[USA].[WA]})}},\n" + " {[Store].[USA].[WA].[Bremerton]}),\n" + " [XL_Row_Dim_0]))\n" + "DIMENSION PROPERTIES \n" + " PARENT_UNIQUE_NAME, \n" + " [Store].[Store Name].[Store Type],\n" + " [Store].[Store Name].[Store Manager],\n" + " [Store].[Store Name].[Store Sqft],\n" + " [Store].[Store Name].[Grocery Sqft],\n" + " [Store].[Store Name].[Frozen Sqft],\n" + " [Store].[Store Name].[Meat Sqft],\n" + " [Store].[Store Name].[Has coffee bar],\n" + " [Store].[Store Name].[Street address] ON COLUMNS \n" + "FROM [HR]\n" + "WHERE \n" + " ([Measures].[Number of Employees])\n" + "CELL PROPERTIES\n" + " VALUE,\n" + " FORMAT_STRING,\n" + " LANGUAGE,\n" + " BACK_COLOR,\n" + " FORE_COLOR,\n" + " FONT_FLAGS", "Axis #0:\n" + "{[Measures].[Number of Employees]}\n" + "Axis #1:\n" + "{[Store].[All Stores]}\n" + "{[Store].[USA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "{[Store].[USA].[WA].[Bremerton]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Seattle]}\n" + "{[Store].[USA].[WA].[Spokane]}\n" + "{[Store].[USA].[WA].[Tacoma]}\n" + "{[Store].[USA].[WA].[Walla Walla]}\n" + "{[Store].[USA].[WA].[Yakima]}\n" + "Row #0: 419\n" + "Row #0: 419\n" + "Row #0: 136\n" + "Row #0: 283\n" + "Row #0: 62\n" + "Row #0: 62\n" + "Row #0: 62\n" + "Row #0: 62\n" + "Row #0: 74\n" + "Row #0: 4\n" + "Row #0: 19\n"); } /** *

Testcase for * bug MONDRIAN-678, "VisualTotals gives UnsupportedOperationException * calling getOrdinal". Key difference from previous test is that there * are multiple hierarchies in Named set. */ public void testVisualTotalsWithNamedSetOfTuples() { assertQueryReturns( "WITH SET [XL_Row_Dim_0] AS\n" + " VisualTotals(\n" + " Distinct(\n" + " Hierarchize(\n" + " {Ascendants([Customers].[All Customers].[USA].[CA].[Beverly Hills].[Ari Tweten]),\n" + " Descendants([Customers].[All Customers].[USA].[CA].[Beverly Hills].[Ari Tweten]),\n" + " Ascendants([Customers].[All Customers].[Mexico]),\n" + " Descendants([Customers].[All Customers].[Mexico])})))\n" + "select NON EMPTY \n" + " Hierarchize(\n" + " Intersect(\n" + " (DrilldownMember(\n" + " {{DrilldownMember(\n" + " {{DrilldownLevel(\n" + " {[Customers].[All Customers]})}},\n" + " {[Customers].[All Customers].[USA]})}},\n" + " {[Customers].[All Customers].[USA].[CA]})),\n" + " [XL_Row_Dim_0])) ON COLUMNS\n" + "from [Sales]\n" + "where [Measures].[Sales count]\n", "Axis #0:\n" + "{[Measures].[Sales Count]}\n" + "Axis #1:\n" + "{[Customers].[All Customers]}\n" + "{[Customers].[USA]}\n" + "{[Customers].[USA].[CA]}\n" + "{[Customers].[USA].[CA].[Beverly Hills]}\n" + "Row #0: 4\n" + "Row #0: 4\n" + "Row #0: 4\n" + "Row #0: 4\n"); } public void testVisualTotalsLevel() { Result result = getTestContext().executeQuery( "select {[Measures].[Unit Sales]} on columns,\n" + "{[Product].[All Products],\n" + " [Product].[All Products].[Food].[Baked Goods].[Bread],\n" + " VisualTotals(\n" + " {[Product].[All Products].[Food].[Baked Goods].[Bread],\n" + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Bagels],\n" + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Muffins]},\n" + " \"**Subtotal - *\")} on rows\n" + "from [Sales]"); final List rowPos = result.getAxes()[1].getPositions(); final Member member0 = rowPos.get(0).get(0); assertEquals("All Products", member0.getName()); assertEquals("(All)", member0.getLevel().getName()); final Member member1 = rowPos.get(1).get(0); assertEquals("Bread", member1.getName()); assertEquals("Product Category", member1.getLevel().getName()); final Member member2 = rowPos.get(2).get(0); assertEquals("*Subtotal - Bread", member2.getName()); assertEquals("Product Category", member2.getLevel().getName()); final Member member3 = rowPos.get(3).get(0); assertEquals("Bagels", member3.getName()); assertEquals("Product Subcategory", member3.getLevel().getName()); final Member member4 = rowPos.get(4).get(0); assertEquals("Muffins", member4.getName()); assertEquals("Product Subcategory", member4.getLevel().getName()); } /** * Testcase for bug * MONDRIAN-749, "Cannot use visual totals members in calculations". * *

The bug is not currently fixed, so it is a negative test case. Row #2 * cell #1 contains an exception, but should be "**Subtotal - Bread : * Product Subcategory". */ public void testVisualTotalsMemberInCalculation() { getTestContext().assertQueryReturns( "with member [Measures].[Foo] as\n" + " [Product].CurrentMember.Name || ' : ' || [Product].Level.Name\n" + "select {[Measures].[Unit Sales], [Measures].[Foo]} on columns,\n" + "{[Product].[All Products],\n" + " [Product].[All Products].[Food].[Baked Goods].[Bread],\n" + " VisualTotals(\n" + " {[Product].[All Products].[Food].[Baked Goods].[Bread],\n" + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Bagels],\n" + " [Product].[All Products].[Food].[Baked Goods].[Bread].[Muffins]},\n" + " \"**Subtotal - *\")} on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Foo]}\n" + "Axis #2:\n" + "{[Product].[All Products]}\n" + "{[Product].[Food].[Baked Goods].[Bread]}\n" + "{[Product].[Food].[Baked Goods].[*Subtotal - Bread]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Bagels]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Muffins]}\n" + "Row #0: 266,773\n" + "Row #0: All Products : (All)\n" + "Row #1: 7,870\n" + "Row #1: Bread : Product Category\n" + "Row #2: 4,312\n" + "Row #2: #ERR: mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context\n" + "Row #3: 815\n" + "Row #3: Bagels : Product Subcategory\n" + "Row #4: 3,497\n" + "Row #4: Muffins : Product Subcategory\n"); } public void testCalculatedChild() { // Construct calculated children with the same name for both [Drink] and // [Non-Consumable]. Then, create a metric to select the calculated // child based on current product member. assertQueryReturns( "with\n" + " member [Product].[All Products].[Drink].[Calculated Child] as '[Product].[All Products].[Drink].[Alcoholic Beverages]'\n" + " member [Product].[All Products].[Non-Consumable].[Calculated Child] as '[Product].[All Products].[Non-Consumable].[Carousel]'\n" + " member [Measures].[Unit Sales CC] as '([Measures].[Unit Sales],[Product].currentmember.CalculatedChild(\"Calculated Child\"))'\n" + " select non empty {[Measures].[Unit Sales CC]} on columns,\n" + " non empty {[Product].[Drink], [Product].[Non-Consumable]} on rows\n" + " from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales CC]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 6,838\n" // Calculated child for [Drink] + "Row #1: 841\n"); // Calculated child for [Non-Consumable] Member member = executeSingletonAxis( "[Product].[All Products].CalculatedChild(\"foobar\")"); Assert.assertEquals(member, null); } public void testCalculatedChildUsingItem() { // Construct calculated children with the same name for both [Drink] and // [Non-Consumable]. Then, create a metric to select the first // calculated child. assertQueryReturns( "with\n" + " member [Product].[All Products].[Drink].[Calculated Child] as '[Product].[All Products].[Drink].[Alcoholic Beverages]'\n" + " member [Product].[All Products].[Non-Consumable].[Calculated Child] as '[Product].[All Products].[Non-Consumable].[Carousel]'\n" + " member [Measures].[Unit Sales CC] as '([Measures].[Unit Sales],AddCalculatedMembers([Product].currentmember.children).Item(\"Calculated Child\"))'\n" + " select non empty {[Measures].[Unit Sales CC]} on columns,\n" + " non empty {[Product].[Drink], [Product].[Non-Consumable]} on rows\n" + " from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales CC]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 6,838\n" // Note: For [Non-Consumable], the calculated child for [Drink] was // selected! + "Row #1: 6,838\n"); Member member = executeSingletonAxis( "[Product].[All Products].CalculatedChild(\"foobar\")"); Assert.assertEquals(member, null); } public void testCalculatedChildOnMemberWithNoChildren() { Member member = executeSingletonAxis( "[Measures].[Store Sales].CalculatedChild(\"foobar\")"); Assert.assertEquals(member, null); } public void testCalculatedChildOnNullMember() { Member member = executeSingletonAxis( "[Measures].[Store Sales].parent.CalculatedChild(\"foobar\")"); Assert.assertEquals(member, null); } public void testCast() { // NOTE: Some of these tests fail with 'cannot convert ...', and they // probably shouldn't. Feel free to fix the conversion. // -- jhyde, 2006/9/3 // From integer // To integer (trivial) assertExprReturns("0 + Cast(1 + 2 AS Integer)", "3"); // To String assertExprReturns("'' || Cast(1 + 2 AS String)", "3.0"); // To Boolean assertExprReturns("1=1 AND Cast(1 + 2 AS Boolean)", "true"); assertExprReturns("1=1 AND Cast(1 - 1 AS Boolean)", "false"); // From boolean // To String assertExprReturns("'' || Cast((1 = 1 AND 1 = 2) AS String)", "false"); // This case demonstrates the relative precedence of 'AS' in 'CAST' // and 'AS' for creating inline named sets. See also bug MONDRIAN-648. Util.discard(Bug.BugMondrian648Fixed); assertExprReturns( "'xxx' || Cast(1 = 1 AND 1 = 2 AS String)", "xxxfalse"); // To boolean (trivial) assertExprReturns( "1=1 AND Cast((1 = 1 AND 1 = 2) AS Boolean)", "false"); assertExprReturns( "1=1 OR Cast(1 = 1 AND 1 = 2 AS Boolean)", "true"); // From null : should not throw exceptions since RolapResult.executeBody // can receive NULL values when the cell value is not loaded yet, so // should return null instead. // To Integer : Expect to return NULL // Expect to return NULL assertExprReturns("0 * Cast(NULL AS Integer)", ""); // To Numeric : Expect to return NULL // Expect to return NULL assertExprReturns("0 * Cast(NULL AS Numeric)", ""); // To String : Expect to return "null" assertExprReturns("'' || Cast(NULL AS String)", "null"); // To Boolean : Expect to return NULL, but since FunUtil.BooleanNull // does not implement three-valued boolean logic yet, this will return // false assertExprReturns("1=1 AND Cast(NULL AS Boolean)", "false"); // Double is not allowed as a type assertExprThrows( "Cast(1 AS Double)", "Unknown type 'Double'; values are NUMERIC, STRING, BOOLEAN"); // An integer constant is not allowed as a type assertExprThrows( "Cast(1 AS 5)", "Syntax error at line 1, column 11, token '5'"); assertExprReturns("Cast('tr' || 'ue' AS boolean)", "true"); } /** * Testcase for bug * MONDRIAN-524, "VB functions: expected primitive type, got * java.lang.Object". */ public void testCastBug524() { assertExprReturns( "Cast(Int([Measures].[Store Sales] / 3600) as String)", "157"); } /** * Tests {@link mondrian.olap.FunTable#getFunInfoList()}, but more * importantly, generates an HTML table of all implemented functions into * a file called "functions.html". You can manually include that table * in the MDX * specification. */ public void testDumpFunctions() throws IOException { final List funInfoList = new ArrayList(); funInfoList.addAll(BuiltinFunTable.instance().getFunInfoList()); // Add some UDFs. funInfoList.add( new FunInfo( new UdfResolver( new UdfResolver.ClassUdfFactory( CurrentDateMemberExactUdf.class, null)))); funInfoList.add( new FunInfo( new UdfResolver( new UdfResolver.ClassUdfFactory( CurrentDateMemberUdf.class, null)))); funInfoList.add( new FunInfo( new UdfResolver( new UdfResolver.ClassUdfFactory( CurrentDateStringUdf.class, null)))); Collections.sort(funInfoList); final File file = new File("functions.html"); final FileOutputStream os = new FileOutputStream(file); final PrintWriter pw = new PrintWriter(os); pw.println("

"); pw.println(""); pw.println(""); pw.println(""); pw.println(""); for (FunInfo funInfo : funInfoList) { pw.println(""); pw.print(" "); pw.print(" "); pw.println(""); } pw.println("
NameDescription
"); printHtml(pw, funInfo.getName()); pw.println(""); if (funInfo.getDescription() != null) { printHtml(pw, funInfo.getDescription()); } pw.println(); final String[] signatures = funInfo.getSignatures(); if (signatures != null) { pw.println("

Syntax

"); for (int j = 0; j < signatures.length; j++) { if (j > 0) { pw.println("
"); } String signature = signatures[j]; pw.print(" "); printHtml(pw, signature); } pw.println(); } pw.println("
"); pw.close(); } public void testComplexOrExpr() { switch (TestContext.instance().getDialect().getDatabaseProduct()) { case INFOBRIGHT: // Skip this test on Infobright, because [Promotion Sales] is // defined wrong. return; } // make sure all aggregates referenced in the OR expression are // processed in a single load request by setting the eval depth to // a value smaller than the number of measures int origDepth = MondrianProperties.instance().MaxEvalDepth.get(); MondrianProperties.instance().MaxEvalDepth.set(3); assertQueryReturns( "with set [*NATIVE_CJ_SET] as '[Store].[Store Country].members' " + "set [*GENERATED_MEMBERS_Measures] as " + " '{[Measures].[Unit Sales], [Measures].[Store Cost], " + " [Measures].[Sales Count], [Measures].[Customer Count], " + " [Measures].[Promotion Sales]}' " + "set [*GENERATED_MEMBERS] as " + " 'Generate([*NATIVE_CJ_SET], {[Store].CurrentMember})' " + "member [Store].[*SUBTOTAL_MEMBER_SEL~SUM] as 'Sum([*GENERATED_MEMBERS])' " + "select [*GENERATED_MEMBERS_Measures] ON COLUMNS, " + "NON EMPTY " + " Filter(" + " Generate(" + " [*NATIVE_CJ_SET], " + " {[Store].CurrentMember}), " + " (((((NOT IsEmpty([Measures].[Unit Sales])) OR " + " (NOT IsEmpty([Measures].[Store Cost]))) OR " + " (NOT IsEmpty([Measures].[Sales Count]))) OR " + " (NOT IsEmpty([Measures].[Customer Count]))) OR " + " (NOT IsEmpty([Measures].[Promotion Sales])))) " + "on rows " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Sales Count]}\n" + "{[Measures].[Customer Count]}\n" + "{[Measures].[Promotion Sales]}\n" + "Axis #2:\n" + "{[Store].[USA]}\n" + "Row #0: 266,773\n" + "Row #0: 225,627.23\n" + "Row #0: 86,837\n" + "Row #0: 5,581\n" + "Row #0: 151,211.21\n"); MondrianProperties.instance().MaxEvalDepth.set(origDepth); } public void testLeftFunctionWithValidArguments() { assertQueryReturns( "select filter([Store].MEMBERS," + "Left([Store].CURRENTMEMBER.Name, 4)=\"Bell\") on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testLeftFunctionWithLengthValueZero() { assertQueryReturns( "select filter([Store].MEMBERS," + "Left([Store].CURRENTMEMBER.Name, 0)=\"\" And " + "[Store].CURRENTMEMBER.Name = \"Bellingham\") on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testLeftFunctionWithLengthValueEqualToStringLength() { assertQueryReturns( "select filter([Store].MEMBERS," + "Left([Store].CURRENTMEMBER.Name, 10)=\"Bellingham\") " + "on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testLeftFunctionWithLengthMoreThanStringLength() { assertQueryReturns( "select filter([Store].MEMBERS," + "Left([Store].CURRENTMEMBER.Name, 20)=\"Bellingham\") " + "on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testLeftFunctionWithZeroLengthString() { assertQueryReturns( "select filter([Store].MEMBERS,Left(\"\", 20)=\"\" " + "And [Store].CURRENTMEMBER.Name = \"Bellingham\") " + "on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testLeftFunctionWithNegativeLength() { assertQueryThrows( "select filter([Store].MEMBERS," + "Left([Store].CURRENTMEMBER.Name, -20)=\"Bellingham\") " + "on 0 from sales", Util.IBM_JVM ? "StringIndexOutOfBoundsException: null" : "StringIndexOutOfBoundsException: String index out of range: " + "-20"); } public void testMidFunctionWithValidArguments() { assertQueryReturns( "select filter([Store].MEMBERS," + "[Store].CURRENTMEMBER.Name = \"Bellingham\"" + "And Mid(\"Bellingham\", 4, 6) = \"lingha\")" + "on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testMidFunctionWithZeroLengthStringArgument() { assertQueryReturns( "select filter([Store].MEMBERS," + "[Store].CURRENTMEMBER.Name = \"Bellingham\"" + "And Mid(\"\", 4, 6) = \"\")" + "on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testMidFunctionWithLengthArgumentLargerThanStringLength() { assertQueryReturns( "select filter([Store].MEMBERS," + "[Store].CURRENTMEMBER.Name = \"Bellingham\"" + "And Mid(\"Bellingham\", 4, 20) = \"lingham\")" + "on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testMidFunctionWithStartIndexGreaterThanStringLength() { assertQueryReturns( "select filter([Store].MEMBERS," + "[Store].CURRENTMEMBER.Name = \"Bellingham\"" + "And Mid(\"Bellingham\", 20, 2) = \"\")" + "on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testMidFunctionWithStartIndexZeroFails() { // Note: SSAS 2005 treats start<=0 as 1, therefore gives different // result for this query. We favor the VBA spec over SSAS 2005. if (Bug.Ssas2005Compatible) { assertQueryReturns( "select filter([Store].MEMBERS," + "[Store].CURRENTMEMBER.Name = \"Bellingham\"" + "And Mid(\"Bellingham\", 0, 2) = \"Be\")" + "on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } else { assertQueryThrows( "select filter([Store].MEMBERS," + "[Store].CURRENTMEMBER.Name = \"Bellingham\"" + "And Mid(\"Bellingham\", 0, 2) = \"Be\")" + "on 0 from sales", "Invalid parameter. Start parameter of Mid function must be " + "positive"); } } public void testMidFunctionWithStartIndexOne() { assertQueryReturns( "select filter([Store].MEMBERS," + "[Store].CURRENTMEMBER.Name = \"Bellingham\"" + "And Mid(\"Bellingham\", 1, 2) = \"Be\")" + "on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testMidFunctionWithNegativeStartIndex() { assertQueryThrows( "select filter([Store].MEMBERS," + "[Store].CURRENTMEMBER.Name = \"Bellingham\"" + "And Mid(\"Bellingham\", -20, 2) = \"\")" + "on 0 from sales", "Invalid parameter. " + "Start parameter of Mid function must be positive"); } public void testMidFunctionWithNegativeLength() { assertQueryThrows( "select filter([Store].MEMBERS," + "[Store].CURRENTMEMBER.Name = \"Bellingham\"" + "And Mid(\"Bellingham\", 2, -2) = \"\")" + "on 0 from sales", "Invalid parameter. " + "Length parameter of Mid function must be non-negative"); } public void testMidFunctionWithoutLength() { assertQueryReturns( "select filter([Store].MEMBERS," + "[Store].CURRENTMEMBER.Name = \"Bellingham\"" + "And Mid(\"Bellingham\", 2) = \"ellingham\")" + "on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testLenFunctionWithNonEmptyString() { assertQueryReturns( "select filter([Store].MEMBERS, " + "Len([Store].CURRENTMEMBER.Name) = 3) on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA]}\n" + "Row #0: 266,773\n"); } public void testLenFunctionWithAnEmptyString() { assertQueryReturns( "select filter([Store].MEMBERS,Len(\"\")=0 " + "And [Store].CURRENTMEMBER.Name = \"Bellingham\") " + "on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testLenFunctionWithNullString() { // SSAS2005 returns 0 assertQueryReturns( "with member [Measures].[Foo] as ' NULL '\n" + " member [Measures].[Bar] as ' len([Measures].[Foo]) '\n" + "select [Measures].[Bar] on 0\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Bar]}\n" + "Row #0: 0\n"); // same, but inline assertExprReturns("len(null)", 0, 0); } public void testUCaseWithNonEmptyString() { assertQueryReturns( "select filter([Store].MEMBERS, " + " UCase([Store].CURRENTMEMBER.Name) = \"BELLINGHAM\") " + "on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testUCaseWithEmptyString() { assertQueryReturns( "select filter([Store].MEMBERS, " + " UCase(\"\") = \"\" " + "And [Store].CURRENTMEMBER.Name = \"Bellingham\") " + "on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testInStrFunctionWithValidArguments() { assertQueryReturns( "select filter([Store].MEMBERS,InStr(\"Bellingham\", \"ingha\")=5 " + "And [Store].CURRENTMEMBER.Name = \"Bellingham\") " + "on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testIifFWithBooleanBooleanAndNumericParameterForReturningTruePart() { assertQueryReturns( "SELECT Filter(Store.allmembers, " + "iif(measures.profit < 400000," + "[store].currentMember.NAME = \"USA\", 0)) on 0 FROM SALES", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA]}\n" + "Row #0: 266,773\n"); } public void testIifWithBooleanBooleanAndNumericParameterForReturningFalsePart() { assertQueryReturns( "SELECT Filter([Store].[USA].[CA].[Beverly Hills].children, " + "iif(measures.profit > 400000," + "[store].currentMember.NAME = \"USA\", 1)) on 0 FROM SALES", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "Row #0: 21,333\n"); } public void testIIFWithBooleanBooleanAndNumericParameterForReturningZero() { assertQueryReturns( "SELECT Filter(Store.allmembers, " + "iif(measures.profit > 400000," + "[store].currentMember.NAME = \"USA\", 0)) on 0 FROM SALES", "Axis #0:\n" + "{}\n" + "Axis #1:\n"); } public void testInStrFunctionWithEmptyString1() { assertQueryReturns( "select filter([Store].MEMBERS,InStr(\"\", \"ingha\")=0 " + "And [Store].CURRENTMEMBER.Name = \"Bellingham\") " + "on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testInStrFunctionWithEmptyString2() { assertQueryReturns( "select filter([Store].MEMBERS,InStr(\"Bellingham\", \"\")=1 " + "And [Store].CURRENTMEMBER.Name = \"Bellingham\") " + "on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "Row #0: 2,237\n"); } public void testGetCaptionUsingMemberDotCaption() { assertQueryReturns( "SELECT Filter(Store.allmembers, " + "[store].currentMember.caption = \"USA\") on 0 FROM SALES", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA]}\n" + "Row #0: 266,773\n"); } private static void printHtml(PrintWriter pw, String s) { final String escaped = StringEscaper.htmlEscaper.escapeString(s); pw.print(escaped); } public void testCache() { // test various data types: integer, string, member, set, tuple assertExprReturns("Cache(1 + 2)", "3"); assertExprReturns("Cache('foo' || 'bar')", "foobar"); assertAxisReturns( "[Gender].Children", "[Gender].[F]\n" + "[Gender].[M]"); assertAxisReturns( "([Gender].[M], [Marital Status].[S].PrevMember)", "{[Gender].[M], [Marital Status].[M]}"); // inside another expression assertAxisReturns( "Order(Cache([Gender].Children), Cache(([Measures].[Unit Sales], [Time].[1997].[Q1])), BDESC)", "[Gender].[M]\n" + "[Gender].[F]"); // doesn't work with multiple args assertExprThrows( "Cache(1, 2)", "No function matches signature 'Cache(, )'"); } // The following methods test VBA functions. They don't test all of them, // because the raw methods are tested in VbaTest, but they test the core // functionalities like error handling and operator overloading. public void testVbaBasic() { // Exp is a simple function: one arg. assertExprReturns("exp(0)", "1"); assertExprReturns("exp(1)", Math.E, 0.00000001); assertExprReturns("exp(-2)", 1d / (Math.E * Math.E), 0.00000001); // If any arg is null, result is null. assertExprReturns("exp(cast(null as numeric))", ""); } // Test a VBA function with variable number of args. public void testVbaOverloading() { assertExprReturns("replace('xyzxyz', 'xy', 'a')", "azaz"); assertExprReturns("replace('xyzxyz', 'xy', 'a', 2)", "xyzaz"); assertExprReturns("replace('xyzxyz', 'xy', 'a', 1, 1)", "azxyz"); } // Test VBA exception handling public void testVbaExceptions() { assertExprThrows( "right(\"abc\", -4)", Util.IBM_JVM ? "StringIndexOutOfBoundsException: null" : "StringIndexOutOfBoundsException: " + "String index out of range: -4"); } public void testVbaDateTime() { // function which returns date assertExprReturns( "Format(DateSerial(2006, 4, 29), \"Long Date\")", "Saturday, April 29, 2006"); // function with date parameter assertExprReturns("Year(DateSerial(2006, 4, 29))", "2,006"); } public void testExcelPi() { // The PI function is defined in the Excel class. assertExprReturns("Pi()", "3"); } public void testExcelPower() { assertExprReturns("Power(8, 0.333333)", 2.0, 0.01); assertExprReturns("Power(-2, 0.5)", Double.NaN, 0.001); } // Comment from the bug: the reason for this is that in AbstractExpCompiler // in the compileInteger method we are casting an IntegerCalc into a // DoubleCalc and there is no check for IntegerCalc in the NumericType // conditional path. public void testBug1881739() { assertExprReturns("LEFT(\"TEST\", LEN(\"TEST\"))", "TEST"); } /** * Testcase for bug * MONDRIAN-296, "Cube getTimeDimension use when Cube has no Time * dimension". */ public void testCubeTimeDimensionFails() { assertQueryThrows( "select LastPeriods(1) on columns from [Store]", "'LastPeriods', no time dimension"); assertQueryThrows( "select OpeningPeriod() on columns from [Store]", "'OpeningPeriod', no time dimension"); assertQueryThrows( "select OpeningPeriod([Store Type]) on columns from [Store]", "'OpeningPeriod', no time dimension"); assertQueryThrows( "select ClosingPeriod() on columns from [Store]", "'ClosingPeriod', no time dimension"); assertQueryThrows( "select ClosingPeriod([Store Type]) on columns from [Store]", "'ClosingPeriod', no time dimension"); assertQueryThrows( "select ParallelPeriod() on columns from [Store]", "'ParallelPeriod', no time dimension"); assertQueryThrows( "select PeriodsToDate() on columns from [Store]", "'PeriodsToDate', no time dimension"); assertQueryThrows( "select Mtd() on columns from [Store]", "'Mtd', no time dimension"); } public void testFilterEmpty() { // Unlike "Descendants(, ...)", we do not need to know the precise // type of the set, therefore it is OK if the set is empty. assertAxisReturns( "Filter({}, 1=0)", ""); assertAxisReturns( "Filter({[Time].[Time].Children}, 1=0)", ""); } public void testFilterCalcSlicer() { assertQueryReturns( "with member [Time].[Time].[Date Range] as \n" + "'Aggregate({[Time].[1997].[Q1]:[Time].[1997].[Q3]})'\n" + "select\n" + "{[Measures].[Unit Sales],[Measures].[Store Cost],\n" + "[Measures].[Store Sales]} ON columns,\n" + "NON EMPTY Filter ([Store].[Store State].members,\n" + "[Measures].[Store Cost] > 75000) ON rows\n" + "from [Sales] where [Time].[Date Range]", "Axis #0:\n" + "{[Time].[Date Range]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 90,131\n" + "Row #0: 76,151.59\n" + "Row #0: 190,776.88\n"); assertQueryReturns( "with member [Time].[Time].[Date Range] as \n" + "'Aggregate({[Time].[1997].[Q1]:[Time].[1997].[Q3]})'\n" + "select\n" + "{[Measures].[Unit Sales],[Measures].[Store Cost],\n" + "[Measures].[Store Sales]} ON columns,\n" + "NON EMPTY Order (Filter ([Store].[Store State].members,\n" + "[Measures].[Store Cost] > 100),[Measures].[Store Cost], DESC) ON rows\n" + "from [Sales] where [Time].[Date Range]", "Axis #0:\n" + "{[Time].[Date Range]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[WA]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "Row #0: 90,131\n" + "Row #0: 76,151.59\n" + "Row #0: 190,776.88\n" + "Row #1: 53,312\n" + "Row #1: 45,435.93\n" + "Row #1: 113,966.00\n" + "Row #2: 51,306\n" + "Row #2: 43,033.82\n" + "Row #2: 107,823.63\n"); } public void testExistsMembersAll() { assertQueryReturns( "select exists(\n" + " {[Customers].[All Customers],\n" + " [Customers].[Country].Members,\n" + " [Customers].[State Province].[CA],\n" + " [Customers].[Canada].[BC].[Richmond]},\n" + " {[Customers].[All Customers]})\n" + "on 0 from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[All Customers]}\n" + "{[Customers].[Canada]}\n" + "{[Customers].[Mexico]}\n" + "{[Customers].[USA]}\n" + "{[Customers].[USA].[CA]}\n" + "{[Customers].[Canada].[BC].[Richmond]}\n" + "Row #0: 266,773\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 266,773\n" + "Row #0: 74,748\n" + "Row #0: \n"); } public void testExistsMembersLevel2() { assertQueryReturns( "select exists(\n" + " {[Customers].[All Customers],\n" + " [Customers].[Country].Members,\n" + " [Customers].[State Province].[CA],\n" + " [Customers].[Canada].[BC].[Richmond]},\n" + " {[Customers].[Country].[USA]})\n" + "on 0 from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[All Customers]}\n" + "{[Customers].[USA]}\n" + "{[Customers].[USA].[CA]}\n" + "Row #0: 266,773\n" + "Row #0: 266,773\n" + "Row #0: 74,748\n"); } public void testExistsMembersDiffDim() { assertQueryReturns( "select exists(\n" + " {[Customers].[All Customers],\n" + " [Customers].[All Customers].Children,\n" + " [Customers].[State Province].Members},\n" + " {[Product].Members})\n" + "on 0 from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n"); } public void testExistsMembers2Hierarchies() { assertQueryReturns( "select exists(\n" + " {[Customers].[All Customers],\n" + " [Customers].[All Customers].Children,\n" + " [Customers].[State Province].Members,\n" + " [Customers].[Country].[Canada],\n" + " [Customers].[Country].[Mexico]},\n" + " {[Customers].[Country].[USA],\n" + " [Customers].[State Province].[Veracruz]})\n" + "on 0 from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[All Customers]}\n" + "{[Customers].[Mexico]}\n" + "{[Customers].[USA]}\n" + "{[Customers].[Mexico].[Veracruz]}\n" + "{[Customers].[USA].[CA]}\n" + "{[Customers].[USA].[OR]}\n" + "{[Customers].[USA].[WA]}\n" + "{[Customers].[Mexico]}\n" + "Row #0: 266,773\n" + "Row #0: \n" + "Row #0: 266,773\n" + "Row #0: \n" + "Row #0: 74,748\n" + "Row #0: 67,659\n" + "Row #0: 124,366\n" + "Row #0: \n"); } public void testExistsTuplesAll() { assertQueryReturns( "select exists(\n" + " crossjoin({[Product].[All Products]},{[Customers].[All Customers]}),\n" + " {[Customers].[All Customers]})\n" + "on 0 from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[All Products], [Customers].[All Customers]}\n" + "Row #0: 266,773\n"); } public void testExistsTuplesLevel2() { assertQueryReturns( "select exists(\n" + " crossjoin({[Product].[All Products]},{[Customers].[All Customers].Children}),\n" + " {[Customers].[All Customers].[USA]})\n" + "on 0 from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[All Products], [Customers].[USA]}\n" + "Row #0: 266,773\n"); } public void testExistsTuplesLevel23() { assertQueryReturns( "select exists(\n" + " crossjoin({[Customers].[State Province].Members}, {[Product].[All Products]}),\n" + " {[Customers].[All Customers].[USA]})\n" + "on 0 from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[CA], [Product].[All Products]}\n" + "{[Customers].[USA].[OR], [Product].[All Products]}\n" + "{[Customers].[USA].[WA], [Product].[All Products]}\n" + "Row #0: 74,748\n" + "Row #0: 67,659\n" + "Row #0: 124,366\n"); } public void testExistsTuples2Dim() { assertQueryReturns( "select exists(\n" + " crossjoin({[Customers].[State Province].Members}, {[Product].[Product Family].Members}),\n" + " {([Product].[Product Department].[Dairy],[Customers].[All Customers].[USA])})\n" + "on 0 from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[CA], [Product].[Drink]}\n" + "{[Customers].[USA].[OR], [Product].[Drink]}\n" + "{[Customers].[USA].[WA], [Product].[Drink]}\n" + "Row #0: 7,102\n" + "Row #0: 6,106\n" + "Row #0: 11,389\n"); } public void testExistsTuplesDiffDim() { assertQueryReturns( "select exists(\n" + " crossjoin(\n" + " crossjoin({[Customers].[State Province].Members},\n" + " {[Time].[Year].[1997]}), \n" + " {[Product].[Product Family].Members}),\n" + " {([Product].[Product Department].[Dairy],\n" + " [Promotions].[All Promotions], \n" + " [Customers].[All Customers].[USA])})\n" + "on 0 from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n"); } /** * Executes a query that has a complex parse tree. Goal is to find * algorithmic complexity bugs in the validator which would make the query * run extremely slowly. */ public void testComplexQuery() { final String expected = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 266,773\n" + "Row #1: 131,558\n" + "Row #2: 135,215\n"; // hand written case assertQueryReturns( "select\n" + " [Measures].[Unit Sales] on 0,\n" + " Distinct({\n" + " [Gender],\n" + " Tail(\n" + " Head({\n" + " [Gender],\n" + " [Gender].[F],\n" + " [Gender].[M]},\n" + " 2),\n" + " 1),\n" + " Tail(\n" + " Head({\n" + " [Gender],\n" + " [Gender].[F],\n" + " [Gender].[M]},\n" + " 2),\n" + " 1),\n" + " [Gender].[M]}) on 1\n" + "from [Sales]", expected); // generated equivalent StringBuilder buf = new StringBuilder(); buf.append( "select\n" + " [Measures].[Unit Sales] on 0,\n"); generateComplex(buf, " ", 0, 7, 3); buf.append( " on 1\n" + "from [Sales]"); if (false) { System.out.println(buf.toString().length() + ": " + buf.toString()); } assertQueryReturns(buf.toString(), expected); } /** * Recursive routine to generate a complex MDX expression. * * @param buf String builder * @param indent Indent * @param depth Current depth * @param depthLimit Max recursion depth * @param breadth Number of iterations at each depth */ private void generateComplex( StringBuilder buf, String indent, int depth, int depthLimit, int breadth) { buf.append(indent + "Distinct({\n"); buf.append(indent + " [Gender],\n"); for (int i = 0; i < breadth; i++) { if (depth < depthLimit) { buf.append(indent + " Tail(\n"); buf.append(indent + " Head({\n"); generateComplex( buf, indent + " ", depth + 1, depthLimit, breadth); buf.append("},\n"); buf.append(indent + " 2),\n"); buf.append(indent + " 1),\n"); } else { buf.append(indent + " [Gender].[F],\n"); } } buf.append(indent + " [Gender].[M]})"); } /** * Testcase for bug * MONDRIAN-1050, "MDX Order function fails when using DateTime expression * for ordering". */ public void testDateParameter() throws Exception { executeQuery( "SELECT {[Measures].[Unit Sales]} ON COLUMNS, Order([Gender].Members, Now(), ASC) ON ROWS FROM [Sales]"); } /** * Testcase for bug * MONDRIAN-1043, "Hierarchize with Except sort set members differently than * in Mondrian 3.2.1". * *

This test makes sure that * Hierarchize and Except can be used within each other and that the * sort order is maintained.

*/ public void testHierarchizeExcept() throws Exception { final String[] mdxA = new String[] { "SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS, Hierarchize(Except({[Customers].[USA].Children, [Customers].[USA].[CA].Children}, [Customers].[USA].[CA])) ON ROWS FROM [Sales]", "SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} ON COLUMNS, Except(Hierarchize({[Customers].[USA].Children, [Customers].[USA].[CA].Children}), [Customers].[USA].[CA]) ON ROWS FROM [Sales] " }; for (String mdx : mdxA) { assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Customers].[USA].[CA].[Altadena]}\n" + "{[Customers].[USA].[CA].[Arcadia]}\n" + "{[Customers].[USA].[CA].[Bellflower]}\n" + "{[Customers].[USA].[CA].[Berkeley]}\n" + "{[Customers].[USA].[CA].[Beverly Hills]}\n" + "{[Customers].[USA].[CA].[Burbank]}\n" + "{[Customers].[USA].[CA].[Burlingame]}\n" + "{[Customers].[USA].[CA].[Chula Vista]}\n" + "{[Customers].[USA].[CA].[Colma]}\n" + "{[Customers].[USA].[CA].[Concord]}\n" + "{[Customers].[USA].[CA].[Coronado]}\n" + "{[Customers].[USA].[CA].[Daly City]}\n" + "{[Customers].[USA].[CA].[Downey]}\n" + "{[Customers].[USA].[CA].[El Cajon]}\n" + "{[Customers].[USA].[CA].[Fremont]}\n" + "{[Customers].[USA].[CA].[Glendale]}\n" + "{[Customers].[USA].[CA].[Grossmont]}\n" + "{[Customers].[USA].[CA].[Imperial Beach]}\n" + "{[Customers].[USA].[CA].[La Jolla]}\n" + "{[Customers].[USA].[CA].[La Mesa]}\n" + "{[Customers].[USA].[CA].[Lakewood]}\n" + "{[Customers].[USA].[CA].[Lemon Grove]}\n" + "{[Customers].[USA].[CA].[Lincoln Acres]}\n" + "{[Customers].[USA].[CA].[Long Beach]}\n" + "{[Customers].[USA].[CA].[Los Angeles]}\n" + "{[Customers].[USA].[CA].[Mill Valley]}\n" + "{[Customers].[USA].[CA].[National City]}\n" + "{[Customers].[USA].[CA].[Newport Beach]}\n" + "{[Customers].[USA].[CA].[Novato]}\n" + "{[Customers].[USA].[CA].[Oakland]}\n" + "{[Customers].[USA].[CA].[Palo Alto]}\n" + "{[Customers].[USA].[CA].[Pomona]}\n" + "{[Customers].[USA].[CA].[Redwood City]}\n" + "{[Customers].[USA].[CA].[Richmond]}\n" + "{[Customers].[USA].[CA].[San Carlos]}\n" + "{[Customers].[USA].[CA].[San Diego]}\n" + "{[Customers].[USA].[CA].[San Francisco]}\n" + "{[Customers].[USA].[CA].[San Gabriel]}\n" + "{[Customers].[USA].[CA].[San Jose]}\n" + "{[Customers].[USA].[CA].[Santa Cruz]}\n" + "{[Customers].[USA].[CA].[Santa Monica]}\n" + "{[Customers].[USA].[CA].[Spring Valley]}\n" + "{[Customers].[USA].[CA].[Torrance]}\n" + "{[Customers].[USA].[CA].[West Covina]}\n" + "{[Customers].[USA].[CA].[Woodland Hills]}\n" + "{[Customers].[USA].[OR]}\n" + "{[Customers].[USA].[WA]}\n" + "Row #0: 2,574\n" + "Row #0: 5,585.59\n" + "Row #1: 2,440\n" + "Row #1: 5,136.59\n" + "Row #2: 3,106\n" + "Row #2: 6,633.97\n" + "Row #3: 136\n" + "Row #3: 320.17\n" + "Row #4: 2,907\n" + "Row #4: 6,194.37\n" + "Row #5: 3,086\n" + "Row #5: 6,577.33\n" + "Row #6: 198\n" + "Row #6: 407.38\n" + "Row #7: 2,999\n" + "Row #7: 6,284.30\n" + "Row #8: 129\n" + "Row #8: 287.78\n" + "Row #9: 105\n" + "Row #9: 219.77\n" + "Row #10: 2,391\n" + "Row #10: 5,051.15\n" + "Row #11: 129\n" + "Row #11: 271.60\n" + "Row #12: 3,440\n" + "Row #12: 7,367.06\n" + "Row #13: 2,543\n" + "Row #13: 5,460.42\n" + "Row #14: 163\n" + "Row #14: 350.22\n" + "Row #15: 3,284\n" + "Row #15: 7,082.91\n" + "Row #16: 2,131\n" + "Row #16: 4,458.60\n" + "Row #17: 1,616\n" + "Row #17: 3,409.34\n" + "Row #18: 1,938\n" + "Row #18: 4,081.37\n" + "Row #19: 1,834\n" + "Row #19: 3,908.26\n" + "Row #20: 2,487\n" + "Row #20: 5,174.12\n" + "Row #21: 2,651\n" + "Row #21: 5,636.82\n" + "Row #22: 2,176\n" + "Row #22: 4,691.94\n" + "Row #23: 2,973\n" + "Row #23: 6,422.37\n" + "Row #24: 2,009\n" + "Row #24: 4,312.99\n" + "Row #25: 58\n" + "Row #25: 109.36\n" + "Row #26: 2,031\n" + "Row #26: 4,237.46\n" + "Row #27: 3,098\n" + "Row #27: 6,696.06\n" + "Row #28: 163\n" + "Row #28: 335.98\n" + "Row #29: 70\n" + "Row #29: 145.90\n" + "Row #30: 133\n" + "Row #30: 272.08\n" + "Row #31: 2,712\n" + "Row #31: 5,595.62\n" + "Row #32: 144\n" + "Row #32: 312.43\n" + "Row #33: 110\n" + "Row #33: 212.45\n" + "Row #34: 145\n" + "Row #34: 289.80\n" + "Row #35: 1,535\n" + "Row #35: 3,348.69\n" + "Row #36: 88\n" + "Row #36: 195.28\n" + "Row #37: 2,631\n" + "Row #37: 5,663.60\n" + "Row #38: 161\n" + "Row #38: 343.20\n" + "Row #39: 185\n" + "Row #39: 367.78\n" + "Row #40: 2,660\n" + "Row #40: 5,739.63\n" + "Row #41: 1,790\n" + "Row #41: 3,862.79\n" + "Row #42: 2,570\n" + "Row #42: 5,405.02\n" + "Row #43: 2,503\n" + "Row #43: 5,302.08\n" + "Row #44: 2,516\n" + "Row #44: 5,406.21\n" + "Row #45: 67,659\n" + "Row #45: 142,277.07\n" + "Row #46: 124,366\n" + "Row #46: 263,793.22\n"); } } } // End FunctionTest.java mondrian-3.4.1/testsrc/main/mondrian/olap/fun/VisualTotalsTest.java0000644000175000017500000000722011735330606025331 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.test.TestContext; import junit.framework.TestCase; import org.olap4j.*; import org.olap4j.metadata.Member; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; import java.util.List; /** * VisualTotalsTest tests the internal functions defined in * {@link VisualTotalsFunDef}. Right now, only tests substitute(). * * @author efine */ public class VisualTotalsTest extends TestCase { public void testSubstituteEmpty() { final String actual = VisualTotalsFunDef.substitute("", "anything"); final String expected = ""; assertEquals(expected, actual); } public void testSubstituteOneStarOnly() { final String actual = VisualTotalsFunDef.substitute("*", "anything"); final String expected = "anything"; assertEquals(expected, actual); } public void testSubstituteOneStarBegin() { final String actual = VisualTotalsFunDef.substitute("* is the word.", "Grease"); final String expected = "Grease is the word."; assertEquals(expected, actual); } public void testSubstituteOneStarEnd() { final String actual = VisualTotalsFunDef.substitute( "Lies, damned lies, and *!", "statistics"); final String expected = "Lies, damned lies, and statistics!"; assertEquals(expected, actual); } public void testSubstituteTwoStars() { final String actual = VisualTotalsFunDef.substitute("**", "anything"); final String expected = "*"; assertEquals(expected, actual); } public void testSubstituteCombined() { final String actual = VisualTotalsFunDef.substitute( "*: see small print**** for *", "disclaimer"); final String expected = "disclaimer: see small print** for disclaimer"; assertEquals(expected, actual); } /** * Test case for bug * MONDRIAN-925, "VisualTotals + drillthrough throws Exception". * * @throws java.sql.SQLException on error */ public void testDrillthroughVisualTotal() throws SQLException { CellSet cellSet = TestContext.instance().executeOlap4jQuery( "select {[Measures].[Unit Sales]} on columns, " + "{VisualTotals(" + " {[Product].[Food].[Baked Goods].[Bread]," + " [Product].[Food].[Baked Goods].[Bread].[Bagels]," + " [Product].[Food].[Baked Goods].[Bread].[Muffins]}," + " \"**Subtotal - *\")} on rows " + "from [Sales]"); List positions = cellSet.getAxes().get(1).getPositions(); Cell cell; ResultSet resultSet; Member member; cell = cellSet.getCell(Arrays.asList(0, 0)); member = positions.get(0).getMembers().get(0); assertEquals("*Subtotal - Bread", member.getName()); resultSet = cell.drillThrough(); assertNull(resultSet); cell = cellSet.getCell(Arrays.asList(0, 1)); member = positions.get(1).getMembers().get(0); assertEquals("Bagels", member.getName()); resultSet = cell.drillThrough(); assertNotNull(resultSet); resultSet.close(); } } // End VisualTotalsTest.java mondrian-3.4.1/testsrc/main/mondrian/olap/fun/SortTest.java0000644000175000017500000001513611735330606023633 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2010 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.test.FoodMartTestCase; /** * SortTest tests the collation order of positive and negative * infinity, and {@link Double#NaN}. * * @author jhyde * @since Sep 21, 2006 */ public class SortTest extends FoodMartTestCase { public void testFoo() { // Check that each value compares according to its position in the total // order. For example, NaN compares greater than // Double.NEGATIVE_INFINITY, -34.5, -0.001, 0, 0.00000567, 1, 3.14; // equal to NaN; and less than Double.POSITIVE_INFINITY. double[] values = { Double.NEGATIVE_INFINITY, FunUtil.DoubleNull, -34.5, -0.001, 0, 0.00000567, 1, 3.14, Double.NaN, Double.POSITIVE_INFINITY, }; for (int i = 0; i < values.length; i++) { for (int j = 0; j < values.length; j++) { int expected = i < j ? -1 : i == j ? 0 : 1; assertEquals( "values[" + i + "]=" + values[i] + ", values[" + j + "]=" + values[j], expected, FunUtil.compareValues(values[i], values[j])); } } } public void testOrderDesc() { // In MSAS, NULLs collate last (or almost last, along with +inf and // NaN) whereas in Mondrian NULLs collate least (that is, before -inf). assertQueryReturns( "with" + " member [Measures].[Foo] as '\n" + " Iif([Promotion Media].CurrentMember IS [Promotion Media].[TV], 1.0 / 0.0,\n" + " Iif([Promotion Media].CurrentMember IS [Promotion Media].[Radio], -1.0 / 0.0,\n" + " Iif([Promotion Media].CurrentMember IS [Promotion Media].[Bulk Mail], 0.0 / 0.0,\n" + " Iif([Promotion Media].CurrentMember IS [Promotion Media].[Daily Paper], NULL,\n" + " [Measures].[Unit Sales])))) '\n" + "select \n" + " {[Measures].[Foo]} on columns, \n" + " order(except([Promotion Media].[Media Type].members,{[Promotion Media].[Media Type].[No Media]}),[Measures].[Foo],DESC) on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Axis #2:\n" + "{[Promotion Media].[TV]}\n" + "{[Promotion Media].[Bulk Mail]}\n" + "{[Promotion Media].[Daily Paper, Radio, TV]}\n" + "{[Promotion Media].[Product Attachment]}\n" + "{[Promotion Media].[Daily Paper, Radio]}\n" + "{[Promotion Media].[Cash Register Handout]}\n" + "{[Promotion Media].[Sunday Paper, Radio]}\n" + "{[Promotion Media].[Street Handout]}\n" + "{[Promotion Media].[Sunday Paper]}\n" + "{[Promotion Media].[In-Store Coupon]}\n" + "{[Promotion Media].[Sunday Paper, Radio, TV]}\n" + "{[Promotion Media].[Radio]}\n" + "{[Promotion Media].[Daily Paper]}\n" + "Row #0: Infinity\n" + "Row #1: NaN\n" + "Row #2: 9,513\n" + "Row #3: 7,544\n" + "Row #4: 6,891\n" + "Row #5: 6,697\n" + "Row #6: 5,945\n" + "Row #7: 5,753\n" + "Row #8: 4,339\n" + "Row #9: 3,798\n" + "Row #10: 2,726\n" + "Row #11: -Infinity\n" + "Row #12: \n"); } public void testOrderAndRank() { assertQueryReturns( "with " + " member [Measures].[Foo] as '\n" + " Iif([Promotion Media].CurrentMember IS [Promotion Media].[TV], 1.0 / 0.0,\n" + " Iif([Promotion Media].CurrentMember IS [Promotion Media].[Radio], -1.0 / 0.0,\n" + " Iif([Promotion Media].CurrentMember IS [Promotion Media].[Bulk Mail], 0.0 / 0.0,\n" + " Iif([Promotion Media].CurrentMember IS [Promotion Media].[Daily Paper], NULL,\n" + " [Measures].[Unit Sales])))) '\n" + " member [Measures].[R] as '\n" + " Rank([Promotion Media].CurrentMember, [Promotion Media].Members, [Measures].[Foo]) '\n" + "select\n" + " {[Measures].[Foo], [Measures].[R]} on columns, \n" + " order([Promotion Media].[Media Type].members,[Measures].[Foo]) on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "{[Measures].[R]}\n" + "Axis #2:\n" + "{[Promotion Media].[Daily Paper]}\n" + "{[Promotion Media].[Radio]}\n" + "{[Promotion Media].[Sunday Paper, Radio, TV]}\n" + "{[Promotion Media].[In-Store Coupon]}\n" + "{[Promotion Media].[Sunday Paper]}\n" + "{[Promotion Media].[Street Handout]}\n" + "{[Promotion Media].[Sunday Paper, Radio]}\n" + "{[Promotion Media].[Cash Register Handout]}\n" + "{[Promotion Media].[Daily Paper, Radio]}\n" + "{[Promotion Media].[Product Attachment]}\n" + "{[Promotion Media].[Daily Paper, Radio, TV]}\n" + "{[Promotion Media].[No Media]}\n" + "{[Promotion Media].[Bulk Mail]}\n" + "{[Promotion Media].[TV]}\n" + "Row #0: \n" + "Row #0: 15\n" + "Row #1: -Infinity\n" + "Row #1: 14\n" + "Row #2: 2,726\n" + "Row #2: 13\n" + "Row #3: 3,798\n" + "Row #3: 12\n" + "Row #4: 4,339\n" + "Row #4: 11\n" + "Row #5: 5,753\n" + "Row #5: 10\n" + "Row #6: 5,945\n" + "Row #6: 9\n" + "Row #7: 6,697\n" + "Row #7: 8\n" + "Row #8: 6,891\n" + "Row #8: 7\n" + "Row #9: 7,544\n" + "Row #9: 6\n" + "Row #10: 9,513\n" + "Row #10: 5\n" + "Row #11: 195,448\n" + "Row #11: 4\n" + "Row #12: NaN\n" + "Row #12: 2\n" + "Row #13: Infinity\n" + "Row #13: 1\n"); } } // End SortTest.java mondrian-3.4.1/testsrc/main/mondrian/olap/fun/CrossJoinTest.java0000644000175000017500000002643311735330606024617 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.ArrayTupleList; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.*; import mondrian.test.FoodMartTestCase; import junit.framework.Assert; import java.io.PrintWriter; import java.util.*; /** * CrossJoint tests the collation order of positive and negative * infinity, and {@link Double#NaN}. * * @author Richard M. Emberson * @since Jan 14, 2007 */ public class CrossJoinTest extends FoodMartTestCase { static List> m3 = Arrays.asList( Arrays.asList(new TestMember("k"), new TestMember("l")), Arrays.asList(new TestMember("m"), new TestMember("n"))); static List> m4 = Arrays.asList( Arrays.asList(new TestMember("U"), new TestMember("V")), Arrays.asList(new TestMember("W"), new TestMember("X")), Arrays.asList(new TestMember("Y"), new TestMember("Z"))); static final Comparator> memberComparator = new Comparator>() { public int compare(List ma1, List ma2) { for (int i = 0; i < ma1.size(); i++) { int c = ma1.get(i).compareTo(ma2.get(i)); if (c < 0) { return c; } else if (c > 0) { return c; } } return 0; } }; private CrossJoinFunDef crossJoinFunDef; public CrossJoinTest() { } public CrossJoinTest(String name) { super(name); } protected void setUp() throws Exception { super.setUp(); crossJoinFunDef = new CrossJoinFunDef(new NullFunDef()); } protected void tearDown() throws Exception { super.tearDown(); } //////////////////////////////////////////////////////////////////////// // Iterable //////////////////////////////////////////////////////////////////////// public void testListTupleListTupleIterCalc() { if (! Util.Retrowoven) { CrossJoinFunDef.CrossJoinIterCalc calc = crossJoinFunDef.new CrossJoinIterCalc( getResolvedFunCall(), null); doTupleTupleIterTest(calc); } } protected void doTupleTupleIterTest( CrossJoinFunDef.CrossJoinIterCalc calc) { TupleList l4 = makeListTuple(m4); String s4 = toString(l4); String e4 = "{[U, V], [W, X], [Y, Z]}"; Assert.assertEquals(e4, s4); TupleList l3 = makeListTuple(m3); String s3 = toString(l3); String e3 = "{[k, l], [m, n]}"; Assert.assertEquals(e3, s3); TupleIterable iterable = calc.makeIterable(l4, l3); String s = toString(iterable); String e = "{[U, V, k, l], [U, V, m, n], [W, X, k, l], " + "[W, X, m, n], [Y, Z, k, l], [Y, Z, m, n]}"; Assert.assertEquals(e, s); } //////////////////////////////////////////////////////////////////////// // Immutable List //////////////////////////////////////////////////////////////////////// public void testImmutableListTupleListTupleListCalc() { CrossJoinFunDef.ImmutableListCalc calc = crossJoinFunDef.new ImmutableListCalc( getResolvedFunCall(), null); doTupleTupleListTest(calc); } protected void doTupleTupleListTest( CrossJoinFunDef.BaseListCalc calc) { TupleList l4 = makeListTuple(m4); String s4 = toString(l4); String e4 = "{[U, V], [W, X], [Y, Z]}"; Assert.assertEquals(e4, s4); TupleList l3 = makeListTuple(m3); String s3 = toString(l3); String e3 = "{[k, l], [m, n]}"; Assert.assertEquals(e3, s3); TupleList list = calc.makeList(l4, l3); String s = toString(list); String e = "{[U, V, k, l], [U, V, m, n], [W, X, k, l], " + "[W, X, m, n], [Y, Z, k, l], [Y, Z, m, n]}"; Assert.assertEquals(e, s); TupleList subList = list.subList(0, 6); s = toString(subList); Assert.assertEquals(6, subList.size()); Assert.assertEquals(e, s); subList = subList.subList(0, 6); s = toString(subList); Assert.assertEquals(6, subList.size()); Assert.assertEquals(e, s); subList = subList.subList(1, 5); s = toString(subList); e = "{[U, V, m, n], [W, X, k, l], [W, X, m, n], [Y, Z, k, l]}"; Assert.assertEquals(4, subList.size()); Assert.assertEquals(e, s); subList = subList.subList(2, 4); s = toString(subList); e = "{[W, X, m, n], [Y, Z, k, l]}"; Assert.assertEquals(2, subList.size()); Assert.assertEquals(e, s); subList = subList.subList(1, 2); s = toString(subList); e = "{[Y, Z, k, l]}"; Assert.assertEquals(1, subList.size()); Assert.assertEquals(e, s); subList = list.subList(1, 4); s = toString(subList); e = "{[U, V, m, n], [W, X, k, l], [W, X, m, n]}"; Assert.assertEquals(3, subList.size()); Assert.assertEquals(e, s); subList = list.subList(2, 4); s = toString(subList); e = "{[W, X, k, l], [W, X, m, n]}"; Assert.assertEquals(2, subList.size()); Assert.assertEquals(e, s); subList = list.subList(2, 3); s = toString(subList); e = "{[W, X, k, l]}"; Assert.assertEquals(1, subList.size()); Assert.assertEquals(e, s); subList = list.subList(4, 4); s = toString(subList); e = "{}"; Assert.assertEquals(0, subList.size()); Assert.assertEquals(e, s); } //////////////////////////////////////////////////////////////////////// // Mutable List //////////////////////////////////////////////////////////////////////// public void testMutableListTupleListTupleListCalc() { CrossJoinFunDef.MutableListCalc calc = crossJoinFunDef.new MutableListCalc( getResolvedFunCall(), null); doMTupleTupleListTest(calc); } protected void doMTupleTupleListTest( CrossJoinFunDef.BaseListCalc calc) { TupleList l1 = makeListTuple(m3); String s1 = toString(l1); String e1 = "{[k, l], [m, n]}"; Assert.assertEquals(e1, s1); TupleList l2 = makeListTuple(m4); String s2 = toString(l2); String e2 = "{[U, V], [W, X], [Y, Z]}"; Assert.assertEquals(e2, s2); TupleList list = calc.makeList(l1, l2); String s = toString(list); String e = "{[k, l, U, V], [k, l, W, X], [k, l, Y, Z], " + "[m, n, U, V], [m, n, W, X], [m, n, Y, Z]}"; Assert.assertEquals(e, s); if (false) { // Cannot apply Collections.reverse to TupleList // because TupleList.set always returns null. // (This is a violation of the List contract, but it is inefficient // to construct a list to return.) Collections.reverse(list); s = toString(list); e = "{[m, n, Y, Z], [m, n, W, X], [m, n, U, V], " + "[k, l, Y, Z], [k, l, W, X], [k, l, U, V]}"; Assert.assertEquals(e, s); } // sort Collections.sort(list, memberComparator); s = toString(list); e = "{[k, l, U, V], [k, l, W, X], [k, l, Y, Z], " + "[m, n, U, V], [m, n, W, X], [m, n, Y, Z]}"; Assert.assertEquals(e, s); List members = list.remove(1); s = toString(list); e = "{[k, l, U, V], [k, l, Y, Z], [m, n, U, V], " + "[m, n, W, X], [m, n, Y, Z]}"; Assert.assertEquals(e, s); } //////////////////////////////////////////////////////////////////////// // Helper methods //////////////////////////////////////////////////////////////////////// protected String toString(TupleIterable l) { StringBuffer buf = new StringBuffer(100); buf.append('{'); int j = 0; for (List o : l) { if (j++ > 0) { buf.append(", "); } buf.append(o); } buf.append('}'); return buf.toString(); } protected TupleList makeListTuple(List> ms) { final TupleList list = new ArrayTupleList(ms.get(0).size()); for (List m : ms) { list.add(m); } return list; } protected ResolvedFunCall getResolvedFunCall() { FunDef funDef = new TestFunDef(); Exp[] args = new Exp[0]; Type returnType = new SetType( new TupleType( new Type[] { new MemberType(null, null, null, null), new MemberType(null, null, null, null)})); return new ResolvedFunCall(funDef, args, returnType); } //////////////////////////////////////////////////////////////////////// // Helper classes //////////////////////////////////////////////////////////////////////// public static class TestFunDef implements FunDef { TestFunDef() { } public Syntax getSyntax() { throw new UnsupportedOperationException(); } public String getName() { throw new UnsupportedOperationException(); } public String getDescription() { throw new UnsupportedOperationException(); } public int getReturnCategory() { throw new UnsupportedOperationException(); } public int[] getParameterCategories() { throw new UnsupportedOperationException(); } public Exp createCall(Validator validator, Exp[] args) { throw new UnsupportedOperationException(); } public String getSignature() { throw new UnsupportedOperationException(); } public void unparse(Exp[] args, PrintWriter pw) { throw new UnsupportedOperationException(); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { throw new UnsupportedOperationException(); } } public static class NullFunDef implements FunDef { NullFunDef() { } public Syntax getSyntax() { return Syntax.Function; } public String getName() { return ""; } public String getDescription() { return ""; } public int getReturnCategory() { return 0; } public int[] getParameterCategories() { return new int[0]; } public Exp createCall(Validator validator, Exp[] args) { return null; } public String getSignature() { return ""; } public void unparse(Exp[] args, PrintWriter pw) { // } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { return null; } } } // End CrossJoinTest.java mondrian-3.4.1/testsrc/main/mondrian/olap/fun/ResultStyleCompiler.java0000644000175000017500000002566011735330606026041 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.DelegatingExpCompiler; import mondrian.olap.*; import mondrian.olap.type.Type; import java.util.*; /** * The ResultStyleCompiler can be used to assure that * the use of the container ResultStyle: ITERABLE, LIST and MUTABLE_LIST; * can be requested by any Calc. This ExpCompiler injects into the * Exp hierarchy a special Calc, the MultiCalc, that evaluates * its three child Calc's (one for ITERABLE, LIST and MUTABLE_LIST) * and compares the lists returned to make sure that they are the * same. This comparison can only be done when the Member evaluation * stage of query evaluation is begin done the last time. * [Think about it - how can you tell when the evaluation is happening * for the last time.] Evaluation is called from the RolapResult's * constructor calling the method executeAxis. This happens one or * more times in the while-loop. These evaluations may not be complete; * you can not necessarily compare results. Then, evaluation occurs * just below the while-loop, again calling executeAxis. In this * case the evaluation is complete. The trick is to llok a the * stack and when one changes the line number from which one is * being called, then one knows one is being called by the second * executeAxis call in the RolapResult constructor. * * * @author Richard M. Emberson * @since Feb 10 2007 */ public class ResultStyleCompiler extends DelegatingExpCompiler { static { // This is here so that folks can see that this compiler is // being used. It should be removed in the future. System.out.println("ResultStyleCompiler being used"); } private static ExpCompiler generateCompiler( Evaluator evaluator, Validator validator, List resultStyles) { // pop and then push class name Object context = ExpCompiler.Factory.getFactory().removeContext(); try { return ExpCompiler.Factory.getExpCompiler( evaluator, validator, resultStyles); } finally { // reset ExpCompiler.Factory test context ExpCompiler.Factory.getFactory().restoreContext(context); } } /** * Constructor which uses the ExpCompiler.Factory to get the * default ExpCompiler as an instance variable - ResultStyleCompiler * is a wrapper. * */ public ResultStyleCompiler( Evaluator evaluator, Validator validator, List resultStyles) { super(generateCompiler(evaluator, validator, resultStyles)); } public Calc compile(Exp exp) { List resultStyles = getAcceptableResultStyles(); return compileAs(exp, null, resultStyles); } public Calc compileAs( Exp exp, Type resultType, List resultStyles) { // This applies only to the ITERABE, LIST and MUTABLE_LIST // ResultStyles. For each we compile and save the Calc // in a Multi-Calc, then during evaluation, each of the // calcs are evaluated and results compared. // If the request is for a MUTABLE_LIST, then that result HAS to // be returned to caller. if (resultStyles.size() > 0) { boolean foundIterable = false; boolean foundList = false; boolean foundMutableList = false; for (ResultStyle resultStyle : resultStyles) { switch (resultStyle) { case LIST: foundList = true; break; case MUTABLE_LIST: foundMutableList = true; break; case ITERABLE: foundIterable = true; break; } } // found at least one of the container Calcs if (foundIterable || foundList || foundMutableList) { Calc calcIter = compileAs( exp, resultType, ResultStyle.ITERABLE_ONLY); Calc calcList = compileAs( exp, resultType, ResultStyle.LIST_ONLY); Calc calcMList = compileAs( exp, resultType, ResultStyle.MUTABLELIST_ONLY); return new MultiCalc( calcIter, calcList, calcMList, // only a mutable list was requested // so that is the one that MUST be returned ! (foundList || foundIterable)); } } return compile(exp); } /** * Calc with three child Calcs, one for ITERABLE, LIST and MUTABLE_LIST, * which are evaluated during the normal evaluation process. */ static class MultiCalc implements Calc { static int counter = 0; final Calc calcIter; final Calc calcList; final Calc calcMList; final boolean onlyMutableList; int lineNumber; int cnt; MultiCalc( Calc calcIter, Calc calcList, Calc calcMList, boolean onlyMutableList) { this.calcIter = calcIter; this.calcList = calcList; this.calcMList = calcMList; this.onlyMutableList = onlyMutableList; this.lineNumber = -1; this.cnt = counter++; } public boolean isWrapperFor(Class iface) { return iface.isInstance(this); } public T unwrap(Class iface) { return iface.cast(this); } /** * Returns whether this is a final evaluation; the one that * takes place after the while-loop in the RolapResult * constructor. * * @return true if this is a final evaluation */ protected boolean finalEval() { StackTraceElement[] stEls = new Throwable().getStackTrace(); for (int i = stEls.length - 1; i > 0; i--) { StackTraceElement stEl = stEls[i]; if (stEl.getClassName().equals("mondrian.rolap.RolapResult")) { int ln = stEl.getLineNumber(); //System.out.println("MultiCalc.finalEval: ln="+ln + ", cnt="+cnt); if (this.lineNumber == -1) { this.lineNumber = ln; return false; } else { return this.lineNumber != ln; } } } throw new AssertionError("MultiCalc.finalEval: MISS"); } public Object evaluate(Evaluator evaluator) { // We have to make copies of the Evaluator because of // the single test: NonEmptyTest.testVCNativeCJWithTopPercent Evaluator eval1 = evaluator.push(); Evaluator eval2 = evaluator.push(); Object valueIter = calcIter.evaluate(evaluator); Object valueList = calcList.evaluate(eval1); Object valueMList = calcMList.evaluate(eval2); if (finalEval()) { if (! compare(valueIter, valueList)) { throw new RuntimeException( "MultiCalc.evaluator: ERROR Iter-List"); } if (! compare(valueIter, valueMList)) { throw new RuntimeException( "MultiCalc.evaluator: ERROR Iter-MList"); } } return (onlyMutableList) ? valueMList : valueIter; } public boolean dependsOn(Hierarchy hierarchy) { return calcIter.dependsOn(hierarchy); } public Type getType() { return calcIter.getType(); } public void accept(CalcWriter calcWriter) { calcIter.accept(calcWriter); } public ResultStyle getResultStyle() { return calcIter.getResultStyle(); } protected boolean compare(Object v1, Object v2) { if (v1 == null) { return (v2 == null); } else if (v2 == null) { return false; } else { if (! (v1 instanceof Iterable)) { return false; } if (! (v2 instanceof Iterable)) { return false; } Iterable iter1 = (Iterable) v1; Iterable iter2 = (Iterable) v2; Iterator it1 = iter1.iterator(); Iterator it2 = iter2.iterator(); while (it1.hasNext()) { if (! it2.hasNext()) { // too few return false; } Object o1 = it1.next(); Object o2 = it2.next(); if (o1 == null) { if (o2 != null) { return false; } } else if (o2 == null) { return false; } else { if (o1 instanceof Member) { if (! o1.equals(o2)) { return false; } } else { Member[] ma1 = (Member[]) o1; Member[] ma2 = (Member[]) o2; if (! Arrays.equals(ma1, ma2)) { /* System.out.println("MultiCalc.compare: not equals"); System.out.println("MultiCalc.compare: o1="+o1); print(ma1); System.out.println("MultiCalc.compare: o2="+o2); print(ma2); */ return false; } } } } if (it2.hasNext()) { // too many return false; } return true; } } protected void print(Member[] ma) { StringBuilder buf = new StringBuilder(100); if (ma == null) { buf.append("null"); } else { for (int i = 0; i < ma.length; i++) { Member m = ma[i]; if (i > 0) { buf.append(','); } buf.append(m.getUniqueName()); buf.append(' '); } } System.out.println(buf.toString()); } } } // End ResultStyleCompiler.java mondrian-3.4.1/testsrc/main/mondrian/olap/fun/PartialSortTest.java0000644000175000017500000004350511735330606025151 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2008-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.test.PerformanceTest; import junit.framework.TestCase; import org.apache.commons.collections.ComparatorUtils; import org.apache.commons.collections.comparators.ReverseComparator; import org.apache.log4j.Logger; import java.util.*; /** * PartialSortTest is a unit test for the partial-sort algorithm * {@link FunUtil#partialSort}, which supports MDX functions like TopCount and * BottomCount. No MDX here; there are tests of TopCount etc in FunctionTest. * * @author Marc Berkowitz * @since Nov 2008 */ public class PartialSortTest extends TestCase { final Random random = new Random(); // subroutines // returns a new array of random integers private Integer[] newRandomIntegers(int length, int minValue, int maxValue) { final int delta = maxValue - minValue; Integer[] vec = new Integer[length]; for (int i = 0; i < length; i++) { vec[i] = minValue + random.nextInt(delta); } return vec; } // calls partialSort() for natural ascending or descending order @SuppressWarnings({"unchecked"}) private void doPartialSort(Object[] items, boolean descending, int limit) { Comparator comp = ComparatorUtils.naturalComparator(); if (descending) { comp = ComparatorUtils.reversedComparator(comp); } FunUtil.partialSort(items, comp, limit); } // predicate: checks that results have been partially sorted; // simplest case, on int[] private static boolean isPartiallySorted( int[] vec, int limit, boolean descending) { // elements {0 <= i < limit} are sorted for (int i = 1; i < limit; i++) { int delta = vec[i] - vec[i - 1]; if (descending) { if (delta > 0) { return false; } } else { if (delta < 0) { return false; } } } // elements {limit <= i} are just bigger or smaller than the // bound vec[limit - 1]; int bound = vec[limit - 1]; for (int i = limit; i < vec.length; i++) { int delta = vec[i] - bound; if (descending) { if (delta > 0) { return false; } } else { if (delta < 0) { return false; } } } return true; } // same predicate generalized: uses natural comparison private static boolean isPartiallySorted( T [] vec, int limit, boolean descending) { //noinspection unchecked return isPartiallySorted( vec, limit, (Comparator) ComparatorUtils.naturalComparator(), descending); } // same predicate generalized: uses a given Comparator private static boolean isPartiallySorted( T[] vec, int limit, Comparator order, boolean descending) { return isPartiallySorted(vec, limit, order, descending, null); } // Same predicate generalized: uses two Comparators, a sort key // (ascending or descending), and a tie-breaker (always // ascending). This is a contrivance to verify a stable partial // sort, on a special input array. private static boolean isPartiallySorted( T[] vec, int limit, Comparator order, boolean descending, Comparator tieBreaker) { // elements {0 <= i < limit} are sorted for (int i = 1; i < limit; i++) { int delta = order.compare(vec[i], vec[i - 1]); if (delta == 0) { if (tieBreaker != null && tieBreaker.compare(vec[i], vec[i - 1]) < 0) { return false; } } else if (descending) { if (delta > 0) { return false; } } else { if (delta < 0) { return false; } } } // elements {limit <= i} are just bigger or smaller than the // bound vec[limit - 1]; T bound = vec[limit - 1]; for (int i = limit; i < vec.length; i++) { int delta = order.compare(vec[i], bound); if (descending) { if (delta > 0) { return false; } } else { if (delta < 0) { return false; } } } return true; } // validate the predicate isPartiallySorted() public void testPredicate1() { int errct = 0; int size = 10 * 1000; int[] vec = new int[size]; // all sorted, ascending int key = 0; for (int i = 0; i < size; i++) { vec[i] = key; key += i % 3; } if (isPartiallySorted(vec, size, true)) { errct++; } if (!isPartiallySorted(vec, size, false)) { errct++; } // partially sorted, ascending key = 0; int limit = 2000; for (int i = 0; i < limit; i++) { vec[i] = key; key += i % 3; } for (int i = limit; i < size; i++) { vec[i] = 2 * key + random.nextInt(1000); } if (isPartiallySorted(vec, limit, true)) { errct++; } if (!isPartiallySorted(vec, limit, false)) { errct++; } // all sorted, descending; key = 2 * size; for (int i = 0; i < size; i++) { vec[i] = key; key -= i % 3; } if (!isPartiallySorted(vec, size, true)) { errct++; } if (isPartiallySorted(vec, size, false)) { errct++; } // partially sorted, descending key = 2 * size; limit = 2000; for (int i = 0; i < limit; i++) { vec[i] = key; key -= i % 3; } for (int i = limit; i < size; i++) { vec[i] = key - random.nextInt(size); } if (!isPartiallySorted(vec, limit, true)) { errct++; } if (isPartiallySorted(vec, limit, false)) { errct++; } assertTrue(errct == 0); } // same as testPredicate() but boxed public void testPredicate2() { int errct = 0; int size = 10 * 1000; Integer[] vec = new Integer[size]; Random random = new Random(); // all sorted, ascending int key = 0; for (int i = 0; i < size; i++) { vec[i] = key; key += i % 3; } if (isPartiallySorted(vec, size, true)) { errct++; } if (!isPartiallySorted(vec, size, false)) { errct++; } // partially sorted, ascending key = 0; int limit = 2000; for (int i = 0; i < limit; i++) { vec[i] = key; key += i % 3; } for (int i = limit; i < size; i++) { vec[i] = 2 * key + random.nextInt(1000); } if (isPartiallySorted(vec, limit, true)) { errct++; } if (!isPartiallySorted(vec, limit, false)) { errct++; } // all sorted, descending; key = 2 * size; for (int i = 0; i < size; i++) { vec[i] = key; key -= i % 3; } if (!isPartiallySorted(vec, size, true)) { errct++; } if (isPartiallySorted(vec, size, false)) { errct++; } // partially sorted, descending key = 2 * size; limit = 2000; for (int i = 0; i < limit; i++) { vec[i] = key; key -= i % 3; } for (int i = limit; i < size; i++) { vec[i] = key - random.nextInt(size); } if (!isPartiallySorted(vec, limit, true)) { errct++; } if (isPartiallySorted(vec, limit, false)) { errct++; } assertTrue(errct == 0); } public void testQuick() { final int length = 40; final int limit = 4; Integer vec[] = newRandomIntegers(length, 0, length); // sort descending doPartialSort(vec, true, limit); assertTrue(isPartiallySorted(vec, limit, true)); } public void testOnAlreadySorted() { final int length = 200; final int limit = 8; Integer vec[] = new Integer[length]; for (int i = 0; i < length; i++) { vec[i] = i; } // sort ascending doPartialSort(vec, false, limit); assertTrue(isPartiallySorted(vec, limit, false)); } public void testOnAlreadyReverseSorted() { final int length = 200; final int limit = 8; Integer vec[] = new Integer[length]; for (int i = 0; i < length; i++) { vec[i] = length - i; } // sort ascending doPartialSort(vec, false, limit); assertTrue(isPartiallySorted(vec, limit, false)); } // tests partial sort on arras of random integers private void randomIntegerTests(int length, int limit) { Integer vec[] = newRandomIntegers(length, 0, length); // sort descending doPartialSort(vec, true, limit); assertTrue(isPartiallySorted(vec, limit, true)); // sort ascending vec = newRandomIntegers(length, 0, length); doPartialSort(vec, false, limit); assertTrue(isPartiallySorted(vec, limit, false)); // both again with a wider range of values vec = newRandomIntegers(length, 10, 4 * length); doPartialSort(vec, true, limit); assertTrue(isPartiallySorted(vec, limit, true)); vec = newRandomIntegers(length, 10, 4 * length); doPartialSort(vec, false, limit); assertTrue(isPartiallySorted(vec, limit, false)); // and again with a narrower range values vec = newRandomIntegers(length, 0, length / 10); doPartialSort(vec, true, limit); assertTrue(isPartiallySorted(vec, limit, true)); vec = newRandomIntegers(length, 0, length / 10); doPartialSort(vec, false, limit); assertTrue(isPartiallySorted(vec, limit, false)); } // test correctness public void testOnRandomIntegers() { randomIntegerTests(100, 20); randomIntegerTests(50000, 10); randomIntegerTests(50000, 500); randomIntegerTests(50000, 12000); } // test with large vector public void testOnManyRandomIntegers() { randomIntegerTests(1000 * 1000, 5000); randomIntegerTests(1000 * 1000, 10); } // Test stable partial sort, Test input; a vector of itens with an explicit // index; sort should not pernute the index. static class Item { final int index; final int key; Item(int index, int key) { this.index = index; this.key = key; } static final Comparator byIndex = new Comparator() { public int compare(Item x, Item y) { return x.index - y.index; } }; static final Comparator byKey = new Comparator() { public int compare(Item x, Item y) { return x.key - y.key; } }; // returns true iff VEC is partially sorted 1st by key (ascending or // descending), 2nd by index (always ascending) static boolean isStablySorted(Item[] vec, int limit, boolean desc) { return isPartiallySorted(vec, limit, byKey, desc, byIndex); } } // returns a new array of partially sorted Items private Item[] newPartlySortedItems(int length, int limit, boolean desc) { Item[] vec = new Item[length]; int factor = desc? -1 : 1; int key = desc ? (2 * length) : 0; int i; for (i = 0; i < limit; i++) { vec[i] = new Item(i, key); key += factor * (i % 3); // stutter } for (; i < length; i++) { vec[i] = new Item(i, key + factor * random.nextInt(length)); } return vec; } // returns a new array of random Items private Item[] newRandomItems(int length, int minKey, int maxKey) { final int delta = maxKey - minKey; Item[] vec = new Item[length]; for (int i = 0; i < length; i++) { vec[i] = new Item(i, minKey + random.nextInt(delta)); } return vec; } // just glue private Item[] doStablePartialSort(Item[] vec, boolean desc, int limit) { Comparator comp = Item.byKey; if (desc) { //noinspection unchecked comp = new ReverseComparator(comp); } List sorted = FunUtil.stablePartialSort(Arrays.asList(vec), comp, limit); return sorted.toArray(new Item[sorted.size()]); } public void testPredicateIsStablySorted() { Item[] vec = newPartlySortedItems(24, 4, false); assertTrue(Item.isStablySorted(vec, 4, false)); assertFalse(Item.isStablySorted(vec, 4, true)); vec = newPartlySortedItems(24, 8, true); assertTrue(Item.isStablySorted(vec, 4, true)); assertFalse(Item.isStablySorted(vec, 4, false)); vec = newPartlySortedItems(1000, 100, true); assertTrue(Item.isStablySorted(vec, 100, true)); assertTrue(Item.isStablySorted(vec, 20, true)); assertTrue(Item.isStablySorted(vec, 4, true)); } public void testStableQuick() { final int length = 40; final int limit = 4; Item vec[] = newRandomItems(length, 0, length); // sort descending vec = doStablePartialSort(vec, true, limit); assertTrue(Item.isStablySorted(vec, limit, true)); } // tests stable partial sort on arras of random Items private void randomItemTests(int length, int limit) { Item vec[] = newRandomItems(length, 0, length); // sort descending vec = doStablePartialSort(vec, true, limit); assertTrue(Item.isStablySorted(vec, limit, true)); // sort ascending vec = newRandomItems(length, 0, length); vec = doStablePartialSort(vec, false, limit); assertTrue(Item.isStablySorted(vec, limit, false)); // both again with a wider range of values vec = newRandomItems(length, 10, 4 * length); vec = doStablePartialSort(vec, true, limit); assertTrue(Item.isStablySorted(vec, limit, true)); vec = newRandomItems(length, 10, 4 * length); vec = doStablePartialSort(vec, false, limit); assertTrue(Item.isStablySorted(vec, limit, false)); // and again with a narrower range values vec = newRandomItems(length, 0, length / 10); vec = doStablePartialSort(vec, true, limit); assertTrue(Item.isStablySorted(vec, limit, true)); vec = newRandomItems(length, 0, length / 10); vec = doStablePartialSort(vec, false, limit); assertTrue(Item.isStablySorted(vec, limit, false)); } public void testStableOnRandomItems() { randomItemTests(100, 20); randomItemTests(50000, 10); randomItemTests(50000, 500); randomItemTests(50000, 12000); } // Compares elapsed time of full sort (mergesort), partial sort, and stable // partial sort on the same input set. private void speedTest(Logger logger, int length, int limit) { logger.debug( "sorting the max " + limit + " of " + length + " random Integers"); // random input, 3 copies // repeated keys Integer[] vec1 = newRandomIntegers(length, 0, length / 5); Integer[] vec2 = new Integer[length]; Integer[] vec3 = new Integer[length]; System.arraycopy(vec1, 0, vec2, 0, length); System.arraycopy(vec1, 0, vec3, 0, length); // full sort vec1 long now = System.currentTimeMillis(); Arrays.sort(vec1); long dt = System.currentTimeMillis() - now; logger.debug(" full mergesort took " + dt + " msecs"); // partial sort vec2 now = System.currentTimeMillis(); doPartialSort(vec2, true, limit); dt = System.currentTimeMillis() - now; logger.debug(" partial quicksort took " + dt + " msecs"); // stable partial sort vec3 @SuppressWarnings({"unchecked"}) Comparator comp = new ReverseComparator(ComparatorUtils.naturalComparator()); List vec3List = Arrays.asList(vec3); now = System.currentTimeMillis(); FunUtil.stablePartialSort(vec3List, comp, limit); dt = System.currentTimeMillis() - now; logger.debug(" stable partial quicksort took " + dt + " msecs"); } // compare speed on different sizes of input public void testSpeed() { if (!PerformanceTest.LOGGER.isDebugEnabled()) { return; } speedTest(PerformanceTest.LOGGER, 60, 2); // tiny speedTest(PerformanceTest.LOGGER, 600, 12); // small speedTest(PerformanceTest.LOGGER, 600, 200); speedTest(PerformanceTest.LOGGER, 16000, 4); // medium speedTest(PerformanceTest.LOGGER, 16000, 160); speedTest(PerformanceTest.LOGGER, 1000000, 4); // large speedTest(PerformanceTest.LOGGER, 1000000, 400); // large // very large; needs bigger heap //speedTest(PerformanceTest.LOGGER, 1600 * 1600, 4); } } // End PartialSortTest.java mondrian-3.4.1/testsrc/main/mondrian/olap/fun/TestMember.java0000644000175000017500000001441411735330606024111 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.olap.*; import java.util.*; /** * Mock implementation of {@link Member} for testing. * * @author Richard M. Emberson */ public class TestMember implements Member { private final String identifer; public TestMember(String identifer) { this.identifer = identifer; } public String toString() { return identifer; } public int compareTo(Object o) { TestMember other = (TestMember) o; return this.identifer.compareTo(other.identifer); } public Member getParentMember() { throw new UnsupportedOperationException(); } public Level getLevel() { throw new UnsupportedOperationException(); } public Hierarchy getHierarchy() { throw new UnsupportedOperationException(); } public String getParentUniqueName() { throw new UnsupportedOperationException(); } public MemberType getMemberType() { throw new UnsupportedOperationException(); } public boolean isParentChildLeaf() { return false; } public void setName(String name) { throw new UnsupportedOperationException(); } public boolean isAll() { return false; } public boolean isMeasure() { throw new UnsupportedOperationException(); } public boolean isNull() { return true; } public boolean isChildOrEqualTo(Member member) { throw new UnsupportedOperationException(); } public boolean isCalculated() { throw new UnsupportedOperationException(); } public boolean isEvaluated() { throw new UnsupportedOperationException(); } public int getSolveOrder() { throw new UnsupportedOperationException(); } public Exp getExpression() { throw new UnsupportedOperationException(); } public List getAncestorMembers() { throw new UnsupportedOperationException(); } public boolean isCalculatedInQuery() { throw new UnsupportedOperationException(); } public Object getPropertyValue(String propertyName) { throw new UnsupportedOperationException(); } public Object getPropertyValue(String propertyName, boolean matchCase) { throw new UnsupportedOperationException(); } public String getPropertyFormattedValue(String propertyName) { throw new UnsupportedOperationException(); } public void setProperty(String name, Object value) { throw new UnsupportedOperationException(); } public Property[] getProperties() { throw new UnsupportedOperationException(); } public int getOrdinal() { throw new UnsupportedOperationException(); } public Comparable getOrderKey() { throw new UnsupportedOperationException(); } public boolean isHidden() { throw new UnsupportedOperationException(); } public int getDepth() { throw new UnsupportedOperationException(); } public Member getDataMember() { throw new UnsupportedOperationException(); } public String getUniqueName() { throw new UnsupportedOperationException(); } public String getName() { throw new UnsupportedOperationException(); } public String getDescription() { throw new UnsupportedOperationException(); } public OlapElement lookupChild( SchemaReader schemaReader, Id.Segment s, MatchType matchType) { throw new UnsupportedOperationException(); } public String getQualifiedName() { throw new UnsupportedOperationException(); } public String getCaption() { throw new UnsupportedOperationException(); } public String getLocalized(LocalizedProperty prop, Locale locale) { throw new UnsupportedOperationException(); } public boolean isVisible() { throw new UnsupportedOperationException(); } public Dimension getDimension() { return new MockDimension(); } public Map getAnnotationMap() { throw new UnsupportedOperationException(); } private static class MockDimension implements Dimension { public Hierarchy[] getHierarchies() { throw new UnsupportedOperationException(); } public boolean isMeasures() { throw new UnsupportedOperationException(); } public DimensionType getDimensionType() { throw new UnsupportedOperationException(); } public Schema getSchema() { throw new UnsupportedOperationException(); } public boolean isHighCardinality() { return false; } public String getUniqueName() { throw new UnsupportedOperationException(); } public String getName() { throw new UnsupportedOperationException(); } public String getDescription() { throw new UnsupportedOperationException(); } public boolean isVisible() { throw new UnsupportedOperationException(); } public OlapElement lookupChild( SchemaReader schemaReader, Id.Segment s, MatchType matchType) { throw new UnsupportedOperationException(); } public String getQualifiedName() { throw new UnsupportedOperationException(); } public String getCaption() { throw new UnsupportedOperationException(); } public String getLocalized(LocalizedProperty prop, Locale locale) { throw new UnsupportedOperationException(); } public Hierarchy getHierarchy() { throw new UnsupportedOperationException(); } public Dimension getDimension() { throw new UnsupportedOperationException(); } public Map getAnnotationMap() { throw new UnsupportedOperationException(); } } } // End TestMember.java mondrian-3.4.1/testsrc/main/mondrian/olap/fun/vba/0000755000175000017500000000000011735330606021743 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/olap/fun/vba/ExcelTest.java0000644000175000017500000001027311735330606024511 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2010 Pentaho // All Rights Reserved. */ package mondrian.olap.fun.vba; import junit.framework.TestCase; /** * Unit tests for implementations of Excel worksheet functions. * *

Every function defined in {@link Excel} must have a test here. In addition, * there should be MDX tests (usually in * {@link mondrian.olap.fun.FunctionTest}) if handling of argument types, * result types, operator overloading, exception handling or null handling * are non-trivial. * * @author jhyde * @since Jan 16, 2008 */ public class ExcelTest extends TestCase { private static final double SMALL = 1e-10d; public void testAcos() { // Cos(0) = 1 // Cos(60 degrees) = .5 // Cos(90 degrees) = 0 // Cos(180 degrees) = -1 assertEquals(0.0, Excel.acos(1.0)); assertEquals(Math.PI / 3.0, Excel.acos(.5), SMALL); assertEquals(Math.PI / 2.0, Excel.acos(0.0)); assertEquals(Math.PI, Excel.acos(-1.0)); } public void testAcosh() { // acosh(1) = 0 // acosh(2) ~= 1 // acosh(4) ~= 2 assertEquals(0.0, Excel.acosh(1.0)); assertEquals(1.3169578969248166, Excel.acosh(2.0), SMALL); assertEquals(2.0634370688955608, Excel.acosh(4.0), SMALL); } public void testAsinh() { // asinh(0) = 0 // asinh(1) ~= 1 // asinh(10) ~= 3 // asinh(-x) = -asinh(x) assertEquals(0.0, Excel.asinh(0.0)); assertEquals(0.8813735870195429, Excel.asinh(1.0), SMALL); assertEquals(2.99822295029797, Excel.asinh(10.0), SMALL); assertEquals(-2.99822295029797, Excel.asinh(-10.0), SMALL); } public void testAtan2() { assertEquals(Math.atan2(0, 10), Excel.atan2(0, 10)); assertEquals(Math.atan2(1, .8), Excel.atan2(1, .8)); assertEquals(Math.atan2(-5, 0), Excel.atan2(-5, 0)); } public void testAtanh() { // atanh(0) = 0 // atanh(1) = +inf // atanh(-x) = -atanh(x) assertEquals(0.0, Excel.atanh(0)); assertEquals(0.0100003333533347, Excel.atanh(0.01), SMALL); assertEquals(0.549306144334054, Excel.atanh(0.5), SMALL); assertEquals(1.4722194895832, Excel.atanh(0.9), SMALL); assertEquals(2.64665241236224, Excel.atanh(0.99), SMALL); assertEquals(6.1030338227611125, Excel.atanh(0.99999), SMALL); assertEquals(-6.1030338227611125, Excel.atanh(-0.99999), SMALL); } public void testCosh() { assertEquals(Math.cosh(0), Excel.cosh(0)); } public void testDegrees() { assertEquals(90.0, Excel.degrees(Math.PI / 2)); } public void testLog10() { assertEquals(1.0, Excel.log10(10)); assertEquals(-2.0, Excel.log10(.01), 0.00000000000001); } public void testPi() { assertEquals(Math.PI, Excel.pi()); } public void testPower() { assertEquals(0.0, Excel.power(0, 5)); assertEquals(1.0, Excel.power(5, 0)); assertEquals(2.0, Excel.power(4, 0.5)); assertEquals(0.125, Excel.power(2, -3)); } public void testRadians() { assertEquals(Math.PI, Excel.radians(180.0)); assertEquals(-Math.PI * 3.0, Excel.radians(-540.0)); } public void testSinh() { assertEquals(Math.sinh(0), Excel.sinh(0)); } public void testSqrtPi() { // sqrt(2 pi) = sqrt(6.28) ~ 2.5 assertEquals(2.506628274631, Excel.sqrtPi(2.0), SMALL); } public void testTanh() { assertEquals(Math.tanh(0), Excel.tanh(0)); assertEquals(Math.tanh(0.44), Excel.tanh(0.44)); } public void testMod() { assertEquals(2.0, Excel.mod(28, 13)); assertEquals(-11.0, Excel.mod(28, -13)); } public void testIntNative() { assertEquals(5, Vba.intNative(5.1)); assertEquals(5, Vba.intNative(5.9)); assertEquals(-6, Vba.intNative(-5.9)); assertEquals(0, Vba.intNative(0.1)); assertEquals(0, Vba.intNative(0)); } } // End ExcelTest.java mondrian-3.4.1/testsrc/main/mondrian/olap/fun/vba/VbaTest.java0000644000175000017500000013656511735330606024176 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun.vba; import mondrian.olap.InvalidArgumentException; import mondrian.util.Bug; import junit.framework.TestCase; import java.text.*; import java.util.*; /** * Unit tests for implementations of Visual Basic for Applications (VBA) * functions. * *

Every function defined in {@link Vba} must have a test here. In addition, * there should be MDX tests (usually in * {@link mondrian.olap.fun.FunctionTest}) if handling of argument types, * result types, operator overloading, exception handling or null handling * are non-trivial. * * @author jhyde * @since Dec 31, 2007 */ public class VbaTest extends TestCase { private static final double SMALL = 1e-10d; private static final Date SAMPLE_DATE = sampleDate(); private static final String timeZoneName = TimeZone.getDefault().getDisplayName(); private static final boolean isPST = timeZoneName.equals("America/Los_Angeles") || timeZoneName.equals("Pacific Standard Time"); // Conversion functions public void testCBool() { assertEquals(true, Vba.cBool(Boolean.TRUE)); // not quite to spec assertEquals(false, Vba.cBool(Boolean.FALSE)); // not quite to spec assertEquals(true, Vba.cBool(1.5)); assertEquals(true, Vba.cBool("1.5")); assertEquals(false, Vba.cBool("0.00")); try { Object o = Vba.cBool("a"); fail("expected error, got " + o); } catch (RuntimeException e) { assertMessage(e, "NumberFormatException"); } // Per the spec, the string "true" is no different from any other try { Object o = Vba.cBool("true"); fail("expected error, got " + o); } catch (RuntimeException e) { assertMessage(e, "NumberFormatException"); } } private void assertMessage(RuntimeException e, final String expected) { final String message = e.getClass().getName() + ": " + e.getMessage(); assertTrue( "expected message to contain '" + expected + "', got '" + message + "'", message.indexOf(expected) >= 0); } public void testCInt() { assertEquals(1, Vba.cInt(1)); assertEquals(1, Vba.cInt(1.4)); // CInt rounds to the nearest even number assertEquals(2, Vba.cInt(1.5)); assertEquals(2, Vba.cInt(2.5)); assertEquals(2, Vba.cInt(1.6)); assertEquals(-1, Vba.cInt(-1.4)); assertEquals(-2, Vba.cInt(-1.5)); assertEquals(-2, Vba.cInt(-1.6)); assertEquals(Integer.MAX_VALUE, Vba.cInt((double) Integer.MAX_VALUE)); assertEquals(Integer.MIN_VALUE, Vba.cInt((double) Integer.MIN_VALUE)); assertEquals( Short.MAX_VALUE, Vba.cInt(((float) Short.MAX_VALUE) + .4)); assertEquals( Short.MIN_VALUE, Vba.cInt(((float) Short.MIN_VALUE) + .4)); try { Object o = Vba.cInt("a"); fail("expected error, got " + o); } catch (RuntimeException e) { assertMessage(e, "NumberFormatException"); } } public void testInt() { // if negative, Int() returns the closest number less than or // equal to the number. assertEquals(1, Vba.int_(1)); assertEquals(1, Vba.int_(1.4)); assertEquals(1, Vba.int_(1.5)); assertEquals(2, Vba.int_(2.5)); assertEquals(1, Vba.int_(1.6)); assertEquals(-2, Vba.int_(-2)); assertEquals(-2, Vba.int_(-1.4)); assertEquals(-2, Vba.int_(-1.5)); assertEquals(-2, Vba.int_(-1.6)); assertEquals(Integer.MAX_VALUE, Vba.int_((double) Integer.MAX_VALUE)); assertEquals(Integer.MIN_VALUE, Vba.int_((double) Integer.MIN_VALUE)); try { Object o = Vba.int_("a"); fail("expected error, got " + o); } catch (RuntimeException e) { assertMessage(e, "Invalid parameter."); } } public void testFix() { // if negative, Fix() returns the closest number greater than or // equal to the number. assertEquals(1, Vba.fix(1)); assertEquals(1, Vba.fix(1.4)); assertEquals(1, Vba.fix(1.5)); assertEquals(2, Vba.fix(2.5)); assertEquals(1, Vba.fix(1.6)); assertEquals(-1, Vba.fix(-1)); assertEquals(-1, Vba.fix(-1.4)); assertEquals(-1, Vba.fix(-1.5)); assertEquals(-1, Vba.fix(-1.6)); assertEquals(Integer.MAX_VALUE, Vba.fix((double) Integer.MAX_VALUE)); assertEquals(Integer.MIN_VALUE, Vba.fix((double) Integer.MIN_VALUE)); try { Object o = Vba.fix("a"); fail("expected error, got " + o); } catch (RuntimeException e) { assertMessage(e, "Invalid parameter."); } } public void testCDbl() { assertEquals(1.0, Vba.cDbl(1)); assertEquals(1.4, Vba.cDbl(1.4)); // CInt rounds to the nearest even number assertEquals(1.5, Vba.cDbl(1.5)); assertEquals(2.5, Vba.cDbl(2.5)); assertEquals(1.6, Vba.cDbl(1.6)); assertEquals(-1.4, Vba.cDbl(-1.4)); assertEquals(-1.5, Vba.cDbl(-1.5)); assertEquals(-1.6, Vba.cDbl(-1.6)); assertEquals(Double.MAX_VALUE, Vba.cDbl(Double.MAX_VALUE)); assertEquals(Double.MIN_VALUE, Vba.cDbl(Double.MIN_VALUE)); try { Object o = Vba.cDbl("a"); fail("expected error, got " + o); } catch (RuntimeException e) { assertMessage(e, "NumberFormatException"); } } public void testHex() { assertEquals("0", Vba.hex(0)); assertEquals("1", Vba.hex(1)); assertEquals("A", Vba.hex(10)); assertEquals("64", Vba.hex(100)); assertEquals("FFFFFFFF", Vba.hex(-1)); assertEquals("FFFFFFF6", Vba.hex(-10)); assertEquals("FFFFFF9C", Vba.hex(-100)); try { Object o = Vba.hex("a"); fail("expected error, got " + o); } catch (RuntimeException e) { assertMessage(e, "Invalid parameter."); } } public void testOct() { assertEquals("0", Vba.oct(0)); assertEquals("1", Vba.oct(1)); assertEquals("12", Vba.oct(10)); assertEquals("144", Vba.oct(100)); assertEquals("37777777777", Vba.oct(-1)); assertEquals("37777777766", Vba.oct(-10)); assertEquals("37777777634", Vba.oct(-100)); try { Object o = Vba.oct("a"); fail("expected error, got " + o); } catch (RuntimeException e) { assertMessage(e, "Invalid parameter."); } } public void testStr() { assertEquals(" 0", Vba.str(0)); assertEquals(" 1", Vba.str(1)); assertEquals(" 10", Vba.str(10)); assertEquals(" 100", Vba.str(100)); assertEquals("-1", Vba.str(-1)); assertEquals("-10", Vba.str(-10)); assertEquals("-100", Vba.str(-100)); assertEquals("-10.123", Vba.str(-10.123)); assertEquals(" 10.123", Vba.str(10.123)); try { Object o = Vba.oct("a"); fail("expected error, got " + o); } catch (RuntimeException e) { assertMessage(e, "Invalid parameter."); } } public void testVal() { assertEquals(-1615198.0, Vba.val(" - 1615 198th Street N.E.")); assertEquals(1615198.0, Vba.val(" 1615 198th Street N.E.")); assertEquals(1615.198, Vba.val(" 1615 . 198th Street N.E.")); assertEquals(1615.19, Vba.val(" 1615 . 19 . 8th Street N.E.")); assertEquals((double)0xffff, Vba.val("&HFFFF")); assertEquals(668.0, Vba.val("&O1234")); } public void testCDate() throws ParseException { Date date = new Date(); assertEquals(date, Vba.cDate(date)); assertNull(Vba.cDate(null)); // CInt rounds to the nearest even number try { assertEquals( DateFormat.getDateInstance().parse("Jan 12, 1952"), Vba.cDate("Jan 12, 1952")); assertEquals( DateFormat.getDateInstance().parse("October 19, 1962"), Vba.cDate("October 19, 1962")); assertEquals( DateFormat.getTimeInstance().parse("4:35:47 PM"), Vba.cDate("4:35:47 PM")); assertEquals( DateFormat.getDateTimeInstance().parse( "October 19, 1962 4:35:47 PM"), Vba.cDate("October 19, 1962 4:35:47 PM")); } catch (ParseException e) { e.printStackTrace(); fail(); } try { Vba.cDate("Jan, 1952"); fail(); } catch (InvalidArgumentException e) { assertTrue(e.getMessage().indexOf("Jan, 1952") >= 0); } } public void testIsDate() throws ParseException { // CInt rounds to the nearest even number assertFalse(Vba.isDate(null)); assertTrue(Vba.isDate(new Date())); assertTrue(Vba.isDate("Jan 12, 1952")); assertTrue(Vba.isDate("October 19, 1962")); assertTrue(Vba.isDate("4:35:47 PM")); assertTrue(Vba.isDate("October 19, 1962 4:35:47 PM")); assertFalse(Vba.isDate("Jan, 1952")); } // DateTime public void testDateAdd() { assertEquals("2008/04/24 19:10:45", SAMPLE_DATE); // 2008-02-01 0:00:00 Calendar calendar = Calendar.getInstance(); calendar.set(2007, 1 /* 0-based! */, 1, 0, 0, 0); final Date feb2007 = calendar.getTime(); assertEquals("2007/02/01 00:00:00", feb2007); assertEquals( "2008/04/24 19:10:45", Vba.dateAdd("yyyy", 0, SAMPLE_DATE)); assertEquals( "2009/04/24 19:10:45", Vba.dateAdd("yyyy", 1, SAMPLE_DATE)); assertEquals( "2006/04/24 19:10:45", Vba.dateAdd("yyyy", -2, SAMPLE_DATE)); // partial years interpolate final Date sampleDatePlusTwoPointFiveYears = Vba.dateAdd("yyyy", 2.5, SAMPLE_DATE); if (isPST) { // Only run test in PST, because test would produce different // results if start and end are not both in daylight savings time. final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US); final String dateString = dateFormat.format( sampleDatePlusTwoPointFiveYears); // We allow "2010/10/24 07:10:45" for computers that have an out of // date timezone database. 2010/10/24 is in daylight savings time, // but was not according to the old rules. assertTrue( "Got " + dateString, dateString.equals("2010/10/24 06:40:45") || dateString.equals("2010/10/24 07:10:45")); } assertEquals("2009/01/24 19:10:45", Vba.dateAdd("q", 3, SAMPLE_DATE)); // partial months are interesting! assertEquals("2008/06/24 19:10:45", Vba.dateAdd("m", 2, SAMPLE_DATE)); assertEquals("2007/01/01 00:00:00", Vba.dateAdd("m", -1, feb2007)); assertEquals("2007/03/01 00:00:00", Vba.dateAdd("m", 1, feb2007)); assertEquals("2007/02/08 00:00:00", Vba.dateAdd("m", .25, feb2007)); // feb 2008 is a leap month, so a quarter month is 7.25 days assertEquals("2008/02/08 06:00:00", Vba.dateAdd("m", 12.25, feb2007)); assertEquals("2008/05/01 19:10:45", Vba.dateAdd("y", 7, SAMPLE_DATE)); assertEquals( "2008/05/02 01:10:45", Vba.dateAdd("y", 7.25, SAMPLE_DATE)); assertEquals("2008/04/24 23:10:45", Vba.dateAdd("h", 4, SAMPLE_DATE)); assertEquals("2008/04/24 20:00:45", Vba.dateAdd("n", 50, SAMPLE_DATE)); assertEquals("2008/04/24 19:10:36", Vba.dateAdd("s", -9, SAMPLE_DATE)); } public void testDateDiff() { // TODO: } public void testDatePart2() { assertEquals(2008, Vba.datePart("yyyy", SAMPLE_DATE)); assertEquals(2, Vba.datePart("q", SAMPLE_DATE)); // 2nd quarter assertEquals(4, Vba.datePart("m", SAMPLE_DATE)); assertEquals(5, Vba.datePart("w", SAMPLE_DATE)); // thursday assertEquals(17, Vba.datePart("ww", SAMPLE_DATE)); assertEquals(115, Vba.datePart("y", SAMPLE_DATE)); assertEquals(19, Vba.datePart("h", SAMPLE_DATE)); assertEquals(10, Vba.datePart("n", SAMPLE_DATE)); assertEquals(45, Vba.datePart("s", SAMPLE_DATE)); } public void testDatePart3() { assertEquals(5, Vba.datePart("w", SAMPLE_DATE, Calendar.SUNDAY)); assertEquals(4, Vba.datePart("w", SAMPLE_DATE, Calendar.MONDAY)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.SUNDAY)); assertEquals(18, Vba.datePart("ww", SAMPLE_DATE, Calendar.WEDNESDAY)); assertEquals(18, Vba.datePart("ww", SAMPLE_DATE, Calendar.THURSDAY)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.FRIDAY)); } public void testDatePart4() { // 2008 starts on a Tuesday // 2008-04-29 is a Thursday // That puts it in week 17 by most ways of computing weeks assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.SUNDAY, 0)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.SUNDAY, 1)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.SUNDAY, 2)); assertEquals(16, Vba.datePart("ww", SAMPLE_DATE, Calendar.SUNDAY, 3)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.MONDAY, 0)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.MONDAY, 1)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.MONDAY, 2)); assertEquals(16, Vba.datePart("ww", SAMPLE_DATE, Calendar.MONDAY, 3)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.TUESDAY, 0)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.TUESDAY, 1)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.TUESDAY, 2)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.TUESDAY, 3)); assertEquals( 18, Vba.datePart("ww", SAMPLE_DATE, Calendar.WEDNESDAY, 0)); assertEquals( 18, Vba.datePart("ww", SAMPLE_DATE, Calendar.WEDNESDAY, 1)); assertEquals( 17, Vba.datePart("ww", SAMPLE_DATE, Calendar.WEDNESDAY, 2)); assertEquals( 17, Vba.datePart("ww", SAMPLE_DATE, Calendar.WEDNESDAY, 3)); assertEquals(18, Vba.datePart("ww", SAMPLE_DATE, Calendar.THURSDAY, 0)); assertEquals(18, Vba.datePart("ww", SAMPLE_DATE, Calendar.THURSDAY, 1)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.THURSDAY, 2)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.THURSDAY, 3)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.FRIDAY, 0)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.FRIDAY, 1)); assertEquals(16, Vba.datePart("ww", SAMPLE_DATE, Calendar.FRIDAY, 2)); assertEquals(16, Vba.datePart("ww", SAMPLE_DATE, Calendar.FRIDAY, 3)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.SATURDAY, 0)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.SATURDAY, 1)); assertEquals(17, Vba.datePart("ww", SAMPLE_DATE, Calendar.SATURDAY, 2)); assertEquals(16, Vba.datePart("ww", SAMPLE_DATE, Calendar.SATURDAY, 3)); try { int i = Vba.datePart("ww", SAMPLE_DATE, Calendar.SUNDAY, 4); fail("expected error, got " + i); } catch (RuntimeException e) { assertMessage(e, "ArrayIndexOutOfBoundsException"); } } public void testDate() { final Date date = Vba.date(); assertNotNull(date); Calendar calendar = Calendar.getInstance(); calendar.setTime(date); assertEquals(0, calendar.get(Calendar.HOUR_OF_DAY)); assertEquals(0, calendar.get(Calendar.MILLISECOND)); } public void testDateSerial() { final Date date = Vba.dateSerial(2008, 2, 1); assertEquals("2008/02/01 00:00:00", date); } private void assertEquals( String expected, Date date) { final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.US); final String dateString = dateFormat.format(date); assertEquals(expected, dateString); } public void testFormatDateTime() { try { Date date = DateFormat.getDateTimeInstance().parse( "October 19, 1962 4:35:47 PM"); assertEquals("Oct 19, 1962 4:35:47 PM", Vba.formatDateTime(date)); assertEquals( "Oct 19, 1962 4:35:47 PM", Vba.formatDateTime(date, 0)); assertEquals("October 19, 1962", Vba.formatDateTime(date, 1)); assertEquals("10/19/62", Vba.formatDateTime(date, 2)); String datestr = Vba.formatDateTime(date, 3); assertNotNull(datestr); // skip the timezone so this test runs everywhere // in EST, this string is "4:35:47 PM EST" assertTrue(datestr.startsWith("4:35:47 PM")); assertEquals("4:35 PM", Vba.formatDateTime(date, 4)); } catch (ParseException e) { e.printStackTrace(); fail(); } } public void testDateValue() { Date date = new Date(); final Date date1 = Vba.dateValue(date); Calendar calendar = Calendar.getInstance(); calendar.setTime(date1); assertEquals(0, calendar.get(Calendar.HOUR_OF_DAY)); assertEquals(0, calendar.get(Calendar.MINUTE)); assertEquals(0, calendar.get(Calendar.SECOND)); assertEquals(0, calendar.get(Calendar.MILLISECOND)); } private static Date sampleDate() { Calendar calendar = Calendar.getInstance(); // Thursday 2008-04-24 7:10:45pm // Chose a Thursday because 2008 starts on a Tuesday - it makes weeks // interesting. calendar.set(2008, 3 /* 0-based! */, 24, 19, 10, 45); return calendar.getTime(); } public void testDay() { assertEquals(24, Vba.day(SAMPLE_DATE)); } public void testHour() { assertEquals(19, Vba.hour(SAMPLE_DATE)); } public void testMinute() { assertEquals(10, Vba.minute(SAMPLE_DATE)); } public void testMonth() { assertEquals(4, Vba.month(SAMPLE_DATE)); } public void testNow() { final Date date = Vba.now(); assertNotNull(date); } public void testSecond() { assertEquals(45, Vba.second(SAMPLE_DATE)); } public void testTimeSerial() { final Date date = Vba.timeSerial(17, 42, 10); assertEquals("1970/01/01 17:42:10", date); } public void testTimeValue() { assertEquals("1970/01/01 19:10:45", Vba.timeValue(SAMPLE_DATE)); } public void testTimer() { final float v = Vba.timer(); assertTrue(v >= 0); assertTrue(v < 24 * 60 * 60); } public void testWeekday1() { if (Calendar.getInstance().getFirstDayOfWeek() == Calendar.SUNDAY) { assertEquals(Calendar.THURSDAY, Vba.weekday(SAMPLE_DATE)); } } public void testWeekday2() { // 2008/4/24 falls on a Thursday. // If Sunday is the first day of the week, Thursday is day 5. assertEquals(5, Vba.weekday(SAMPLE_DATE, Calendar.SUNDAY)); // If Monday is the first day of the week, then 2008/4/24 falls on the // 4th day of the week assertEquals(4, Vba.weekday(SAMPLE_DATE, Calendar.MONDAY)); assertEquals(3, Vba.weekday(SAMPLE_DATE, Calendar.TUESDAY)); assertEquals(2, Vba.weekday(SAMPLE_DATE, Calendar.WEDNESDAY)); assertEquals(1, Vba.weekday(SAMPLE_DATE, Calendar.THURSDAY)); assertEquals(7, Vba.weekday(SAMPLE_DATE, Calendar.FRIDAY)); assertEquals(6, Vba.weekday(SAMPLE_DATE, Calendar.SATURDAY)); } public void testYear() { assertEquals(2008, Vba.year(SAMPLE_DATE)); } public void testFormatNumber() { assertEquals("1", Vba.formatNumber(1.0)); assertEquals("1.0", Vba.formatNumber(1.0, 1)); assertEquals("0.1", Vba.formatNumber(0.1, -1, -1)); assertEquals(".1", Vba.formatNumber(0.1, -1, 0)); assertEquals("0.1", Vba.formatNumber(0.1, -1, 1)); assertEquals("-1", Vba.formatNumber(-1, -1, 1, -1)); assertEquals("-1", Vba.formatNumber(-1, -1, 1, 0)); assertEquals("(1)", Vba.formatNumber(-1, -1, 1, 1)); assertEquals("1", Vba.formatNumber(1, -1, 1, -1)); assertEquals("1", Vba.formatNumber(1, -1, 1, 0)); assertEquals("1", Vba.formatNumber(1, -1, 1, 1)); assertEquals("1,000", Vba.formatNumber(1000.0, -1, -1, -1, -1)); assertEquals("1000.0", Vba.formatNumber(1000.0, 1, -1, -1, 0)); assertEquals("1,000.0", Vba.formatNumber(1000.0, 1, -1, -1, 1)); } public void testFormatPercent() { assertEquals("100%", Vba.formatPercent(1.0)); assertEquals("100.0%", Vba.formatPercent(1.0, 1)); assertEquals("0.1%", Vba.formatPercent(0.001,1, -1)); assertEquals(".1%", Vba.formatPercent(0.001, 1, 0)); assertEquals("0.1%", Vba.formatPercent(0.001, 1, 1)); assertEquals("11%", Vba.formatPercent(0.111, -1)); assertEquals("11%", Vba.formatPercent(0.111, 0)); assertEquals("11.100%", Vba.formatPercent(0.111, 3)); assertEquals("-100%", Vba.formatPercent(-1, -1, 1, -1)); assertEquals("-100%", Vba.formatPercent(-1, -1, 1, 0)); assertEquals("(100%)", Vba.formatPercent(-1, -1, 1, 1)); assertEquals("100%", Vba.formatPercent(1, -1, 1, -1)); assertEquals("100%", Vba.formatPercent(1, -1, 1, 0)); assertEquals("100%", Vba.formatPercent(1, -1, 1, 1)); assertEquals("100,000%", Vba.formatPercent(1000.0, -1, -1, -1, -1)); assertEquals("100000.0%", Vba.formatPercent(1000.0, 1, -1, -1, 0)); assertEquals("100,000.0%", Vba.formatPercent(1000.0, 1, -1, -1, 1)); } public void testFormatCurrency() { assertEquals("$1.00", Vba.formatCurrency(1.0)); assertEquals("$0.00", Vba.formatCurrency(0.0)); assertEquals("$1.0", Vba.formatCurrency(1.0, 1)); assertEquals("$1", Vba.formatCurrency(1.0, 0)); assertEquals("$.10", Vba.formatCurrency(0.10, -1, 0)); assertEquals("$0.10", Vba.formatCurrency(0.10, -1, -1)); // todo: still need to implement parens customization // assertEquals("-$0.10", Vba.formatCurrency(-0.10, -1, -1, -1)); assertEquals("($0.10)", Vba.formatCurrency(-0.10, -1, -1, 0)); assertEquals("$1,000.00", Vba.formatCurrency(1000.0, -1, -1, 0, 0)); assertEquals("$1000.00", Vba.formatCurrency(1000.0, -1, -1, 0, -1)); } public void testTypeName() { assertEquals("Double", Vba.typeName(1.0)); assertEquals("Integer", Vba.typeName(1)); assertEquals("Float", Vba.typeName(1.0f)); assertEquals("Byte", Vba.typeName((byte)1)); assertEquals("NULL", Vba.typeName(null)); assertEquals("String", Vba.typeName("")); assertEquals("Date", Vba.typeName(new Date())); } // Financial public void testFv() { double f, r, y, p, x; int n; boolean t; r = 0; n = 3; y = 2; p = 7; t = true; f = Vba.fV(r, n, y, p, t); x = -13; assertEquals(x, f); r = 1; n = 10; y = 100; p = 10000; t = false; f = Vba.fV(r, n, y, p, t); x = -10342300; assertEquals(x, f); r = 1; n = 10; y = 100; p = 10000; t = true; f = Vba.fV(r, n, y, p, t); x = -10444600; assertEquals(x, f); r = 2; n = 12; y = 120; p = 12000; t = false; f = Vba.fV(r, n, y, p, t); x = -6409178400d; assertEquals(x, f); r = 2; n = 12; y = 120; p = 12000; t = true; f = Vba.fV(r, n, y, p, t); x = -6472951200d; assertEquals(x, f); // cross tests with pv r = 2.95; n = 13; y = 13000; p = -4406.78544294496; t = false; f = Vba.fV(r, n, y, p, t); x = 333891.230010986; // as returned by excel assertEquals(x, f, 1e-2); r = 2.95; n = 13; y = 13000; p = -17406.7852148156; t = true; f = Vba.fV(r, n, y, p, t); x = 333891.230102539; // as returned by excel assertEquals(x, f, 1e-2); } public void testNpv() { double r, v[], npv, x; r = 1; v = new double[] {100, 200, 300, 400}; npv = Vba.nPV(r, v); x = 162.5; assertEquals(x, npv); r = 2.5; v = new double[] {1000, 666.66666, 333.33, 12.2768416}; npv = Vba.nPV(r, v); x = 347.99232604144827; assertEquals(x, npv, SMALL); r = 12.33333; v = new double[] {1000, 0, -900, -7777.5765}; npv = Vba.nPV(r, v); x = 74.3742433377061; assertEquals(x, npv, 1e-12); r = 0.05; v = new double[] { 200000, 300000.55, 400000, 1000000, 6000000, 7000000, -300000 }; npv = Vba.nPV(r, v); x = 11342283.4233124; assertEquals(x, npv, 1e-8); } public void testPmt() { double f, r, y, p, x; int n; boolean t; r = 0; n = 3; p = 2; f = 7; t = true; y = Vba.pmt(r, n, p, f, t); x = -3; assertEquals(x, y); // cross check with pv r = 1; n = 10; p = -109.66796875; f = 10000; t = false; y = Vba.pmt(r, n, p, f, t); x = 100; assertEquals(x, y); r = 1; n = 10; p = -209.5703125; f = 10000; t = true; y = Vba.pmt(r, n, p, f, t); x = 100; assertEquals(x, y); // cross check with fv r = 2; n = 12; f = -6409178400d; p = 12000; t = false; y = Vba.pmt(r, n, p, f, t); x = 120; assertEquals(x, y); r = 2; n = 12; f = -6472951200d; p = 12000; t = true; y = Vba.pmt(r, n, p, f, t); x = 120; assertEquals(x, y); } public void testPv() { double f, r, y, p, x; int n; boolean t; r = 0; n = 3; y = 2; f = 7; t = true; f = Vba.pV(r, n, y, f, t); x = -13; assertEquals(x, f); r = 1; n = 10; y = 100; f = 10000; t = false; p = Vba.pV(r, n, y, f, t); x = -109.66796875; assertEquals(x, p); r = 1; n = 10; y = 100; f = 10000; t = true; p = Vba.pV(r, n, y, f, t); x = -209.5703125; assertEquals(x, p); r = 2.95; n = 13; y = 13000; f = 333891.23; t = false; p = Vba.pV(r, n, y, f, t); x = -4406.78544294496; assertEquals(x, p, 1e-10); r = 2.95; n = 13; y = 13000; f = 333891.23; t = true; p = Vba.pV(r, n, y, f, t); x = -17406.7852148156; assertEquals(x, p, 1e-10); // cross tests with fv r = 2; n = 12; y = 120; f = -6409178400d; t = false; p = Vba.pV(r, n, y, f, t); x = 12000; assertEquals(x, p); r = 2; n = 12; y = 120; f = -6472951200d; t = true; p = Vba.pV(r, n, y, f, t); x = 12000; assertEquals(x, p); } public void testDdb() { double cost, salvage, life, period, factor, result; cost = 100; salvage = 0; life = 10; period = 1; factor = 2; result = Vba.dDB(cost, salvage, life, period, factor); assertEquals(20.0, result); result = Vba.dDB(cost, salvage, life, period + 1, factor); assertEquals(40.0, result); result = Vba.dDB(cost, salvage, life, period + 2, factor); assertEquals(60.0, result); result = Vba.dDB(cost, salvage, life, period + 3, factor); assertEquals(80.0, result); } public void testRate() { double nPer, pmt, PV, fv, guess, result; boolean type = false; nPer = 12 * 30; pmt = -877.57; PV = 100000; fv = 0; guess = 0.10 / 12; result = Vba.rate(nPer, pmt, PV, fv, type, guess); // compare rate to pV calculation double expRate = 0.0083333; double expPV = Vba.pV(expRate, 12 * 30, -877.57, 0, false); result = Vba.rate(12 * 30, -877.57, expPV, 0, false, 0.10 / 12); assertTrue(Math.abs(expRate - result) < 0.0000001); // compare rate to fV calculation double expFV = Vba.fV(expRate, 12, -100, 0, false); result = Vba.rate(12, -100, 0, expFV, false, 0.10 / 12); assertTrue(Math.abs(expRate - result) < 0.0000001); } public void testIRR() { double vals[] = {-1000, 50, 50, 50, 50, 50, 1050}; assertTrue(Math.abs(0.05 - Vba.IRR(vals, 0.1)) < 0.0000001); vals = new double[] {-1000, 200, 200, 200, 200, 200, 200}; assertTrue(Math.abs(0.05471796 - Vba.IRR(vals, 0.1)) < 0.0000001); // what happens if the numbers are inversed? this may not be // accurate vals = new double[] {1000, -200, -200, -200, -200, -200, -200}; assertTrue(Math.abs(0.05471796 - Vba.IRR(vals, 0.1)) < 0.0000001); } public void testMIRR() { double vals[] = {-1000, 50, 50, 50, 50, 50, 1050}; assertTrue(Math.abs(0.05 - Vba.MIRR(vals, 0.05, 0.05)) < 0.0000001); vals = new double[] {-1000, 200, 200, 200, 200, 200, 200}; assertTrue( Math.abs(0.05263266 - Vba.MIRR(vals, 0.05, 0.05)) < 0.0000001); vals = new double[] {-1000, 200, 200, 200, 200, 200, 200}; assertTrue( Math.abs(0.04490701 - Vba.MIRR(vals, 0.06, 0.04)) < 0.0000001); } public void testIPmt() { assertEquals(-10000.0, Vba.iPmt(0.10, 1, 30, 100000, 0, false)); assertEquals( -2185.473324557822, Vba.iPmt(0.10, 15, 30, 100000, 0, false)); assertEquals( -60.79248252633988, Vba.iPmt(0.10, 30, 30, 100000, 0, false)); } public void testPPmt() { assertEquals( -607.9248252633897, Vba.pPmt(0.10, 1, 30, 100000, 0, false)); assertEquals( -8422.451500705567, Vba.pPmt(0.10, 15, 30, 100000, 0, false)); assertEquals( -10547.13234273705, Vba.pPmt(0.10, 30, 30, 100000, 0, false)); // verify that pmt, ipmt, and ppmt add up double pmt = Vba.pmt(0.10, 30, 100000, 0, false); double ipmt = Vba.iPmt(0.10, 15, 30, 100000, 0, false); double ppmt = Vba.pPmt(0.10, 15, 30, 100000, 0, false); assertTrue(Math.abs(pmt - (ipmt + ppmt)) < 0.0000001); } public void testSLN() { assertEquals(18.0, Vba.sLN(100, 10, 5)); assertEquals(Double.POSITIVE_INFINITY, Vba.sLN(100, 10, 0)); } public void testSYD() { assertEquals(300.0, Vba.sYD(1000, 100, 5, 5)); assertEquals(240.0, Vba.sYD(1000, 100, 4, 5)); assertEquals(180.0, Vba.sYD(1000, 100, 3, 5)); assertEquals(120.0, Vba.sYD(1000, 100, 2, 5)); assertEquals(60.0, Vba.sYD(1000, 100, 1, 5)); } public void testInStr() { assertEquals( 1, Vba.inStr("the quick brown fox jumps over the lazy dog", "the")); assertEquals( 32, Vba.inStr( 16, "the quick brown fox jumps over the lazy dog", "the")); assertEquals( 0, Vba.inStr( 16, "the quick brown fox jumps over the lazy dog", "cat")); assertEquals( 0, Vba.inStr(1, "the quick brown fox jumps over the lazy dog", "cat")); assertEquals( 0, Vba.inStr(1, "", "cat")); assertEquals( 0, Vba.inStr(100, "short string", "str")); try { Vba.inStr(0, "the quick brown fox jumps over the lazy dog", "the"); fail(); } catch (InvalidArgumentException e) { assertTrue(e.getMessage().indexOf("-1 or a location") >= 0); } } public void testInStrRev() { assertEquals( 32, Vba.inStrRev("the quick brown fox jumps over the lazy dog", "the")); assertEquals( 1, Vba.inStrRev( "the quick brown fox jumps over the lazy dog", "the", 16)); try { Vba.inStrRev( "the quick brown fox jumps over the lazy dog", "the", 0); fail(); } catch (InvalidArgumentException e) { assertTrue(e.getMessage().indexOf("-1 or a location") >= 0); } } public void testStrComp() { assertEquals(-1, Vba.strComp("a", "b", 0)); assertEquals(0, Vba.strComp("a", "a", 0)); assertEquals(1, Vba.strComp("b", "a", 0)); } public void testNper() { double f, r, y, p, x, n; boolean t; r = 0; y = 7; p = 2; f = 3; t = false; n = Vba.nPer(r, y, p, f, t); // can you believe it? excel returns nper as a fraction!?? x = -0.71428571429; assertEquals(x, n, 1e-10); // cross check with pv r = 1; y = 100; p = -109.66796875; f = 10000; t = false; n = Vba.nPer(r, y, p, f, t); x = 10; assertEquals(x, n, 1e-12); r = 1; y = 100; p = -209.5703125; f = 10000; t = true; n = Vba.nPer(r, y, p, f, t); x = 10; assertEquals(x, n, 1e-14); // cross check with fv r = 2; y = 120; f = -6409178400d; p = 12000; t = false; n = Vba.nPer(r, y, p, f, t); x = 12; assertEquals(x, n, SMALL); r = 2; y = 120; f = -6472951200d; p = 12000; t = true; n = Vba.nPer(r, y, p, f, t); x = 12; assertEquals(x, n, SMALL); } // String functions public void testAsc() { assertEquals(0x61, Vba.asc("abc")); assertEquals(0x1234, Vba.asc("\u1234abc")); try { Object o = Vba.asc(""); fail("expected error, got " + o); } catch (RuntimeException e) { assertMessage(e, "StringIndexOutOfBoundsException"); } } public void testAscB() { assertEquals(0x61, Vba.ascB("abc")); assertEquals(0x34, Vba.ascB("\u1234abc")); // not sure about this try { Object o = Vba.ascB(""); fail("expected error, got " + o); } catch (RuntimeException e) { assertMessage(e, "StringIndexOutOfBoundsException"); } } public void testAscW() { // ascW behaves identically to asc assertEquals(0x61, Vba.ascW("abc")); assertEquals(0x1234, Vba.ascW("\u1234abc")); try { Object o = Vba.ascW(""); fail("expected error, got " + o); } catch (RuntimeException e) { assertMessage(e, "StringIndexOutOfBoundsException"); } } public void testChr() { assertEquals("a", Vba.chr(0x61)); assertEquals("\u1234", Vba.chr(0x1234)); } public void testChrB() { assertEquals("a", Vba.chrB(0x61)); assertEquals("\u0034", Vba.chrB(0x1234)); } public void testChrW() { assertEquals("a", Vba.chrW(0x61)); assertEquals("\u1234", Vba.chrW(0x1234)); } public void testLCase() { assertEquals("", Vba.lCase("")); assertEquals("abc", Vba.lCase("AbC")); } // NOTE: BuiltinFunTable already implements Left; todo: use this public void testLeft() { assertEquals("abc", Vba.left("abcxyz", 3)); // length=0 is OK assertEquals("", Vba.left("abcxyz", 0)); // Spec says: "If greater than or equal to the number of characters in // string, the entire string is returned." assertEquals("abcxyz", Vba.left("abcxyz", 8)); assertEquals("", Vba.left("", 3)); // Length<0 is illegal. // Note: SSAS 2005 allows length<0, giving the same result as length=0. // We favor the VBA spec over SSAS 2005. if (Bug.Ssas2005Compatible) { assertEquals("", Vba.left("xyz", -2)); } else { try { String s = Vba.left("xyz", -2); fail("expected error, got " + s); } catch (RuntimeException e) { assertMessage(e, "StringIndexOutOfBoundsException"); } } assertEquals("Hello", Vba.left("Hello World!", 5)); } public void testLTrim() { assertEquals("", Vba.lTrim("")); assertEquals("", Vba.lTrim(" ")); assertEquals("abc \r", Vba.lTrim(" \n\tabc \r")); } public void testMid() { String testString = "Mid Function Demo"; assertEquals("Mid", Vba.mid(testString, 1, 3)); assertEquals("Demo", Vba.mid(testString, 14, 4)); // It's OK if start+length = string.length assertEquals("Demo", Vba.mid(testString, 14, 5)); // It's OK if start+length > string.length assertEquals("Demo", Vba.mid(testString, 14, 500)); assertEquals("Function Demo", Vba.mid(testString, 5)); assertEquals("o", Vba.mid("yahoo", 5, 1)); // Start=0 illegal // Note: SSAS 2005 accepts start<=0, treating it as 1, therefore gives // different results. We favor the VBA spec over SSAS 2005. if (Bug.Ssas2005Compatible) { assertEquals("Mid Function Demo", Vba.mid(testString, 0)); assertEquals("Mid Function Demo", Vba.mid(testString, -2)); assertEquals("Mid Function Demo", Vba.mid(testString, -2, 5)); } else { try { String s = Vba.mid(testString, 0); fail("expected error, got " + s); } catch (RuntimeException e) { assertMessage( e, "Invalid parameter. Start parameter of Mid function must " + "be positive"); } // Start<0 illegal try { String s = Vba.mid(testString, -2); fail("expected error, got " + s); } catch (RuntimeException e) { assertMessage( e, "Invalid parameter. Start parameter of Mid function must " + "be positive"); } // Start<0 illegal to 3 args version try { String s = Vba.mid(testString, -2, 5); fail("expected error, got " + s); } catch (RuntimeException e) { assertMessage( e, "Invalid parameter. Start parameter of Mid function must " + "be positive"); } } // Length=0 OK assertEquals("", Vba.mid(testString, 14, 0)); // Length<0 illegal // Note: SSAS 2005 accepts length<0, treating it as 0, therefore gives // different results. We favor the VBA spec over SSAS 2005. if (Bug.Ssas2005Compatible) { assertEquals("", Vba.mid(testString, 14, -1)); } else { try { String s = Vba.mid(testString, 14, -1); fail("expected error, got " + s); } catch (RuntimeException e) { assertMessage( e, "Invalid parameter. Length parameter of Mid function must " + "be non-negative"); } } } public void testMonthName() { assertEquals("January", Vba.monthName(1, false)); assertEquals("Jan", Vba.monthName(1, true)); assertEquals("Dec", Vba.monthName(12, true)); try { String s = Vba.monthName(0, true); fail("expected error, got " + s); } catch (RuntimeException e) { assertMessage(e, "ArrayIndexOutOfBoundsException"); } } public void testReplace3() { // replace with longer string assertEquals("abczabcz", Vba.replace("xyzxyz", "xy", "abc")); // replace with shorter string assertEquals("wazwaz", Vba.replace("wxyzwxyz", "xy", "a")); // replace with string which contains seek assertEquals("wxyz", Vba.replace("xyz", "xy", "wxy")); // replace with string which combines with following char to make seek assertEquals("wxyzwx", Vba.replace("xyyzxy", "xy", "wx")); // replace with empty string assertEquals("wxyza", Vba.replace("wxxyyzxya", "xy", "")); } public void testReplace4() { assertEquals("azaz", Vba.replace("xyzxyz", "xy", "a", 1)); assertEquals("xyzaz", Vba.replace("xyzxyz", "xy", "a", 2)); assertEquals("xyzxyz", Vba.replace("xyzxyz", "xy", "a", 30)); // spec doesn't say, but assume starting before start of string is ok assertEquals("azaz", Vba.replace("xyzxyz", "xy", "a", 0)); assertEquals("azaz", Vba.replace("xyzxyz", "xy", "a", -5)); } public void testReplace5() { assertEquals("azaz", Vba.replace("xyzxyz", "xy", "a", 1, -1)); assertEquals("azxyz", Vba.replace("xyzxyz", "xy", "a", 1, 1)); assertEquals("azaz", Vba.replace("xyzxyz", "xy", "a", 1, 2)); assertEquals("xyzazxyz", Vba.replace("xyzxyzxyz", "xy", "a", 2, 1)); } public void testReplace6() { // compare is currently ignored assertEquals("azaz", Vba.replace("xyzxyz", "xy", "a", 1, -1, 1000)); assertEquals("azxyz", Vba.replace("xyzxyz", "xy", "a", 1, 1, 0)); assertEquals("azaz", Vba.replace("xyzxyz", "xy", "a", 1, 2, -6)); assertEquals( "xyzazxyz", Vba.replace("xyzxyzxyz", "xy", "a", 2, 1, 11)); } public void testRight() { assertEquals("xyz", Vba.right("abcxyz", 3)); // length=0 is OK assertEquals("", Vba.right("abcxyz", 0)); // Spec says: "If greater than or equal to the number of characters in // string, the entire string is returned." assertEquals("abcxyz", Vba.right("abcxyz", 8)); assertEquals("", Vba.right("", 3)); // The VBA spec says that length<0 is error. // Note: SSAS 2005 allows length<0, giving the same result as length=0. // We favor the VBA spec over SSAS 2005. if (Bug.Ssas2005Compatible) { assertEquals("", Vba.right("xyz", -2)); } else { try { String s = Vba.right("xyz", -2); fail("expected error, got " + s); } catch (RuntimeException e) { assertMessage(e, "StringIndexOutOfBoundsException"); } } assertEquals("World!", Vba.right("Hello World!", 6)); } public void testRTrim() { assertEquals("", Vba.rTrim("")); assertEquals("", Vba.rTrim(" ")); assertEquals(" \n\tabc", Vba.rTrim(" \n\tabc")); assertEquals(" \n\tabc", Vba.rTrim(" \n\tabc \r")); } public void testSpace() { assertEquals(" ", Vba.space(3)); assertEquals("", Vba.space(0)); try { String s = Vba.space(-2); fail("expected error, got " + s); } catch (RuntimeException e) { assertMessage(e, "NegativeArraySizeException"); } } public void testString() { assertEquals("xxx", Vba.string(3, 'x')); assertEquals("", Vba.string(0, 'y')); try { String s = Vba.string(-2, 'z'); fail("expected error, got " + s); } catch (RuntimeException e) { assertMessage(e, "NegativeArraySizeException"); } assertEquals("", Vba.string(100, '\0')); } public void testStrReverse() { // odd length assertEquals("cba", Vba.strReverse("abc")); // even length assertEquals("wxyz", Vba.strReverse("zyxw")); // zero length assertEquals("", Vba.strReverse("")); } public void testTrim() { assertEquals("", Vba.trim("")); assertEquals("", Vba.trim(" ")); assertEquals("abc", Vba.trim("abc")); assertEquals("abc", Vba.trim(" \n\tabc \r")); } public void testWeekdayName() { // If Sunday (1) is the first day of the week // then day 1 is Sunday, // then day 2 is Monday, // and day 7 is Saturday assertEquals("Sunday", Vba.weekdayName(1, false, 1)); assertEquals("Monday", Vba.weekdayName(2, false, 1)); assertEquals("Saturday", Vba.weekdayName(7, false, 1)); assertEquals("Sat", Vba.weekdayName(7, true, 1)); // If Monday (2) is the first day of the week // then day 1 is Monday, // and day 7 is Sunday assertEquals("Monday", Vba.weekdayName(1, false, 2)); assertEquals("Sunday", Vba.weekdayName(7, false, 2)); // Use weekday start from locale. Test for the 2 most common. switch (Calendar.getInstance().getFirstDayOfWeek()) { case Calendar.SUNDAY: assertEquals("Sunday", Vba.weekdayName(1, false, 0)); assertEquals("Monday", Vba.weekdayName(2, false, 0)); assertEquals("Saturday", Vba.weekdayName(7, false, 0)); assertEquals("Sat", Vba.weekdayName(7, true, 0)); break; case Calendar.MONDAY: assertEquals("Monday", Vba.weekdayName(1, false, 0)); assertEquals("Tuesday", Vba.weekdayName(2, false, 0)); assertEquals("Sunday", Vba.weekdayName(7, false, 0)); assertEquals("Sun", Vba.weekdayName(7, true, 0)); break; } } // Mathematical public void testAbs() { assertEquals(Vba.abs(-1.7d), 1.7d); } public void testAtn() { assertEquals(0d, Vba.atn(0), SMALL); assertEquals(Math.PI / 4d, Vba.atn(1), SMALL); } public void testCos() { assertEquals(1d, Vba.cos(0), 0d); assertEquals(Vba.sqr(0.5d), Vba.cos(Math.PI / 4d), 0d); assertEquals(0d, Vba.cos(Math.PI / 2d), SMALL); assertEquals(-1d, Vba.cos(Math.PI), 0d); } public void testExp() { assertEquals(1d, Vba.exp(0)); assertEquals(Math.E, Vba.exp(1), 1e-10); } public void testRound() { assertEquals(123d, Vba.round(123.4567d), SMALL); } public void testRound2() { assertEquals(123d, Vba.round(123.4567d, 0), SMALL); assertEquals(123.46d, Vba.round(123.4567d, 2), SMALL); assertEquals(120d, Vba.round(123.45d, -1), SMALL); assertEquals(-123.46d, Vba.round(-123.4567d, 2), SMALL); } public void testSgn() { assertEquals(1, Vba.sgn(3.11111d), 0d); assertEquals(-1, Vba.sgn(-Math.PI), 0d); assertTrue(0 == Vba.sgn(-0d)); assertTrue(0 == Vba.sgn(0d)); } public void testSin() { assertEquals(Vba.sqr(0.5d), Vba.sin(Math.PI / 4d), SMALL); } public void testSqr() { assertEquals(2d, Vba.sqr(4d), 0d); assertEquals(0d, Vba.sqr(0d), 0d); assertTrue(Double.isNaN(Vba.sqr(-4))); } public void testTan() { assertEquals(1d, Vba.tan(Math.PI / 4d), SMALL); } } // End VbaTest.java mondrian-3.4.1/testsrc/main/mondrian/olap/NullMemberRepresentationTest.java0000644000175000017500000000465511735330606027105 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho // All Rights Reserved. */ package mondrian.olap; import mondrian.rolap.RolapUtil; import mondrian.test.FoodMartTestCase; import java.io.IOException; /** * NullMemberRepresentationTest tests the null member * custom representation feature supported via * {@link mondrian.olap.MondrianProperties#NullMemberRepresentation} property. * @author ajogleka */ public class NullMemberRepresentationTest extends FoodMartTestCase { public void testClosingPeriodMemberLeafWithCustomNullRepresentation() { assertQueryReturns( "with member [Measures].[Foo] as ' ClosingPeriod().uniquename '\n" + "select {[Measures].[Foo]} on columns,\n" + " {[Time].[1997],\n" + " [Time].[1997].[Q2],\n" + " [Time].[1997].[Q2].[4]} on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Axis #2:\n" + "{[Time].[1997]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "Row #0: [Time].[1997].[Q4]\n" + "Row #1: [Time].[1997].[Q2].[6]\n" + "Row #2: [Time].[" + getNullMemberRepresentation() + "]\n" + ""); } public void testItemMemberWithCustomNullMemberRepresentation() throws IOException { assertExprReturns( "[Time].[1997].Children.Item(6).UniqueName", "[Time].[" + getNullMemberRepresentation() + "]"); assertExprReturns( "[Time].[1997].Children.Item(-1).UniqueName", "[Time].[" + getNullMemberRepresentation() + "]"); } public void testNullMemberWithCustomRepresentation() throws IOException { assertExprReturns( "[Gender].[All Gender].Parent.UniqueName", "[Gender].[" + getNullMemberRepresentation() + "]"); assertExprReturns( "[Gender].[All Gender].Parent.Name", getNullMemberRepresentation()); } private String getNullMemberRepresentation() { return RolapUtil.mdxNullLiteral(); } } // End NullMemberRepresentationTest.java mondrian-3.4.1/testsrc/main/mondrian/olap/QueryTest.java0000644000175000017500000000415311735330606023216 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1998-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. // // Shishir, 08 May, 2007 */ package mondrian.olap; import mondrian.server.Statement; import mondrian.test.FoodMartTestCase; import mondrian.test.TestContext; /** * Query test. */ public class QueryTest extends FoodMartTestCase { private QueryPart[] cellProps = { new CellProperty("[Value]"), new CellProperty("[Formatted_Value]"), new CellProperty("[Format_String]") }; private QueryAxis[] axes = new QueryAxis[0]; private Formula[] formulas = new Formula[0]; private Query queryWithCellProps; private Query queryWithoutCellProps; protected void setUp() throws Exception { super.setUp(); TestContext testContext = getTestContext(); ConnectionBase connection = (ConnectionBase) testContext.getConnection(); final Statement statement = connection.getInternalStatement(); try { queryWithCellProps = new Query( statement, formulas, axes, "Sales", null, cellProps, false); queryWithoutCellProps = new Query( statement, formulas, axes, "Sales", null, new QueryPart[0], false); } finally { statement.close(); } } protected void tearDown() throws Exception { super.tearDown(); queryWithCellProps = null; queryWithoutCellProps = null; } public void testHasCellPropertyWhenQueryHasCellProperties() { assertTrue(queryWithCellProps.hasCellProperty("Value")); assertFalse(queryWithCellProps.hasCellProperty("Language")); } public void testIsCellPropertyEmpty() { assertTrue(queryWithoutCellProps.isCellPropertyEmpty()); } } // End QueryTest.java mondrian-3.4.1/testsrc/main/mondrian/olap/UtilTestCase.java0000644000175000017500000015067111735330606023631 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.rolap.RolapUtil; import mondrian.util.*; import junit.framework.TestCase; import java.sql.Driver; import java.util.*; /** * Tests for methods in {@link mondrian.olap.Util} and, sometimes, classes in * the {@code mondrian.util} package. */ public class UtilTestCase extends TestCase { public UtilTestCase(String s) { super(s); } public void testParseConnectStringSimple() { // Simple connect string Util.PropertyList properties = Util.parseConnectString("foo=x;bar=y;foo=z"); assertEquals("y", properties.get("bar")); assertEquals("y", properties.get("BAR")); // get is case-insensitive assertNull(properties.get(" bar")); // get does not ignore spaces assertEquals("z", properties.get("foo")); // later occurrence overrides assertNull(properties.get("kipper")); assertEquals(2, properties.list.size()); assertEquals("foo=z; bar=y", properties.toString()); } public void testParseConnectStringComplex() { Util.PropertyList properties = Util.parseConnectString( "normalProp=value;" + "emptyValue=;" + " spaceBeforeProp=abc;" + " spaceBeforeAndAfterProp =def;" + " space in prop = foo bar ;" + "equalsInValue=foo=bar;" + "semiInProp;Name=value;" + " singleQuotedValue = " + "'single quoted value ending in space ' ;" + " doubleQuotedValue = " + "\"=double quoted value preceded by equals\" ;" + " singleQuotedValueWithSemi = 'one; two';" + " singleQuotedValueWithSpecials = " + "'one; two \"three''four=five'"); assertEquals(11, properties.list.size()); String value; value = properties.get("normalProp"); assertEquals("value", value); value = properties.get("emptyValue"); assertEquals("", value); // empty string, not null! value = properties.get("spaceBeforeProp"); assertEquals("abc", value); value = properties.get("spaceBeforeAndAfterProp"); assertEquals("def", value); value = properties.get("space in prop"); assertEquals(value, "foo bar"); value = properties.get("equalsInValue"); assertEquals("foo=bar", value); value = properties.get("semiInProp;Name"); assertEquals("value", value); value = properties.get("singleQuotedValue"); assertEquals("single quoted value ending in space ", value); value = properties.get("doubleQuotedValue"); assertEquals("=double quoted value preceded by equals", value); value = properties.get("singleQuotedValueWithSemi"); assertEquals(value, "one; two"); value = properties.get("singleQuotedValueWithSpecials"); assertEquals(value, "one; two \"three'four=five"); assertEquals( "normalProp=value;" + " emptyValue=;" + " spaceBeforeProp=abc;" + " spaceBeforeAndAfterProp=def;" + " space in prop=foo bar;" + " equalsInValue=foo=bar;" + " semiInProp;Name=value;" + " singleQuotedValue=single quoted value ending in space ;" + " doubleQuotedValue==double quoted value preceded by equals;" + " singleQuotedValueWithSemi='one; two';" + " singleQuotedValueWithSpecials='one; two \"three''four=five'", properties.toString()); } public void testConnectStringMore() { p("singleQuote=''''", "singleQuote", "'"); p("doubleQuote=\"\"\"\"", "doubleQuote", "\""); p("empty= ;foo=bar", "empty", ""); } /** * Test case for bug * MONDRIAN-397, "Connect string parser gives * StringIndexOutOfBoundsException instead of a meaningful error". */ public void testBugMondrian397() { Util.PropertyList properties; // ends in semi properties = Util.parseConnectString("foo=true; bar=xxx;"); assertEquals(2, properties.list.size()); // ends in semi+space properties = Util.parseConnectString("foo=true; bar=xxx; "); assertEquals(2, properties.list.size()); // ends in space properties = Util.parseConnectString(" "); assertEquals(0, properties.list.size()); // actual testcase for bug properties = Util.parseConnectString( "provider=mondrian; JdbcDrivers=org.hsqldb.jdbcDriver;" + "Jdbc=jdbc:hsqldb:./sql/sampledata;" + "Catalog=C:\\cygwin\\home\\src\\jfreereport\\engines\\classic" + "\\extensions-mondrian\\demo\\steelwheels.mondrian.xml;" + "JdbcUser=sa; JdbcPassword=; "); assertEquals(6, properties.list.size()); assertEquals("", properties.get("JdbcPassword")); } /** * Checks that connectString contains a property called * name, whose value is value. */ void p(String connectString, String name, String expectedValue) { Util.PropertyList list = Util.parseConnectString(connectString); String value = list.get(name); assertEquals(expectedValue, value); } public void testOleDbSpec() { p("Provider='MSDASQL'", "Provider", "MSDASQL"); p("Provider='MSDASQL.1'", "Provider", "MSDASQL.1"); if (false) { // If no Provider keyword is in the string, the OLE DB Provider for // ODBC (MSDASQL) is the default value. This provides backward // compatibility with ODBC connection strings. The ODBC connection // string in the following example can be passed in, and it will // successfully connect. p( "Driver={SQL Server};Server={localhost};Trusted_Connection={yes};" + "db={Northwind};", "Provider", "MSDASQL"); } // Specifying a Keyword // // To identify a keyword used after the Provider keyword, use the // property description of the OLE DB initialization property that you // want to set. For example, the property description of the standard // OLE DB initialization property DBPROP_INIT_LOCATION is // Location. Therefore, to include this property in a connection // string, use the keyword Location. p( "Provider='MSDASQL';Location='3Northwind'", "Location", "3Northwind"); // Keywords can contain any printable character except for the equal // sign (=). p( "Jet OLE DB:System Database=c:\\system.mda", "Jet OLE DB:System Database", "c:\\system.mda"); p( "Authentication;Info=Column 5", "Authentication;Info", "Column 5"); // If a keyword contains an equal sign (=), it must be preceded by an // additional equal sign to indicate that it is part of the keyword. p( "Verification==Security=True", "Verification=Security", "True"); // If multiple equal signs appear, each one must be preceded by an // additional equal sign. p("Many====One=Valid", "Many==One", "Valid"); p("TooMany===False", "TooMany=", "False"); // Setting Values That Use Reserved Characters // // To include values that contain a semicolon, single-quote character, // or double-quote character, the value must be enclosed in double // quotes. p( "ExtendedProperties=\"Integrated Security='SSPI';" + "Initial Catalog='Northwind'\"", "ExtendedProperties", "Integrated Security='SSPI';Initial Catalog='Northwind'"); // If the value contains both a semicolon and a double-quote character, // the value can be enclosed in single quotes. p( "ExtendedProperties='Integrated Security=\"SSPI\";" + "Databse=\"My Northwind DB\"'", "ExtendedProperties", "Integrated Security=\"SSPI\";Databse=\"My Northwind DB\""); // The single quote is also useful if the value begins with a // double-quote character. p( "DataSchema='\"MyCustTable\"'", "DataSchema", "\"MyCustTable\""); // Conversely, the double quote can be used if the value begins with a // single quote. p( "DataSchema=\"'MyOtherCustTable'\"", "DataSchema", "'MyOtherCustTable'"); // If the value contains both single-quote and double-quote characters, // the quote character used to enclose the value must be doubled each // time it occurs within the value. p( "NewRecordsCaption='\"Company''s \"new\" customer\"'", "NewRecordsCaption", "\"Company's \"new\" customer\""); p( "NewRecordsCaption=\"\"\"Company's \"\"new\"\" customer\"\"\"", "NewRecordsCaption", "\"Company's \"new\" customer\""); // Setting Values That Use Spaces // // Any leading or trailing spaces around a keyword or value are // ignored. However, spaces within a keyword or value are allowed and // recognized. p("MyKeyword=My Value", "MyKeyword", "My Value"); p("MyKeyword= My Value ;MyNextValue=Value", "MyKeyword", "My Value"); // To include preceding or trailing spaces in the value, the value must // be enclosed in either single quotes or double quotes. p("MyKeyword=' My Value '", "MyKeyword", " My Value "); p("MyKeyword=\" My Value \"", "MyKeyword", " My Value "); if (false) { // (Not supported.) // // If the keyword does not correspond to a standard OLE DB // initialization property (in which case the keyword value is // placed in the Extended Properties (DBPROP_INIT_PROVIDERSTRING) // property), the spaces around the value will be included in the // value even though quote marks are not used. This is to support // backward compatibility for ODBC connection strings. Trailing // spaces after keywords might also be preserved. } if (false) { // (Not supported) // // Returning Multiple Values // // For standard OLE DB initialization properties that can return // multiple values, such as the Mode property, each value returned // is separated with a pipe (|) character. The pipe character can // have spaces around it or not. // // Example Mode=Deny Write|Deny Read } // Listing Keywords Multiple Times // // If a specific keyword in a keyword=value pair occurs multiple times // in a connection string, the last occurrence listed is used in the // value set. p( "Provider='MSDASQL';Location='Northwind';" + "Cache Authentication='True';Prompt='Complete';" + "Location='Customers'", "Location", "Customers"); // One exception to the preceding rule is the Provider keyword. If this // keyword occurs multiple times in the string, the first occurrence is // used. p( "Provider='MSDASQL';Location='Northwind'; Provider='SQLOLEDB'", "Provider", "MSDASQL"); if (false) { // (Not supported) // // Setting the Window Handle Property // // To set the Window Handle (DBPROP_INIT_HWND) property in a // connection string, a long integer value is typically used. } } /** * Unit test for {@link Util#convertOlap4jConnectStringToNativeMondrian}. */ public void testConvertConnectString() { assertEquals( "Provider=Mondrian; Datasource=jdbc/SampleData;" + "Catalog=foodmart/FoodMart.xml;", Util.convertOlap4jConnectStringToNativeMondrian( "jdbc:mondrian:Datasource=jdbc/SampleData;" + "Catalog=foodmart/FoodMart.xml;")); } public void testQuoteMdxIdentifier() { assertEquals( "[San Francisco]", Util.quoteMdxIdentifier("San Francisco")); assertEquals( "[a [bracketed]] string]", Util.quoteMdxIdentifier("a [bracketed] string")); assertEquals( "[Store].[USA].[California]", Util.quoteMdxIdentifier( Arrays.asList( new Id.Segment("Store", Id.Quoting.QUOTED), new Id.Segment("USA", Id.Quoting.QUOTED), new Id.Segment("California", Id.Quoting.QUOTED)))); } public void testQuoteJava() { assertEquals( "\"San Francisco\"", Util.quoteJavaString("San Francisco")); assertEquals( "\"null\"", Util.quoteJavaString("null")); assertEquals( "null", Util.quoteJavaString(null)); assertEquals( "\"a\\\\b\\\"c\"", Util.quoteJavaString("a\\b\"c")); } public void testBufReplace() { // Replace with longer string. Search pattern at beginning & end. checkReplace("xoxox", "x", "yy", "yyoyyoyy"); // Replace with shorter string. checkReplace("xxoxxoxx", "xx", "z", "zozoz"); // Replace with empty string. checkReplace("xxoxxoxx", "xx", "", "oo"); // Replacement string contains search string. (A bad implementation // might loop!) checkReplace("xox", "x", "xx", "xxoxx"); // Replacement string combines with characters in the original to // match search string. checkReplace("cacab", "cab", "bb", "cabb"); // Seek string does not exist. checkReplace( "the quick brown fox", "coyote", "wolf", "the quick brown fox"); // Empty buffer. checkReplace("", "coyote", "wolf", ""); // Empty seek string. This is a bit mean! checkReplace("fox", "", "dog", "dogfdogodogxdog"); } private static void checkReplace( String original, String seek, String replace, String expected) { // Check whether the JDK does what we expect. (If it doesn't it's // probably a bug in the test, not the JDK.) assertEquals(expected, original.replaceAll(seek, replace)); // Check the StringBuilder version of replace. StringBuilder buf = new StringBuilder(original); StringBuilder buf2 = Util.replace(buf, 0, seek, replace); assertEquals(expected, buf.toString()); assertEquals(expected, buf2.toString()); assertTrue(buf == buf2); // Check the String version of replace. assertEquals(expected, Util.replace(original, seek, replace)); } public void testImplode() { List fooBar = Arrays.asList( new Id.Segment("foo", Id.Quoting.UNQUOTED), new Id.Segment("bar", Id.Quoting.UNQUOTED)); assertEquals("[foo].[bar]", Util.implode(fooBar)); List empty = Collections.emptyList(); assertEquals("", Util.implode(empty)); List nasty = Arrays.asList( new Id.Segment("string", Id.Quoting.UNQUOTED), new Id.Segment("with", Id.Quoting.UNQUOTED), new Id.Segment("a [bracket] in it", Id.Quoting.UNQUOTED)); assertEquals( "[string].[with].[a [bracket]] in it]", Util.implode(nasty)); } public void testParseIdentifier() { List strings = Util.parseIdentifier("[string].[with].[a [bracket]] in it]"); assertEquals(3, strings.size()); assertEquals("a [bracket] in it", strings.get(2).name); strings = Util.parseIdentifier("[Worklog].[All].[calendar-[LANGUAGE]].js]"); assertEquals(3, strings.size()); assertEquals("calendar-[LANGUAGE].js", strings.get(2).name); // allow spaces before, after and between strings = Util.parseIdentifier(" [foo] . [bar].[baz] "); assertEquals(3, strings.size()); assertEquals("foo", strings.get(0).name); // first segment not quoted strings = Util.parseIdentifier("Time.1997.[Q3]"); assertEquals(3, strings.size()); assertEquals("Time", strings.get(0).name); assertEquals("1997", strings.get(1).name); assertEquals("Q3", strings.get(2).name); // spaces ignored after unquoted segment strings = Util.parseIdentifier("[Time . Weekly ] . 1997 . [Q3]"); assertEquals(3, strings.size()); assertEquals("Time . Weekly ", strings.get(0).name); assertEquals("1997", strings.get(1).name); assertEquals("Q3", strings.get(2).name); // identifier ending in '.' is invalid try { strings = Util.parseIdentifier("[foo].[bar]."); fail("expected exception, got " + strings); } catch (IllegalArgumentException e) { assertEquals( "Expected identifier after '.', " + "in member identifier '[foo].[bar].'", e.getMessage()); } try { strings = Util.parseIdentifier("[foo].[bar"); fail("expected exception, got " + strings); } catch (IllegalArgumentException e) { assertEquals( "Expected ']', in member identifier '[foo].[bar'", e.getMessage()); } try { strings = Util.parseIdentifier("[Foo].[Bar], [Baz]"); fail("expected exception, got " + strings); } catch (IllegalArgumentException e) { assertEquals( "Invalid member identifier '[Foo].[Bar], [Baz]'", e.getMessage()); } } public void testReplaceProperties() { Map map = new HashMap(); map.put("foo", "bar"); map.put("empty", ""); map.put("null", null); map.put("foobarbaz", "bang!"); map.put("malformed${foo", "groovy"); assertEquals( "abarb", Util.replaceProperties("a${foo}b", map)); assertEquals( "twicebarbar", Util.replaceProperties("twice${foo}${foo}", map)); assertEquals( "bar at start", Util.replaceProperties("${foo} at start", map)); assertEquals( "xyz", Util.replaceProperties("x${empty}y${empty}${empty}z", map)); assertEquals( "x${nonexistent}bar", Util.replaceProperties("x${nonexistent}${foo}", map)); // malformed tokens are left as is assertEquals( "${malformedbarbar", Util.replaceProperties("${malformed${foo}${foo}", map)); // string can contain '$' assertEquals("x$foo", Util.replaceProperties("x$foo", map)); // property with empty name is always ignored -- even if it's in the map assertEquals("${}", Util.replaceProperties("${}", map)); map.put("", "v"); assertEquals("${}", Util.replaceProperties("${}", map)); // if a property's value is null, it's as if it doesn't exist assertEquals("${null}", Util.replaceProperties("${null}", map)); // nested properties are expanded, but not recursively assertEquals( "${foobarbaz}", Util.replaceProperties("${foo${foo}baz}", map)); } public void testWildcard() { assertEquals( ".\\QFoo\\E.|\\QBar\\E.*\\QBAZ\\E", Util.wildcardToRegexp( Arrays.asList("_Foo_", "Bar%BAZ"))); } public void testCamel() { assertEquals( "FOO_BAR", Util.camelToUpper("FooBar")); assertEquals( "FOO_BAR", Util.camelToUpper("fooBar")); assertEquals( "URL", Util.camelToUpper("URL")); assertEquals( "URLTO_CLICK_ON", Util.camelToUpper("URLtoClickOn")); assertEquals( "", Util.camelToUpper("")); } public void testParseCommaList() { assertEquals(new ArrayList(), Util.parseCommaList("")); assertEquals(Arrays.asList("x"), Util.parseCommaList("x")); assertEquals(Arrays.asList("x", "y"), Util.parseCommaList("x,y")); assertEquals(Arrays.asList("x,y"), Util.parseCommaList("x,,y")); assertEquals(Arrays.asList(",x", "y"), Util.parseCommaList(",,x,y")); assertEquals(Arrays.asList("x,", "y"), Util.parseCommaList("x,,,y")); assertEquals(Arrays.asList("x,,y"), Util.parseCommaList("x,,,,y")); // ignore trailing comma assertEquals(Arrays.asList("x", "y"), Util.parseCommaList("x,y,")); assertEquals(Arrays.asList("x", "y,"), Util.parseCommaList("x,y,,")); } public void testUnionIterator() { final List xyList = Arrays.asList("x", "y"); final List abcList = Arrays.asList("a", "b", "c"); final List emptyList = Collections.emptyList(); String total = ""; for (String s : UnionIterator.over(xyList, abcList)) { total += s + ";"; } assertEquals("x;y;a;b;c;", total); total = ""; for (String s : UnionIterator.over(xyList, emptyList)) { total += s + ";"; } assertEquals("x;y;", total); total = ""; for (String s : UnionIterator.over(emptyList, xyList, emptyList)) { total += s + ";"; } assertEquals("x;y;", total); total = ""; for (String s : UnionIterator.over()) { total += s + ";"; } assertEquals("", total); total = ""; UnionIterator unionIterator = new UnionIterator(xyList, abcList); while (unionIterator.hasNext()) { total += unionIterator.next() + ";"; } assertEquals("x;y;a;b;c;", total); if (Util.Retrowoven) { // Retrowoven code gives 'ArrayStoreException' when it encounters // 'Util.union()' applied to java.util.Iterator objects. return; } total = ""; for (String s : UnionIterator.over((Iterable) xyList, abcList)) { total += s + ";"; } assertEquals("x;y;a;b;c;", total); } public void testAreOccurrencesEqual() { assertFalse(Util.areOccurencesEqual(Collections.emptyList())); assertTrue(Util.areOccurencesEqual(Arrays.asList("x"))); assertTrue(Util.areOccurencesEqual(Arrays.asList("x", "x"))); assertFalse(Util.areOccurencesEqual(Arrays.asList("x", "y"))); assertFalse(Util.areOccurencesEqual(Arrays.asList("x", "y", "x"))); assertTrue(Util.areOccurencesEqual(Arrays.asList("x", "x", "x"))); assertFalse(Util.areOccurencesEqual(Arrays.asList("x", "x", "y", "z"))); } /** * Tests {@link mondrian.util.ServiceDiscovery}. */ public void testServiceDiscovery() { final ServiceDiscovery serviceDiscovery = ServiceDiscovery.forClass(Driver.class); final List> list = serviceDiscovery.getImplementor(); assertFalse(list.isEmpty()); // Check that discovered classes include AT LEAST: // JdbcOdbcDriver (in the JDK), // MondrianOlap4jDriver (in mondrian) and // XmlaOlap4jDriver (in olap4j.jar). List expectedClassNames = new ArrayList( Arrays.asList( // Usually on the list, but not guaranteed: // "sun.jdbc.odbc.JdbcOdbcDriver", "mondrian.olap4j.MondrianOlap4jDriver", "org.olap4j.driver.xmla.XmlaOlap4jDriver")); for (Class driverClass : list) { expectedClassNames.remove(driverClass.getName()); } if (Util.PreJdk15) { // JDK only discovers services from jars in JDK 1.5 and later. return; } assertTrue(expectedClassNames.toString(), expectedClassNames.isEmpty()); } /** * Unit test for {@link mondrian.util.ArrayStack}. */ public void testArrayStack() { final ArrayStack stack = new ArrayStack(); assertEquals(0, stack.size()); stack.add("a"); assertEquals(1, stack.size()); assertEquals("a", stack.peek()); stack.push("b"); assertEquals(2, stack.size()); assertEquals("b", stack.peek()); assertEquals("b", stack.pop()); assertEquals(1, stack.size()); stack.add(0, "z"); assertEquals("a", stack.peek()); assertEquals(2, stack.size()); stack.push(null); assertEquals(3, stack.size()); assertEquals(stack, Arrays.asList("z", "a", null)); String z = ""; for (String s : stack) { z += s; } assertEquals("zanull", z); stack.clear(); try { String x = stack.peek(); fail("expected error, got " + x); } catch (EmptyStackException e) { // ok } try { String x = stack.pop(); fail("expected error, got " + x); } catch (EmptyStackException e) { // ok } } /** * Tests {@link Util#appendArrays(Object[], Object[][])}. */ public void testAppendArrays() { String[] a0 = {"a", "b", "c"}; String[] a1 = {"foo", "bar"}; String[] empty = {}; final String[] strings1 = Util.appendArrays(a0, a1); assertEquals(5, strings1.length); assertEquals( Arrays.asList("a", "b", "c", "foo", "bar"), Arrays.asList(strings1)); final String[] strings2 = Util.appendArrays( empty, a0, empty, a1, empty); assertEquals( Arrays.asList("a", "b", "c", "foo", "bar"), Arrays.asList(strings2)); Number[] n0 = {Math.PI}; Integer[] i0 = {123, null, 45}; Float[] f0 = {0f}; final Number[] numbers = Util.appendArrays(n0, i0, f0); assertEquals(5, numbers.length); assertEquals( Arrays.asList((Number) Math.PI, 123, null, 45, 0f), Arrays.asList(numbers)); } public void testCanCast() { assertTrue(Util.canCast(Collections.EMPTY_LIST, Integer.class)); assertTrue(Util.canCast(Collections.EMPTY_LIST, String.class)); assertTrue(Util.canCast(Collections.EMPTY_SET, String.class)); assertTrue(Util.canCast(Arrays.asList(1, 2), Integer.class)); assertTrue(Util.canCast(Arrays.asList(1, 2), Number.class)); assertFalse(Util.canCast(Arrays.asList(1, 2), String.class)); assertTrue(Util.canCast(Arrays.asList(1, null, 2d), Number.class)); assertTrue( Util.canCast( new HashSet(Arrays.asList(1, null, 2d)), Number.class)); assertFalse(Util.canCast(Arrays.asList(1, null, 2d), Integer.class)); } /** * Unit test for {@link Util#parseLocale(String)} method. */ public void testParseLocale() { Locale[] locales = { Locale.CANADA, Locale.CANADA_FRENCH, Locale.getDefault(), Locale.US, Locale.TRADITIONAL_CHINESE, }; for (Locale locale : locales) { assertEquals(locale, Util.parseLocale(locale.toString())); } // Example locale names in Locale.toString() javadoc. String[] localeNames = { "en", "de_DE", "_GB", "en_US_WIN", "de__POSIX", "fr__MAC" }; for (String localeName : localeNames) { assertEquals(localeName, Util.parseLocale(localeName).toString()); } } /** * Unit test for {@link mondrian.util.LockBox}. */ public void testLockBox() { final LockBox box = new LockBox(); final String abc = "abc"; final String xy = "xy"; // Register an object. final LockBox.Entry abcEntry0 = box.register(abc); assertNotNull(abcEntry0); assertSame(abc, abcEntry0.getValue()); checkMonikerValid(abcEntry0.getMoniker()); // Register another object final LockBox.Entry xyEntry = box.register(xy); checkMonikerValid(xyEntry.getMoniker()); assertNotSame(abcEntry0.getMoniker(), xyEntry.getMoniker()); // Register first object again. Moniker is different. It is a different // registration. final LockBox.Entry abcEntry1 = box.register(abc); checkMonikerValid(abcEntry1.getMoniker()); assertFalse(abcEntry1.getMoniker().equals(abcEntry0.getMoniker())); assertSame(abcEntry1.getValue(), abc); // Retrieve. final LockBox.Entry abcEntry0b = box.get(abcEntry0.getMoniker()); assertNotNull(abcEntry0b); assertEquals(abcEntry0b.getMoniker(), abcEntry0.getMoniker()); assertSame(abcEntry0, abcEntry0b); assertNotSame(abcEntry0b, abcEntry1); assertTrue(!abcEntry0b.getMoniker().equals(abcEntry1.getMoniker())); // Arbitrary moniker retrieves nothing. (A random moniker might, // with very very small probability, happen to match that of one of // the two registered entries. However, I know that our generation // scheme never generates monikers starting with 'x'.) assertNull(box.get("xxx")); // Deregister. assertTrue(abcEntry0b.isRegistered()); final boolean b = box.deregister(abcEntry0b); assertTrue(b); assertFalse(abcEntry0b.isRegistered()); assertNull(box.get(abcEntry0.getMoniker())); // The other entry created by the same call to 'register' is also // deregistered. assertFalse(abcEntry0.isRegistered()); assertTrue(abcEntry1.isRegistered()); // Deregister again. final boolean b2 = box.deregister(abcEntry0b); assertFalse(b2); assertFalse(abcEntry0b.isRegistered()); assertFalse(abcEntry0.isRegistered()); assertNull(box.get(abcEntry0.getMoniker())); // Entry it no longer registered, therefore cannot get value. try { Object value = abcEntry0.getValue(); fail("expected exception, got " + value); } catch (RuntimeException e) { assertTrue( e.getMessage().startsWith("LockBox has no entry with moniker")); } assertNotNull(abcEntry0.getMoniker()); // Other registration of same object still works. final LockBox.Entry abcEntry1b = box.get(abcEntry1.getMoniker()); assertNotNull(abcEntry1b); assertSame(abcEntry1b, abcEntry1); assertSame(abcEntry1b.getValue(), abc); assertSame(abcEntry1.getValue(), abc); // Other entry still exists. final LockBox.Entry xyEntry2 = box.get(xyEntry.getMoniker()); assertNotNull(xyEntry2); assertSame(xyEntry2, xyEntry); assertSame(xyEntry2.getValue(), xy); assertSame(xyEntry.getValue(), xy); // Register again. Moniker is different. (Monikers are never recycled.) final LockBox.Entry abcEntry3 = box.register(abc); checkMonikerValid(abcEntry3.getMoniker()); assertFalse(abcEntry3.getMoniker().equals(abcEntry0.getMoniker())); assertFalse(abcEntry3.getMoniker().equals(abcEntry1.getMoniker())); assertFalse(abcEntry3.getMoniker().equals(abcEntry0b.getMoniker())); // Previous entry is no longer valid. assertTrue(abcEntry1.isRegistered()); assertFalse(abcEntry0.isRegistered()); } void checkMonikerValid(String moniker) { final String digits = "0123456789"; final String validChars = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "$_"; assertTrue(moniker.length() > 0); // all chars are valid for (int i = 0; i < moniker.length(); i++) { assertTrue(moniker, validChars.indexOf(moniker.charAt(i)) >= 0); } // does not start with digit assertFalse(moniker, digits.indexOf(moniker.charAt(0)) >= 0); } /** * Unit test that ensures that {@link mondrian.util.LockBox} can "forget" * entries whose key has been forgotten. */ public void testLockBoxFull() { final LockBox box = new LockBox(); // Generate large, unique strings for values by concatenating a base // of "xxx ... x" with a unique integer. StringBuilder builder = new StringBuilder(); final int prevLength = 10000; for (int i = 0; i < prevLength; i++) { builder.append("x"); } final int max = 1000000; LockBox.Entry[] entries = new LockBox.Entry[567]; for (int i = 0; i < max; i++) { builder.setLength(prevLength); builder.append(i); String value = builder.toString(); final LockBox.Entry entry = box.register(value); // Save most recent keys in a circular array to prevent GC. Older // keys are forgotten, therefore the entries can be removed from the // lock box. entries[i % entries.length] = entry; // Test one of the previous entries. assertNotNull(entries[Math.min(i, (i * 19) % 567)].getValue()); } } public void testCartesianProductList() { final CartesianProductList list = new CartesianProductList( Arrays.asList( Arrays.asList("a", "b"), Arrays.asList("1", "2", "3"))); assertEquals(6, list.size()); assertFalse(list.isEmpty()); checkCartesianListContents(list); assertEquals( "[[a, 1], [a, 2], [a, 3], [b, 1], [b, 2], [b, 3]]", list.toString()); // One element empty final CartesianProductList list2 = new CartesianProductList( Arrays.asList( Arrays.asList(), Arrays.asList("1", "2", "3"))); assertTrue(list2.isEmpty()); assertEquals("[]", list2.toString()); checkCartesianListContents(list2); // Other component empty final CartesianProductList list3 = new CartesianProductList( Arrays.asList( Arrays.asList("a", "b"), Arrays.asList())); assertTrue(list3.isEmpty()); assertEquals("[]", list3.toString()); checkCartesianListContents(list3); // Zeroary final CartesianProductList list4 = new CartesianProductList( Collections.>emptyList()); assertFalse(list4.isEmpty()); // assertEquals("[[]]", list4.toString()); checkCartesianListContents(list4); // 1-ary final CartesianProductList list5 = new CartesianProductList( Collections.singletonList( Arrays.asList("a", "b"))); assertEquals("[[a], [b]]", list5.toString()); checkCartesianListContents(list5); // 3-ary final CartesianProductList list6 = new CartesianProductList( Arrays.asList( Arrays.asList("a", "b", "c", "d"), Arrays.asList("1", "2"), Arrays.asList("x", "y", "z"))); assertEquals(24, list6.size()); // 4 * 2 * 3 assertFalse(list6.isEmpty()); assertEquals("[a, 1, x]", list6.get(0).toString()); assertEquals("[a, 1, y]", list6.get(1).toString()); assertEquals("[d, 2, z]", list6.get(23).toString()); checkCartesianListContents(list6); final Object[] strings = new Object[6]; list6.getIntoArray(1, strings); assertEquals( "[a, 1, y, null, null, null]", Arrays.asList(strings).toString()); CartesianProductList list7 = new CartesianProductList( Arrays.>asList( Arrays.asList( "1", Arrays.asList("2a", null, "2c"), "3"), Arrays.asList( "a", Arrays.asList("bb", "bbb"), "c", "d"))); list7.getIntoArray(1, strings); assertEquals( "[1, bb, bbb, null, null, null]", Arrays.asList(strings).toString()); list7.getIntoArray(5, strings); assertEquals( "[2a, null, 2c, bb, bbb, null]", Arrays.asList(strings).toString()); checkCartesianListContents(list7); } private void checkCartesianListContents(CartesianProductList list) { List> arrayList = new ArrayList>(); for (List ts : list) { arrayList.add(ts); } assertEquals(arrayList, list); } public void testFlatList() { final List flatAB = Util.flatList("a", "b"); final List arrayAB = Arrays.asList("a", "b"); assertEquals(flatAB, flatAB); assertEquals(flatAB, arrayAB); assertEquals(arrayAB, flatAB); assertEquals(arrayAB.hashCode(), flatAB.hashCode()); final List flatABC = Util.flatList("a", "b", "c"); final List arrayABC = Arrays.asList("a", "b", "c"); assertEquals(flatABC, flatABC); assertEquals(flatABC, arrayABC); assertEquals(arrayABC, flatABC); assertEquals(arrayABC.hashCode(), flatABC.hashCode()); assertEquals("[a, b, c]", flatABC.toString()); assertEquals("[a, b]", flatAB.toString()); final List arrayEmpty = Arrays.asList(); final List arrayA = Collections.singletonList("a"); // mixed 2 & 3 final List> notAB = Arrays.asList(arrayEmpty, arrayA, arrayABC, flatABC); for (List strings : notAB) { assertFalse(strings.equals(flatAB)); assertFalse(flatAB.equals(strings)); } final List> notABC = Arrays.asList(arrayEmpty, arrayA, arrayAB, flatAB); for (List strings : notABC) { assertFalse(strings.equals(flatABC)); assertFalse(flatABC.equals(strings)); } } /** * Unit test for {@link Composite#of(Iterable[])}. */ public void testCompositeIterable() { final Iterable beatles = Arrays.asList("john", "paul", "george", "ringo"); final Iterable stones = Arrays.asList("mick", "keef", "brian", "bill", "charlie"); final List empty = Collections.emptyList(); final StringBuilder buf = new StringBuilder(); for (String s : Composite.of(beatles, stones)) { buf.append(s).append(";"); } assertEquals( "john;paul;george;ringo;mick;keef;brian;bill;charlie;", buf.toString()); buf.setLength(0); for (String s : Composite.of(empty, stones)) { buf.append(s).append(";"); } assertEquals( "mick;keef;brian;bill;charlie;", buf.toString()); buf.setLength(0); for (String s : Composite.of(stones, empty)) { buf.append(s).append(";"); } assertEquals( "mick;keef;brian;bill;charlie;", buf.toString()); buf.setLength(0); for (String s : Composite.of(empty)) { buf.append(s).append(";"); } assertEquals( "", buf.toString()); buf.setLength(0); for (String s : Composite.of(empty, empty, beatles, empty, empty)) { buf.append(s).append(";"); } assertEquals( "john;paul;george;ringo;", buf.toString()); } /** * Unit test for {@link CombiningGenerator}. */ public void testCombiningGenerator() { assertEquals( 1, new CombiningGenerator(Collections.emptyList()) .size()); assertEquals( 1, CombiningGenerator.of(Collections.emptyList()) .size()); assertEquals( "[[]]", CombiningGenerator.of(Collections.emptyList()).toString()); assertEquals( "[[], [a]]", CombiningGenerator.of(Collections.singletonList("a")).toString()); assertEquals( "[[], [a], [b], [a, b]]", CombiningGenerator.of(Arrays.asList("a", "b")).toString()); assertEquals( "[[], [a], [b], [a, b], [c], [a, c], [b, c], [a, b, c]]", CombiningGenerator.of(Arrays.asList("a", "b", "c")).toString()); final List integerList = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8); int i = 0; for (List integers : CombiningGenerator.of(integerList)) { switch (i++) { case 0: assertTrue(integers.isEmpty()); break; case 1: assertEquals(Arrays.asList(0), integers); break; case 6: assertEquals(Arrays.asList(1, 2), integers); break; case 131: assertEquals(Arrays.asList(0, 1, 7), integers); break; } } assertEquals(512, i); // Check that can iterate over 2^20 (~ 1m) elements in reasonable time. i = 0; for (List xx : CombiningGenerator.of(Collections.nCopies(20, "x"))) { Util.discard(xx); i++; } assertEquals(1 << 20, i); } /** * Unit test for {@link ByteString}. */ public void testByteString() { final ByteString empty0 = new ByteString(new byte[]{}); final ByteString empty1 = new ByteString(new byte[]{}); assertTrue(empty0.equals(empty1)); assertEquals(empty0.hashCode(), empty1.hashCode()); assertEquals("", empty0.toString()); assertEquals(0, empty0.length()); assertEquals(0, empty0.compareTo(empty0)); assertEquals(0, empty0.compareTo(empty1)); final ByteString two = new ByteString(new byte[]{ (byte) 0xDE, (byte) 0xAD}); assertFalse(empty0.equals(two)); assertFalse(two.equals(empty0)); assertEquals("dead", two.toString()); assertEquals(2, two.length()); assertEquals(0, two.compareTo(two)); assertTrue(empty0.compareTo(two) < 0); assertTrue(two.compareTo(empty0) > 0); final ByteString three = new ByteString(new byte[]{ (byte) 0xDE, (byte) 0x02, (byte) 0xAD}); assertEquals(3, three.length()); assertEquals("de02ad", three.toString()); assertTrue(two.compareTo(three) < 0); assertTrue(three.compareTo(two) > 0); assertEquals(0x02, three.byteAt(1)); final HashSet set = new HashSet(); set.addAll(Arrays.asList(empty0, two, three, two, empty1, three)); assertEquals(3, set.size()); } /** * Unit test for {@link Util#binarySearch}. */ public void testBinarySearch() { final String[] abce = {"a", "b", "c", "e"}; assertEquals(0, Util.binarySearch(abce, 0, 4, "a")); assertEquals(1, Util.binarySearch(abce, 0, 4, "b")); assertEquals(1, Util.binarySearch(abce, 1, 4, "b")); assertEquals(-4, Util.binarySearch(abce, 0, 4, "d")); assertEquals(-4, Util.binarySearch(abce, 1, 4, "d")); assertEquals(-4, Util.binarySearch(abce, 2, 4, "d")); assertEquals(-4, Util.binarySearch(abce, 2, 3, "d")); assertEquals(-4, Util.binarySearch(abce, 2, 3, "e")); assertEquals(-4, Util.binarySearch(abce, 2, 3, "f")); assertEquals(-5, Util.binarySearch(abce, 0, 4, "f")); assertEquals(-5, Util.binarySearch(abce, 2, 4, "f")); } /** * Unit test for {@link mondrian.util.ArraySortedSet}. */ public void testArraySortedSet() { String[] abce = {"a", "b", "c", "e"}; final SortedSet abceSet = new ArraySortedSet(abce); // test size, isEmpty, contains assertEquals(4, abceSet.size()); assertFalse(abceSet.isEmpty()); assertEquals("a", abceSet.first()); assertEquals("e", abceSet.last()); assertTrue(abceSet.contains("a")); assertFalse(abceSet.contains("aa")); assertFalse(abceSet.contains("z")); assertFalse(abceSet.contains(null)); checkToString("[a, b, c, e]", abceSet); // test iterator String z = ""; for (String s : abceSet) { z += s + ";"; } assertEquals("a;b;c;e;", z); // empty set String[] empty = {}; final SortedSet emptySet = new ArraySortedSet(empty); int n = 0; for (String s : emptySet) { ++n; } assertEquals(0, n); assertEquals(0, emptySet.size()); assertTrue(emptySet.isEmpty()); try { String s = emptySet.first(); fail("expected exception, got " + s); } catch (NoSuchElementException e) { // ok } try { String s = emptySet.last(); fail("expected exception, got " + s); } catch (NoSuchElementException e) { // ok } assertFalse(emptySet.contains("a")); assertFalse(emptySet.contains("aa")); assertFalse(emptySet.contains("z")); checkToString("[]", emptySet); // same hashCode etc. as similar hashset final HashSet abcHashset = new HashSet(); abcHashset.addAll(Arrays.asList(abce)); assertEquals(abcHashset, abceSet); assertEquals(abceSet, abcHashset); assertEquals(abceSet.hashCode(), abcHashset.hashCode()); // subset to end final Set subsetEnd = new ArraySortedSet(abce, 1, 4); checkToString("[b, c, e]", subsetEnd); assertEquals(3, subsetEnd.size()); assertFalse(subsetEnd.isEmpty()); assertTrue(subsetEnd.contains("c")); assertFalse(subsetEnd.contains("a")); assertFalse(subsetEnd.contains("z")); // subset from start final Set subsetStart = new ArraySortedSet(abce, 0, 2); checkToString("[a, b]", subsetStart); assertEquals(2, subsetStart.size()); assertFalse(subsetStart.isEmpty()); assertTrue(subsetStart.contains("a")); assertFalse(subsetStart.contains("c")); // subset from neither start or end final Set subset = new ArraySortedSet(abce, 1, 2); checkToString("[b]", subset); assertEquals(1, subset.size()); assertFalse(subset.isEmpty()); assertTrue(subset.contains("b")); assertFalse(subset.contains("a")); assertFalse(subset.contains("e")); // empty subset final Set subsetEmpty = new ArraySortedSet(abce, 1, 1); checkToString("[]", subsetEmpty); assertEquals(0, subsetEmpty.size()); assertTrue(subsetEmpty.isEmpty()); assertFalse(subsetEmpty.contains("e")); // subsets based on elements, not ordinals assertEquals(abceSet.subSet("a", "c"), subsetStart); assertEquals("[a, b, c]", abceSet.subSet("a", "d").toString()); assertFalse(abceSet.subSet("a", "e").equals(subsetStart)); assertFalse(abceSet.subSet("b", "c").equals(subsetStart)); assertEquals("[c, e]", abceSet.subSet("c", "z").toString()); assertEquals("[e]", abceSet.subSet("d", "z").toString()); assertFalse(abceSet.subSet("e", "c").equals(subsetStart)); assertEquals("[]", abceSet.subSet("e", "c").toString()); assertFalse(abceSet.subSet("z", "c").equals(subsetStart)); assertEquals("[]", abceSet.subSet("z", "c").toString()); // merge final ArraySortedSet ar1 = new ArraySortedSet(abce); final ArraySortedSet ar2 = new ArraySortedSet( new String[] {"d"}); final ArraySortedSet ar3 = new ArraySortedSet( new String[] {"b", "c"}); checkToString("[a, b, c, e]", ar1); checkToString("[d]", ar2); checkToString("[b, c]", ar3); checkToString("[a, b, c, d, e]", ar1.merge(ar2)); checkToString("[a, b, c, e]", ar1.merge(ar3)); } private void checkToString(String expected, Set set) { assertEquals(expected, set.toString()); final List list = new ArrayList(); list.addAll(set); assertEquals(expected, list.toString()); list.clear(); for (String s : set) { list.add(s); } assertEquals(expected, list.toString()); } public void testIntersectSortedSet() { final ArraySortedSet ace = new ArraySortedSet(new String[]{ "a", "c", "e"}); final ArraySortedSet cd = new ArraySortedSet(new String[]{ "c", "d"}); final ArraySortedSet bdf = new ArraySortedSet(new String[]{ "b", "d", "f"}); final ArraySortedSet bde = new ArraySortedSet(new String[]{ "b", "d", "e"}); final ArraySortedSet empty = new ArraySortedSet(new String[]{}); checkToString("[a, c, e]", Util.intersect(ace, ace)); checkToString("[c]", Util.intersect(ace, cd)); checkToString("[]", Util.intersect(ace, empty)); checkToString("[]", Util.intersect(empty, ace)); checkToString("[]", Util.intersect(empty, empty)); checkToString("[]", Util.intersect(ace, bdf)); checkToString("[e]", Util.intersect(ace, bde)); } /** * Unit test for {@link Triple}. */ public void testTriple() { if (Util.PreJdk15) { // Boolean is not Comparable until JDK 1.5. Triple works, but this // test does not. return; } final Triple triple0 = Triple.of(5, "foo", true); final Triple triple1 = Triple.of(5, "foo", false); final Triple triple2 = Triple.of(5, "foo", true); final Triple triple3 = Triple.of(null, "foo", true); assertEquals(triple0, triple0); assertFalse(triple0.equals(triple1)); assertFalse(triple1.equals(triple0)); assertFalse(triple0.hashCode() == triple1.hashCode()); assertEquals(triple0, triple2); assertEquals(triple0.hashCode(), triple2.hashCode()); assertEquals(triple3, triple3); assertFalse(triple0.equals(triple3)); assertFalse(triple3.equals(triple0)); assertFalse(triple0.hashCode() == triple3.hashCode()); final SortedSet> set = new TreeSet>( Arrays.asList(triple0, triple1, triple2, triple3, triple1)); assertEquals(3, set.size()); assertEquals( "[, <5, foo, false>, <5, foo, true>]", set.toString()); assertEquals("<5, foo, true>", triple0.toString()); assertEquals("<5, foo, false>", triple1.toString()); assertEquals("<5, foo, true>", triple2.toString()); assertEquals("", triple3.toString()); } public void testRolapUtilComparator() throws Exception { final Comparable[] compArray = new Comparable[] { "1", "2", "3", "4" }; // Will throw a ClassCastException if it fails. Util.binarySearch( compArray, 0, compArray.length, RolapUtil.sqlNullValue); } } // End UtilTestCase.java mondrian-3.4.1/testsrc/main/mondrian/olap/CustomizedParserTest.java0000644000175000017500000003020611735330606025412 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.olap.fun.CustomizedFunctionTable; import mondrian.olap.fun.ParenthesesFunDef; import mondrian.server.Statement; import mondrian.test.FoodMartTestCase; import java.util.HashSet; import java.util.Set; /** * Tests a customized MDX Parser. * * @author Rushan Chen */ public class CustomizedParserTest extends FoodMartTestCase { public CustomizedParserTest(String name) { super(name); } CustomizedFunctionTable getCustomizedFunctionTable(Set funNameSet) { Set specialFunctions = new HashSet(); specialFunctions.add(new ParenthesesFunDef(Category.Numeric)); CustomizedFunctionTable cftab = new CustomizedFunctionTable(funNameSet, specialFunctions); cftab.init(); return cftab; } private String wrapExpr(String expr) { return "with member [Measures].[Foo] as " + expr + "\n select from [Sales]"; } private void checkErrorMsg(Throwable e, String expectedErrorMsg) { while (e.getCause() != null && !e.getCause().equals(e)) { e = e.getCause(); } String actualMsg = e.getMessage(); assertEquals(expectedErrorMsg, actualMsg); } private Query getParsedQueryForExpr( CustomizedFunctionTable cftab, String expr, boolean strictValidation) { String mdx = wrapExpr(expr); final ConnectionBase connectionBase = (ConnectionBase) getConnection(); final Statement statement = connectionBase.getInternalStatement(); try { return (Query) connectionBase.parseStatement( statement, mdx, cftab, strictValidation); } finally { statement.close(); } } private Query getParsedQueryForExpr( CustomizedFunctionTable cftab, String expr) { return getParsedQueryForExpr(cftab, expr, false); } public void testAddition() { Set functionNameSet = new HashSet(); functionNameSet.add("+"); CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet); try { Query q = getParsedQueryForExpr( cftab, "([Measures].[Store Cost] + [Measures].[Unit Sales])"); q.resolve(q.createValidator(cftab, true)); } catch (Throwable e) { fail(e.getMessage()); } } public void testSubtraction() { Set functionNameSet = new HashSet(); functionNameSet.add("-"); CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet); try { Query q = getParsedQueryForExpr( cftab, "([Measures].[Store Cost] - [Measures].[Unit Sales])"); q.resolve(q.createValidator(cftab, true)); } catch (Throwable e) { fail(e.getMessage()); } } public void testSingleMultiplication() { Set functionNameSet = new HashSet(); functionNameSet.add("*"); CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet); try { Query q = getParsedQueryForExpr( cftab, "[Measures].[Store Cost] * [Measures].[Unit Sales]"); q.resolve(q.createValidator(cftab, true)); } catch (Throwable e) { fail(e.getMessage()); } } public void testMultipleMultiplication() { Set functionNameSet = new HashSet(); functionNameSet.add("*"); CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet); try { Query q = getParsedQueryForExpr( cftab, "([Measures].[Store Cost] * [Measures].[Unit Sales] * [Measures].[Store Sales])"); q.resolve(q.createValidator(cftab, true)); } catch (Throwable e) { fail(e.getMessage()); } } public void testLiterals() { Set functionNameSet = new HashSet(); functionNameSet.add("+"); CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet); try { Query q = getParsedQueryForExpr( cftab, "([Measures].[Store Cost] + 10)"); q.resolve(q.createValidator(cftab, true)); } catch (Throwable e) { fail(e.getMessage()); } } public void testMissingObjectFail() { Set functionNameSet = new HashSet(); functionNameSet.add("+"); CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet); try { Query q = getParsedQueryForExpr( cftab, "'[Measures].[Store Cost] + [Measures].[Unit Salese]'"); q.resolve(q.createValidator(cftab, true)); // Shouldn't reach here fail("Expected error did not occur."); } catch (Throwable e) { checkErrorMsg( e, "Mondrian Error:MDX object '[Measures].[Unit Salese]' not found in cube 'Sales'"); } } public void testMissingObjectFailWithStrict() { testMissingObject(true); } public void testMissingObjectSucceedWithoutStrict() { testMissingObject(false); } private void testMissingObject(boolean strictValidation) { Set functionNameSet = new HashSet(); functionNameSet.add("+"); CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet); MondrianProperties properties = MondrianProperties.instance(); propSaver.set( properties.IgnoreInvalidMembers, true); propSaver.set( properties.IgnoreInvalidMembersDuringQuery, true); try { Query q = getParsedQueryForExpr( cftab, "'[Measures].[Store Cost] + [Measures].[Unit Salese]'", strictValidation); q.resolve(q.createValidator(cftab, true)); // Shouldn't reach here if strictValidation fail( "Expected error does not occur when strictValidation is set:" + strictValidation); } catch (Throwable e) { if (strictValidation) { checkErrorMsg( e, "Mondrian Error:MDX object '[Measures].[Unit Salese]' not found in cube 'Sales'"); } else { checkErrorMsg( e, "Expected error does not occur when strictValidation is set:" + strictValidation); } } } public void testMultiplicationFail() { Set functionNameSet = new HashSet(); functionNameSet.add("+"); CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet); try { Query q = getParsedQueryForExpr( cftab, "([Measures].[Store Cost] * [Measures].[Unit Sales])"); q.resolve(q.createValidator(cftab, true)); // Shouldn't reach here fail("Expected error did not occur."); } catch (Throwable e) { checkErrorMsg( e, "Mondrian Error:No function matches signature ' * '"); } } public void testMixingAttributesFail() { Set functionNameSet = new HashSet(); functionNameSet.add("+"); CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet); try { Query q = getParsedQueryForExpr( cftab, "([Measures].[Store Cost] + [Store].[Store Country])"); q.resolve(q.createValidator(cftab, true)); // Shouldn't reach here fail("Expected error did not occur."); } catch (Throwable e) { checkErrorMsg( e, "Mondrian Error:No function matches signature ' + '"); } } public void testCrossJoinFail() { Set functionNameSet = new HashSet(); functionNameSet.add("+"); functionNameSet.add("-"); functionNameSet.add("*"); functionNameSet.add("/"); CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet); try { Query q = getParsedQueryForExpr( cftab, "CrossJoin([Measures].[Store Cost], [Measures].[Unit Sales])"); q.resolve(q.createValidator(cftab, true)); // Shouldn't reach here fail("Expected error did not occur."); } catch (Throwable e) { checkErrorMsg( e, "Mondrian Error:Tuple contains more than one member of hierarchy '[Measures]'."); } } public void testMeasureSlicerFail() { Set functionNameSet = new HashSet(); functionNameSet.add("+"); functionNameSet.add("-"); functionNameSet.add("*"); functionNameSet.add("/"); CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet); try { Query q = getParsedQueryForExpr( cftab, "([Measures].[Store Cost], [Gender].[F])"); q.resolve(q.createValidator(cftab, true)); // Shouldn't reach here fail("Expected error did not occur."); } catch (Throwable e) { checkErrorMsg( e, "Mondrian Error:No function matches signature '(, )'"); } } public void testTupleFail() { Set functionNameSet = new HashSet(); functionNameSet.add("+"); functionNameSet.add("-"); functionNameSet.add("*"); functionNameSet.add("/"); CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet); try { Query q = getParsedQueryForExpr( cftab, "([Store].[USA], [Gender].[F])"); q.resolve(q.createValidator(cftab, true)); // Shouldn't reach here fail("Expected error did not occur."); } catch (Throwable e) { checkErrorMsg( e, "Mondrian Error:No function matches signature '(, )'"); } } /** * Mondrian is not strict about referencing a dimension member in calculated * measures. * *

The following expression passes parsing and validation. * Its computation is strange: the result is as if the measure is defined as * ([Measures].[Store Cost] + [Measures].[Store Cost]) */ public void testMixingMemberLimitation() { Set functionNameSet = new HashSet(); functionNameSet.add("+"); CustomizedFunctionTable cftab = getCustomizedFunctionTable(functionNameSet); try { Query q = getParsedQueryForExpr( cftab, "([Measures].[Store Cost] + [Store].[USA])"); q.resolve(q.createValidator(cftab, true)); // Shouldn't reach here fail("Expected error did not occur."); } catch (Throwable e) { checkErrorMsg(e, "Expected error did not occur."); } } } // End CustomizedParserTest.java mondrian-3.4.1/testsrc/main/mondrian/olap/type/0000755000175000017500000000000011735330606021364 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/olap/type/TypeTest.java0000644000175000017500000003010011735330606024002 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2008-2012 Pentaho // All Rights Reserved. */ package mondrian.olap.type; import mondrian.olap.*; import mondrian.olap.fun.Resolver; import mondrian.test.TestContext; import junit.framework.TestCase; import java.util.ArrayList; import java.util.List; /** * Unit test for mondrian type facility. * * @author jhyde * @since Jan 17, 2008 */ public class TypeTest extends TestCase { public void testConversions() { final Connection connection = getTestContext().getConnection(); Cube salesCube = getCubeWithName("Sales", connection.getSchema().getCubes()); assertTrue(salesCube != null); Dimension customersDimension = null; for (Dimension dimension : salesCube.getDimensions()) { if (dimension.getName().equals("Customers")) { customersDimension = dimension; } } assertTrue(customersDimension != null); Hierarchy hierarchy = customersDimension.getHierarchy(); Member member = hierarchy.getDefaultMember(); Level level = member.getLevel(); Type memberType = new MemberType( customersDimension, hierarchy, level, member); final LevelType levelType = new LevelType(customersDimension, hierarchy, level); final HierarchyType hierarchyType = new HierarchyType(customersDimension, hierarchy); final DimensionType dimensionType = new DimensionType(customersDimension); final StringType stringType = new StringType(); final ScalarType scalarType = new ScalarType(); final NumericType numericType = new NumericType(); final DateTimeType dateTimeType = new DateTimeType(); final DecimalType decimalType = new DecimalType(10, 2); final DecimalType integerType = new DecimalType(7, 0); final NullType nullType = new NullType(); final MemberType unknownMemberType = MemberType.Unknown; final TupleType tupleType = new TupleType( new Type[] {memberType, unknownMemberType}); final SetType tupleSetType = new SetType(tupleType); final SetType setType = new SetType(memberType); final LevelType unknownLevelType = LevelType.Unknown; final HierarchyType unknownHierarchyType = HierarchyType.Unknown; final DimensionType unknownDimensionType = DimensionType.Unknown; final BooleanType booleanType = new BooleanType(); Type[] types = { memberType, levelType, hierarchyType, dimensionType, numericType, dateTimeType, decimalType, integerType, scalarType, nullType, stringType, booleanType, tupleType, tupleSetType, setType, unknownDimensionType, unknownHierarchyType, unknownLevelType, unknownMemberType }; for (Type type : types) { // Check that each type is assignable to itself. final String desc = type.toString() + ":" + type.getClass(); assertEquals(desc, type, type.computeCommonType(type, null)); int[] conversionCount = {0}; assertEquals( desc, type, type.computeCommonType(type, conversionCount)); assertEquals(0, conversionCount[0]); // Check that each scalar type is assignable to nullable with zero // conversions. if (type instanceof ScalarType) { assertEquals(type, type.computeCommonType(nullType, null)); assertEquals( type, type.computeCommonType(nullType, conversionCount)); assertEquals(0, conversionCount[0]); } } for (Type fromType : types) { for (Type toType : types) { Type type = fromType.computeCommonType(toType, null); Type type2 = toType.computeCommonType(fromType, null); final String desc = "symmetric, from " + fromType + ", to " + toType; assertEquals(desc, type, type2); int[] conversionCount = {0}; int[] conversionCount2 = {0}; type = fromType.computeCommonType(toType, conversionCount); type2 = toType.computeCommonType(fromType, conversionCount2); if (conversionCount[0] == 0 && conversionCount2[0] == 0) { assertEquals(desc, type, type2); } final int toCategory = TypeUtil.typeToCategory(toType); final List conversions = new ArrayList(); final boolean canConvert = TypeUtil.canConvert( 0, fromType, toCategory, conversions); if (canConvert && conversions.size() == 0 && type == null) { if (!(fromType == memberType && toType == tupleType || fromType == tupleSetType && toType == setType || fromType == setType && toType == tupleSetType)) { fail( "can convert from " + fromType + " to " + toType + ", but their most general type is null"); } } if (!canConvert && type != null && type.equals(toType)) { fail( "cannot convert from " + fromType + " to " + toType + ", but they have a most general type " + type); } } } } protected TestContext getTestContext() { return TestContext.instance(); } public void testCommonTypeWhenSetTypeHavingMemberTypeAndTupleType() { MemberType measureMemberType = getMemberTypeHavingMeasureInIt(getUnitSalesMeasure()); MemberType genderMemberType = getMemberTypeHavingMaleChild(getMaleChild()); MemberType storeMemberType = getStoreMemberType(getStoreChild()); TupleType tupleType = new TupleType( new Type[] {storeMemberType, genderMemberType}); SetType setTypeWithMember = new SetType(measureMemberType); SetType setTypeWithTuple = new SetType(tupleType); Type type1 = setTypeWithMember.computeCommonType(setTypeWithTuple, null); assertNotNull(type1); assertTrue(((SetType) type1).getElementType() instanceof TupleType); Type type2 = setTypeWithTuple.computeCommonType(setTypeWithMember, null); assertNotNull(type2); assertTrue(((SetType) type2).getElementType() instanceof TupleType); assertEquals(type1, type2); } public void testCommonTypeOfMemberandTupleTypeIsTupleType() { MemberType measureMemberType = getMemberTypeHavingMeasureInIt(getUnitSalesMeasure()); MemberType genderMemberType = getMemberTypeHavingMaleChild(getMaleChild()); MemberType storeMemberType = getStoreMemberType(getStoreChild()); TupleType tupleType = new TupleType( new Type[] {storeMemberType, genderMemberType}); Type type1 = measureMemberType.computeCommonType(tupleType, null); assertNotNull(type1); assertTrue(type1 instanceof TupleType); Type type2 = tupleType.computeCommonType(measureMemberType, null); assertNotNull(type2); assertTrue(type2 instanceof TupleType); assertEquals(type1, type2); } public void testCommonTypeBetweenTuplesOfDifferentSizesIsATupleType() { MemberType measureMemberType = getMemberTypeHavingMeasureInIt(getUnitSalesMeasure()); MemberType genderMemberType = getMemberTypeHavingMaleChild(getMaleChild()); MemberType storeMemberType = getStoreMemberType(getStoreChild()); TupleType tupleTypeLarger = new TupleType( new Type[] {storeMemberType, genderMemberType, measureMemberType}); TupleType tupleTypeSmaller = new TupleType( new Type[] {storeMemberType, genderMemberType}); Type type1 = tupleTypeSmaller.computeCommonType(tupleTypeLarger, null); assertNotNull(type1); assertTrue(type1 instanceof TupleType); assertTrue(((TupleType) type1).elementTypes[0] instanceof MemberType); assertTrue(((TupleType) type1).elementTypes[1] instanceof MemberType); assertTrue(((TupleType) type1).elementTypes[2] instanceof ScalarType); Type type2 = tupleTypeLarger.computeCommonType(tupleTypeSmaller, null); assertNotNull(type2); assertTrue(type2 instanceof TupleType); assertTrue(((TupleType) type2).elementTypes[0] instanceof MemberType); assertTrue(((TupleType) type2).elementTypes[1] instanceof MemberType); assertTrue(((TupleType) type2).elementTypes[2] instanceof ScalarType); assertEquals(type1, type2); } private MemberType getStoreMemberType(Member storeChild) { return new MemberType( storeChild.getDimension(), storeChild.getDimension().getHierarchy(), storeChild.getLevel(), storeChild); } private Member getStoreChild() { List storeParts = new ArrayList(); storeParts.add(new Id.Segment("Store", Id.Quoting.UNQUOTED)); storeParts.add(new Id.Segment("All Stores", Id.Quoting.UNQUOTED)); storeParts.add(new Id.Segment("USA", Id.Quoting.UNQUOTED)); storeParts.add(new Id.Segment("CA", Id.Quoting.UNQUOTED)); return getSalesCubeSchemaReader().getMemberByUniqueName( storeParts, false); } private MemberType getMemberTypeHavingMaleChild(Member maleChild) { return new MemberType( maleChild.getDimension(), maleChild.getDimension().getHierarchy(), maleChild.getLevel(), maleChild); } private MemberType getMemberTypeHavingMeasureInIt(Member unitSalesMeasure) { return new MemberType( unitSalesMeasure.getDimension(), unitSalesMeasure.getDimension().getHierarchy(), unitSalesMeasure.getDimension().getHierarchy().getLevels()[0], unitSalesMeasure); } private Member getMaleChild() { List genderParts = new ArrayList(); genderParts.add(new Id.Segment("Gender", Id.Quoting.UNQUOTED)); genderParts.add(new Id.Segment("M", Id.Quoting.UNQUOTED)); return getSalesCubeSchemaReader().getMemberByUniqueName( genderParts, false); } private Member getUnitSalesMeasure() { List measureParts = new ArrayList(); measureParts.add(new Id.Segment("Measures", Id.Quoting.UNQUOTED)); measureParts.add(new Id.Segment("Unit Sales", Id.Quoting.UNQUOTED)); return getSalesCubeSchemaReader().getMemberByUniqueName( measureParts, false); } private SchemaReader getSalesCubeSchemaReader() { final Cube salesCube = getCubeWithName( "Sales", getSchemaReader().getCubes()); return salesCube.getSchemaReader( getTestContext().getConnection().getRole()).withLocus(); } private SchemaReader getSchemaReader() { return getTestContext().getConnection().getSchemaReader().withLocus(); } private Cube getCubeWithName(String cubeName, Cube[] cubes) { Cube resultCube = null; for (Cube cube : cubes) { if (cubeName.equals(cube.getName())) { resultCube = cube; break; } } return resultCube; } } // End TypeTest.java mondrian-3.4.1/testsrc/main/mondrian/olap/ParserTest.java0000644000175000017500000010603011735330606023342 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.mdx.QueryPrintWriter; import mondrian.mdx.UnresolvedFunCall; import mondrian.olap.fun.BuiltinFunTable; import mondrian.parser.JavaccParserValidatorImpl; import mondrian.parser.MdxParserValidator; import mondrian.server.Statement; import mondrian.test.FoodMartTestCase; import mondrian.test.TestContext; import mondrian.util.Bug; import java.io.PrintWriter; import java.io.StringWriter; import java.util.List; /** * Tests the MDX parser. * * @author gjohnson */ public class ParserTest extends FoodMartTestCase { public ParserTest(String name) { super(name); } static final BuiltinFunTable funTable = BuiltinFunTable.instance(); public void testAxisParsing() throws Exception { checkAxisAllWays(0, "COLUMNS"); checkAxisAllWays(1, "ROWS"); checkAxisAllWays(2, "PAGES"); checkAxisAllWays(3, "CHAPTERS"); checkAxisAllWays(4, "SECTIONS"); } private void checkAxisAllWays(int axisOrdinal, String axisName) { checkAxis(axisOrdinal + "", axisName); checkAxis("AXIS(" + axisOrdinal + ")", axisName); checkAxis(axisName, axisName); } private void checkAxis( String s, String expectedName) { TestParser p = new TestParser(); String q = "select [member] on " + s + " from [cube]"; QueryPart query = p.parseInternal(null, q, false, funTable, false); assertNull("Test parser should return null query", query); QueryAxis[] axes = p.getAxes(); assertEquals("Number of axes must be 1", 1, axes.length); assertEquals( "Axis index name must be correct", expectedName, axes[0].getAxisName()); } public void testNegativeCases() throws Exception { assertParseQueryFails( "select [member] on axis(1.7) from sales", "Invalid axis specification. " + "The axis number must be non-negative integer, but it was 1.7."); assertParseQueryFails( "select [member] on axis(-1) from sales", "Syntax error at line"); // used to be an error, no longer assertParseQuery( "select [member] on axis(5) from sales", "select [member] ON AXIS(5)\n" + "from [sales]\n"); assertParseQueryFails( "select [member] on axes(0) from sales", "Syntax error at line"); assertParseQueryFails( "select [member] on 0.5 from sales", "Invalid axis specification. " + "The axis number must be non-negative integer, but it was 0.5."); assertParseQuery( "select [member] on 555 from sales", "select [member] ON AXIS(555)\n" + "from [sales]\n"); } /** * Test case for bug * MONDRIAN-831, "Failure parsing queries with member identifiers beginning * with '_' and not expressed between brackets". * *

According to the spec * * Identifiers (MDX), the first character of a regular identifier * must be a letter (per the unicode standard 2.0) or underscore. Subsequent * characters must be a letter, and underscore, or a digit. */ public void testScannerPunc() { assertParseQuery( "with member [Measures].__Foo as 1 + 2\n" + "select __Foo on 0\n" + "from _Bar_Baz", "with member [Measures].__Foo as '(1 + 2)'\n" + "select __Foo ON COLUMNS\n" + "from [_Bar_Baz]\n"); // # is not allowed assertParseQueryFails( "with member [Measures].#_Foo as 1 + 2\n" + "select __Foo on 0\n" + "from _Bar#Baz", "Unexpected character '#'"); assertParseQueryFails( "with member [Measures].Foo as 1 + 2\n" + "select Foo on 0\n" + "from Bar#Baz", "Unexpected character '#'"); // The spec doesn't allow $ but SSAS allows it so we allow it too. assertParseQuery( "with member [Measures].$Foo as 1 + 2\n" + "select $Foo on 0\n" + "from Bar$Baz", "with member [Measures].$Foo as '(1 + 2)'\n" + "select $Foo ON COLUMNS\n" + "from [Bar$Baz]\n"); // '$' is OK inside brackets too assertParseQuery( "select [measures].[$foo] on columns from sales", "select [measures].[$foo] ON COLUMNS\n" + "from [sales]\n"); // ']' unexcpected assertParseQueryFails( "select { Customers].Children } on columns from [Sales]", "Unexpected character ']'"); } public void testUnderscore() { } public void testUnparse() { checkUnparse( "with member [Measures].[Foo] as ' 123 '\n" + "select {[Measures].members} on columns,\n" + " CrossJoin([Product].members, {[Gender].Children}) on rows\n" + "from [Sales]\n" + "where [Marital Status].[S]", "with member [Measures].[Foo] as '123'\n" + "select {[Measures].Members} ON COLUMNS,\n" + " Crossjoin([Product].Members, {[Gender].Children}) ON ROWS\n" + "from [Sales]\n" + "where [Marital Status].[S]\n"); } private void checkUnparse(String queryString, final String expected) { final TestContext testContext = TestContext.instance(); final Query query = testContext.getConnection().parseQuery(queryString); String unparsedQueryString = Util.unparse(query); TestContext.assertEqualsVerbose(expected, unparsedQueryString); } private void assertParseQueryFails(String query, String expected) { checkFails(new TestParser(), query, expected); } private void assertParseExprFails(String expr, String expected) { checkFails(new TestParser(), wrapExpr(expr), expected); } private void checkFails(TestParser p, String query, String expected) { try { p.parseInternal(null, query, false, funTable, false); fail("Must return an error"); } catch (Exception e) { Exception nested = (Exception) e.getCause(); String message = nested.getMessage(); if (message.indexOf(expected) < 0) { fail( "Actual result [" + message + "] did not contain [" + expected + "]"); } } } public void testMultipleAxes() throws Exception { TestParser p = new TestParser(); String query = "select {[axis0mbr]} on axis(0), " + "{[axis1mbr]} on axis(1) from cube"; assertNull( "Test parser should return null query", p.parseInternal(null, query, false, funTable, false)); QueryAxis[] axes = p.getAxes(); assertEquals("Number of axes", 2, axes.length); assertEquals( "Axis index name must be correct", AxisOrdinal.StandardAxisOrdinal.forLogicalOrdinal(0).name(), axes[0].getAxisName()); assertEquals( "Axis index name must be correct", AxisOrdinal.StandardAxisOrdinal.forLogicalOrdinal(1).name(), axes[1].getAxisName()); query = "select {[axis1mbr]} on aXiS(1), " + "{[axis0mbr]} on AxIs(0) from cube"; assertNull( "Test parser should return null query", p.parseInternal(null, query, false, funTable, false)); assertEquals("Number of axes", 2, axes.length); assertEquals( "Axis index name must be correct", AxisOrdinal.StandardAxisOrdinal.forLogicalOrdinal(0).name(), axes[0].getAxisName()); assertEquals( "Axis index name must be correct", AxisOrdinal.StandardAxisOrdinal.forLogicalOrdinal(1).name(), axes[1].getAxisName()); Exp colsSetExpr = axes[0].getSet(); assertNotNull("Column tuples", colsSetExpr); UnresolvedFunCall fun = (UnresolvedFunCall)colsSetExpr; Id.Segment id = ((Id)(fun.getArgs()[0])).getElement(0); assertEquals("Correct member on axis", "axis0mbr", id.name); Exp rowsSetExpr = axes[1].getSet(); assertNotNull("Row tuples", rowsSetExpr); fun = (UnresolvedFunCall) rowsSetExpr; id = ((Id) (fun.getArgs()[0])).getElement(0); assertEquals("Correct member on axis", "axis1mbr", id.name); } /** * If an axis expression is a member, implicitly convert it to a set. */ public void testMemberOnAxis() { assertParseQuery( "select [Measures].[Sales Count] on 0, non empty [Store].[Store State].members on 1 from [Sales]", "select [Measures].[Sales Count] ON COLUMNS,\n" + " NON EMPTY [Store].[Store State].members ON ROWS\n" + "from [Sales]\n"); } public void testCaseTest() { assertParseQuery( "with member [Measures].[Foo] as " + " ' case when x = y then \"eq\" when x < y then \"lt\" else \"gt\" end '" + "select {[foo]} on axis(0) from cube", "with member [Measures].[Foo] as 'CASE WHEN (x = y) THEN \"eq\" WHEN (x < y) THEN \"lt\" ELSE \"gt\" END'\n" + "select {[foo]} ON COLUMNS\n" + "from [cube]\n"); } public void testCaseSwitch() { assertParseQuery( "with member [Measures].[Foo] as " + " ' case x when 1 then 2 when 3 then 4 else 5 end '" + "select {[foo]} on axis(0) from cube", "with member [Measures].[Foo] as 'CASE x WHEN 1 THEN 2 WHEN 3 THEN 4 ELSE 5 END'\n" + "select {[foo]} ON COLUMNS\n" + "from [cube]\n"); } /** * Test case for bug * MONDRIAN-306, "Parser should not require braces around range op in WITH * SET". */ public void testSetExpr() { assertParseQuery( "with set [Set1] as '[Product].[Drink]:[Product].[Food]' \n" + "select [Set1] on columns, {[Measures].defaultMember} on rows \n" + "from Sales", "with set [Set1] as '([Product].[Drink] : [Product].[Food])'\n" + "select [Set1] ON COLUMNS,\n" + " {[Measures].defaultMember} ON ROWS\n" + "from [Sales]\n"); // set expr in axes assertParseQuery( "select [Product].[Drink]:[Product].[Food] on columns,\n" + " {[Measures].defaultMember} on rows \n" + "from Sales", "select ([Product].[Drink] : [Product].[Food]) ON COLUMNS,\n" + " {[Measures].defaultMember} ON ROWS\n" + "from [Sales]\n"); } public void testDimensionProperties() { assertParseQuery( "select {[foo]} properties p1, p2 on columns from [cube]", "select {[foo]} DIMENSION PROPERTIES p1, p2 ON COLUMNS\n" + "from [cube]\n"); } public void testCellProperties() { assertParseQuery( "select {[foo]} on columns " + "from [cube] CELL PROPERTIES FORMATTED_VALUE", "select {[foo]} ON COLUMNS\n" + "from [cube]\n" + "[FORMATTED_VALUE]"); } public void testIsEmpty() { assertParseExpr( "[Measures].[Unit Sales] IS EMPTY", "([Measures].[Unit Sales] IS EMPTY)"); assertParseExpr( "[Measures].[Unit Sales] IS EMPTY AND 1 IS NULL", "(([Measures].[Unit Sales] IS EMPTY) AND (1 IS NULL))"); // FIXME: "NULL" should associate as "IS NULL" rather than "NULL + 56" // FIXME: Gives error at token '+' with new parser. assertParseExpr( "- x * 5 is empty is empty is null + 56", "(((((- x) * 5) IS EMPTY) IS EMPTY) IS (NULL + 56))", true); } public void testIs() { assertParseExpr( "[Measures].[Unit Sales] IS [Measures].[Unit Sales] " + "AND [Measures].[Unit Sales] IS NULL", "(([Measures].[Unit Sales] IS [Measures].[Unit Sales]) " + "AND ([Measures].[Unit Sales] IS NULL))"); } public void testIsNull() { assertParseExpr( "[Measures].[Unit Sales] IS NULL", "([Measures].[Unit Sales] IS NULL)"); assertParseExpr( "[Measures].[Unit Sales] IS NULL AND 1 <> 2", "(([Measures].[Unit Sales] IS NULL) AND (1 <> 2))"); assertParseExpr( "x is null or y is null and z = 5", "((x IS NULL) OR ((y IS NULL) AND (z = 5)))"); assertParseExpr( "(x is null) + 56 > 6", "((((x IS NULL)) + 56) > 6)"); // FIXME: Should be // "(((((x IS NULL) AND (a = b)) OR ((c = (d + 5))) IS NULL) + 5)" // FIXME: Gives error at token '+' with new parser. assertParseExpr( "x is null and a = b or c = d + 5 is null + 5", "(((x IS NULL) AND (a = b)) OR ((c = (d + 5)) IS (NULL + 5)))", true); } public void testNull() { assertParseExpr( "Filter({[Measures].[Foo]}, Iif(1 = 2, NULL, 'X'))", "Filter({[Measures].[Foo]}, Iif((1 = 2), NULL, \"X\"))"); } public void testCast() { assertParseExpr( "Cast([Measures].[Unit Sales] AS Numeric)", "CAST([Measures].[Unit Sales] AS Numeric)"); assertParseExpr( "Cast(1 + 2 AS String)", "CAST((1 + 2) AS String)"); } /** * Verifies that calculated measures made of several '*' operators * can resolve them correctly. */ public void testMultiplication() { Parser p = new Parser(); final String mdx = wrapExpr( "([Measures].[Unit Sales]" + " * [Measures].[Store Cost]" + " * [Measures].[Store Sales])"); final Statement statement = ((ConnectionBase) getConnection()).getInternalStatement(); try { final QueryPart query = p.parseInternal( new Parser.FactoryImpl(), statement, mdx, false, funTable, false); assertTrue(query instanceof Query); ((Query) query).resolve(); } finally { statement.close(); } } public void testBangFunction() { // Parser accepts ' [! ] *' as a function name, but ignores // all but last name. assertParseExpr("foo!bar!Exp(2.0)", "Exp(2.0)"); assertParseExpr("1 + VBA!Exp(2.0 + 3)", "(1 + Exp((2.0 + 3)))"); } public void testId() { assertParseExpr("foo", "foo"); assertParseExpr("fOo", "fOo"); assertParseExpr("[Foo].[Bar Baz]", "[Foo].[Bar Baz]"); assertParseExpr("[Foo].&[Bar]", "[Foo].&[Bar]"); } public void testCloneQuery() { Connection connection = TestContext.instance().getConnection(); Query query = connection.parseQuery( "select {[Measures].Members} on columns,\n" + " {[Store].Members} on rows\n" + "from [Sales]\n" + "where ([Gender].[M])"); Object queryClone = query.clone(); assertTrue(queryClone instanceof Query); assertEquals(query.toString(), queryClone.toString()); } /** * Tests parsing of numbers. */ public void testNumbers() { // Number: [+-] [ . ] [e [+-] ] assertParseExpr("2", "2"); // leading '-' is treated as an operator -- that's ok assertParseExpr("-3", "(- 3)"); // leading '+' is ignored -- that's ok assertParseExpr("+45", "45"); // space bad assertParseExprFails( "4 5", "Syntax error at line 1, column 35, token '5'"); assertParseExpr("3.14", "3.14"); assertParseExpr(".12345", "0.12345"); // lots of digits left and right of point assertParseExpr("31415926535.89793", "31415926535.89793"); assertParseExpr( "31415926535897.9314159265358979", "31415926535897.9314159265358979"); assertParseExpr("3.141592653589793", "3.141592653589793"); assertParseExpr( "-3141592653589793.14159265358979", "(- 3141592653589793.14159265358979)"); // exponents akimbo assertParseExpr("1e2", "100", true); assertParseExpr("1e2", Util.PreJdk15 ? "100" : "1E+2", false); assertParseExprFails( "1e2e3", "Syntax error at line 1, column 37, token 'e3'"); assertParseExpr("1.2e3", "1200", true); assertParseExpr("1.2e3", Util.PreJdk15 ? "1200" : "1.2E+3", false); assertParseExpr("-1.2345e3", "(- 1234.5)"); assertParseExprFails( "1.2e3.4", "Syntax error at line 1, column 39, token '0.4'"); assertParseExpr(".00234e0003", "2.34"); assertParseExpr( ".00234e-0067", Util.PreJdk15 ? "0.00000000000000000000000000000000000000000000000000000000" + "0000000000000234" : "2.34E-70"); } /** * Testcase for bug * MONDRIAN-272, "High precision number in MDX causes overflow". * The problem was that "5000001234" exceeded the precision of the int being * used to gather the mantissa. */ public void testLargePrecision() { // Now, a query with several numeric literals. This is the original // testcase for the bug. assertParseQuery( "with member [Measures].[Small Number] as '[Measures].[Store Sales] / 9000'\n" + "select\n" + "{[Measures].[Small Number]} on columns,\n" + "{Filter([Product].[Product Department].members, [Measures].[Small Number] >= 0.3\n" + "and [Measures].[Small Number] <= 0.5000001234)} on rows\n" + "from Sales\n" + "where ([Time].[1997].[Q2].[4])", "with member [Measures].[Small Number] as '([Measures].[Store Sales] / 9000)'\n" + "select {[Measures].[Small Number]} ON COLUMNS,\n" + " {Filter([Product].[Product Department].members, (([Measures].[Small Number] >= 0.3) AND ([Measures].[Small Number] <= 0.5000001234)))} ON ROWS\n" + "from [Sales]\n" + "where ([Time].[1997].[Q2].[4])\n"); } public void testEmptyExpr() { assertParseQuery( "SELECT NON EMPTY HIERARCHIZE({DrillDownLevelTop({[Product].[All Products]},\n" + "3, , [Measures].[Unit Sales])}) on columns from [Sales]", "select NON EMPTY HIERARCHIZE({DrillDownLevelTop({[Product].[All Products]}, 3, , [Measures].[Unit Sales])}) ON COLUMNS\n" + "from [Sales]\n"); } /** * Test case for bug * MONDRIAN-648, "AS operator has lower precedence than required by MDX * specification". * *

Currently that bug is not fixed. We give the AS operator low * precedence, so CAST works as it should but 'expr AS namedSet' does not. */ public void testAsPrecedence() { // low precedence operator (AND) in CAST. assertParseQuery( "select cast(a and b as string) on 0 from cube", "select CAST((a AND b) AS string) ON COLUMNS\n" + "from [cube]\n"); // medium precedence operator (:) in CAST assertParseQuery( "select cast(a : b as string) on 0 from cube", "select CAST((a : b) AS string) ON COLUMNS\n" + "from [cube]\n"); // high precedence operator (IS) in CAST assertParseQuery( "select cast(a is b as string) on 0 from cube", "select CAST((a IS b) AS string) ON COLUMNS\n" + "from [cube]\n"); // low precedence operator in axis expression. According to spec, 'AS' // has higher precedence than '*' but we give it lower. Bug. assertParseQuery( "select a * b as c on 0 from cube", Bug.BugMondrian648Fixed ? "select (a * (b AS c) ON COLUMNS\n" + "from [cube]\n" : "select ((a * b) AS c) ON COLUMNS\n" + "from [cube]\n"); if (Bug.BugMondrian648Fixed) { // Note that 'AS' has higher precedence than '*'. assertParseQuery( "select a * b as c * d on 0 from cube", "select (((a * b) AS c) * d) ON COLUMNS\n" + "from [cube]\n"); } else { // Bug. Even with MONDRIAN-648, Mondrian should parse this query. assertParseQueryFails( "select a * b as c * d on 0 from cube", "Syntax error at line 1, column 19, token '*'"); } // Spec says that ':' has a higher precedence than '*'. // Mondrian currently does it wrong. assertParseQuery( "select a : b * c : d on 0 from cube", Bug.BugMondrian648Fixed ? "select ((a : b) * (c : d)) ON COLUMNS\n" + "from [cube]\n" : "select ((a : (b * c)) : d) ON COLUMNS\n" + "from [cube]\n"); if (Bug.BugMondrian648Fixed) { // Note that 'AS' has higher precedence than ':', has higher // precedence than '*'. assertParseQuery( "select a : b as n * c : d as n2 as n3 on 0 from cube", "select (((a : b) as n) * ((c : d) AS n2) as n3) ON COLUMNS\n" + "from [cube]\n"); } else { // Bug. Even with MONDRIAN-648, Mondrian should parse this query. assertParseQueryFails( "select a : b as n * c : d as n2 as n3 on 0 from cube", "Syntax error at line 1, column 19, token '*'"); } } public void testDrillThrough() { assertParseQuery( "DRILLTHROUGH SELECT [Foo] on 0, [Bar] on 1 FROM [Cube]", "drillthrough\n" + "select [Foo] ON COLUMNS,\n" + " [Bar] ON ROWS\n" + "from [Cube]\n"); } public void testDrillThroughExtended1() { assertParseQuery( "DRILLTHROUGH MAXROWS 5 FIRSTROWSET 7\n" + "SELECT [Foo] on 0, [Bar] on 1 FROM [Cube]\n" + "RETURN [Xxx].[AAa]", "drillthrough maxrows 5 firstrowset 7\n" + "select [Foo] ON COLUMNS,\n" + " [Bar] ON ROWS\n" + "from [Cube]\n" + " return return [Xxx].[AAa]"); } public void testDrillThroughExtended() { assertParseQuery( "DRILLTHROUGH MAXROWS 5 FIRSTROWSET 7\n" + "SELECT [Foo] on 0, [Bar] on 1 FROM [Cube]\n" + "RETURN [Xxx].[AAa], [YYY]", "drillthrough maxrows 5 firstrowset 7\n" + "select [Foo] ON COLUMNS,\n" + " [Bar] ON ROWS\n" + "from [Cube]\n" + " return return [Xxx].[AAa], [YYY]"); } public void testDrillThroughExtended3() { assertParseQuery( "DRILLTHROUGH MAXROWS 5 FIRSTROWSET 7\n" + "SELECT [Foo] on 0, [Bar] on 1 FROM [Cube]\n" + "RETURN [Xxx].[AAa], [YYY], [zzz]", "drillthrough maxrows 5 firstrowset 7\n" + "select [Foo] ON COLUMNS,\n" + " [Bar] ON ROWS\n" + "from [Cube]\n" + " return return [Xxx].[AAa], [YYY], [zzz]"); } public void testExplain() { assertParseQuery( "explain plan for\n" + "with member [Mesaures].[Foo] as 1 + 3\n" + "select [Measures].[Unit Sales] on 0,\n" + " [Product].Children on 1\n" + "from [Sales]", "explain plan for\n" + "with member [Mesaures].[Foo] as '(1 + 3)'\n" + "select [Measures].[Unit Sales] ON COLUMNS,\n" + " [Product].Children ON ROWS\n" + "from [Sales]\n"); assertParseQuery( "explain plan for\n" + "drillthrough maxrows 5\n" + "with member [Mesaures].[Foo] as 1 + 3\n" + "select [Measures].[Unit Sales] on 0,\n" + " [Product].Children on 1\n" + "from [Sales]", "explain plan for\n" + "drillthrough maxrows 5\n" + "with member [Mesaures].[Foo] as '(1 + 3)'\n" + "select [Measures].[Unit Sales] ON COLUMNS,\n" + " [Product].Children ON ROWS\n" + "from [Sales]\n"); } /** * Test case for bug * MONDRIAN-924, "Parsing fails with multiple spaces between words". */ public void testMultipleSpaces() { assertParseQuery( "select [Store].[With multiple spaces] on 0\n" + "from [Sales]", "select [Store].[With multiple spaces] ON COLUMNS\n" + "from [Sales]\n"); // Only enable the following if you have manually renamed 'Store 23' to // 'Store 23' (note: 3 spaces) in your database. if (false) { assertQueryReturns( "select [Store].[USA].[WA].[Yakima].[Store 23] on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Yakima].[Store 23]}\n" + "Row #0: 11,491\n"); assertQueryReturns( "With \n" + "Set [*NATIVE_CJ_SET] as '[*BASE_MEMBERS_Store]' \n" + "Set [*SORTED_ROW_AXIS] as 'Order([*CJ_ROW_AXIS],[Store].CurrentMember.OrderKey,BASC," + "Ancestor([Store].CurrentMember,[Store].[Store City]).OrderKey,BASC)' \n" + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*ZERO]}' \n" + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Store].currentMember)})' \n" + "Set [*BASE_MEMBERS_Store] as '{[Store].[USA].[WA].[Yakima].[Store 23]}' \n" + "Set [*CJ_COL_AXIS] as '[*NATIVE_CJ_SET]' \n" + "Member [Measures].[*ZERO] as '0', SOLVE_ORDER=0 \n" + "Select \n" + "[*BASE_MEMBERS_Measures] on columns, \n" + "[*SORTED_ROW_AXIS] on rows \n" + "From [Sales] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*ZERO]}\n" + "Axis #2:\n" + "{[Store].[USA].[WA].[Yakima].[Store 23]}\n" + "Row #0: 0\n"); } } /** * Bad token should cause unchecked exception, not {@link Error}. */ public void testBadToken() { assertParseQueryFails( "select\nfrom", "Mondrian Error:Syntax error at line 2, column 7, token 'EOF'"); assertParseQueryFails( "`\nselect\nfrom Sales\n", "Unexpected character '`'"); assertParseQueryFails( "'\nselect\nfrom Sales\n", "Mondrian Error:Syntax error at line 1, column 1, token '\n" + "select\n" + "from Sales\n" + "'"); assertParseQueryFails( "select\nfrom 'Sales", "Mondrian Error:Syntax error at line 2, column 7, token 'Sales'"); assertParseQueryFails( "with member Measures.Test as '\n" + "select\n" + "from Sales", "Mondrian Error:Syntax error at line 3, column 13, token 'EOF'"); } /** * Parses an MDX query and asserts that the result is as expected when * unparsed. * * @param mdx MDX query * @param expected Expected result of unparsing */ private void assertParseQuery(String mdx, final String expected) { assertParseQuery(mdx, expected, true); assertParseQuery(mdx, expected, false); } private void assertParseQuery( String mdx, final String expected, boolean old) { TestParser p = new TestParser(); final QueryPart query; if (old) { query = p.parseInternal(null, mdx, false, funTable, false); } else { MdxParserValidator parser = new JavaccParserValidatorImpl(p); query = parser.parseInternal( null, mdx, false, funTable, false); } if (!(query instanceof DrillThrough || query instanceof Explain)) { assertNull("Test parser should return null query", query); } final String actual = p.toMdxString(); TestContext.assertEqualsVerbose(expected, actual); } /** * Parses an MDX expression and asserts that the result is as expected when * unparsed. * * @param expr MDX query * @param expected Expected result of unparsing */ private void assertParseExpr(String expr, final String expected) { assertParseExpr(expr, expected, true); assertParseExpr(expr, expected, false); } private void assertParseExpr( String expr, final String expected, boolean old) { TestParser p = new TestParser(); final String mdx = wrapExpr(expr); final QueryPart query; if (old) { query = p.parseInternal(null, mdx, false, funTable, false); } else { MdxParserValidator parser = new JavaccParserValidatorImpl(p); query = parser.parseInternal( null, mdx, false, funTable, false); } assertNull("Test parser should return null query", query); final String actual = Util.unparse(p.formulas[0].getExpression()); TestContext.assertEqualsVerbose(expected, actual); } private String wrapExpr(String expr) { return "with member [Measures].[Foo] as " + expr + "\n select from [Sales]"; } public static class TestParser extends Parser implements MdxParserValidator.QueryPartFactory { private Formula[] formulas; private QueryAxis[] axes; private String cube; private Exp slicer; private QueryPart[] cellProps; private boolean drillThrough; private int maxRowCount; private int firstRowOrdinal; private List returnList; private boolean explain; public TestParser() { super(); } public QueryPart parseInternal( Statement statement, String queryString, boolean debug, FunTable funTable, boolean strictValidation) { return super.parseInternal( this, statement, queryString, debug, funTable, strictValidation); } public Query makeQuery( Statement statement, Formula[] formulae, QueryAxis[] axes, String cube, Exp slicer, QueryPart[] cellProps, boolean strictValidation) { setFormulas(formulae); setAxes(axes); setCube(cube); setSlicer(slicer); setCellProps(cellProps); return null; } public DrillThrough makeDrillThrough( Query query, int maxRowCount, int firstRowOrdinal, List returnList) { this.drillThrough = true; this.maxRowCount = maxRowCount; this.firstRowOrdinal = firstRowOrdinal; this.returnList = returnList; return null; } public Explain makeExplain(QueryPart query) { this.explain = true; return null; } public QueryAxis[] getAxes() { return axes; } public void setAxes(QueryAxis[] axes) { this.axes = axes; } public QueryPart[] getCellProps() { return cellProps; } public void setCellProps(QueryPart[] cellProps) { this.cellProps = cellProps; } public String getCube() { return cube; } public void setCube(String cube) { this.cube = cube; } public Formula[] getFormulas() { return formulas; } public void setFormulas(Formula[] formulas) { this.formulas = formulas; } public Exp getSlicer() { return slicer; } public void setSlicer(Exp slicer) { this.slicer = slicer; } /** * Converts this query to a string. * * @return This query converted to a string */ public String toMdxString() { StringWriter sw = new StringWriter(); PrintWriter pw = new QueryPrintWriter(sw); unparse(pw); return sw.toString(); } private void unparse(PrintWriter pw) { if (explain) { pw.println("explain plan for"); } if (drillThrough) { pw.print("drillthrough"); if (maxRowCount > 0) { pw.print(" maxrows " + maxRowCount); } if (firstRowOrdinal > 0) { pw.print(" firstrowset " + firstRowOrdinal); } pw.println(); } if (formulas != null) { for (int i = 0; i < formulas.length; i++) { if (i == 0) { pw.print("with "); } else { pw.print(" "); } formulas[i].unparse(pw); pw.println(); } } pw.print("select "); if (axes != null) { for (int i = 0; i < axes.length; i++) { axes[i].unparse(pw); if (i < axes.length - 1) { pw.println(","); pw.print(" "); } else { pw.println(); } } } if (cube != null) { pw.println("from [" + cube + "]"); } if (slicer != null) { pw.print("where "); slicer.unparse(pw); pw.println(); } if (cellProps != null) { for (QueryPart cellProp : cellProps) { cellProp.unparse(pw); } } if (drillThrough && returnList != null) { pw.print(" return "); ExpBase.unparseList( pw, returnList.toArray(new Exp[returnList.size()]), " return ", ", ", ""); } } } } // End ParserTest.java mondrian-3.4.1/testsrc/main/mondrian/olap/CellPropertyTest.java0000644000175000017500000000216211735330606024533 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1998-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import junit.framework.TestCase; /** * Test for Cell Property. * * @author Shishir * @since 08 May, 2007 */ public class CellPropertyTest extends TestCase { private CellProperty cellProperty; protected void setUp() throws Exception { super.setUp(); cellProperty = new CellProperty("[Format_String]"); } public void testIsNameEquals() { assertTrue(cellProperty.isNameEquals("Format_String")); } public void testIsNameEqualsDoesCaseInsensitiveMatch() { assertTrue(cellProperty.isNameEquals("format_string")); } public void testIsNameEqualsParameterShouldNotBeQuoted() { assertFalse(cellProperty.isNameEquals("[Format_String]")); } } // End CellPropertyTest.javamondrian-3.4.1/testsrc/main/mondrian/xmla/0000755000175000017500000000000011735330606020411 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/xmla/XmlaExcelXPTest.ref.xml0000644000175000017500000064153411735330606024715 0ustar drazzibdrazzib

DISCOVER_DATASOURCES 1033 SchemaData Tabular ]]> FoodMart Mondrian FoodMart data source http://localhost:8080/mondrian/xmla FoodMart Mondrian MDP Unauthenticated ]]>
DISCOVER_SCHEMA_ROWSETS 1033 FoodMart SchemaData Tabular ]]>
DBSCHEMA_CATALOGS CATALOG_NAME xsd:string Identifies the physical attributes associated with catalogs accessible from the provider. DBSCHEMA_COLUMNS TABLE_CATALOG xsd:string TABLE_SCHEMA xsd:string TABLE_NAME xsd:string COLUMN_NAME xsd:string DBSCHEMA_PROVIDER_TYPES DATA_TYPE xsd:unsignedShort BEST_MATCH xsd:boolean DBSCHEMA_SCHEMATA CATALOG_NAME xsd:string SCHEMA_NAME xsd:string SCHEMA_OWNER xsd:string DBSCHEMA_TABLES TABLE_CATALOG xsd:string TABLE_SCHEMA xsd:string TABLE_NAME xsd:string TABLE_TYPE xsd:string DBSCHEMA_TABLES_INFO TABLE_CATALOG xsd:string TABLE_SCHEMA xsd:string TABLE_NAME xsd:string TABLE_TYPE xsd:string DISCOVER_DATASOURCES DataSourceName xsd:string URL xsd:string ProviderName xsd:string ProviderType xsd:string AuthenticationMode xsd:string Returns a list of XML for Analysis data sources available on the server or Web Service. DISCOVER_ENUMERATORS EnumName xsd:string Returns a list of names, data types, and enumeration values for enumerators supported by the provider of a specific data source. DISCOVER_KEYWORDS Keyword xsd:string Returns an XML list of keywords reserved by the provider. DISCOVER_LITERALS LiteralName xsd:string Returns information about literals supported by the provider. DISCOVER_PROPERTIES PropertyName xsd:string Returns a list of information and values about the requested properties that are supported by the specified data source provider. DISCOVER_SCHEMA_ROWSETS SchemaName xsd:string Returns the names, values, and other information of all supported RequestType enumeration values. MDSCHEMA_ACTIONS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string ACTION_NAME xsd:string COORDINATE xsd:string COORDINATE_TYPE xsd:int MDSCHEMA_CUBES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string CUBE_TYPE xsd:string MDSCHEMA_DIMENSIONS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string MDSCHEMA_FUNCTIONS FUNCTION_NAME xsd:string ORIGIN xsd:int INTERFACE_NAME xsd:string LIBRARY_NAME xsd:string MDSCHEMA_HIERARCHIES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string MDSCHEMA_LEVELS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string LEVEL_NAME xsd:string LEVEL_UNIQUE_NAME xsd:string MDSCHEMA_MEASURES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string MEASURE_NAME xsd:string MEASURE_UNIQUE_NAME xsd:string MDSCHEMA_MEMBERS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string LEVEL_UNIQUE_NAME xsd:string LEVEL_NUMBER xsd:unsignedInt MEMBER_NAME xsd:string MEMBER_UNIQUE_NAME xsd:string MEMBER_TYPE xsd:int MEMBER_CAPTION xsd:string TREE_OP xsd:int MDSCHEMA_PROPERTIES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string LEVEL_UNIQUE_NAME xsd:string MEMBER_UNIQUE_NAME xsd:string PROPERTY_NAME xsd:string PROPERTY_TYPE xsd:short PROPERTY_CONTENT_TYPE xsd:short MDSCHEMA_SETS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string SET_NAME xsd:string SCOPE xsd:int ]]>
DBSCHEMA_CATALOGS 1033 FoodMart SchemaData Tabular ]]>
FoodMart No description available California manager,No HR Cube ]]>
DISCOVER_PROPERTIES ProviderVersion 1033 FoodMart SchemaData Tabular ]]>
ProviderVersion The version of the Mondrian XMLA Provider string Read false ${mondrianVersion} ]]>
DISCOVER_PROPERTIES Catalog 1033 FoodMart SchemaData Tabular ]]>
Catalog When establishing a session with an Analysis Services instance to send an XMLA command, this property is equivalent to the OLE DB property, DBPROP_INIT_CATALOG. When you set this property during a session to change the current database for the session, this property is equivalent to the OLE DB property, DBPROP_CURRENTCATALOG. The default value for this property is an empty string. string ReadWrite false ]]>
1033 FoodMart ]]>
]]>
1033 FoodMart FoodMart ]]>
]]>
MDSCHEMA_CUBES This restriction should never match. 1033 FoodMart FoodMart SchemaData Tabular ]]>
]]>
MDSCHEMA_CUBES Sales 1033 FoodMart FoodMart SchemaData Tabular ]]>
FoodMart FoodMart Sales CUBE xxxx-xx-xxTxx:xx:xx true false false false Sales FoodMart Schema - Sales Cube ]]>
MDSCHEMA_HIERARCHIES This restriction should never match. 1033 FoodMart FoodMart SchemaData Tabular ]]>
]]>
MDSCHEMA_MEASURES This restriction should never match. 1033 FoodMart FoodMart SchemaData Tabular ]]>
]]>
MDSCHEMA_MEASURES Sales 1033 FoodMart FoodMart SchemaData Tabular ]]>
FoodMart FoodMart Sales Customer Count [Measures].[Customer Count] Customer Count 0 3 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Customer Count Member FoodMart FoodMart Sales Profit [Measures].[Profit] Profit 127 130 true Sales Cube - Profit Member FoodMart FoodMart Sales Profit Growth [Measures].[Profit Growth] Gewinn-Wachstum 127 130 true Sales Cube - Profit Growth Member FoodMart FoodMart Sales Promotion Sales [Measures].[Promotion Sales] Promotion Sales 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Promotion Sales Member FoodMart FoodMart Sales Sales Count [Measures].[Sales Count] Sales Count 2 3 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Sales Count Member FoodMart FoodMart Sales Store Cost [Measures].[Store Cost] Store Cost 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Store Cost Member FoodMart FoodMart Sales Store Sales [Measures].[Store Sales] Store Sales 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Store Sales Member FoodMart FoodMart Sales Unit Sales [Measures].[Unit Sales] Unit Sales 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Unit Sales Member ]]>
MDSCHEMA_DIMENSIONS This restriction should never match. 1033 FoodMart FoodMart SchemaData Tabular ]]>
]]>
MDSCHEMA_DIMENSIONS Sales 1033 FoodMart FoodMart SchemaData Tabular ]]>
FoodMart FoodMart Sales Customers [Customers] Customers 8 3 10282 [Customers] Sales Cube - Customers Dimension false false 0 true FoodMart FoodMart Sales Education Level [Education Level] Education Level 9 3 6 [Education Level] Sales Cube - Education Level Dimension false false 0 true FoodMart FoodMart Sales Gender [Gender] Gender 10 3 3 [Gender] Sales Cube - Gender Dimension false false 0 true FoodMart FoodMart Sales Marital Status [Marital Status] Marital Status 11 3 112 [Marital Status] Sales Cube - Marital Status Dimension false false 0 true FoodMart FoodMart Sales Measures [Measures] Measures 0 2 10 [Measures] Sales Cube - Measures Dimension false false 0 true FoodMart FoodMart Sales Product [Product] Product 5 3 1561 [Product] Sales Cube - Product Dimension false false 0 true FoodMart FoodMart Sales Promotion Media [Promotion Media] Promotion Media 6 3 15 [Promotion Media] Sales Cube - Promotion Media Dimension false false 0 true FoodMart FoodMart Sales Promotions [Promotions] Promotions 7 3 52 [Promotions] Sales Cube - Promotions Dimension false false 0 true FoodMart FoodMart Sales Store [Store] Store 1 3 26 [Store] Sales Cube - Store Dimension false false 0 true FoodMart FoodMart Sales Store Size in SQFT [Store Size in SQFT] Store Size in SQFT 2 3 22 [Store Size in SQFT] Sales Cube - Store Size in SQFT Dimension false false 0 true FoodMart FoodMart Sales Store Type [Store Type] Store Type 3 3 7 [Store Type] Sales Cube - Store Type Dimension false false 0 true FoodMart FoodMart Sales Time [Time] Time 4 1 25 [Time] Sales Cube - Time Dimension false false 0 true FoodMart FoodMart Sales Yearly Income [Yearly Income] Yearly Income 12 3 9 [Yearly Income] Sales Cube - Yearly Income Dimension false false 0 true ]]>
MDSCHEMA_CUBES Sales 1033 FoodMart FoodMart SchemaData Tabular ]]>
FoodMart FoodMart Sales CUBE xxxx-xx-xxTxx:xx:xx true false false false Sales FoodMart Schema - Sales Cube ]]>
MDSCHEMA_LEVELS This restriction should never match. 1033 FoodMart FoodMart SchemaData Tabular ]]>
]]>
MDSCHEMA_LEVELS Sales [Time] 1033 FoodMart FoodMart SchemaData Tabular ]]>
FoodMart FoodMart Sales [Time] [Time] Year [Time].[Year] Year 0 2 20 0 1 true Sales Cube - Time Hierarchy - Year Level FoodMart FoodMart Sales [Time] [Time] Quarter [Time].[Quarter] Quarter 1 8 68 0 0 true Sales Cube - Time Hierarchy - Quarter Level FoodMart FoodMart Sales [Time] [Time] Month [Time].[Month] Month 2 24 132 0 0 true Sales Cube - Time Hierarchy - Month Level ]]>
MDSCHEMA_CUBES Sales 1033 FoodMart FoodMart SchemaData Tabular ]]>
FoodMart FoodMart Sales CUBE xxxx-xx-xxTxx:xx:xx true false false false Sales FoodMart Schema - Sales Cube ]]>
MDSCHEMA_LEVELS Sales [Customers] 1033 FoodMart FoodMart SchemaData Tabular ]]>
FoodMart FoodMart Sales [Customers] [Customers] (All) [Customers].[(All)] (All) 0 1 1 0 3 true Sales Cube - Customers Hierarchy - (All) Level FoodMart FoodMart Sales [Customers] [Customers] Country [Customers].[Country] Country 1 3 0 0 1 true Sales Cube - Customers Hierarchy - Country Level FoodMart FoodMart Sales [Customers] [Customers] State Province [Customers].[State Province] State Province 2 13 0 0 1 true Sales Cube - Customers Hierarchy - State Province Level FoodMart FoodMart Sales [Customers] [Customers] City [Customers].[City] City 3 109 0 0 0 true Sales Cube - Customers Hierarchy - City Level FoodMart FoodMart Sales [Customers] [Customers] Name [Customers].[Name] Name 4 10281 0 0 1 true Sales Cube - Customers Hierarchy - Name Level ]]>
1033 FoodMart FoodMart ]]>
]]>
SELECT {[Measures].[Sales Count], [Measures].[Store Cost]} DIMENSION PROPERTIES PARENT_UNIQUE_NAME ON COLUMNS , NON EMPTY HIERARCHIZE({DrillDownMember({{DrillDownMember({{DrillDownMember({DrillDownLevel({[Store].[All Stores]})}, {[Store].[USA]})}}, {[Store].[USA].[OR]})}}, {[Store].[USA].[OR].[Salem]})}) DIMENSION PROPERTIES PARENT_UNIQUE_NAME ON ROWS FROM [Sales] 1033 FoodMart FoodMart Multidimensional TupleFormat SchemaData ]]>
Sales [Measures].[Sales Count] Sales Count [Measures].[MeasuresLevel] 0 0 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 0 [Store].[All Stores] All Stores [Store].[(All)] 0 65539 [Store].[USA] USA [Store].[Store Country] 1 65539 [Store].[All Stores] [Store].[USA].[CA] CA [Store].[Store State] 2 5 [Store].[USA] [Store].[USA].[OR] OR [Store].[Store State] 2 196610 [Store].[USA] [Store].[USA].[OR].[Portland] Portland [Store].[Store City] 3 1 [Store].[USA].[OR] [Store].[USA].[OR].[Salem] Salem [Store].[Store City] 3 196609 [Store].[USA].[OR] [Store].[USA].[OR].[Salem].[Store 13] Store 13 [Store].[Store Name] 4 0 [Store].[USA].[OR].[Salem] [Store].[USA].[WA] WA [Store].[Store State] 2 7 [Store].[USA] 86837 86,837 #,### 225627.2336 225,627.23 #,###.00 86837 86,837 #,### 225627.2336 225,627.23 #,###.00 24442 24,442 #,### 63530.4251 63,530.43 #,###.00 21611 21,611 #,### 56772.5006 56,772.50 #,###.00 8264 8,264 #,### 21948.944 21,948.94 #,###.00 13347 13,347 #,### 34823.5566 34,823.56 #,###.00 13347 13,347 #,### 34823.5566 34,823.56 #,###.00 40784 40,784 #,### 105324.3079 105,324.31 #,###.00 ]]>
DISCOVER_DATASOURCES 1033 SchemaData Tabular ]]> FoodMart Mondrian FoodMart data source http://localhost:8080/mondrian/xmla FoodMart Mondrian MDP Unauthenticated ]]>
DISCOVER_SCHEMA_ROWSETS 1033 FoodMart SchemaData Tabular ]]>
DBSCHEMA_CATALOGS CATALOG_NAME xsd:string Identifies the physical attributes associated with catalogs accessible from the provider. DBSCHEMA_COLUMNS TABLE_CATALOG xsd:string TABLE_SCHEMA xsd:string TABLE_NAME xsd:string COLUMN_NAME xsd:string DBSCHEMA_PROVIDER_TYPES DATA_TYPE xsd:unsignedShort BEST_MATCH xsd:boolean DBSCHEMA_SCHEMATA CATALOG_NAME xsd:string SCHEMA_NAME xsd:string SCHEMA_OWNER xsd:string DBSCHEMA_TABLES TABLE_CATALOG xsd:string TABLE_SCHEMA xsd:string TABLE_NAME xsd:string TABLE_TYPE xsd:string DBSCHEMA_TABLES_INFO TABLE_CATALOG xsd:string TABLE_SCHEMA xsd:string TABLE_NAME xsd:string TABLE_TYPE xsd:string DISCOVER_DATASOURCES DataSourceName xsd:string URL xsd:string ProviderName xsd:string ProviderType xsd:string AuthenticationMode xsd:string Returns a list of XML for Analysis data sources available on the server or Web Service. DISCOVER_ENUMERATORS EnumName xsd:string Returns a list of names, data types, and enumeration values for enumerators supported by the provider of a specific data source. DISCOVER_KEYWORDS Keyword xsd:string Returns an XML list of keywords reserved by the provider. DISCOVER_LITERALS LiteralName xsd:string Returns information about literals supported by the provider. DISCOVER_PROPERTIES PropertyName xsd:string Returns a list of information and values about the requested properties that are supported by the specified data source provider. DISCOVER_SCHEMA_ROWSETS SchemaName xsd:string Returns the names, values, and other information of all supported RequestType enumeration values. MDSCHEMA_ACTIONS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string ACTION_NAME xsd:string COORDINATE xsd:string COORDINATE_TYPE xsd:int MDSCHEMA_CUBES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string CUBE_TYPE xsd:string MDSCHEMA_DIMENSIONS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string MDSCHEMA_FUNCTIONS FUNCTION_NAME xsd:string ORIGIN xsd:int INTERFACE_NAME xsd:string LIBRARY_NAME xsd:string MDSCHEMA_HIERARCHIES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string MDSCHEMA_LEVELS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string LEVEL_NAME xsd:string LEVEL_UNIQUE_NAME xsd:string MDSCHEMA_MEASURES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string MEASURE_NAME xsd:string MEASURE_UNIQUE_NAME xsd:string MDSCHEMA_MEMBERS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string LEVEL_UNIQUE_NAME xsd:string LEVEL_NUMBER xsd:unsignedInt MEMBER_NAME xsd:string MEMBER_UNIQUE_NAME xsd:string MEMBER_TYPE xsd:int MEMBER_CAPTION xsd:string TREE_OP xsd:int MDSCHEMA_PROPERTIES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string LEVEL_UNIQUE_NAME xsd:string MEMBER_UNIQUE_NAME xsd:string PROPERTY_NAME xsd:string PROPERTY_TYPE xsd:short PROPERTY_CONTENT_TYPE xsd:short MDSCHEMA_SETS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string SET_NAME xsd:string SCOPE xsd:int ]]>
DBSCHEMA_CATALOGS 1033 FoodMart SchemaData Tabular ]]>
FoodMart No description available California manager,No HR Cube ]]>
DISCOVER_PROPERTIES ProviderVersion 1033 FoodMart SchemaData Tabular ]]>
ProviderVersion The version of the Mondrian XMLA Provider string Read false ${mondrianVersion} ]]>
DISCOVER_PROPERTIES Catalog 1033 FoodMart SchemaData Tabular ]]>
Catalog When establishing a session with an Analysis Services instance to send an XMLA command, this property is equivalent to the OLE DB property, DBPROP_INIT_CATALOG. When you set this property during a session to change the current database for the session, this property is equivalent to the OLE DB property, DBPROP_CURRENTCATALOG. The default value for this property is an empty string. string ReadWrite false ]]>
1033 FoodMart ]]>
]]>
mondrian-3.4.1/testsrc/main/mondrian/xmla/XmlaCognosTest.ref.xml0000644000175000017500003123063111735330606024631 0ustar drazzibdrazzib WITH MEMBER [Measures].[COG_OQP_INT_am1] AS ' ([Time].[COG_OQP_INT_m3], [Measures].[Unit Sales])', SOLVE_ORDER = 8 MEMBER [Time].[Time].[COG_OQP_INT_m3] AS ' AGGREGATE( UNION( GENERATE( INTERSECT( {[Customers].[Country].MEMBERS}, {([Customers].CURRENTMEMBER)}), DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City])), EXCEPT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City]), GENERATE( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[Country]), DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City]), ALL)), ALL), [Time].DEFAULTMEMBER)', SOLVE_ORDER = 8 MEMBER [Customers].[COG_OQP_USR_Aggregate(Country)] AS ' IIF( [Measures].CURRENTMEMBER IS [Measures].[Unit Sales], ([Customers].[COG_OQP_INT_m2], [Measures].[Unit Sales]), AGGREGATE( UNION( GENERATE( {[Customers].[Country].MEMBERS}, UNION( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City]), HEAD( {([Customers].CURRENTMEMBER)}, IIF( COUNT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City]), INCLUDEEMPTY)= 0, 1, 0)), ALL), ALL), EXCEPT( {[Customers].[City].MEMBERS}, GENERATE( {[Customers].[Country].MEMBERS}, DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City]), ALL)), ALL)))', SOLVE_ORDER = 4 MEMBER [Customers].[COG_OQP_INT_t4]AS '1', SOLVE_ORDER = 65535 MEMBER [Customers].[COG_OQP_INT_t3]AS '1', SOLVE_ORDER = 65535 MEMBER [Customers].[COG_OQP_INT_t2]AS '1', SOLVE_ORDER = 65535 MEMBER [Customers].[COG_OQP_INT_t1]AS '1', SOLVE_ORDER = 65535 MEMBER [Customers].[COG_OQP_INT_m2] AS ' AGGREGATE( UNION( GENERATE( {[Customers].[Country].MEMBERS}, UNION( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City]), HEAD( {([Customers].CURRENTMEMBER)}, IIF( COUNT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City]), INCLUDEEMPTY)= 0, 1, 0)), ALL), ALL), EXCEPT( {[Customers].[City].MEMBERS}, GENERATE( {[Customers].[Country].MEMBERS}, DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City]), ALL)), ALL))', SOLVE_ORDER = 4 SELECT CROSSJOIN( UNION( {[Measures].[Unit Sales]}, {[Measures].[COG_OQP_INT_am1]}, ALL), {[Product].[Product Category].MEMBERS}) ON AXIS(0), UNION( UNION( UNION( UNION( UNION( UNION( UNION( GENERATE( {[Customers].[Country].MEMBERS}, UNION( UNION( UNION( UNION( UNION( {([Customers].[COG_OQP_INT_t2])}, {([Customers].CURRENTMEMBER)}, ALL), {([Customers].[COG_OQP_INT_t1])}, ALL), DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City]), ALL), {([Customers].[COG_OQP_INT_t3])}, ALL), HEAD( {([Customers].CURRENTMEMBER)}, IIF( COUNT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City]), INCLUDEEMPTY)> 0, 1, 0)), ALL), ALL), {([Customers].[COG_OQP_INT_t2])}, ALL), {([Customers].[COG_OQP_INT_t1])}, ALL), EXCEPT( {[Customers].[City].MEMBERS}, GENERATE( {[Customers].[Country].MEMBERS}, DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City]), ALL)), ALL), {([Customers].[COG_OQP_INT_t3])}, ALL), HEAD( HEAD( {([Customers].CURRENTMEMBER)}, IIF( COUNT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City]), INCLUDEEMPTY)> 0, 1, 0)), IIF( COUNT( EXCEPT( {[Customers].[City].MEMBERS}, GENERATE( {[Customers].[Country].MEMBERS}, DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City]), ALL)), INCLUDEEMPTY)> 0, 1, 0)), ALL), {([Customers].[COG_OQP_INT_t4])}, ALL), HEAD( {([Customers].[COG_OQP_USR_Aggregate(Country)])}, IIF( COUNT( GENERATE( {[Customers].[Country].MEMBERS}, UNION( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City]), HEAD( {([Customers].CURRENTMEMBER)}, IIF( COUNT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City]), INCLUDEEMPTY)= 0, 1, 0)), ALL), ALL), INCLUDEEMPTY)> 0, 1, 0)), ALL) ON AXIS(1) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 9 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 5 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 2 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 1 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 1 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 3 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 4 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 1 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 1 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 4 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 4 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 1 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 3 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 1 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 2 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 9 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 3 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 2 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 1 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 1 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 4 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 1 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 5 [Customers].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Customers].[(All)] 0 0 [Customers].[Canada] Canada [Customers].[Country] 1 1 [Customers].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Customers].[(All)] 0 0 [Customers].[Canada].[BC].[Burnaby] Burnaby [Customers].[City] 3 85 [Customers].[Canada].[BC].[Cliffside] Cliffside [Customers].[City] 3 131166 [Customers].[Canada].[BC].[Haney] Haney [Customers].[City] 3 131162 [Customers].[Canada].[BC].[Ladner] Ladner [Customers].[City] 3 131172 [Customers].[Canada].[BC].[Langford] Langford [Customers].[City] 3 131177 [Customers].[Canada].[BC].[Langley] Langley [Customers].[City] 3 131164 [Customers].[Canada].[BC].[Metchosin] Metchosin [Customers].[City] 3 131168 [Customers].[Canada].[BC].[N. Vancouver] N. Vancouver [Customers].[City] 3 131171 [Customers].[Canada].[BC].[Newton] Newton [Customers].[City] 3 131162 [Customers].[Canada].[BC].[Oak Bay] Oak Bay [Customers].[City] 3 131171 [Customers].[Canada].[BC].[Port Hammond] Port Hammond [Customers].[City] 3 131168 [Customers].[Canada].[BC].[Richmond] Richmond [Customers].[City] 3 131165 [Customers].[Canada].[BC].[Royal Oak] Royal Oak [Customers].[City] 3 131166 [Customers].[Canada].[BC].[Shawnee] Shawnee [Customers].[City] 3 131183 [Customers].[Canada].[BC].[Sooke] Sooke [Customers].[City] 3 131169 [Customers].[Canada].[BC].[Vancouver] Vancouver [Customers].[City] 3 131164 [Customers].[Canada].[BC].[Victoria] Victoria [Customers].[City] 3 131164 [Customers].[Canada].[BC].[Westminster] Westminster [Customers].[City] 3 131164 [Customers].[COG_OQP_INT_t3] COG_OQP_INT_t3 [Customers].[(All)] 0 0 [Customers].[Canada] Canada [Customers].[Country] 1 1 [Customers].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Customers].[(All)] 0 0 [Customers].[Mexico] Mexico [Customers].[Country] 1 9 [Customers].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Customers].[(All)] 0 0 [Customers].[Mexico].[DF].[San Andres] San Andres [Customers].[City] 3 99 [Customers].[Mexico].[DF].[Santa Anita] Santa Anita [Customers].[City] 3 131169 [Customers].[Mexico].[DF].[Santa Fe] Santa Fe [Customers].[City] 3 131123 [Customers].[Mexico].[DF].[Tixapan] Tixapan [Customers].[City] 3 131172 [Customers].[Mexico].[Guerrero].[Acapulco] Acapulco [Customers].[City] 3 106 [Customers].[Mexico].[Jalisco].[Guadalajara] Guadalajara [Customers].[City] 3 104 [Customers].[Mexico].[Mexico].[Mexico City] Mexico City [Customers].[City] 3 97 [Customers].[Mexico].[Oaxaca].[Tlaxiaco] Tlaxiaco [Customers].[City] 3 90 [Customers].[Mexico].[Sinaloa].[La Cruz] La Cruz [Customers].[City] 3 78 [Customers].[Mexico].[Veracruz].[Orizaba] Orizaba [Customers].[City] 3 93 [Customers].[Mexico].[Yucatan].[Merida] Merida [Customers].[City] 3 99 [Customers].[Mexico].[Zacatecas].[Camacho] Camacho [Customers].[City] 3 91 [Customers].[Mexico].[Zacatecas].[Hidalgo] Hidalgo [Customers].[City] 3 131172 [Customers].[COG_OQP_INT_t3] COG_OQP_INT_t3 [Customers].[(All)] 0 0 [Customers].[Mexico] Mexico [Customers].[Country] 1 9 [Customers].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Customers].[(All)] 0 0 [Customers].[USA] USA [Customers].[Country] 1 3 [Customers].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Customers].[(All)] 0 0 [Customers].[USA].[CA].[Altadena] Altadena [Customers].[City] 3 96 [Customers].[USA].[CA].[Arcadia] Arcadia [Customers].[City] 3 131170 [Customers].[USA].[CA].[Bellflower] Bellflower [Customers].[City] 3 131169 [Customers].[USA].[CA].[Berkeley] Berkeley [Customers].[City] 3 131172 [Customers].[USA].[CA].[Beverly Hills] Beverly Hills [Customers].[City] 3 131166 [Customers].[USA].[CA].[Burbank] Burbank [Customers].[City] 3 131168 [Customers].[USA].[CA].[Burlingame] Burlingame [Customers].[City] 3 131171 [Customers].[USA].[CA].[Chula Vista] Chula Vista [Customers].[City] 3 131175 [Customers].[USA].[CA].[Colma] Colma [Customers].[City] 3 131156 [Customers].[USA].[CA].[Concord] Concord [Customers].[City] 3 131178 [Customers].[USA].[CA].[Coronado] Coronado [Customers].[City] 3 131174 [Customers].[USA].[CA].[Daly City] Daly City [Customers].[City] 3 131159 [Customers].[USA].[CA].[Downey] Downey [Customers].[City] 3 131182 [Customers].[USA].[CA].[El Cajon] El Cajon [Customers].[City] 3 131176 [Customers].[USA].[CA].[Fremont] Fremont [Customers].[City] 3 131147 [Customers].[USA].[CA].[Glendale] Glendale [Customers].[City] 3 131174 [Customers].[USA].[CA].[Grossmont] Grossmont [Customers].[City] 3 131172 [Customers].[USA].[CA].[Imperial Beach] Imperial Beach [Customers].[City] 3 131164 [Customers].[USA].[CA].[La Jolla] La Jolla [Customers].[City] 3 131166 [Customers].[USA].[CA].[La Mesa] La Mesa [Customers].[City] 3 131167 [Customers].[USA].[CA].[Lakewood] Lakewood [Customers].[City] 3 131154 [Customers].[USA].[CA].[Lemon Grove] Lemon Grove [Customers].[City] 3 131179 [Customers].[USA].[CA].[Lincoln Acres] Lincoln Acres [Customers].[City] 3 131175 [Customers].[USA].[CA].[Long Beach] Long Beach [Customers].[City] 3 131167 [Customers].[USA].[CA].[Los Angeles] Los Angeles [Customers].[City] 3 131162 [Customers].[USA].[CA].[Mill Valley] Mill Valley [Customers].[City] 3 131157 [Customers].[USA].[CA].[National City] National City [Customers].[City] 3 131173 [Customers].[USA].[CA].[Newport Beach] Newport Beach [Customers].[City] 3 131162 [Customers].[USA].[CA].[Novato] Novato [Customers].[City] 3 131162 [Customers].[USA].[CA].[Oakland] Oakland [Customers].[City] 3 131159 [Customers].[USA].[CA].[Palo Alto] Palo Alto [Customers].[City] 3 131163 [Customers].[USA].[CA].[Pomona] Pomona [Customers].[City] 3 131178 [Customers].[USA].[CA].[Redwood City] Redwood City [Customers].[City] 3 131156 [Customers].[USA].[CA].[Richmond] Richmond [Customers].[City] 3 131170 [Customers].[USA].[CA].[San Carlos] San Carlos [Customers].[City] 3 131160 [Customers].[USA].[CA].[San Diego] San Diego [Customers].[City] 3 131163 [Customers].[USA].[CA].[San Francisco] San Francisco [Customers].[City] 3 131119 [Customers].[USA].[CA].[San Gabriel] San Gabriel [Customers].[City] 3 131168 [Customers].[USA].[CA].[San Jose] San Jose [Customers].[City] 3 131170 [Customers].[USA].[CA].[Santa Cruz] Santa Cruz [Customers].[City] 3 131164 [Customers].[USA].[CA].[Santa Monica] Santa Monica [Customers].[City] 3 131164 [Customers].[USA].[CA].[Spring Valley] Spring Valley [Customers].[City] 3 131163 [Customers].[USA].[CA].[Torrance] Torrance [Customers].[City] 3 131173 [Customers].[USA].[CA].[West Covina] West Covina [Customers].[City] 3 131159 [Customers].[USA].[CA].[Woodland Hills] Woodland Hills [Customers].[City] 3 131168 [Customers].[USA].[OR].[Albany] Albany [Customers].[City] 3 84 [Customers].[USA].[OR].[Beaverton] Beaverton [Customers].[City] 3 131176 [Customers].[USA].[OR].[Corvallis] Corvallis [Customers].[City] 3 131168 [Customers].[USA].[OR].[Lake Oswego] Lake Oswego [Customers].[City] 3 131176 [Customers].[USA].[OR].[Lebanon] Lebanon [Customers].[City] 3 131180 [Customers].[USA].[OR].[Milwaukie] Milwaukie [Customers].[City] 3 131176 [Customers].[USA].[OR].[Oregon City] Oregon City [Customers].[City] 3 131159 [Customers].[USA].[OR].[Portland] Portland [Customers].[City] 3 131162 [Customers].[USA].[OR].[Salem] Salem [Customers].[City] 3 131169 [Customers].[USA].[OR].[W. Linn] W. Linn [Customers].[City] 3 131159 [Customers].[USA].[OR].[Woodburn] Woodburn [Customers].[City] 3 131162 [Customers].[USA].[WA].[Anacortes] Anacortes [Customers].[City] 3 97 [Customers].[USA].[WA].[Ballard] Ballard [Customers].[City] 3 131171 [Customers].[USA].[WA].[Bellingham] Bellingham [Customers].[City] 3 131176 [Customers].[USA].[WA].[Bremerton] Bremerton [Customers].[City] 3 131163 [Customers].[USA].[WA].[Burien] Burien [Customers].[City] 3 131178 [Customers].[USA].[WA].[Edmonds] Edmonds [Customers].[City] 3 131166 [Customers].[USA].[WA].[Everett] Everett [Customers].[City] 3 131167 [Customers].[USA].[WA].[Issaquah] Issaquah [Customers].[City] 3 131175 [Customers].[USA].[WA].[Kirkland] Kirkland [Customers].[City] 3 131165 [Customers].[USA].[WA].[Lynnwood] Lynnwood [Customers].[City] 3 131169 [Customers].[USA].[WA].[Marysville] Marysville [Customers].[City] 3 131165 [Customers].[USA].[WA].[Olympia] Olympia [Customers].[City] 3 131164 [Customers].[USA].[WA].[Port Orchard] Port Orchard [Customers].[City] 3 131161 [Customers].[USA].[WA].[Puyallup] Puyallup [Customers].[City] 3 131167 [Customers].[USA].[WA].[Redmond] Redmond [Customers].[City] 3 131166 [Customers].[USA].[WA].[Renton] Renton [Customers].[City] 3 131164 [Customers].[USA].[WA].[Seattle] Seattle [Customers].[City] 3 131164 [Customers].[USA].[WA].[Sedro Woolley] Sedro Woolley [Customers].[City] 3 131159 [Customers].[USA].[WA].[Spokane] Spokane [Customers].[City] 3 131157 [Customers].[USA].[WA].[Tacoma] Tacoma [Customers].[City] 3 131165 [Customers].[USA].[WA].[Walla Walla] Walla Walla [Customers].[City] 3 131171 [Customers].[USA].[WA].[Yakima] Yakima [Customers].[City] 3 131168 [Customers].[COG_OQP_INT_t3] COG_OQP_INT_t3 [Customers].[(All)] 0 0 [Customers].[USA] USA [Customers].[Country] 1 3 [Customers].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Customers].[(All)] 0 0 [Customers].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Customers].[(All)] 0 0 [Customers].[COG_OQP_INT_t3] COG_OQP_INT_t3 [Customers].[(All)] 0 0 [Customers].[COG_OQP_INT_t4] COG_OQP_INT_t4 [Customers].[(All)] 0 0 [Customers].[COG_OQP_USR_Aggregate(Country)] COG_OQP_USR_Aggregate(Country) [Customers].[(All)] 0 0 [Store].[All Stores] All Stores [Store].[(All)] 0 3 [Store Size in SQFT].[All Store Size in SQFTs] All Store Size in SQFTs [Store Size in SQFT].[(All)] 0 21 [Store Type].[All Store Types] All Store Types [Store Type].[(All)] 0 6 [Time].[1997] 1997 [Time].[Year] 0 4 [Promotion Media].[All Media] All Media [Promotion Media].[(All)] 0 14 [Promotions].[All Promotions] All Promotions [Promotions].[(All)] 0 51 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 5 [Gender].[All Gender] All Gender [Gender].[(All)] 0 2 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 [Yearly Income].[All Yearly Incomes] All Yearly Incomes [Yearly Income].[(All)] 0 8 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 6838 6,838 Standard 3407 3,407 Standard 2469 2,469 Standard 4301 4,301 Standard 3396 3,396 Standard 4186 4,186 Standard 7870 7,870 Standard 8357 8,357 Standard 11888 11,888 Standard 3317 3,317 Standard 900 900 Standard 882 882 Standard 708 708 Standard 819 819 Standard 804 804 Standard 8006 8,006 Standard 1710 1,710 Standard 5197 5,197 Standard 1812 1,812 Standard 12885 12,885 Standard 9433 9,433 Standard 2604 2,604 Standard 4132 4,132 Standard 5004 5,004 Standard 6192 6,192 Standard 2585 2,585 Standard 2580 2,580 Standard 3310 3,310 Standard 6984 6,984 Standard 1714 1,714 Standard 11767 11,767 Standard 886 886 Standard 4400 4,400 Standard 20739 20,739 Standard 1764 1,764 Standard 30545 30,545 Standard 6884 6,884 Standard 5262 5,262 Standard 841 841 Standard 810 810 Standard 969 969 Standard 5885 5,885 Standard 1776 1,776 Standard 1813 1,813 Standard 3556 3,556 Standard 3254 3,254 Standard 833 833 Standard 815 815 Standard 3441 3,441 Standard 6782 6,782 Standard 1682 1,682 Standard 4205 4,205 Standard 6803 6,803 Standard 2477 2,477 Standard 4294 4,294 Standard 6838 6,838 Standard 3407 3,407 Standard 2469 2,469 Standard 4301 4,301 Standard 3396 3,396 Standard 4186 4,186 Standard 7870 7,870 Standard 8357 8,357 Standard 11888 11,888 Standard 3317 3,317 Standard 900 900 Standard 882 882 Standard 708 708 Standard 819 819 Standard 804 804 Standard 8006 8,006 Standard 1710 1,710 Standard 5197 5,197 Standard 1812 1,812 Standard 12885 12,885 Standard 9433 9,433 Standard 2604 2,604 Standard 4132 4,132 Standard 5004 5,004 Standard 6192 6,192 Standard 2585 2,585 Standard 2580 2,580 Standard 3310 3,310 Standard 6984 6,984 Standard 1714 1,714 Standard 11767 11,767 Standard 886 886 Standard 4400 4,400 Standard 20739 20,739 Standard 1764 1,764 Standard 30545 30,545 Standard 6884 6,884 Standard 5262 5,262 Standard 841 841 Standard 810 810 Standard 969 969 Standard 5885 5,885 Standard 1776 1,776 Standard 1813 1,813 Standard 3556 3,556 Standard 3254 3,254 Standard 833 833 Standard 815 815 Standard 3441 3,441 Standard 6782 6,782 Standard 1682 1,682 Standard 4205 4,205 Standard 6803 6,803 Standard 2477 2,477 Standard 4294 4,294 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 51 51 Standard 23 23 Standard 36 36 Standard 20 20 Standard 24 24 Standard 32 32 Standard 83 83 Standard 89 89 Standard 97 97 Standard 27 27 Standard 3 3 Standard 2 2 Standard 2 2 Standard 9 9 Standard 12 12 Standard 78 78 Standard 9 9 Standard 57 57 Standard 21 21 Standard 126 126 Standard 96 96 Standard 19 19 Standard 40 40 Standard 36 36 Standard 58 58 Standard 15 15 Standard 27 27 Standard 34 34 Standard 100 100 Standard 26 26 Standard 160 160 Standard 11 11 Standard 71 71 Standard 163 163 Standard 21 21 Standard 293 293 Standard 46 46 Standard 56 56 Standard 6 6 Standard 13 13 Standard 7 7 Standard 64 64 Standard 27 27 Standard 26 26 Standard 30 30 Standard 25 25 Standard 21 21 Standard 41 41 Standard 68 68 Standard 28 28 Standard 35 35 Standard 51 51 Standard 22 22 Standard 37 37 Standard 51 51 Standard 23 23 Standard 36 36 Standard 20 20 Standard 24 24 Standard 32 32 Standard 83 83 Standard 89 89 Standard 97 97 Standard 27 27 Standard 3 3 Standard 2 2 Standard 2 2 Standard 9 9 Standard 12 12 Standard 78 78 Standard 9 9 Standard 57 57 Standard 21 21 Standard 126 126 Standard 96 96 Standard 19 19 Standard 40 40 Standard 36 36 Standard 58 58 Standard 15 15 Standard 27 27 Standard 34 34 Standard 100 100 Standard 26 26 Standard 160 160 Standard 11 11 Standard 71 71 Standard 163 163 Standard 21 21 Standard 293 293 Standard 46 46 Standard 56 56 Standard 6 6 Standard 13 13 Standard 7 7 Standard 64 64 Standard 27 27 Standard 26 26 Standard 30 30 Standard 25 25 Standard 21 21 Standard 41 41 Standard 68 68 Standard 28 28 Standard 35 35 Standard 51 51 Standard 22 22 Standard 37 37 Standard 51 51 Standard 31 31 Standard 30 30 Standard 43 43 Standard 12 12 Standard 23 23 Standard 78 78 Standard 40 40 Standard 79 79 Standard 39 39 Standard 4 4 Standard 3 3 Standard 10 10 Standard 2 2 Standard 50 50 Standard 18 18 Standard 57 57 Standard 11 11 Standard 100 100 Standard 72 72 Standard 13 13 Standard 38 38 Standard 42 42 Standard 59 59 Standard 15 15 Standard 19 19 Standard 24 24 Standard 68 68 Standard 16 16 Standard 137 137 Standard 5 5 Standard 61 61 Standard 250 250 Standard 26 26 Standard 311 311 Standard 79 79 Standard 68 68 Standard 3 3 Standard 11 11 Standard 40 40 Standard 15 15 Standard 8 8 Standard 58 58 Standard 25 25 Standard 9 9 Standard 10 10 Standard 32 32 Standard 62 62 Standard 20 20 Standard 49 49 Standard 46 46 Standard 32 32 Standard 36 36 Standard 51 51 Standard 31 31 Standard 30 30 Standard 43 43 Standard 12 12 Standard 23 23 Standard 78 78 Standard 40 40 Standard 79 79 Standard 39 39 Standard 4 4 Standard 3 3 Standard 10 10 Standard 2 2 Standard 50 50 Standard 18 18 Standard 57 57 Standard 11 11 Standard 100 100 Standard 72 72 Standard 13 13 Standard 38 38 Standard 42 42 Standard 59 59 Standard 15 15 Standard 19 19 Standard 24 24 Standard 68 68 Standard 16 16 Standard 137 137 Standard 5 5 Standard 61 61 Standard 250 250 Standard 26 26 Standard 311 311 Standard 79 79 Standard 68 68 Standard 3 3 Standard 11 11 Standard 40 40 Standard 15 15 Standard 8 8 Standard 58 58 Standard 25 25 Standard 9 9 Standard 10 10 Standard 32 32 Standard 62 62 Standard 20 20 Standard 49 49 Standard 46 46 Standard 32 32 Standard 36 36 Standard 82 82 Standard 40 40 Standard 39 39 Standard 63 63 Standard 50 50 Standard 45 45 Standard 103 103 Standard 97 97 Standard 113 113 Standard 26 26 Standard 5 5 Standard 2 2 Standard 4 4 Standard 115 115 Standard 4 4 Standard 59 59 Standard 12 12 Standard 150 150 Standard 104 104 Standard 31 31 Standard 67 67 Standard 67 67 Standard 76 76 Standard 32 32 Standard 35 35 Standard 32 32 Standard 89 89 Standard 15 15 Standard 129 129 Standard 7 7 Standard 45 45 Standard 241 241 Standard 12 12 Standard 374 374 Standard 79 79 Standard 59 59 Standard 10 10 Standard 12 12 Standard 9 9 Standard 67 67 Standard 21 21 Standard 23 23 Standard 49 49 Standard 32 32 Standard 10 10 Standard 21 21 Standard 35 35 Standard 87 87 Standard 16 16 Standard 42 42 Standard 95 95 Standard 22 22 Standard 52 52 Standard 82 82 Standard 40 40 Standard 39 39 Standard 63 63 Standard 50 50 Standard 45 45 Standard 103 103 Standard 97 97 Standard 113 113 Standard 26 26 Standard 5 5 Standard 2 2 Standard 4 4 Standard 115 115 Standard 4 4 Standard 59 59 Standard 12 12 Standard 150 150 Standard 104 104 Standard 31 31 Standard 67 67 Standard 67 67 Standard 76 76 Standard 32 32 Standard 35 35 Standard 32 32 Standard 89 89 Standard 15 15 Standard 129 129 Standard 7 7 Standard 45 45 Standard 241 241 Standard 12 12 Standard 374 374 Standard 79 79 Standard 59 59 Standard 10 10 Standard 12 12 Standard 9 9 Standard 67 67 Standard 21 21 Standard 23 23 Standard 49 49 Standard 32 32 Standard 10 10 Standard 21 21 Standard 35 35 Standard 87 87 Standard 16 16 Standard 42 42 Standard 95 95 Standard 22 22 Standard 52 52 Standard 4 4 Standard 5 5 Standard 4 4 Standard 2 2 Standard 2 2 Standard 3 3 Standard 4 4 Standard 14 14 Standard 1 1 Standard 4 4 Standard 2 2 Standard 7 7 Standard 1 1 Standard 8 8 Standard 5 5 Standard 1 1 Standard 4 4 Standard 3 3 Standard 1 1 Standard 5 5 Standard 4 4 Standard 2 2 Standard 7 7 Standard 20 20 Standard 1 1 Standard 2 2 Standard 2 2 Standard 4 4 Standard 2 2 Standard 3 3 Standard 3 3 Standard 1 1 Standard 4 4 Standard 1 1 Standard 4 4 Standard 5 5 Standard 4 4 Standard 2 2 Standard 2 2 Standard 3 3 Standard 4 4 Standard 14 14 Standard 1 1 Standard 4 4 Standard 2 2 Standard 7 7 Standard 1 1 Standard 8 8 Standard 5 5 Standard 1 1 Standard 4 4 Standard 3 3 Standard 1 1 Standard 5 5 Standard 4 4 Standard 2 2 Standard 7 7 Standard 20 20 Standard 1 1 Standard 2 2 Standard 2 2 Standard 4 4 Standard 2 2 Standard 3 3 Standard 3 3 Standard 1 1 Standard 4 4 Standard 1 1 Standard 45 45 Standard 28 28 Standard 24 24 Standard 76 76 Standard 37 37 Standard 68 68 Standard 71 71 Standard 104 104 Standard 150 150 Standard 48 48 Standard 12 12 Standard 9 9 Standard 21 21 Standard 14 14 Standard 4 4 Standard 65 65 Standard 19 19 Standard 41 41 Standard 17 17 Standard 157 157 Standard 93 93 Standard 25 25 Standard 32 32 Standard 70 70 Standard 92 92 Standard 32 32 Standard 3 3 Standard 50 50 Standard 73 73 Standard 13 13 Standard 121 121 Standard 3 3 Standard 30 30 Standard 214 214 Standard 10 10 Standard 308 308 Standard 73 73 Standard 45 45 Standard 19 19 Standard 13 13 Standard 6 6 Standard 86 86 Standard 13 13 Standard 15 15 Standard 32 32 Standard 35 35 Standard 8 8 Standard 54 54 Standard 62 62 Standard 32 32 Standard 51 51 Standard 101 101 Standard 22 22 Standard 61 61 Standard 45 45 Standard 28 28 Standard 24 24 Standard 76 76 Standard 37 37 Standard 68 68 Standard 71 71 Standard 104 104 Standard 150 150 Standard 48 48 Standard 12 12 Standard 9 9 Standard 21 21 Standard 14 14 Standard 4 4 Standard 65 65 Standard 19 19 Standard 41 41 Standard 17 17 Standard 157 157 Standard 93 93 Standard 25 25 Standard 32 32 Standard 70 70 Standard 92 92 Standard 32 32 Standard 3 3 Standard 50 50 Standard 73 73 Standard 13 13 Standard 121 121 Standard 3 3 Standard 30 30 Standard 214 214 Standard 10 10 Standard 308 308 Standard 73 73 Standard 45 45 Standard 19 19 Standard 13 13 Standard 6 6 Standard 86 86 Standard 13 13 Standard 15 15 Standard 32 32 Standard 35 35 Standard 8 8 Standard 54 54 Standard 62 62 Standard 32 32 Standard 51 51 Standard 101 101 Standard 22 22 Standard 61 61 Standard 81 81 Standard 20 20 Standard 32 32 Standard 71 71 Standard 55 55 Standard 58 58 Standard 100 100 Standard 148 148 Standard 202 202 Standard 38 38 Standard 14 14 Standard 13 13 Standard 3 3 Standard 4 4 Standard 2 2 Standard 62 62 Standard 20 20 Standard 77 77 Standard 17 17 Standard 125 125 Standard 113 113 Standard 39 39 Standard 63 63 Standard 51 51 Standard 51 51 Standard 30 30 Standard 21 21 Standard 36 36 Standard 86 86 Standard 25 25 Standard 98 98 Standard 9 9 Standard 41 41 Standard 206 206 Standard 27 27 Standard 322 322 Standard 98 98 Standard 53 53 Standard 15 15 Standard 8 8 Standard 25 25 Standard 54 54 Standard 28 28 Standard 19 19 Standard 49 49 Standard 32 32 Standard 6 6 Standard 6 6 Standard 27 27 Standard 78 78 Standard 17 17 Standard 49 49 Standard 84 84 Standard 25 25 Standard 53 53 Standard 81 81 Standard 20 20 Standard 32 32 Standard 71 71 Standard 55 55 Standard 58 58 Standard 100 100 Standard 148 148 Standard 202 202 Standard 38 38 Standard 14 14 Standard 13 13 Standard 3 3 Standard 4 4 Standard 2 2 Standard 62 62 Standard 20 20 Standard 77 77 Standard 17 17 Standard 125 125 Standard 113 113 Standard 39 39 Standard 63 63 Standard 51 51 Standard 51 51 Standard 30 30 Standard 21 21 Standard 36 36 Standard 86 86 Standard 25 25 Standard 98 98 Standard 9 9 Standard 41 41 Standard 206 206 Standard 27 27 Standard 322 322 Standard 98 98 Standard 53 53 Standard 15 15 Standard 8 8 Standard 25 25 Standard 54 54 Standard 28 28 Standard 19 19 Standard 49 49 Standard 32 32 Standard 6 6 Standard 6 6 Standard 27 27 Standard 78 78 Standard 17 17 Standard 49 49 Standard 84 84 Standard 25 25 Standard 53 53 Standard 1 1 Standard 2 2 Standard 3 3 Standard 7 7 Standard 8 8 Standard 5 5 Standard 6 6 Standard 2 2 Standard 1 1 Standard 13 13 Standard 3 3 Standard 6 6 Standard 13 13 Standard 1 1 Standard 4 4 Standard 7 7 Standard 2 2 Standard 5 5 Standard 3 3 Standard 16 16 Standard 5 5 Standard 21 21 Standard 1 1 Standard 17 17 Standard 4 4 Standard 5 5 Standard 1 1 Standard 5 5 Standard 2 2 Standard 3 3 Standard 4 4 Standard 2 2 Standard 7 7 Standard 9 9 Standard 4 4 Standard 1 1 Standard 2 2 Standard 3 3 Standard 7 7 Standard 8 8 Standard 5 5 Standard 6 6 Standard 2 2 Standard 1 1 Standard 13 13 Standard 3 3 Standard 6 6 Standard 13 13 Standard 1 1 Standard 4 4 Standard 7 7 Standard 2 2 Standard 5 5 Standard 3 3 Standard 16 16 Standard 5 5 Standard 21 21 Standard 1 1 Standard 17 17 Standard 4 4 Standard 5 5 Standard 1 1 Standard 5 5 Standard 2 2 Standard 3 3 Standard 4 4 Standard 2 2 Standard 7 7 Standard 9 9 Standard 4 4 Standard 76 76 Standard 27 27 Standard 25 25 Standard 54 54 Standard 43 43 Standard 42 42 Standard 113 113 Standard 109 109 Standard 130 130 Standard 40 40 Standard 8 8 Standard 6 6 Standard 8 8 Standard 3 3 Standard 11 11 Standard 99 99 Standard 12 12 Standard 58 58 Standard 26 26 Standard 146 146 Standard 108 108 Standard 40 40 Standard 38 38 Standard 63 63 Standard 65 65 Standard 32 32 Standard 22 22 Standard 47 47 Standard 110 110 Standard 18 18 Standard 125 125 Standard 8 8 Standard 34 34 Standard 238 238 Standard 7 7 Standard 351 351 Standard 62 62 Standard 71 71 Standard 14 14 Standard 6 6 Standard 14 14 Standard 85 85 Standard 11 11 Standard 13 13 Standard 49 49 Standard 25 25 Standard 15 15 Standard 9 9 Standard 42 42 Standard 67 67 Standard 9 9 Standard 40 40 Standard 80 80 Standard 13 13 Standard 32 32 Standard 76 76 Standard 27 27 Standard 25 25 Standard 54 54 Standard 43 43 Standard 42 42 Standard 113 113 Standard 109 109 Standard 130 130 Standard 40 40 Standard 8 8 Standard 6 6 Standard 8 8 Standard 3 3 Standard 11 11 Standard 99 99 Standard 12 12 Standard 58 58 Standard 26 26 Standard 146 146 Standard 108 108 Standard 40 40 Standard 38 38 Standard 63 63 Standard 65 65 Standard 32 32 Standard 22 22 Standard 47 47 Standard 110 110 Standard 18 18 Standard 125 125 Standard 8 8 Standard 34 34 Standard 238 238 Standard 7 7 Standard 351 351 Standard 62 62 Standard 71 71 Standard 14 14 Standard 6 6 Standard 14 14 Standard 85 85 Standard 11 11 Standard 13 13 Standard 49 49 Standard 25 25 Standard 15 15 Standard 9 9 Standard 42 42 Standard 67 67 Standard 9 9 Standard 40 40 Standard 80 80 Standard 13 13 Standard 32 32 Standard 3 3 Standard 2 2 Standard 1 1 Standard 1 1 Standard 1 1 Standard 3 3 Standard 5 5 Standard 4 4 Standard 1 1 Standard 2 2 Standard 5 5 Standard 13 13 Standard 2 2 Standard 1 1 Standard 6 6 Standard 3 3 Standard 2 2 Standard 2 2 Standard 6 6 Standard 1 1 Standard 8 8 Standard 11 11 Standard 10 10 Standard 5 5 Standard 6 6 Standard 4 4 Standard 1 1 Standard 1 1 Standard 4 4 Standard 4 4 Standard 3 3 Standard 5 5 Standard 3 3 Standard 3 3 Standard 2 2 Standard 1 1 Standard 1 1 Standard 1 1 Standard 3 3 Standard 5 5 Standard 4 4 Standard 1 1 Standard 2 2 Standard 5 5 Standard 13 13 Standard 2 2 Standard 1 1 Standard 6 6 Standard 3 3 Standard 2 2 Standard 2 2 Standard 6 6 Standard 1 1 Standard 8 8 Standard 11 11 Standard 10 10 Standard 5 5 Standard 6 6 Standard 4 4 Standard 1 1 Standard 1 1 Standard 4 4 Standard 4 4 Standard 3 3 Standard 5 5 Standard 3 3 Standard 3 3 Standard 1 1 Standard 3 3 Standard 2 2 Standard 5 5 Standard 5 5 Standard 1 1 Standard 2 2 Standard 1 1 Standard 2 2 Standard 7 7 Standard 2 2 Standard 4 4 Standard 3 3 Standard 5 5 Standard 4 4 Standard 12 12 Standard 13 13 Standard 7 7 Standard 2 2 Standard 2 2 Standard 3 3 Standard 2 2 Standard 3 3 Standard 3 3 Standard 4 4 Standard 2 2 Standard 2 2 Standard 3 3 Standard 1 1 Standard 3 3 Standard 2 2 Standard 5 5 Standard 5 5 Standard 1 1 Standard 2 2 Standard 1 1 Standard 2 2 Standard 7 7 Standard 2 2 Standard 4 4 Standard 3 3 Standard 5 5 Standard 4 4 Standard 12 12 Standard 13 13 Standard 7 7 Standard 2 2 Standard 2 2 Standard 3 3 Standard 2 2 Standard 3 3 Standard 3 3 Standard 4 4 Standard 2 2 Standard 2 2 Standard 68 68 Standard 19 19 Standard 23 23 Standard 25 25 Standard 48 48 Standard 59 59 Standard 68 68 Standard 66 66 Standard 115 115 Standard 19 19 Standard 13 13 Standard 6 6 Standard 11 11 Standard 3 3 Standard 11 11 Standard 94 94 Standard 23 23 Standard 44 44 Standard 17 17 Standard 98 98 Standard 81 81 Standard 36 36 Standard 29 29 Standard 47 47 Standard 52 52 Standard 9 9 Standard 29 29 Standard 45 45 Standard 50 50 Standard 30 30 Standard 106 106 Standard 9 9 Standard 29 29 Standard 195 195 Standard 6 6 Standard 240 240 Standard 71 71 Standard 53 53 Standard 2 2 Standard 11 11 Standard 17 17 Standard 44 44 Standard 6 6 Standard 14 14 Standard 33 33 Standard 16 16 Standard 14 14 Standard 14 14 Standard 36 36 Standard 54 54 Standard 11 11 Standard 32 32 Standard 67 67 Standard 35 35 Standard 38 38 Standard 68 68 Standard 19 19 Standard 23 23 Standard 25 25 Standard 48 48 Standard 59 59 Standard 68 68 Standard 66 66 Standard 115 115 Standard 19 19 Standard 13 13 Standard 6 6 Standard 11 11 Standard 3 3 Standard 11 11 Standard 94 94 Standard 23 23 Standard 44 44 Standard 17 17 Standard 98 98 Standard 81 81 Standard 36 36 Standard 29 29 Standard 47 47 Standard 52 52 Standard 9 9 Standard 29 29 Standard 45 45 Standard 50 50 Standard 30 30 Standard 106 106 Standard 9 9 Standard 29 29 Standard 195 195 Standard 6 6 Standard 240 240 Standard 71 71 Standard 53 53 Standard 2 2 Standard 11 11 Standard 17 17 Standard 44 44 Standard 6 6 Standard 14 14 Standard 33 33 Standard 16 16 Standard 14 14 Standard 14 14 Standard 36 36 Standard 54 54 Standard 11 11 Standard 32 32 Standard 67 67 Standard 35 35 Standard 38 38 Standard 3 3 Standard 2 2 Standard 2 2 Standard 7 7 Standard 8 8 Standard 5 5 Standard 7 7 Standard 3 3 Standard 1 1 Standard 2 2 Standard 2 2 Standard 2 2 Standard 3 3 Standard 5 5 Standard 5 5 Standard 3 3 Standard 3 3 Standard 2 2 Standard 1 1 Standard 3 3 Standard 10 10 Standard 2 2 Standard 23 23 Standard 3 3 Standard 2 2 Standard 4 4 Standard 2 2 Standard 1 1 Standard 5 5 Standard 5 5 Standard 3 3 Standard 3 3 Standard 2 2 Standard 2 2 Standard 7 7 Standard 8 8 Standard 5 5 Standard 7 7 Standard 3 3 Standard 1 1 Standard 2 2 Standard 2 2 Standard 2 2 Standard 3 3 Standard 5 5 Standard 5 5 Standard 3 3 Standard 3 3 Standard 2 2 Standard 1 1 Standard 3 3 Standard 10 10 Standard 2 2 Standard 23 23 Standard 3 3 Standard 2 2 Standard 4 4 Standard 2 2 Standard 1 1 Standard 5 5 Standard 5 5 Standard 3 3 Standard 83 83 Standard 65 65 Standard 41 41 Standard 75 75 Standard 42 42 Standard 56 56 Standard 112 112 Standard 100 100 Standard 138 138 Standard 28 28 Standard 8 8 Standard 12 12 Standard 7 7 Standard 9 9 Standard 15 15 Standard 118 118 Standard 36 36 Standard 54 54 Standard 22 22 Standard 197 197 Standard 123 123 Standard 23 23 Standard 43 43 Standard 78 78 Standard 86 86 Standard 55 55 Standard 30 30 Standard 33 33 Standard 87 87 Standard 14 14 Standard 162 162 Standard 6 6 Standard 56 56 Standard 296 296 Standard 10 10 Standard 379 379 Standard 79 79 Standard 70 70 Standard 10 10 Standard 5 5 Standard 12 12 Standard 50 50 Standard 13 13 Standard 26 26 Standard 36 36 Standard 47 47 Standard 7 7 Standard 12 12 Standard 64 64 Standard 101 101 Standard 23 23 Standard 53 53 Standard 78 78 Standard 24 24 Standard 31 31 Standard 83 83 Standard 65 65 Standard 41 41 Standard 75 75 Standard 42 42 Standard 56 56 Standard 112 112 Standard 100 100 Standard 138 138 Standard 28 28 Standard 8 8 Standard 12 12 Standard 7 7 Standard 9 9 Standard 15 15 Standard 118 118 Standard 36 36 Standard 54 54 Standard 22 22 Standard 197 197 Standard 123 123 Standard 23 23 Standard 43 43 Standard 78 78 Standard 86 86 Standard 55 55 Standard 30 30 Standard 33 33 Standard 87 87 Standard 14 14 Standard 162 162 Standard 6 6 Standard 56 56 Standard 296 296 Standard 10 10 Standard 379 379 Standard 79 79 Standard 70 70 Standard 10 10 Standard 5 5 Standard 12 12 Standard 50 50 Standard 13 13 Standard 26 26 Standard 36 36 Standard 47 47 Standard 7 7 Standard 12 12 Standard 64 64 Standard 101 101 Standard 23 23 Standard 53 53 Standard 78 78 Standard 24 24 Standard 31 31 Standard 51 51 Standard 40 40 Standard 38 38 Standard 26 26 Standard 42 42 Standard 34 34 Standard 64 64 Standard 57 57 Standard 112 112 Standard 25 25 Standard 9 9 Standard 19 19 Standard 3 3 Standard 5 5 Standard 7 7 Standard 74 74 Standard 9 9 Standard 48 48 Standard 16 16 Standard 143 143 Standard 83 83 Standard 13 13 Standard 42 42 Standard 53 53 Standard 51 51 Standard 26 26 Standard 21 21 Standard 32 32 Standard 77 77 Standard 32 32 Standard 110 110 Standard 7 7 Standard 54 54 Standard 162 162 Standard 17 17 Standard 319 319 Standard 75 75 Standard 42 42 Standard 8 8 Standard 9 9 Standard 69 69 Standard 9 9 Standard 14 14 Standard 38 38 Standard 24 24 Standard 20 20 Standard 9 9 Standard 34 34 Standard 74 74 Standard 18 18 Standard 55 55 Standard 72 72 Standard 16 16 Standard 36 36 Standard 51 51 Standard 40 40 Standard 38 38 Standard 26 26 Standard 42 42 Standard 34 34 Standard 64 64 Standard 57 57 Standard 112 112 Standard 25 25 Standard 9 9 Standard 19 19 Standard 3 3 Standard 5 5 Standard 7 7 Standard 74 74 Standard 9 9 Standard 48 48 Standard 16 16 Standard 143 143 Standard 83 83 Standard 13 13 Standard 42 42 Standard 53 53 Standard 51 51 Standard 26 26 Standard 21 21 Standard 32 32 Standard 77 77 Standard 32 32 Standard 110 110 Standard 7 7 Standard 54 54 Standard 162 162 Standard 17 17 Standard 319 319 Standard 75 75 Standard 42 42 Standard 8 8 Standard 9 9 Standard 69 69 Standard 9 9 Standard 14 14 Standard 38 38 Standard 24 24 Standard 20 20 Standard 9 9 Standard 34 34 Standard 74 74 Standard 18 18 Standard 55 55 Standard 72 72 Standard 16 16 Standard 36 36 Standard 7 7 Standard 2 2 Standard 4 4 Standard 1 1 Standard 4 4 Standard 9 9 Standard 8 8 Standard 1 1 Standard 2 2 Standard 2 2 Standard 4 4 Standard 5 5 Standard 8 8 Standard 4 4 Standard 1 1 Standard 1 1 Standard 2 2 Standard 3 3 Standard 4 4 Standard 9 9 Standard 2 2 Standard 3 3 Standard 20 20 Standard 26 26 Standard 3 3 Standard 3 3 Standard 4 4 Standard 2 2 Standard 5 5 Standard 4 4 Standard 1 1 Standard 5 5 Standard 4 4 Standard 7 7 Standard 2 2 Standard 4 4 Standard 1 1 Standard 4 4 Standard 9 9 Standard 8 8 Standard 1 1 Standard 2 2 Standard 2 2 Standard 4 4 Standard 5 5 Standard 8 8 Standard 4 4 Standard 1 1 Standard 1 1 Standard 2 2 Standard 3 3 Standard 4 4 Standard 9 9 Standard 2 2 Standard 3 3 Standard 20 20 Standard 26 26 Standard 3 3 Standard 3 3 Standard 4 4 Standard 2 2 Standard 5 5 Standard 4 4 Standard 1 1 Standard 5 5 Standard 4 4 Standard 79 79 Standard 22 22 Standard 11 11 Standard 36 36 Standard 37 37 Standard 47 47 Standard 105 105 Standard 122 122 Standard 136 136 Standard 28 28 Standard 14 14 Standard 15 15 Standard 15 15 Standard 11 11 Standard 14 14 Standard 71 71 Standard 19 19 Standard 27 27 Standard 19 19 Standard 163 163 Standard 156 156 Standard 35 35 Standard 40 40 Standard 67 67 Standard 54 54 Standard 40 40 Standard 32 32 Standard 44 44 Standard 119 119 Standard 37 37 Standard 143 143 Standard 19 19 Standard 45 45 Standard 251 251 Standard 12 12 Standard 359 359 Standard 72 72 Standard 90 90 Standard 18 18 Standard 18 18 Standard 13 13 Standard 70 70 Standard 26 26 Standard 20 20 Standard 26 26 Standard 31 31 Standard 13 13 Standard 15 15 Standard 40 40 Standard 104 104 Standard 20 20 Standard 75 75 Standard 92 92 Standard 41 41 Standard 56 56 Standard 79 79 Standard 22 22 Standard 11 11 Standard 36 36 Standard 37 37 Standard 47 47 Standard 105 105 Standard 122 122 Standard 136 136 Standard 28 28 Standard 14 14 Standard 15 15 Standard 15 15 Standard 11 11 Standard 14 14 Standard 71 71 Standard 19 19 Standard 27 27 Standard 19 19 Standard 163 163 Standard 156 156 Standard 35 35 Standard 40 40 Standard 67 67 Standard 54 54 Standard 40 40 Standard 32 32 Standard 44 44 Standard 119 119 Standard 37 37 Standard 143 143 Standard 19 19 Standard 45 45 Standard 251 251 Standard 12 12 Standard 359 359 Standard 72 72 Standard 90 90 Standard 18 18 Standard 18 18 Standard 13 13 Standard 70 70 Standard 26 26 Standard 20 20 Standard 26 26 Standard 31 31 Standard 13 13 Standard 15 15 Standard 40 40 Standard 104 104 Standard 20 20 Standard 75 75 Standard 92 92 Standard 41 41 Standard 56 56 Standard 48 48 Standard 27 27 Standard 30 30 Standard 49 49 Standard 31 31 Standard 35 35 Standard 61 61 Standard 77 77 Standard 121 121 Standard 36 36 Standard 6 6 Standard 9 9 Standard 13 13 Standard 9 9 Standard 11 11 Standard 60 60 Standard 21 21 Standard 44 44 Standard 11 11 Standard 119 119 Standard 56 56 Standard 37 37 Standard 28 28 Standard 32 32 Standard 75 75 Standard 18 18 Standard 13 13 Standard 25 25 Standard 41 41 Standard 13 13 Standard 89 89 Standard 4 4 Standard 30 30 Standard 173 173 Standard 4 4 Standard 236 236 Standard 56 56 Standard 37 37 Standard 9 9 Standard 6 6 Standard 12 12 Standard 68 68 Standard 6 6 Standard 4 4 Standard 13 13 Standard 22 22 Standard 2 2 Standard 7 7 Standard 29 29 Standard 36 36 Standard 13 13 Standard 30 30 Standard 33 33 Standard 24 24 Standard 32 32 Standard 48 48 Standard 27 27 Standard 30 30 Standard 49 49 Standard 31 31 Standard 35 35 Standard 61 61 Standard 77 77 Standard 121 121 Standard 36 36 Standard 6 6 Standard 9 9 Standard 13 13 Standard 9 9 Standard 11 11 Standard 60 60 Standard 21 21 Standard 44 44 Standard 11 11 Standard 119 119 Standard 56 56 Standard 37 37 Standard 28 28 Standard 32 32 Standard 75 75 Standard 18 18 Standard 13 13 Standard 25 25 Standard 41 41 Standard 13 13 Standard 89 89 Standard 4 4 Standard 30 30 Standard 173 173 Standard 4 4 Standard 236 236 Standard 56 56 Standard 37 37 Standard 9 9 Standard 6 6 Standard 12 12 Standard 68 68 Standard 6 6 Standard 4 4 Standard 13 13 Standard 22 22 Standard 2 2 Standard 7 7 Standard 29 29 Standard 36 36 Standard 13 13 Standard 30 30 Standard 33 33 Standard 24 24 Standard 32 32 Standard 57 57 Standard 22 22 Standard 6 6 Standard 37 37 Standard 23 23 Standard 25 25 Standard 36 36 Standard 62 62 Standard 52 52 Standard 21 21 Standard 2 2 Standard 8 8 Standard 8 8 Standard 5 5 Standard 21 21 Standard 70 70 Standard 17 17 Standard 54 54 Standard 13 13 Standard 67 67 Standard 59 59 Standard 10 10 Standard 20 20 Standard 39 39 Standard 27 27 Standard 10 10 Standard 9 9 Standard 24 24 Standard 37 37 Standard 11 11 Standard 63 63 Standard 6 6 Standard 26 26 Standard 117 117 Standard 3 3 Standard 184 184 Standard 47 47 Standard 29 29 Standard 8 8 Standard 12 12 Standard 2 2 Standard 39 39 Standard 15 15 Standard 10 10 Standard 22 22 Standard 11 11 Standard 5 5 Standard 3 3 Standard 19 19 Standard 30 30 Standard 10 10 Standard 28 28 Standard 15 15 Standard 9 9 Standard 51 51 Standard 57 57 Standard 22 22 Standard 6 6 Standard 37 37 Standard 23 23 Standard 25 25 Standard 36 36 Standard 62 62 Standard 52 52 Standard 21 21 Standard 2 2 Standard 8 8 Standard 8 8 Standard 5 5 Standard 21 21 Standard 70 70 Standard 17 17 Standard 54 54 Standard 13 13 Standard 67 67 Standard 59 59 Standard 10 10 Standard 20 20 Standard 39 39 Standard 27 27 Standard 10 10 Standard 9 9 Standard 24 24 Standard 37 37 Standard 11 11 Standard 63 63 Standard 6 6 Standard 26 26 Standard 117 117 Standard 3 3 Standard 184 184 Standard 47 47 Standard 29 29 Standard 8 8 Standard 12 12 Standard 2 2 Standard 39 39 Standard 15 15 Standard 10 10 Standard 22 22 Standard 11 11 Standard 5 5 Standard 3 3 Standard 19 19 Standard 30 30 Standard 10 10 Standard 28 28 Standard 15 15 Standard 9 9 Standard 51 51 Standard 51 51 Standard 31 31 Standard 24 24 Standard 34 34 Standard 29 29 Standard 35 35 Standard 37 37 Standard 47 47 Standard 74 74 Standard 12 12 Standard 7 7 Standard 5 5 Standard 10 10 Standard 40 40 Standard 17 17 Standard 35 35 Standard 10 10 Standard 74 74 Standard 98 98 Standard 12 12 Standard 44 44 Standard 49 49 Standard 66 66 Standard 20 20 Standard 25 25 Standard 8 8 Standard 30 30 Standard 5 5 Standard 92 92 Standard 8 8 Standard 31 31 Standard 180 180 Standard 33 33 Standard 220 220 Standard 53 53 Standard 27 27 Standard 2 2 Standard 8 8 Standard 6 6 Standard 66 66 Standard 10 10 Standard 21 21 Standard 23 23 Standard 18 18 Standard 14 14 Standard 4 4 Standard 21 21 Standard 40 40 Standard 5 5 Standard 19 19 Standard 58 58 Standard 9 9 Standard 41 41 Standard 51 51 Standard 31 31 Standard 24 24 Standard 34 34 Standard 29 29 Standard 35 35 Standard 37 37 Standard 47 47 Standard 74 74 Standard 12 12 Standard 7 7 Standard 5 5 Standard 10 10 Standard 40 40 Standard 17 17 Standard 35 35 Standard 10 10 Standard 74 74 Standard 98 98 Standard 12 12 Standard 44 44 Standard 49 49 Standard 66 66 Standard 20 20 Standard 25 25 Standard 8 8 Standard 30 30 Standard 5 5 Standard 92 92 Standard 8 8 Standard 31 31 Standard 180 180 Standard 33 33 Standard 220 220 Standard 53 53 Standard 27 27 Standard 2 2 Standard 8 8 Standard 6 6 Standard 66 66 Standard 10 10 Standard 21 21 Standard 23 23 Standard 18 18 Standard 14 14 Standard 4 4 Standard 21 21 Standard 40 40 Standard 5 5 Standard 19 19 Standard 58 58 Standard 9 9 Standard 41 41 Standard 54 54 Standard 31 31 Standard 12 12 Standard 32 32 Standard 26 26 Standard 40 40 Standard 55 55 Standard 89 89 Standard 59 59 Standard 16 16 Standard 4 4 Standard 2 2 Standard 10 10 Standard 11 11 Standard 4 4 Standard 74 74 Standard 14 14 Standard 42 42 Standard 19 19 Standard 69 69 Standard 48 48 Standard 12 12 Standard 17 17 Standard 46 46 Standard 44 44 Standard 23 23 Standard 4 4 Standard 28 28 Standard 46 46 Standard 16 16 Standard 81 81 Standard 40 40 Standard 96 96 Standard 12 12 Standard 212 212 Standard 75 75 Standard 40 40 Standard 7 7 Standard 2 2 Standard 6 6 Standard 28 28 Standard 20 20 Standard 13 13 Standard 34 34 Standard 10 10 Standard 11 11 Standard 9 9 Standard 10 10 Standard 47 47 Standard 16 16 Standard 38 38 Standard 44 44 Standard 17 17 Standard 19 19 Standard 54 54 Standard 31 31 Standard 12 12 Standard 32 32 Standard 26 26 Standard 40 40 Standard 55 55 Standard 89 89 Standard 59 59 Standard 16 16 Standard 4 4 Standard 2 2 Standard 10 10 Standard 11 11 Standard 4 4 Standard 74 74 Standard 14 14 Standard 42 42 Standard 19 19 Standard 69 69 Standard 48 48 Standard 12 12 Standard 17 17 Standard 46 46 Standard 44 44 Standard 23 23 Standard 4 4 Standard 28 28 Standard 46 46 Standard 16 16 Standard 81 81 Standard 40 40 Standard 96 96 Standard 12 12 Standard 212 212 Standard 75 75 Standard 40 40 Standard 7 7 Standard 2 2 Standard 6 6 Standard 28 28 Standard 20 20 Standard 13 13 Standard 34 34 Standard 10 10 Standard 11 11 Standard 9 9 Standard 10 10 Standard 47 47 Standard 16 16 Standard 38 38 Standard 44 44 Standard 17 17 Standard 19 19 Standard 66 66 Standard 23 23 Standard 28 28 Standard 47 47 Standard 46 46 Standard 52 52 Standard 72 72 Standard 113 113 Standard 130 130 Standard 38 38 Standard 11 11 Standard 7 7 Standard 17 17 Standard 14 14 Standard 6 6 Standard 96 96 Standard 16 16 Standard 30 30 Standard 10 10 Standard 77 77 Standard 95 95 Standard 20 20 Standard 44 44 Standard 26 26 Standard 39 39 Standard 29 29 Standard 19 19 Standard 25 25 Standard 45 45 Standard 24 24 Standard 103 103 Standard 10 10 Standard 24 24 Standard 222 222 Standard 6 6 Standard 300 300 Standard 80 80 Standard 60 60 Standard 7 7 Standard 7 7 Standard 18 18 Standard 28 28 Standard 9 9 Standard 16 16 Standard 34 34 Standard 33 33 Standard 12 12 Standard 10 10 Standard 20 20 Standard 65 65 Standard 28 28 Standard 34 34 Standard 52 52 Standard 16 16 Standard 28 28 Standard 66 66 Standard 23 23 Standard 28 28 Standard 47 47 Standard 46 46 Standard 52 52 Standard 72 72 Standard 113 113 Standard 130 130 Standard 38 38 Standard 11 11 Standard 7 7 Standard 17 17 Standard 14 14 Standard 6 6 Standard 96 96 Standard 16 16 Standard 30 30 Standard 10 10 Standard 77 77 Standard 95 95 Standard 20 20 Standard 44 44 Standard 26 26 Standard 39 39 Standard 29 29 Standard 19 19 Standard 25 25 Standard 45 45 Standard 24 24 Standard 103 103 Standard 10 10 Standard 24 24 Standard 222 222 Standard 6 6 Standard 300 300 Standard 80 80 Standard 60 60 Standard 7 7 Standard 7 7 Standard 18 18 Standard 28 28 Standard 9 9 Standard 16 16 Standard 34 34 Standard 33 33 Standard 12 12 Standard 10 10 Standard 20 20 Standard 65 65 Standard 28 28 Standard 34 34 Standard 52 52 Standard 16 16 Standard 28 28 Standard 76 76 Standard 47 47 Standard 41 41 Standard 45 45 Standard 28 28 Standard 49 49 Standard 69 69 Standard 93 93 Standard 125 125 Standard 20 20 Standard 10 10 Standard 9 9 Standard 14 14 Standard 2 2 Standard 74 74 Standard 10 10 Standard 65 65 Standard 24 24 Standard 140 140 Standard 113 113 Standard 47 47 Standard 49 49 Standard 44 44 Standard 75 75 Standard 11 11 Standard 28 28 Standard 48 48 Standard 95 95 Standard 15 15 Standard 83 83 Standard 12 12 Standard 60 60 Standard 206 206 Standard 9 9 Standard 258 258 Standard 92 92 Standard 42 42 Standard 11 11 Standard 11 11 Standard 8 8 Standard 47 47 Standard 18 18 Standard 11 11 Standard 24 24 Standard 47 47 Standard 7 7 Standard 20 20 Standard 16 16 Standard 51 51 Standard 6 6 Standard 20 20 Standard 64 64 Standard 18 18 Standard 44 44 Standard 76 76 Standard 47 47 Standard 41 41 Standard 45 45 Standard 28 28 Standard 49 49 Standard 69 69 Standard 93 93 Standard 125 125 Standard 20 20 Standard 10 10 Standard 9 9 Standard 14 14 Standard 2 2 Standard 74 74 Standard 10 10 Standard 65 65 Standard 24 24 Standard 140 140 Standard 113 113 Standard 47 47 Standard 49 49 Standard 44 44 Standard 75 75 Standard 11 11 Standard 28 28 Standard 48 48 Standard 95 95 Standard 15 15 Standard 83 83 Standard 12 12 Standard 60 60 Standard 206 206 Standard 9 9 Standard 258 258 Standard 92 92 Standard 42 42 Standard 11 11 Standard 11 11 Standard 8 8 Standard 47 47 Standard 18 18 Standard 11 11 Standard 24 24 Standard 47 47 Standard 7 7 Standard 20 20 Standard 16 16 Standard 51 51 Standard 6 6 Standard 20 20 Standard 64 64 Standard 18 18 Standard 44 44 Standard 74 74 Standard 14 14 Standard 19 19 Standard 51 51 Standard 24 24 Standard 47 47 Standard 48 48 Standard 85 85 Standard 60 60 Standard 40 40 Standard 12 12 Standard 22 22 Standard 9 9 Standard 3 3 Standard 3 3 Standard 35 35 Standard 15 15 Standard 7 7 Standard 98 98 Standard 54 54 Standard 20 20 Standard 36 36 Standard 43 43 Standard 67 67 Standard 11 11 Standard 33 33 Standard 26 26 Standard 56 56 Standard 11 11 Standard 116 116 Standard 4 4 Standard 42 42 Standard 159 159 Standard 26 26 Standard 240 240 Standard 45 45 Standard 43 43 Standard 5 5 Standard 3 3 Standard 19 19 Standard 77 77 Standard 17 17 Standard 7 7 Standard 35 35 Standard 22 22 Standard 7 7 Standard 13 13 Standard 33 33 Standard 69 69 Standard 21 21 Standard 41 41 Standard 41 41 Standard 35 35 Standard 33 33 Standard 74 74 Standard 14 14 Standard 19 19 Standard 51 51 Standard 24 24 Standard 47 47 Standard 48 48 Standard 85 85 Standard 60 60 Standard 40 40 Standard 12 12 Standard 22 22 Standard 9 9 Standard 3 3 Standard 3 3 Standard 35 35 Standard 15 15 Standard 7 7 Standard 98 98 Standard 54 54 Standard 20 20 Standard 36 36 Standard 43 43 Standard 67 67 Standard 11 11 Standard 33 33 Standard 26 26 Standard 56 56 Standard 11 11 Standard 116 116 Standard 4 4 Standard 42 42 Standard 159 159 Standard 26 26 Standard 240 240 Standard 45 45 Standard 43 43 Standard 5 5 Standard 3 3 Standard 19 19 Standard 77 77 Standard 17 17 Standard 7 7 Standard 35 35 Standard 22 22 Standard 7 7 Standard 13 13 Standard 33 33 Standard 69 69 Standard 21 21 Standard 41 41 Standard 41 41 Standard 35 35 Standard 33 33 Standard 89 89 Standard 46 46 Standard 30 30 Standard 54 54 Standard 44 44 Standard 48 48 Standard 77 77 Standard 87 87 Standard 101 101 Standard 25 25 Standard 7 7 Standard 10 10 Standard 6 6 Standard 10 10 Standard 5 5 Standard 107 107 Standard 25 25 Standard 92 92 Standard 26 26 Standard 158 158 Standard 100 100 Standard 36 36 Standard 43 43 Standard 49 49 Standard 54 54 Standard 26 26 Standard 32 32 Standard 43 43 Standard 44 44 Standard 19 19 Standard 137 137 Standard 8 8 Standard 60 60 Standard 260 260 Standard 16 16 Standard 267 267 Standard 65 65 Standard 73 73 Standard 10 10 Standard 4 4 Standard 10 10 Standard 55 55 Standard 28 28 Standard 22 22 Standard 58 58 Standard 39 39 Standard 11 11 Standard 13 13 Standard 57 57 Standard 81 81 Standard 27 27 Standard 52 52 Standard 59 59 Standard 17 17 Standard 51 51 Standard 89 89 Standard 46 46 Standard 30 30 Standard 54 54 Standard 44 44 Standard 48 48 Standard 77 77 Standard 87 87 Standard 101 101 Standard 25 25 Standard 7 7 Standard 10 10 Standard 6 6 Standard 10 10 Standard 5 5 Standard 107 107 Standard 25 25 Standard 92 92 Standard 26 26 Standard 158 158 Standard 100 100 Standard 36 36 Standard 43 43 Standard 49 49 Standard 54 54 Standard 26 26 Standard 32 32 Standard 43 43 Standard 44 44 Standard 19 19 Standard 137 137 Standard 8 8 Standard 60 60 Standard 260 260 Standard 16 16 Standard 267 267 Standard 65 65 Standard 73 73 Standard 10 10 Standard 4 4 Standard 10 10 Standard 55 55 Standard 28 28 Standard 22 22 Standard 58 58 Standard 39 39 Standard 11 11 Standard 13 13 Standard 57 57 Standard 81 81 Standard 27 27 Standard 52 52 Standard 59 59 Standard 17 17 Standard 51 51 Standard 67 67 Standard 24 24 Standard 11 11 Standard 33 33 Standard 7 7 Standard 25 25 Standard 56 56 Standard 47 47 Standard 111 111 Standard 24 24 Standard 6 6 Standard 3 3 Standard 6 6 Standard 6 6 Standard 47 47 Standard 7 7 Standard 34 34 Standard 3 3 Standard 80 80 Standard 49 49 Standard 24 24 Standard 49 49 Standard 26 26 Standard 51 51 Standard 29 29 Standard 26 26 Standard 22 22 Standard 58 58 Standard 20 20 Standard 100 100 Standard 4 4 Standard 39 39 Standard 174 174 Standard 16 16 Standard 195 195 Standard 59 59 Standard 51 51 Standard 17 17 Standard 9 9 Standard 13 13 Standard 51 51 Standard 6 6 Standard 28 28 Standard 16 16 Standard 10 10 Standard 11 11 Standard 10 10 Standard 7 7 Standard 62 62 Standard 9 9 Standard 37 37 Standard 60 60 Standard 12 12 Standard 62 62 Standard 67 67 Standard 24 24 Standard 11 11 Standard 33 33 Standard 7 7 Standard 25 25 Standard 56 56 Standard 47 47 Standard 111 111 Standard 24 24 Standard 6 6 Standard 3 3 Standard 6 6 Standard 6 6 Standard 47 47 Standard 7 7 Standard 34 34 Standard 3 3 Standard 80 80 Standard 49 49 Standard 24 24 Standard 49 49 Standard 26 26 Standard 51 51 Standard 29 29 Standard 26 26 Standard 22 22 Standard 58 58 Standard 20 20 Standard 100 100 Standard 4 4 Standard 39 39 Standard 174 174 Standard 16 16 Standard 195 195 Standard 59 59 Standard 51 51 Standard 17 17 Standard 9 9 Standard 13 13 Standard 51 51 Standard 6 6 Standard 28 28 Standard 16 16 Standard 10 10 Standard 11 11 Standard 10 10 Standard 7 7 Standard 62 62 Standard 9 9 Standard 37 37 Standard 60 60 Standard 12 12 Standard 62 62 Standard 2 2 Standard 1 1 Standard 2 2 Standard 1 1 Standard 3 3 Standard 2 2 Standard 1 1 Standard 2 2 Standard 2 2 Standard 4 4 Standard 2 2 Standard 2 2 Standard 1 1 Standard 3 3 Standard 2 2 Standard 2 2 Standard 3 3 Standard 2 2 Standard 6 6 Standard 4 4 Standard 4 4 Standard 1 1 Standard 1 1 Standard 1 1 Standard 2 2 Standard 1 1 Standard 1 1 Standard 2 2 Standard 1 1 Standard 2 2 Standard 1 1 Standard 3 3 Standard 2 2 Standard 1 1 Standard 2 2 Standard 2 2 Standard 4 4 Standard 2 2 Standard 2 2 Standard 1 1 Standard 3 3 Standard 2 2 Standard 2 2 Standard 3 3 Standard 2 2 Standard 6 6 Standard 4 4 Standard 4 4 Standard 1 1 Standard 1 1 Standard 1 1 Standard 2 2 Standard 1 1 Standard 1 1 Standard 56 56 Standard 38 38 Standard 14 14 Standard 39 39 Standard 21 21 Standard 41 41 Standard 32 32 Standard 76 76 Standard 89 89 Standard 40 40 Standard 6 6 Standard 15 15 Standard 24 24 Standard 4 4 Standard 49 49 Standard 14 14 Standard 32 32 Standard 13 13 Standard 84 84 Standard 73 73 Standard 13 13 Standard 36 36 Standard 54 54 Standard 51 51 Standard 19 19 Standard 12 12 Standard 24 24 Standard 45 45 Standard 11 11 Standard 123 123 Standard 3 3 Standard 33 33 Standard 165 165 Standard 5 5 Standard 260 260 Standard 47 47 Standard 19 19 Standard 4 4 Standard 16 16 Standard 7 7 Standard 30 30 Standard 7 7 Standard 12 12 Standard 16 16 Standard 28 28 Standard 5 5 Standard 12 12 Standard 59 59 Standard 10 10 Standard 16 16 Standard 72 72 Standard 26 26 Standard 31 31 Standard 56 56 Standard 38 38 Standard 14 14 Standard 39 39 Standard 21 21 Standard 41 41 Standard 32 32 Standard 76 76 Standard 89 89 Standard 40 40 Standard 6 6 Standard 15 15 Standard 24 24 Standard 4 4 Standard 49 49 Standard 14 14 Standard 32 32 Standard 13 13 Standard 84 84 Standard 73 73 Standard 13 13 Standard 36 36 Standard 54 54 Standard 51 51 Standard 19 19 Standard 12 12 Standard 24 24 Standard 45 45 Standard 11 11 Standard 123 123 Standard 3 3 Standard 33 33 Standard 165 165 Standard 5 5 Standard 260 260 Standard 47 47 Standard 19 19 Standard 4 4 Standard 16 16 Standard 7 7 Standard 30 30 Standard 7 7 Standard 12 12 Standard 16 16 Standard 28 28 Standard 5 5 Standard 12 12 Standard 59 59 Standard 10 10 Standard 16 16 Standard 72 72 Standard 26 26 Standard 31 31 Standard 88 88 Standard 35 35 Standard 30 30 Standard 37 37 Standard 32 32 Standard 42 42 Standard 78 78 Standard 95 95 Standard 174 174 Standard 41 41 Standard 15 15 Standard 9 9 Standard 8 8 Standard 2 2 Standard 16 16 Standard 72 72 Standard 19 19 Standard 55 55 Standard 14 14 Standard 113 113 Standard 114 114 Standard 21 21 Standard 49 49 Standard 66 66 Standard 82 82 Standard 54 54 Standard 31 31 Standard 37 37 Standard 89 89 Standard 15 15 Standard 102 102 Standard 9 9 Standard 52 52 Standard 221 221 Standard 22 22 Standard 435 435 Standard 79 79 Standard 53 53 Standard 2 2 Standard 6 6 Standard 3 3 Standard 60 60 Standard 25 25 Standard 26 26 Standard 57 57 Standard 38 38 Standard 9 9 Standard 8 8 Standard 67 67 Standard 59 59 Standard 16 16 Standard 67 67 Standard 69 69 Standard 22 22 Standard 58 58 Standard 88 88 Standard 35 35 Standard 30 30 Standard 37 37 Standard 32 32 Standard 42 42 Standard 78 78 Standard 95 95 Standard 174 174 Standard 41 41 Standard 15 15 Standard 9 9 Standard 8 8 Standard 2 2 Standard 16 16 Standard 72 72 Standard 19 19 Standard 55 55 Standard 14 14 Standard 113 113 Standard 114 114 Standard 21 21 Standard 49 49 Standard 66 66 Standard 82 82 Standard 54 54 Standard 31 31 Standard 37 37 Standard 89 89 Standard 15 15 Standard 102 102 Standard 9 9 Standard 52 52 Standard 221 221 Standard 22 22 Standard 435 435 Standard 79 79 Standard 53 53 Standard 2 2 Standard 6 6 Standard 3 3 Standard 60 60 Standard 25 25 Standard 26 26 Standard 57 57 Standard 38 38 Standard 9 9 Standard 8 8 Standard 67 67 Standard 59 59 Standard 16 16 Standard 67 67 Standard 69 69 Standard 22 22 Standard 58 58 Standard 3 3 Standard 7 7 Standard 2 2 Standard 4 4 Standard 3 3 Standard 2 2 Standard 3 3 Standard 7 7 Standard 2 2 Standard 2 2 Standard 2 2 Standard 2 2 Standard 2 2 Standard 5 5 Standard 14 14 Standard 2 2 Standard 11 11 Standard 3 3 Standard 2 2 Standard 7 7 Standard 7 7 Standard 6 6 Standard 6 6 Standard 1 1 Standard 19 19 Standard 8 8 Standard 2 2 Standard 5 5 Standard 1 1 Standard 2 2 Standard 2 2 Standard 3 3 Standard 4 4 Standard 2 2 Standard 1 1 Standard 4 4 Standard 2 2 Standard 3 3 Standard 3 3 Standard 7 7 Standard 2 2 Standard 4 4 Standard 3 3 Standard 2 2 Standard 3 3 Standard 7 7 Standard 2 2 Standard 2 2 Standard 2 2 Standard 2 2 Standard 2 2 Standard 5 5 Standard 14 14 Standard 2 2 Standard 11 11 Standard 3 3 Standard 2 2 Standard 7 7 Standard 7 7 Standard 6 6 Standard 6 6 Standard 1 1 Standard 19 19 Standard 8 8 Standard 2 2 Standard 5 5 Standard 1 1 Standard 2 2 Standard 2 2 Standard 3 3 Standard 4 4 Standard 2 2 Standard 1 1 Standard 4 4 Standard 2 2 Standard 3 3 Standard 2 2 Standard 2 2 Standard 2 2 Standard 1 1 Standard 2 2 Standard 3 3 Standard 1 1 Standard 1 1 Standard 4 4 Standard 4 4 Standard 1 1 Standard 4 4 Standard 3 3 Standard 1 1 Standard 2 2 Standard 1 1 Standard 4 4 Standard 9 9 Standard 1 1 Standard 3 3 Standard 8 8 Standard 1 1 Standard 2 2 Standard 3 3 Standard 1 1 Standard 4 4 Standard 2 2 Standard 2 2 Standard 2 2 Standard 1 1 Standard 2 2 Standard 3 3 Standard 1 1 Standard 1 1 Standard 4 4 Standard 4 4 Standard 1 1 Standard 4 4 Standard 3 3 Standard 1 1 Standard 2 2 Standard 1 1 Standard 4 4 Standard 9 9 Standard 1 1 Standard 3 3 Standard 8 8 Standard 1 1 Standard 2 2 Standard 3 3 Standard 1 1 Standard 4 4 Standard 2 2 Standard 2 2 Standard 6 6 Standard 3 3 Standard 4 4 Standard 12 12 Standard 10 10 Standard 1 1 Standard 1 1 Standard 2 2 Standard 3 3 Standard 7 7 Standard 4 4 Standard 4 4 Standard 5 5 Standard 2 2 Standard 3 3 Standard 4 4 Standard 6 6 Standard 7 7 Standard 3 3 Standard 16 16 Standard 5 5 Standard 3 3 Standard 1 1 Standard 2 2 Standard 2 2 Standard 2 2 Standard 3 3 Standard 3 3 Standard 4 4 Standard 1 1 Standard 2 2 Standard 2 2 Standard 6 6 Standard 3 3 Standard 4 4 Standard 12 12 Standard 10 10 Standard 1 1 Standard 1 1 Standard 2 2 Standard 3 3 Standard 7 7 Standard 4 4 Standard 4 4 Standard 5 5 Standard 2 2 Standard 3 3 Standard 4 4 Standard 6 6 Standard 7 7 Standard 3 3 Standard 16 16 Standard 5 5 Standard 3 3 Standard 1 1 Standard 2 2 Standard 2 2 Standard 2 2 Standard 3 3 Standard 3 3 Standard 4 4 Standard 1 1 Standard 88 88 Standard 55 55 Standard 16 16 Standard 25 25 Standard 43 43 Standard 62 62 Standard 67 67 Standard 71 71 Standard 110 110 Standard 39 39 Standard 9 9 Standard 7 7 Standard 13 13 Standard 18 18 Standard 11 11 Standard 81 81 Standard 16 16 Standard 78 78 Standard 22 22 Standard 143 143 Standard 77 77 Standard 20 20 Standard 25 25 Standard 52 52 Standard 64 64 Standard 18 18 Standard 29 29 Standard 51 51 Standard 82 82 Standard 19 19 Standard 113 113 Standard 8 8 Standard 52 52 Standard 229 229 Standard 17 17 Standard 335 335 Standard 52 52 Standard 51 51 Standard 7 7 Standard 18 18 Standard 10 10 Standard 63 63 Standard 15 15 Standard 16 16 Standard 19 19 Standard 17 17 Standard 13 13 Standard 11 11 Standard 21 21 Standard 41 41 Standard 10 10 Standard 41 41 Standard 69 69 Standard 35 35 Standard 38 38 Standard 88 88 Standard 55 55 Standard 16 16 Standard 25 25 Standard 43 43 Standard 62 62 Standard 67 67 Standard 71 71 Standard 110 110 Standard 39 39 Standard 9 9 Standard 7 7 Standard 13 13 Standard 18 18 Standard 11 11 Standard 81 81 Standard 16 16 Standard 78 78 Standard 22 22 Standard 143 143 Standard 77 77 Standard 20 20 Standard 25 25 Standard 52 52 Standard 64 64 Standard 18 18 Standard 29 29 Standard 51 51 Standard 82 82 Standard 19 19 Standard 113 113 Standard 8 8 Standard 52 52 Standard 229 229 Standard 17 17 Standard 335 335 Standard 52 52 Standard 51 51 Standard 7 7 Standard 18 18 Standard 10 10 Standard 63 63 Standard 15 15 Standard 16 16 Standard 19 19 Standard 17 17 Standard 13 13 Standard 11 11 Standard 21 21 Standard 41 41 Standard 10 10 Standard 41 41 Standard 69 69 Standard 35 35 Standard 38 38 Standard 3 3 Standard 1 1 Standard 6 6 Standard 2 2 Standard 2 2 Standard 5 5 Standard 2 2 Standard 9 9 Standard 3 3 Standard 1 1 Standard 2 2 Standard 5 5 Standard 2 2 Standard 2 2 Standard 18 18 Standard 4 4 Standard 3 3 Standard 4 4 Standard 2 2 Standard 2 2 Standard 2 2 Standard 2 2 Standard 2 2 Standard 1 1 Standard 8 8 Standard 2 2 Standard 16 16 Standard 2 2 Standard 4 4 Standard 1 1 Standard 3 3 Standard 1 1 Standard 4 4 Standard 1 1 Standard 7 7 Standard 4 4 Standard 1 1 Standard 5 5 Standard 3 3 Standard 1 1 Standard 6 6 Standard 2 2 Standard 2 2 Standard 5 5 Standard 2 2 Standard 9 9 Standard 3 3 Standard 1 1 Standard 2 2 Standard 5 5 Standard 2 2 Standard 2 2 Standard 18 18 Standard 4 4 Standard 3 3 Standard 4 4 Standard 2 2 Standard 2 2 Standard 2 2 Standard 2 2 Standard 2 2 Standard 1 1 Standard 8 8 Standard 2 2 Standard 16 16 Standard 2 2 Standard 4 4 Standard 1 1 Standard 3 3 Standard 1 1 Standard 4 4 Standard 1 1 Standard 7 7 Standard 4 4 Standard 1 1 Standard 5 5 Standard 3 3 Standard 1 1 Standard 5 5 Standard 1 1 Standard 9 9 Standard 3 3 Standard 2 2 Standard 3 3 Standard 3 3 Standard 3 3 Standard 4 4 Standard 1 1 Standard 5 5 Standard 5 5 Standard 4 4 Standard 1 1 Standard 4 4 Standard 4 4 Standard 1 1 Standard 4 4 Standard 7 7 Standard 1 1 Standard 7 7 Standard 1 1 Standard 2 2 Standard 2 2 Standard 4 4 Standard 1 1 Standard 6 6 Standard 4 4 Standard 1 1 Standard 5 5 Standard 3 3 Standard 3 3 Standard 1 1 Standard 5 5 Standard 1 1 Standard 9 9 Standard 3 3 Standard 2 2 Standard 3 3 Standard 3 3 Standard 3 3 Standard 4 4 Standard 1 1 Standard 5 5 Standard 5 5 Standard 4 4 Standard 1 1 Standard 4 4 Standard 4 4 Standard 1 1 Standard 4 4 Standard 7 7 Standard 1 1 Standard 7 7 Standard 1 1 Standard 2 2 Standard 2 2 Standard 4 4 Standard 1 1 Standard 6 6 Standard 4 4 Standard 1 1 Standard 5 5 Standard 3 3 Standard 1 1 Standard 2 2 Standard 2 2 Standard 3 3 Standard 5 5 Standard 3 3 Standard 11 11 Standard 1 1 Standard 12 12 Standard 1 1 Standard 4 4 Standard 3 3 Standard 1 1 Standard 3 3 Standard 1 1 Standard 6 6 Standard 4 4 Standard 5 5 Standard 2 2 Standard 2 2 Standard 23 23 Standard 15 15 Standard 8 8 Standard 2 2 Standard 2 2 Standard 5 5 Standard 4 4 Standard 1 1 Standard 2 2 Standard 4 4 Standard 7 7 Standard 1 1 Standard 2 2 Standard 2 2 Standard 3 3 Standard 5 5 Standard 3 3 Standard 11 11 Standard 1 1 Standard 12 12 Standard 1 1 Standard 4 4 Standard 3 3 Standard 1 1 Standard 3 3 Standard 1 1 Standard 6 6 Standard 4 4 Standard 5 5 Standard 2 2 Standard 2 2 Standard 23 23 Standard 15 15 Standard 8 8 Standard 2 2 Standard 2 2 Standard 5 5 Standard 4 4 Standard 1 1 Standard 2 2 Standard 4 4 Standard 7 7 Standard 29 29 Standard 7 7 Standard 8 8 Standard 29 29 Standard 20 20 Standard 25 25 Standard 24 24 Standard 40 40 Standard 80 80 Standard 19 19 Standard 10 10 Standard 3 3 Standard 4 4 Standard 33 33 Standard 3 3 Standard 36 36 Standard 3 3 Standard 111 111 Standard 66 66 Standard 18 18 Standard 12 12 Standard 36 36 Standard 29 29 Standard 12 12 Standard 7 7 Standard 18 18 Standard 54 54 Standard 6 6 Standard 74 74 Standard 9 9 Standard 17 17 Standard 127 127 Standard 10 10 Standard 161 161 Standard 57 57 Standard 34 34 Standard 5 5 Standard 4 4 Standard 2 2 Standard 28 28 Standard 10 10 Standard 16 16 Standard 18 18 Standard 16 16 Standard 8 8 Standard 32 32 Standard 35 35 Standard 7 7 Standard 30 30 Standard 35 35 Standard 16 16 Standard 42 42 Standard 29 29 Standard 7 7 Standard 8 8 Standard 29 29 Standard 20 20 Standard 25 25 Standard 24 24 Standard 40 40 Standard 80 80 Standard 19 19 Standard 10 10 Standard 3 3 Standard 4 4 Standard 33 33 Standard 3 3 Standard 36 36 Standard 3 3 Standard 111 111 Standard 66 66 Standard 18 18 Standard 12 12 Standard 36 36 Standard 29 29 Standard 12 12 Standard 7 7 Standard 18 18 Standard 54 54 Standard 6 6 Standard 74 74 Standard 9 9 Standard 17 17 Standard 127 127 Standard 10 10 Standard 161 161 Standard 57 57 Standard 34 34 Standard 5 5 Standard 4 4 Standard 2 2 Standard 28 28 Standard 10 10 Standard 16 16 Standard 18 18 Standard 16 16 Standard 8 8 Standard 32 32 Standard 35 35 Standard 7 7 Standard 30 30 Standard 35 35 Standard 16 16 Standard 42 42 Standard 4 4 Standard 2 2 Standard 3 3 Standard 3 3 Standard 4 4 Standard 3 3 Standard 1 1 Standard 3 3 Standard 5 5 Standard 2 2 Standard 1 1 Standard 7 7 Standard 1 1 Standard 3 3 Standard 1 1 Standard 2 2 Standard 2 2 Standard 4 4 Standard 3 3 Standard 3 3 Standard 1 1 Standard 8 8 Standard 4 4 Standard 4 4 Standard 2 2 Standard 2 2 Standard 2 2 Standard 4 4 Standard 3 3 Standard 1 1 Standard 4 4 Standard 2 2 Standard 3 3 Standard 3 3 Standard 4 4 Standard 3 3 Standard 1 1 Standard 3 3 Standard 5 5 Standard 2 2 Standard 1 1 Standard 7 7 Standard 1 1 Standard 3 3 Standard 1 1 Standard 2 2 Standard 2 2 Standard 4 4 Standard 3 3 Standard 3 3 Standard 1 1 Standard 8 8 Standard 4 4 Standard 4 4 Standard 2 2 Standard 2 2 Standard 2 2 Standard 4 4 Standard 3 3 Standard 1 1 Standard 61 61 Standard 38 38 Standard 28 28 Standard 17 17 Standard 28 28 Standard 38 38 Standard 106 106 Standard 92 92 Standard 138 138 Standard 42 42 Standard 7 7 Standard 10 10 Standard 9 9 Standard 3 3 Standard 116 116 Standard 10 10 Standard 34 34 Standard 7 7 Standard 120 120 Standard 101 101 Standard 39 39 Standard 34 34 Standard 37 37 Standard 59 59 Standard 22 22 Standard 17 17 Standard 35 35 Standard 45 45 Standard 20 20 Standard 101 101 Standard 11 11 Standard 41 41 Standard 187 187 Standard 9 9 Standard 364 364 Standard 87 87 Standard 56 56 Standard 2 2 Standard 15 15 Standard 14 14 Standard 59 59 Standard 16 16 Standard 17 17 Standard 23 23 Standard 25 25 Standard 7 7 Standard 10 10 Standard 30 30 Standard 60 60 Standard 22 22 Standard 53 53 Standard 56 56 Standard 22 22 Standard 31 31 Standard 61 61 Standard 38 38 Standard 28 28 Standard 17 17 Standard 28 28 Standard 38 38 Standard 106 106 Standard 92 92 Standard 138 138 Standard 42 42 Standard 7 7 Standard 10 10 Standard 9 9 Standard 3 3 Standard 116 116 Standard 10 10 Standard 34 34 Standard 7 7 Standard 120 120 Standard 101 101 Standard 39 39 Standard 34 34 Standard 37 37 Standard 59 59 Standard 22 22 Standard 17 17 Standard 35 35 Standard 45 45 Standard 20 20 Standard 101 101 Standard 11 11 Standard 41 41 Standard 187 187 Standard 9 9 Standard 364 364 Standard 87 87 Standard 56 56 Standard 2 2 Standard 15 15 Standard 14 14 Standard 59 59 Standard 16 16 Standard 17 17 Standard 23 23 Standard 25 25 Standard 7 7 Standard 10 10 Standard 30 30 Standard 60 60 Standard 22 22 Standard 53 53 Standard 56 56 Standard 22 22 Standard 31 31 Standard 2 2 Standard 3 3 Standard 2 2 Standard 2 2 Standard 2 2 Standard 13 13 Standard 2 2 Standard 2 2 Standard 2 2 Standard 6 6 Standard 1 1 Standard 8 8 Standard 11 11 Standard 7 7 Standard 2 2 Standard 4 4 Standard 3 3 Standard 4 4 Standard 3 3 Standard 1 1 Standard 9 9 Standard 3 3 Standard 9 9 Standard 2 2 Standard 9 9 Standard 2 2 Standard 18 18 Standard 3 3 Standard 1 1 Standard 6 6 Standard 1 1 Standard 1 1 Standard 2 2 Standard 6 6 Standard 3 3 Standard 4 4 Standard 2 2 Standard 2 2 Standard 3 3 Standard 2 2 Standard 2 2 Standard 2 2 Standard 13 13 Standard 2 2 Standard 2 2 Standard 2 2 Standard 6 6 Standard 1 1 Standard 8 8 Standard 11 11 Standard 7 7 Standard 2 2 Standard 4 4 Standard 3 3 Standard 4 4 Standard 3 3 Standard 1 1 Standard 9 9 Standard 3 3 Standard 9 9 Standard 2 2 Standard 9 9 Standard 2 2 Standard 18 18 Standard 3 3 Standard 1 1 Standard 6 6 Standard 1 1 Standard 1 1 Standard 2 2 Standard 6 6 Standard 3 3 Standard 4 4 Standard 2 2 Standard 8 8 Standard 5 5 Standard 3 3 Standard 1 1 Standard 2 2 Standard 1 1 Standard 9 9 Standard 7 7 Standard 10 10 Standard 2 2 Standard 3 3 Standard 12 12 Standard 1 1 Standard 14 14 Standard 1 1 Standard 5 5 Standard 1 1 Standard 4 4 Standard 2 2 Standard 3 3 Standard 2 2 Standard 4 4 Standard 4 4 Standard 8 8 Standard 14 14 Standard 1 1 Standard 15 15 Standard 4 4 Standard 3 3 Standard 2 2 Standard 2 2 Standard 1 1 Standard 3 3 Standard 4 4 Standard 4 4 Standard 1 1 Standard 2 2 Standard 1 1 Standard 7 7 Standard 1 1 Standard 2 2 Standard 2 2 Standard 4 4 Standard 8 8 Standard 5 5 Standard 3 3 Standard 1 1 Standard 2 2 Standard 1 1 Standard 9 9 Standard 7 7 Standard 10 10 Standard 2 2 Standard 3 3 Standard 12 12 Standard 1 1 Standard 14 14 Standard 1 1 Standard 5 5 Standard 1 1 Standard 4 4 Standard 2 2 Standard 3 3 Standard 2 2 Standard 4 4 Standard 4 4 Standard 8 8 Standard 14 14 Standard 1 1 Standard 15 15 Standard 4 4 Standard 3 3 Standard 2 2 Standard 2 2 Standard 1 1 Standard 3 3 Standard 4 4 Standard 4 4 Standard 1 1 Standard 2 2 Standard 1 1 Standard 7 7 Standard 1 1 Standard 2 2 Standard 2 2 Standard 4 4 Standard 50 50 Standard 51 51 Standard 36 36 Standard 31 31 Standard 44 44 Standard 47 47 Standard 57 57 Standard 83 83 Standard 140 140 Standard 40 40 Standard 5 5 Standard 3 3 Standard 11 11 Standard 80 80 Standard 5 5 Standard 70 70 Standard 14 14 Standard 128 128 Standard 99 99 Standard 32 32 Standard 40 40 Standard 36 36 Standard 56 56 Standard 29 29 Standard 26 26 Standard 32 32 Standard 75 75 Standard 26 26 Standard 78 78 Standard 7 7 Standard 73 73 Standard 245 245 Standard 27 27 Standard 292 292 Standard 46 46 Standard 37 37 Standard 2 2 Standard 3 3 Standard 2 2 Standard 73 73 Standard 8 8 Standard 14 14 Standard 48 48 Standard 32 32 Standard 7 7 Standard 14 14 Standard 39 39 Standard 70 70 Standard 25 25 Standard 36 36 Standard 69 69 Standard 20 20 Standard 47 47 Standard 50 50 Standard 51 51 Standard 36 36 Standard 31 31 Standard 44 44 Standard 47 47 Standard 57 57 Standard 83 83 Standard 140 140 Standard 40 40 Standard 5 5 Standard 3 3 Standard 11 11 Standard 80 80 Standard 5 5 Standard 70 70 Standard 14 14 Standard 128 128 Standard 99 99 Standard 32 32 Standard 40 40 Standard 36 36 Standard 56 56 Standard 29 29 Standard 26 26 Standard 32 32 Standard 75 75 Standard 26 26 Standard 78 78 Standard 7 7 Standard 73 73 Standard 245 245 Standard 27 27 Standard 292 292 Standard 46 46 Standard 37 37 Standard 2 2 Standard 3 3 Standard 2 2 Standard 73 73 Standard 8 8 Standard 14 14 Standard 48 48 Standard 32 32 Standard 7 7 Standard 14 14 Standard 39 39 Standard 70 70 Standard 25 25 Standard 36 36 Standard 69 69 Standard 20 20 Standard 47 47 Standard 50 50 Standard 21 21 Standard 24 24 Standard 29 29 Standard 33 33 Standard 32 32 Standard 57 57 Standard 47 47 Standard 74 74 Standard 17 17 Standard 3 3 Standard 14 14 Standard 5 5 Standard 44 44 Standard 15 15 Standard 37 37 Standard 16 16 Standard 105 105 Standard 51 51 Standard 20 20 Standard 20 20 Standard 35 35 Standard 44 44 Standard 15 15 Standard 18 18 Standard 20 20 Standard 37 37 Standard 10 10 Standard 55 55 Standard 3 3 Standard 29 29 Standard 149 149 Standard 8 8 Standard 212 212 Standard 49 49 Standard 46 46 Standard 18 18 Standard 7 7 Standard 40 40 Standard 14 14 Standard 10 10 Standard 27 27 Standard 17 17 Standard 4 4 Standard 2 2 Standard 27 27 Standard 58 58 Standard 9 9 Standard 32 32 Standard 39 39 Standard 13 13 Standard 29 29 Standard 50 50 Standard 21 21 Standard 24 24 Standard 29 29 Standard 33 33 Standard 32 32 Standard 57 57 Standard 47 47 Standard 74 74 Standard 17 17 Standard 3 3 Standard 14 14 Standard 5 5 Standard 44 44 Standard 15 15 Standard 37 37 Standard 16 16 Standard 105 105 Standard 51 51 Standard 20 20 Standard 20 20 Standard 35 35 Standard 44 44 Standard 15 15 Standard 18 18 Standard 20 20 Standard 37 37 Standard 10 10 Standard 55 55 Standard 3 3 Standard 29 29 Standard 149 149 Standard 8 8 Standard 212 212 Standard 49 49 Standard 46 46 Standard 18 18 Standard 7 7 Standard 40 40 Standard 14 14 Standard 10 10 Standard 27 27 Standard 17 17 Standard 4 4 Standard 2 2 Standard 27 27 Standard 58 58 Standard 9 9 Standard 32 32 Standard 39 39 Standard 13 13 Standard 29 29 Standard 64 64 Standard 13 13 Standard 28 28 Standard 48 48 Standard 24 24 Standard 56 56 Standard 78 78 Standard 106 106 Standard 128 128 Standard 49 49 Standard 13 13 Standard 10 10 Standard 9 9 Standard 8 8 Standard 6 6 Standard 74 74 Standard 8 8 Standard 74 74 Standard 17 17 Standard 91 91 Standard 76 76 Standard 39 39 Standard 48 48 Standard 54 54 Standard 60 60 Standard 28 28 Standard 19 19 Standard 25 25 Standard 59 59 Standard 25 25 Standard 99 99 Standard 9 9 Standard 35 35 Standard 226 226 Standard 24 24 Standard 270 270 Standard 66 66 Standard 49 49 Standard 4 4 Standard 4 4 Standard 3 3 Standard 56 56 Standard 13 13 Standard 4 4 Standard 24 24 Standard 40 40 Standard 11 11 Standard 2 2 Standard 31 31 Standard 52 52 Standard 23 23 Standard 37 37 Standard 63 63 Standard 19 19 Standard 69 69 Standard 64 64 Standard 13 13 Standard 28 28 Standard 48 48 Standard 24 24 Standard 56 56 Standard 78 78 Standard 106 106 Standard 128 128 Standard 49 49 Standard 13 13 Standard 10 10 Standard 9 9 Standard 8 8 Standard 6 6 Standard 74 74 Standard 8 8 Standard 74 74 Standard 17 17 Standard 91 91 Standard 76 76 Standard 39 39 Standard 48 48 Standard 54 54 Standard 60 60 Standard 28 28 Standard 19 19 Standard 25 25 Standard 59 59 Standard 25 25 Standard 99 99 Standard 9 9 Standard 35 35 Standard 226 226 Standard 24 24 Standard 270 270 Standard 66 66 Standard 49 49 Standard 4 4 Standard 4 4 Standard 3 3 Standard 56 56 Standard 13 13 Standard 4 4 Standard 24 24 Standard 40 40 Standard 11 11 Standard 2 2 Standard 31 31 Standard 52 52 Standard 23 23 Standard 37 37 Standard 63 63 Standard 19 19 Standard 69 69 Standard 77 77 Standard 39 39 Standard 29 29 Standard 33 33 Standard 43 43 Standard 44 44 Standard 87 87 Standard 103 103 Standard 109 109 Standard 42 42 Standard 10 10 Standard 11 11 Standard 13 13 Standard 12 12 Standard 17 17 Standard 91 91 Standard 20 20 Standard 45 45 Standard 16 16 Standard 137 137 Standard 67 67 Standard 26 26 Standard 28 28 Standard 52 52 Standard 38 38 Standard 16 16 Standard 13 13 Standard 19 19 Standard 65 65 Standard 9 9 Standard 98 98 Standard 6 6 Standard 38 38 Standard 171 171 Standard 12 12 Standard 318 318 Standard 32 32 Standard 32 32 Standard 10 10 Standard 7 7 Standard 12 12 Standard 51 51 Standard 15 15 Standard 19 19 Standard 37 37 Standard 45 45 Standard 10 10 Standard 30 30 Standard 52 52 Standard 15 15 Standard 50 50 Standard 58 58 Standard 26 26 Standard 48 48 Standard 77 77 Standard 39 39 Standard 29 29 Standard 33 33 Standard 43 43 Standard 44 44 Standard 87 87 Standard 103 103 Standard 109 109 Standard 42 42 Standard 10 10 Standard 11 11 Standard 13 13 Standard 12 12 Standard 17 17 Standard 91 91 Standard 20 20 Standard 45 45 Standard 16 16 Standard 137 137 Standard 67 67 Standard 26 26 Standard 28 28 Standard 52 52 Standard 38 38 Standard 16 16 Standard 13 13 Standard 19 19 Standard 65 65 Standard 9 9 Standard 98 98 Standard 6 6 Standard 38 38 Standard 171 171 Standard 12 12 Standard 318 318 Standard 32 32 Standard 32 32 Standard 10 10 Standard 7 7 Standard 12 12 Standard 51 51 Standard 15 15 Standard 19 19 Standard 37 37 Standard 45 45 Standard 10 10 Standard 30 30 Standard 52 52 Standard 15 15 Standard 50 50 Standard 58 58 Standard 26 26 Standard 48 48 Standard 81 81 Standard 12 12 Standard 31 31 Standard 21 21 Standard 36 36 Standard 42 42 Standard 94 94 Standard 54 54 Standard 72 72 Standard 22 22 Standard 6 6 Standard 7 7 Standard 4 4 Standard 2 2 Standard 73 73 Standard 14 14 Standard 47 47 Standard 8 8 Standard 113 113 Standard 117 117 Standard 16 16 Standard 41 41 Standard 57 57 Standard 92 92 Standard 26 26 Standard 28 28 Standard 27 27 Standard 62 62 Standard 10 10 Standard 104 104 Standard 7 7 Standard 37 37 Standard 218 218 Standard 21 21 Standard 293 293 Standard 79 79 Standard 38 38 Standard 6 6 Standard 9 9 Standard 9 9 Standard 65 65 Standard 6 6 Standard 14 14 Standard 33 33 Standard 26 26 Standard 4 4 Standard 13 13 Standard 38 38 Standard 95 95 Standard 18 18 Standard 48 48 Standard 54 54 Standard 28 28 Standard 38 38 Standard 81 81 Standard 12 12 Standard 31 31 Standard 21 21 Standard 36 36 Standard 42 42 Standard 94 94 Standard 54 54 Standard 72 72 Standard 22 22 Standard 6 6 Standard 7 7 Standard 4 4 Standard 2 2 Standard 73 73 Standard 14 14 Standard 47 47 Standard 8 8 Standard 113 113 Standard 117 117 Standard 16 16 Standard 41 41 Standard 57 57 Standard 92 92 Standard 26 26 Standard 28 28 Standard 27 27 Standard 62 62 Standard 10 10 Standard 104 104 Standard 7 7 Standard 37 37 Standard 218 218 Standard 21 21 Standard 293 293 Standard 79 79 Standard 38 38 Standard 6 6 Standard 9 9 Standard 9 9 Standard 65 65 Standard 6 6 Standard 14 14 Standard 33 33 Standard 26 26 Standard 4 4 Standard 13 13 Standard 38 38 Standard 95 95 Standard 18 18 Standard 48 48 Standard 54 54 Standard 28 28 Standard 38 38 Standard 158 158 Standard 83 83 Standard 77 77 Standard 100 100 Standard 58 58 Standard 102 102 Standard 239 239 Standard 209 209 Standard 261 261 Standard 79 79 Standard 28 28 Standard 24 24 Standard 5 5 Standard 28 28 Standard 19 19 Standard 190 190 Standard 28 28 Standard 140 140 Standard 59 59 Standard 305 305 Standard 249 249 Standard 64 64 Standard 98 98 Standard 122 122 Standard 156 156 Standard 81 81 Standard 82 82 Standard 60 60 Standard 194 194 Standard 75 75 Standard 323 323 Standard 28 28 Standard 83 83 Standard 589 589 Standard 36 36 Standard 720 720 Standard 168 168 Standard 109 109 Standard 35 35 Standard 11 11 Standard 24 24 Standard 159 159 Standard 33 33 Standard 45 45 Standard 129 129 Standard 69 69 Standard 35 35 Standard 23 23 Standard 101 101 Standard 204 204 Standard 57 57 Standard 106 106 Standard 183 183 Standard 56 56 Standard 107 107 Standard 158 158 Standard 83 83 Standard 77 77 Standard 100 100 Standard 58 58 Standard 102 102 Standard 239 239 Standard 209 209 Standard 261 261 Standard 79 79 Standard 28 28 Standard 24 24 Standard 5 5 Standard 28 28 Standard 19 19 Standard 190 190 Standard 28 28 Standard 140 140 Standard 59 59 Standard 305 305 Standard 249 249 Standard 64 64 Standard 98 98 Standard 122 122 Standard 156 156 Standard 81 81 Standard 82 82 Standard 60 60 Standard 194 194 Standard 75 75 Standard 323 323 Standard 28 28 Standard 83 83 Standard 589 589 Standard 36 36 Standard 720 720 Standard 168 168 Standard 109 109 Standard 35 35 Standard 11 11 Standard 24 24 Standard 159 159 Standard 33 33 Standard 45 45 Standard 129 129 Standard 69 69 Standard 35 35 Standard 23 23 Standard 101 101 Standard 204 204 Standard 57 57 Standard 106 106 Standard 183 183 Standard 56 56 Standard 107 107 Standard 137 137 Standard 40 40 Standard 43 43 Standard 91 91 Standard 60 60 Standard 64 64 Standard 162 162 Standard 127 127 Standard 171 171 Standard 52 52 Standard 6 6 Standard 38 38 Standard 13 13 Standard 14 14 Standard 13 13 Standard 121 121 Standard 31 31 Standard 66 66 Standard 35 35 Standard 200 200 Standard 149 149 Standard 50 50 Standard 62 62 Standard 95 95 Standard 127 127 Standard 47 47 Standard 34 34 Standard 48 48 Standard 126 126 Standard 33 33 Standard 187 187 Standard 3 3 Standard 88 88 Standard 312 312 Standard 51 51 Standard 568 568 Standard 136 136 Standard 89 89 Standard 12 12 Standard 19 19 Standard 18 18 Standard 77 77 Standard 35 35 Standard 42 42 Standard 63 63 Standard 57 57 Standard 26 26 Standard 24 24 Standard 51 51 Standard 138 138 Standard 17 17 Standard 80 80 Standard 103 103 Standard 49 49 Standard 58 58 Standard 137 137 Standard 40 40 Standard 43 43 Standard 91 91 Standard 60 60 Standard 64 64 Standard 162 162 Standard 127 127 Standard 171 171 Standard 52 52 Standard 6 6 Standard 38 38 Standard 13 13 Standard 14 14 Standard 13 13 Standard 121 121 Standard 31 31 Standard 66 66 Standard 35 35 Standard 200 200 Standard 149 149 Standard 50 50 Standard 62 62 Standard 95 95 Standard 127 127 Standard 47 47 Standard 34 34 Standard 48 48 Standard 126 126 Standard 33 33 Standard 187 187 Standard 3 3 Standard 88 88 Standard 312 312 Standard 51 51 Standard 568 568 Standard 136 136 Standard 89 89 Standard 12 12 Standard 19 19 Standard 18 18 Standard 77 77 Standard 35 35 Standard 42 42 Standard 63 63 Standard 57 57 Standard 26 26 Standard 24 24 Standard 51 51 Standard 138 138 Standard 17 17 Standard 80 80 Standard 103 103 Standard 49 49 Standard 58 58 Standard 239 239 Standard 132 132 Standard 69 69 Standard 168 168 Standard 107 107 Standard 145 145 Standard 301 301 Standard 264 264 Standard 436 436 Standard 129 129 Standard 65 65 Standard 39 39 Standard 22 22 Standard 34 34 Standard 27 27 Standard 290 290 Standard 90 90 Standard 164 164 Standard 69 69 Standard 446 446 Standard 383 383 Standard 58 58 Standard 145 145 Standard 156 156 Standard 191 191 Standard 111 111 Standard 94 94 Standard 122 122 Standard 256 256 Standard 55 55 Standard 413 413 Standard 35 35 Standard 148 148 Standard 756 756 Standard 43 43 Standard 1163 1,163 Standard 216 216 Standard 195 195 Standard 42 42 Standard 26 26 Standard 31 31 Standard 228 228 Standard 55 55 Standard 68 68 Standard 139 139 Standard 114 114 Standard 20 20 Standard 36 36 Standard 104 104 Standard 255 255 Standard 63 63 Standard 106 106 Standard 215 215 Standard 98 98 Standard 163 163 Standard 239 239 Standard 132 132 Standard 69 69 Standard 168 168 Standard 107 107 Standard 145 145 Standard 301 301 Standard 264 264 Standard 436 436 Standard 129 129 Standard 65 65 Standard 39 39 Standard 22 22 Standard 34 34 Standard 27 27 Standard 290 290 Standard 90 90 Standard 164 164 Standard 69 69 Standard 446 446 Standard 383 383 Standard 58 58 Standard 145 145 Standard 156 156 Standard 191 191 Standard 111 111 Standard 94 94 Standard 122 122 Standard 256 256 Standard 55 55 Standard 413 413 Standard 35 35 Standard 148 148 Standard 756 756 Standard 43 43 Standard 1163 1,163 Standard 216 216 Standard 195 195 Standard 42 42 Standard 26 26 Standard 31 31 Standard 228 228 Standard 55 55 Standard 68 68 Standard 139 139 Standard 114 114 Standard 20 20 Standard 36 36 Standard 104 104 Standard 255 255 Standard 63 63 Standard 106 106 Standard 215 215 Standard 98 98 Standard 163 163 Standard 107 107 Standard 79 79 Standard 49 49 Standard 34 34 Standard 61 61 Standard 86 86 Standard 131 131 Standard 135 135 Standard 206 206 Standard 69 69 Standard 10 10 Standard 21 21 Standard 9 9 Standard 16 16 Standard 20 20 Standard 131 131 Standard 25 25 Standard 82 82 Standard 27 27 Standard 210 210 Standard 179 179 Standard 48 48 Standard 80 80 Standard 70 70 Standard 133 133 Standard 48 48 Standard 58 58 Standard 70 70 Standard 109 109 Standard 35 35 Standard 191 191 Standard 17 17 Standard 82 82 Standard 421 421 Standard 30 30 Standard 617 617 Standard 129 129 Standard 89 89 Standard 31 31 Standard 10 10 Standard 23 23 Standard 97 97 Standard 46 46 Standard 44 44 Standard 58 58 Standard 51 51 Standard 17 17 Standard 26 26 Standard 71 71 Standard 130 130 Standard 37 37 Standard 105 105 Standard 108 108 Standard 53 53 Standard 89 89 Standard 107 107 Standard 79 79 Standard 49 49 Standard 34 34 Standard 61 61 Standard 86 86 Standard 131 131 Standard 135 135 Standard 206 206 Standard 69 69 Standard 10 10 Standard 21 21 Standard 9 9 Standard 16 16 Standard 20 20 Standard 131 131 Standard 25 25 Standard 82 82 Standard 27 27 Standard 210 210 Standard 179 179 Standard 48 48 Standard 80 80 Standard 70 70 Standard 133 133 Standard 48 48 Standard 58 58 Standard 70 70 Standard 109 109 Standard 35 35 Standard 191 191 Standard 17 17 Standard 82 82 Standard 421 421 Standard 30 30 Standard 617 617 Standard 129 129 Standard 89 89 Standard 31 31 Standard 10 10 Standard 23 23 Standard 97 97 Standard 46 46 Standard 44 44 Standard 58 58 Standard 51 51 Standard 17 17 Standard 26 26 Standard 71 71 Standard 130 130 Standard 37 37 Standard 105 105 Standard 108 108 Standard 53 53 Standard 89 89 Standard 238 238 Standard 96 96 Standard 109 109 Standard 151 151 Standard 136 136 Standard 108 108 Standard 213 213 Standard 277 277 Standard 479 479 Standard 101 101 Standard 28 28 Standard 33 33 Standard 33 33 Standard 28 28 Standard 15 15 Standard 251 251 Standard 64 64 Standard 175 175 Standard 70 70 Standard 472 472 Standard 338 338 Standard 100 100 Standard 191 191 Standard 172 172 Standard 245 245 Standard 106 106 Standard 117 117 Standard 103 103 Standard 268 268 Standard 60 60 Standard 424 424 Standard 38 38 Standard 140 140 Standard 775 775 Standard 73 73 Standard 1091 1,091 Standard 255 255 Standard 197 197 Standard 28 28 Standard 50 50 Standard 26 26 Standard 195 195 Standard 73 73 Standard 45 45 Standard 150 150 Standard 138 138 Standard 29 29 Standard 33 33 Standard 108 108 Standard 253 253 Standard 63 63 Standard 121 121 Standard 249 249 Standard 95 95 Standard 170 170 Standard 238 238 Standard 96 96 Standard 109 109 Standard 151 151 Standard 136 136 Standard 108 108 Standard 213 213 Standard 277 277 Standard 479 479 Standard 101 101 Standard 28 28 Standard 33 33 Standard 33 33 Standard 28 28 Standard 15 15 Standard 251 251 Standard 64 64 Standard 175 175 Standard 70 70 Standard 472 472 Standard 338 338 Standard 100 100 Standard 191 191 Standard 172 172 Standard 245 245 Standard 106 106 Standard 117 117 Standard 103 103 Standard 268 268 Standard 60 60 Standard 424 424 Standard 38 38 Standard 140 140 Standard 775 775 Standard 73 73 Standard 1091 1,091 Standard 255 255 Standard 197 197 Standard 28 28 Standard 50 50 Standard 26 26 Standard 195 195 Standard 73 73 Standard 45 45 Standard 150 150 Standard 138 138 Standard 29 29 Standard 33 33 Standard 108 108 Standard 253 253 Standard 63 63 Standard 121 121 Standard 249 249 Standard 95 95 Standard 170 170 Standard 159 159 Standard 49 49 Standard 40 40 Standard 87 87 Standard 64 64 Standard 85 85 Standard 184 184 Standard 185 185 Standard 172 172 Standard 86 86 Standard 30 30 Standard 21 21 Standard 15 15 Standard 16 16 Standard 19 19 Standard 180 180 Standard 31 31 Standard 96 96 Standard 35 35 Standard 238 238 Standard 145 145 Standard 56 56 Standard 76 76 Standard 76 76 Standard 116 116 Standard 41 41 Standard 37 37 Standard 41 41 Standard 147 147 Standard 27 27 Standard 256 256 Standard 2 2 Standard 70 70 Standard 391 391 Standard 34 34 Standard 607 607 Standard 160 160 Standard 150 150 Standard 19 19 Standard 26 26 Standard 6 6 Standard 136 136 Standard 16 16 Standard 34 34 Standard 53 53 Standard 64 64 Standard 9 9 Standard 15 15 Standard 77 77 Standard 124 124 Standard 23 23 Standard 86 86 Standard 110 110 Standard 39 39 Standard 84 84 Standard 159 159 Standard 49 49 Standard 40 40 Standard 87 87 Standard 64 64 Standard 85 85 Standard 184 184 Standard 185 185 Standard 172 172 Standard 86 86 Standard 30 30 Standard 21 21 Standard 15 15 Standard 16 16 Standard 19 19 Standard 180 180 Standard 31 31 Standard 96 96 Standard 35 35 Standard 238 238 Standard 145 145 Standard 56 56 Standard 76 76 Standard 76 76 Standard 116 116 Standard 41 41 Standard 37 37 Standard 41 41 Standard 147 147 Standard 27 27 Standard 256 256 Standard 2 2 Standard 70 70 Standard 391 391 Standard 34 34 Standard 607 607 Standard 160 160 Standard 150 150 Standard 19 19 Standard 26 26 Standard 6 6 Standard 136 136 Standard 16 16 Standard 34 34 Standard 53 53 Standard 64 64 Standard 9 9 Standard 15 15 Standard 77 77 Standard 124 124 Standard 23 23 Standard 86 86 Standard 110 110 Standard 39 39 Standard 84 84 Standard 76 76 Standard 54 54 Standard 30 30 Standard 38 38 Standard 35 35 Standard 75 75 Standard 116 116 Standard 94 94 Standard 158 158 Standard 42 42 Standard 16 16 Standard 8 8 Standard 15 15 Standard 13 13 Standard 8 8 Standard 126 126 Standard 40 40 Standard 76 76 Standard 18 18 Standard 187 187 Standard 130 130 Standard 24 24 Standard 76 76 Standard 84 84 Standard 62 62 Standard 58 58 Standard 43 43 Standard 41 41 Standard 75 75 Standard 32 32 Standard 160 160 Standard 8 8 Standard 60 60 Standard 285 285 Standard 25 25 Standard 408 408 Standard 78 78 Standard 67 67 Standard 16 16 Standard 9 9 Standard 14 14 Standard 69 69 Standard 34 34 Standard 27 27 Standard 64 64 Standard 46 46 Standard 13 13 Standard 6 6 Standard 36 36 Standard 119 119 Standard 33 33 Standard 64 64 Standard 126 126 Standard 38 38 Standard 53 53 Standard 76 76 Standard 54 54 Standard 30 30 Standard 38 38 Standard 35 35 Standard 75 75 Standard 116 116 Standard 94 94 Standard 158 158 Standard 42 42 Standard 16 16 Standard 8 8 Standard 15 15 Standard 13 13 Standard 8 8 Standard 126 126 Standard 40 40 Standard 76 76 Standard 18 18 Standard 187 187 Standard 130 130 Standard 24 24 Standard 76 76 Standard 84 84 Standard 62 62 Standard 58 58 Standard 43 43 Standard 41 41 Standard 75 75 Standard 32 32 Standard 160 160 Standard 8 8 Standard 60 60 Standard 285 285 Standard 25 25 Standard 408 408 Standard 78 78 Standard 67 67 Standard 16 16 Standard 9 9 Standard 14 14 Standard 69 69 Standard 34 34 Standard 27 27 Standard 64 64 Standard 46 46 Standard 13 13 Standard 6 6 Standard 36 36 Standard 119 119 Standard 33 33 Standard 64 64 Standard 126 126 Standard 38 38 Standard 53 53 Standard 112 112 Standard 48 48 Standard 31 31 Standard 72 72 Standard 59 59 Standard 66 66 Standard 85 85 Standard 96 96 Standard 147 147 Standard 26 26 Standard 23 23 Standard 13 13 Standard 6 6 Standard 8 8 Standard 8 8 Standard 114 114 Standard 25 25 Standard 54 54 Standard 24 24 Standard 139 139 Standard 142 142 Standard 33 33 Standard 63 63 Standard 43 43 Standard 73 73 Standard 27 27 Standard 39 39 Standard 44 44 Standard 63 63 Standard 27 27 Standard 149 149 Standard 15 15 Standard 60 60 Standard 282 282 Standard 27 27 Standard 399 399 Standard 113 113 Standard 89 89 Standard 21 21 Standard 7 7 Standard 15 15 Standard 103 103 Standard 18 18 Standard 26 26 Standard 39 39 Standard 53 53 Standard 19 19 Standard 21 21 Standard 21 21 Standard 88 88 Standard 19 19 Standard 60 60 Standard 124 124 Standard 39 39 Standard 66 66 Standard 112 112 Standard 48 48 Standard 31 31 Standard 72 72 Standard 59 59 Standard 66 66 Standard 85 85 Standard 96 96 Standard 147 147 Standard 26 26 Standard 23 23 Standard 13 13 Standard 6 6 Standard 8 8 Standard 8 8 Standard 114 114 Standard 25 25 Standard 54 54 Standard 24 24 Standard 139 139 Standard 142 142 Standard 33 33 Standard 63 63 Standard 43 43 Standard 73 73 Standard 27 27 Standard 39 39 Standard 44 44 Standard 63 63 Standard 27 27 Standard 149 149 Standard 15 15 Standard 60 60 Standard 282 282 Standard 27 27 Standard 399 399 Standard 113 113 Standard 89 89 Standard 21 21 Standard 7 7 Standard 15 15 Standard 103 103 Standard 18 18 Standard 26 26 Standard 39 39 Standard 53 53 Standard 19 19 Standard 21 21 Standard 21 21 Standard 88 88 Standard 19 19 Standard 60 60 Standard 124 124 Standard 39 39 Standard 66 66 Standard 164 164 Standard 113 113 Standard 73 73 Standard 103 103 Standard 107 107 Standard 119 119 Standard 187 187 Standard 222 222 Standard 297 297 Standard 89 89 Standard 19 19 Standard 31 31 Standard 24 24 Standard 20 20 Standard 25 25 Standard 273 273 Standard 59 59 Standard 146 146 Standard 35 35 Standard 386 386 Standard 302 302 Standard 80 80 Standard 112 112 Standard 150 150 Standard 187 187 Standard 72 72 Standard 53 53 Standard 128 128 Standard 217 217 Standard 68 68 Standard 370 370 Standard 26 26 Standard 114 114 Standard 627 627 Standard 54 54 Standard 784 784 Standard 185 185 Standard 117 117 Standard 12 12 Standard 22 22 Standard 20 20 Standard 216 216 Standard 50 50 Standard 43 43 Standard 89 89 Standard 95 95 Standard 19 19 Standard 17 17 Standard 158 158 Standard 198 198 Standard 71 71 Standard 133 133 Standard 216 216 Standard 64 64 Standard 117 117 Standard 164 164 Standard 113 113 Standard 73 73 Standard 103 103 Standard 107 107 Standard 119 119 Standard 187 187 Standard 222 222 Standard 297 297 Standard 89 89 Standard 19 19 Standard 31 31 Standard 24 24 Standard 20 20 Standard 25 25 Standard 273 273 Standard 59 59 Standard 146 146 Standard 35 35 Standard 386 386 Standard 302 302 Standard 80 80 Standard 112 112 Standard 150 150 Standard 187 187 Standard 72 72 Standard 53 53 Standard 128 128 Standard 217 217 Standard 68 68 Standard 370 370 Standard 26 26 Standard 114 114 Standard 627 627 Standard 54 54 Standard 784 784 Standard 185 185 Standard 117 117 Standard 12 12 Standard 22 22 Standard 20 20 Standard 216 216 Standard 50 50 Standard 43 43 Standard 89 89 Standard 95 95 Standard 19 19 Standard 17 17 Standard 158 158 Standard 198 198 Standard 71 71 Standard 133 133 Standard 216 216 Standard 64 64 Standard 117 117 Standard 103 103 Standard 68 68 Standard 36 36 Standard 40 40 Standard 43 43 Standard 50 50 Standard 112 112 Standard 107 107 Standard 186 186 Standard 67 67 Standard 5 5 Standard 12 12 Standard 27 27 Standard 9 9 Standard 4 4 Standard 137 137 Standard 19 19 Standard 103 103 Standard 39 39 Standard 155 155 Standard 164 164 Standard 33 33 Standard 73 73 Standard 62 62 Standard 109 109 Standard 33 33 Standard 35 35 Standard 59 59 Standard 123 123 Standard 16 16 Standard 181 181 Standard 10 10 Standard 72 72 Standard 374 374 Standard 32 32 Standard 443 443 Standard 143 143 Standard 107 107 Standard 22 22 Standard 7 7 Standard 11 11 Standard 82 82 Standard 31 31 Standard 17 17 Standard 55 55 Standard 32 32 Standard 11 11 Standard 11 11 Standard 43 43 Standard 136 136 Standard 16 16 Standard 88 88 Standard 129 129 Standard 44 44 Standard 49 49 Standard 103 103 Standard 68 68 Standard 36 36 Standard 40 40 Standard 43 43 Standard 50 50 Standard 112 112 Standard 107 107 Standard 186 186 Standard 67 67 Standard 5 5 Standard 12 12 Standard 27 27 Standard 9 9 Standard 4 4 Standard 137 137 Standard 19 19 Standard 103 103 Standard 39 39 Standard 155 155 Standard 164 164 Standard 33 33 Standard 73 73 Standard 62 62 Standard 109 109 Standard 33 33 Standard 35 35 Standard 59 59 Standard 123 123 Standard 16 16 Standard 181 181 Standard 10 10 Standard 72 72 Standard 374 374 Standard 32 32 Standard 443 443 Standard 143 143 Standard 107 107 Standard 22 22 Standard 7 7 Standard 11 11 Standard 82 82 Standard 31 31 Standard 17 17 Standard 55 55 Standard 32 32 Standard 11 11 Standard 11 11 Standard 43 43 Standard 136 136 Standard 16 16 Standard 88 88 Standard 129 129 Standard 44 44 Standard 49 49 Standard 187 187 Standard 96 96 Standard 75 75 Standard 194 194 Standard 87 87 Standard 141 141 Standard 283 283 Standard 217 217 Standard 364 364 Standard 122 122 Standard 23 23 Standard 30 30 Standard 23 23 Standard 29 29 Standard 15 15 Standard 236 236 Standard 64 64 Standard 159 159 Standard 53 53 Standard 393 393 Standard 247 247 Standard 64 64 Standard 143 143 Standard 113 113 Standard 155 155 Standard 45 45 Standard 70 70 Standard 80 80 Standard 173 173 Standard 41 41 Standard 354 354 Standard 43 43 Standard 147 147 Standard 635 635 Standard 46 46 Standard 989 989 Standard 248 248 Standard 143 143 Standard 10 10 Standard 28 28 Standard 43 43 Standard 131 131 Standard 70 70 Standard 55 55 Standard 112 112 Standard 70 70 Standard 8 8 Standard 34 34 Standard 103 103 Standard 213 213 Standard 68 68 Standard 123 123 Standard 184 184 Standard 75 75 Standard 107 107 Standard 187 187 Standard 96 96 Standard 75 75 Standard 194 194 Standard 87 87 Standard 141 141 Standard 283 283 Standard 217 217 Standard 364 364 Standard 122 122 Standard 23 23 Standard 30 30 Standard 23 23 Standard 29 29 Standard 15 15 Standard 236 236 Standard 64 64 Standard 159 159 Standard 53 53 Standard 393 393 Standard 247 247 Standard 64 64 Standard 143 143 Standard 113 113 Standard 155 155 Standard 45 45 Standard 70 70 Standard 80 80 Standard 173 173 Standard 41 41 Standard 354 354 Standard 43 43 Standard 147 147 Standard 635 635 Standard 46 46 Standard 989 989 Standard 248 248 Standard 143 143 Standard 10 10 Standard 28 28 Standard 43 43 Standard 131 131 Standard 70 70 Standard 55 55 Standard 112 112 Standard 70 70 Standard 8 8 Standard 34 34 Standard 103 103 Standard 213 213 Standard 68 68 Standard 123 123 Standard 184 184 Standard 75 75 Standard 107 107 Standard 27 27 Standard 9 9 Standard 7 7 Standard 4 4 Standard 17 17 Standard 18 18 Standard 18 18 Standard 26 26 Standard 37 37 Standard 10 10 Standard 2 2 Standard 2 2 Standard 2 2 Standard 26 26 Standard 8 8 Standard 15 15 Standard 16 16 Standard 39 39 Standard 34 34 Standard 10 10 Standard 8 8 Standard 19 19 Standard 17 17 Standard 6 6 Standard 12 12 Standard 3 3 Standard 20 20 Standard 4 4 Standard 32 32 Standard 1 1 Standard 11 11 Standard 50 50 Standard 89 89 Standard 12 12 Standard 12 12 Standard 3 3 Standard 16 16 Standard 6 6 Standard 7 7 Standard 9 9 Standard 8 8 Standard 6 6 Standard 5 5 Standard 8 8 Standard 24 24 Standard 3 3 Standard 9 9 Standard 17 17 Standard 5 5 Standard 17 17 Standard 27 27 Standard 9 9 Standard 7 7 Standard 4 4 Standard 17 17 Standard 18 18 Standard 18 18 Standard 26 26 Standard 37 37 Standard 10 10 Standard 2 2 Standard 2 2 Standard 2 2 Standard 26 26 Standard 8 8 Standard 15 15 Standard 16 16 Standard 39 39 Standard 34 34 Standard 10 10 Standard 8 8 Standard 19 19 Standard 17 17 Standard 6 6 Standard 12 12 Standard 3 3 Standard 20 20 Standard 4 4 Standard 32 32 Standard 1 1 Standard 11 11 Standard 50 50 Standard 89 89 Standard 12 12 Standard 12 12 Standard 3 3 Standard 16 16 Standard 6 6 Standard 7 7 Standard 9 9 Standard 8 8 Standard 6 6 Standard 5 5 Standard 8 8 Standard 24 24 Standard 3 3 Standard 9 9 Standard 17 17 Standard 5 5 Standard 17 17 Standard 66 66 Standard 31 31 Standard 17 17 Standard 56 56 Standard 24 24 Standard 20 20 Standard 85 85 Standard 81 81 Standard 119 119 Standard 21 21 Standard 15 15 Standard 7 7 Standard 11 11 Standard 16 16 Standard 10 10 Standard 76 76 Standard 12 12 Standard 34 34 Standard 25 25 Standard 147 147 Standard 72 72 Standard 25 25 Standard 24 24 Standard 45 45 Standard 64 64 Standard 17 17 Standard 34 34 Standard 28 28 Standard 73 73 Standard 13 13 Standard 144 144 Standard 10 10 Standard 51 51 Standard 202 202 Standard 30 30 Standard 306 306 Standard 52 52 Standard 50 50 Standard 2 2 Standard 15 15 Standard 4 4 Standard 46 46 Standard 17 17 Standard 30 30 Standard 45 45 Standard 41 41 Standard 11 11 Standard 8 8 Standard 39 39 Standard 38 38 Standard 4 4 Standard 31 31 Standard 66 66 Standard 26 26 Standard 23 23 Standard 66 66 Standard 31 31 Standard 17 17 Standard 56 56 Standard 24 24 Standard 20 20 Standard 85 85 Standard 81 81 Standard 119 119 Standard 21 21 Standard 15 15 Standard 7 7 Standard 11 11 Standard 16 16 Standard 10 10 Standard 76 76 Standard 12 12 Standard 34 34 Standard 25 25 Standard 147 147 Standard 72 72 Standard 25 25 Standard 24 24 Standard 45 45 Standard 64 64 Standard 17 17 Standard 34 34 Standard 28 28 Standard 73 73 Standard 13 13 Standard 144 144 Standard 10 10 Standard 51 51 Standard 202 202 Standard 30 30 Standard 306 306 Standard 52 52 Standard 50 50 Standard 2 2 Standard 15 15 Standard 4 4 Standard 46 46 Standard 17 17 Standard 30 30 Standard 45 45 Standard 41 41 Standard 11 11 Standard 8 8 Standard 39 39 Standard 38 38 Standard 4 4 Standard 31 31 Standard 66 66 Standard 26 26 Standard 23 23 Standard 18 18 Standard 9 9 Standard 6 6 Standard 12 12 Standard 11 11 Standard 12 12 Standard 40 40 Standard 36 36 Standard 27 27 Standard 12 12 Standard 3 3 Standard 1 1 Standard 1 1 Standard 1 1 Standard 24 24 Standard 5 5 Standard 5 5 Standard 36 36 Standard 40 40 Standard 2 2 Standard 6 6 Standard 25 25 Standard 12 12 Standard 17 17 Standard 13 13 Standard 8 8 Standard 2 2 Standard 10 10 Standard 37 37 Standard 2 2 Standard 12 12 Standard 59 59 Standard 3 3 Standard 85 85 Standard 14 14 Standard 10 10 Standard 1 1 Standard 1 1 Standard 1 1 Standard 15 15 Standard 6 6 Standard 11 11 Standard 12 12 Standard 11 11 Standard 9 9 Standard 4 4 Standard 7 7 Standard 19 19 Standard 1 1 Standard 8 8 Standard 13 13 Standard 11 11 Standard 12 12 Standard 18 18 Standard 9 9 Standard 6 6 Standard 12 12 Standard 11 11 Standard 12 12 Standard 40 40 Standard 36 36 Standard 27 27 Standard 12 12 Standard 3 3 Standard 1 1 Standard 1 1 Standard 1 1 Standard 24 24 Standard 5 5 Standard 5 5 Standard 36 36 Standard 40 40 Standard 2 2 Standard 6 6 Standard 25 25 Standard 12 12 Standard 17 17 Standard 13 13 Standard 8 8 Standard 2 2 Standard 10 10 Standard 37 37 Standard 2 2 Standard 12 12 Standard 59 59 Standard 3 3 Standard 85 85 Standard 14 14 Standard 10 10 Standard 1 1 Standard 1 1 Standard 1 1 Standard 15 15 Standard 6 6 Standard 11 11 Standard 12 12 Standard 11 11 Standard 9 9 Standard 4 4 Standard 7 7 Standard 19 19 Standard 1 1 Standard 8 8 Standard 13 13 Standard 11 11 Standard 12 12 Standard 356 356 Standard 158 158 Standard 138 138 Standard 183 183 Standard 156 156 Standard 169 169 Standard 356 356 Standard 347 347 Standard 574 574 Standard 153 153 Standard 31 31 Standard 43 43 Standard 34 34 Standard 25 25 Standard 26 26 Standard 368 368 Standard 94 94 Standard 242 242 Standard 80 80 Standard 546 546 Standard 435 435 Standard 112 112 Standard 215 215 Standard 190 190 Standard 269 269 Standard 114 114 Standard 158 158 Standard 147 147 Standard 332 332 Standard 69 69 Standard 546 546 Standard 30 30 Standard 221 221 Standard 945 945 Standard 88 88 Standard 1425 1,425 Standard 358 358 Standard 223 223 Standard 34 34 Standard 46 46 Standard 65 65 Standard 236 236 Standard 104 104 Standard 83 83 Standard 147 147 Standard 148 148 Standard 32 32 Standard 22 22 Standard 161 161 Standard 289 289 Standard 40 40 Standard 157 157 Standard 342 342 Standard 122 122 Standard 193 193 Standard 356 356 Standard 158 158 Standard 138 138 Standard 183 183 Standard 156 156 Standard 169 169 Standard 356 356 Standard 347 347 Standard 574 574 Standard 153 153 Standard 31 31 Standard 43 43 Standard 34 34 Standard 25 25 Standard 26 26 Standard 368 368 Standard 94 94 Standard 242 242 Standard 80 80 Standard 546 546 Standard 435 435 Standard 112 112 Standard 215 215 Standard 190 190 Standard 269 269 Standard 114 114 Standard 158 158 Standard 147 147 Standard 332 332 Standard 69 69 Standard 546 546 Standard 30 30 Standard 221 221 Standard 945 945 Standard 88 88 Standard 1425 1,425 Standard 358 358 Standard 223 223 Standard 34 34 Standard 46 46 Standard 65 65 Standard 236 236 Standard 104 104 Standard 83 83 Standard 147 147 Standard 148 148 Standard 32 32 Standard 22 22 Standard 161 161 Standard 289 289 Standard 40 40 Standard 157 157 Standard 342 342 Standard 122 122 Standard 193 193 Standard 62 62 Standard 28 28 Standard 29 29 Standard 50 50 Standard 44 44 Standard 38 38 Standard 66 66 Standard 102 102 Standard 128 128 Standard 43 43 Standard 11 11 Standard 8 8 Standard 8 8 Standard 7 7 Standard 14 14 Standard 103 103 Standard 11 11 Standard 49 49 Standard 13 13 Standard 153 153 Standard 91 91 Standard 24 24 Standard 43 43 Standard 58 58 Standard 88 88 Standard 37 37 Standard 31 31 Standard 32 32 Standard 85 85 Standard 16 16 Standard 126 126 Standard 12 12 Standard 59 59 Standard 198 198 Standard 34 34 Standard 306 306 Standard 56 56 Standard 53 53 Standard 5 5 Standard 12 12 Standard 7 7 Standard 79 79 Standard 20 20 Standard 10 10 Standard 37 37 Standard 37 37 Standard 4 4 Standard 6 6 Standard 25 25 Standard 63 63 Standard 41 41 Standard 58 58 Standard 25 25 Standard 38 38 Standard 62 62 Standard 28 28 Standard 29 29 Standard 50 50 Standard 44 44 Standard 38 38 Standard 66 66 Standard 102 102 Standard 128 128 Standard 43 43 Standard 11 11 Standard 8 8 Standard 8 8 Standard 7 7 Standard 14 14 Standard 103 103 Standard 11 11 Standard 49 49 Standard 13 13 Standard 153 153 Standard 91 91 Standard 24 24 Standard 43 43 Standard 58 58 Standard 88 88 Standard 37 37 Standard 31 31 Standard 32 32 Standard 85 85 Standard 16 16 Standard 126 126 Standard 12 12 Standard 59 59 Standard 198 198 Standard 34 34 Standard 306 306 Standard 56 56 Standard 53 53 Standard 5 5 Standard 12 12 Standard 7 7 Standard 79 79 Standard 20 20 Standard 10 10 Standard 37 37 Standard 37 37 Standard 4 4 Standard 6 6 Standard 25 25 Standard 63 63 Standard 41 41 Standard 58 58 Standard 25 25 Standard 38 38 Standard 29 29 Standard 37 37 Standard 8 8 Standard 21 21 Standard 30 30 Standard 41 41 Standard 47 47 Standard 68 68 Standard 94 94 Standard 35 35 Standard 20 20 Standard 11 11 Standard 3 3 Standard 4 4 Standard 8 8 Standard 68 68 Standard 17 17 Standard 52 52 Standard 75 75 Standard 93 93 Standard 29 29 Standard 21 21 Standard 63 63 Standard 48 48 Standard 34 34 Standard 23 23 Standard 42 42 Standard 63 63 Standard 15 15 Standard 74 74 Standard 10 10 Standard 34 34 Standard 139 139 Standard 10 10 Standard 229 229 Standard 53 53 Standard 52 52 Standard 7 7 Standard 5 5 Standard 3 3 Standard 46 46 Standard 11 11 Standard 18 18 Standard 29 29 Standard 23 23 Standard 7 7 Standard 3 3 Standard 25 25 Standard 45 45 Standard 9 9 Standard 18 18 Standard 54 54 Standard 17 17 Standard 21 21 Standard 29 29 Standard 37 37 Standard 8 8 Standard 21 21 Standard 30 30 Standard 41 41 Standard 47 47 Standard 68 68 Standard 94 94 Standard 35 35 Standard 20 20 Standard 11 11 Standard 3 3 Standard 4 4 Standard 8 8 Standard 68 68 Standard 17 17 Standard 52 52 Standard 75 75 Standard 93 93 Standard 29 29 Standard 21 21 Standard 63 63 Standard 48 48 Standard 34 34 Standard 23 23 Standard 42 42 Standard 63 63 Standard 15 15 Standard 74 74 Standard 10 10 Standard 34 34 Standard 139 139 Standard 10 10 Standard 229 229 Standard 53 53 Standard 52 52 Standard 7 7 Standard 5 5 Standard 3 3 Standard 46 46 Standard 11 11 Standard 18 18 Standard 29 29 Standard 23 23 Standard 7 7 Standard 3 3 Standard 25 25 Standard 45 45 Standard 9 9 Standard 18 18 Standard 54 54 Standard 17 17 Standard 21 21 Standard 59 59 Standard 19 19 Standard 20 20 Standard 26 26 Standard 18 18 Standard 66 66 Standard 84 84 Standard 93 93 Standard 149 149 Standard 41 41 Standard 6 6 Standard 11 11 Standard 22 22 Standard 8 8 Standard 10 10 Standard 77 77 Standard 14 14 Standard 63 63 Standard 21 21 Standard 90 90 Standard 134 134 Standard 15 15 Standard 46 46 Standard 52 52 Standard 72 72 Standard 42 42 Standard 22 22 Standard 40 40 Standard 71 71 Standard 2 2 Standard 131 131 Standard 9 9 Standard 35 35 Standard 195 195 Standard 22 22 Standard 283 283 Standard 44 44 Standard 49 49 Standard 10 10 Standard 10 10 Standard 83 83 Standard 26 26 Standard 28 28 Standard 42 42 Standard 24 24 Standard 4 4 Standard 5 5 Standard 39 39 Standard 57 57 Standard 13 13 Standard 55 55 Standard 53 53 Standard 10 10 Standard 70 70 Standard 59 59 Standard 19 19 Standard 20 20 Standard 26 26 Standard 18 18 Standard 66 66 Standard 84 84 Standard 93 93 Standard 149 149 Standard 41 41 Standard 6 6 Standard 11 11 Standard 22 22 Standard 8 8 Standard 10 10 Standard 77 77 Standard 14 14 Standard 63 63 Standard 21 21 Standard 90 90 Standard 134 134 Standard 15 15 Standard 46 46 Standard 52 52 Standard 72 72 Standard 42 42 Standard 22 22 Standard 40 40 Standard 71 71 Standard 2 2 Standard 131 131 Standard 9 9 Standard 35 35 Standard 195 195 Standard 22 22 Standard 283 283 Standard 44 44 Standard 49 49 Standard 10 10 Standard 10 10 Standard 83 83 Standard 26 26 Standard 28 28 Standard 42 42 Standard 24 24 Standard 4 4 Standard 5 5 Standard 39 39 Standard 57 57 Standard 13 13 Standard 55 55 Standard 53 53 Standard 10 10 Standard 70 70 Standard 54 54 Standard 26 26 Standard 21 21 Standard 53 53 Standard 19 19 Standard 30 30 Standard 81 81 Standard 76 76 Standard 83 83 Standard 42 42 Standard 7 7 Standard 5 5 Standard 6 6 Standard 12 12 Standard 38 38 Standard 21 21 Standard 54 54 Standard 21 21 Standard 112 112 Standard 67 67 Standard 17 17 Standard 22 22 Standard 29 29 Standard 45 45 Standard 26 26 Standard 34 34 Standard 29 29 Standard 51 51 Standard 6 6 Standard 79 79 Standard 6 6 Standard 42 42 Standard 124 124 Standard 21 21 Standard 218 218 Standard 47 47 Standard 41 41 Standard 8 8 Standard 7 7 Standard 51 51 Standard 30 30 Standard 18 18 Standard 18 18 Standard 33 33 Standard 4 4 Standard 8 8 Standard 36 36 Standard 58 58 Standard 12 12 Standard 29 29 Standard 48 48 Standard 12 12 Standard 26 26 Standard 54 54 Standard 26 26 Standard 21 21 Standard 53 53 Standard 19 19 Standard 30 30 Standard 81 81 Standard 76 76 Standard 83 83 Standard 42 42 Standard 7 7 Standard 5 5 Standard 6 6 Standard 12 12 Standard 38 38 Standard 21 21 Standard 54 54 Standard 21 21 Standard 112 112 Standard 67 67 Standard 17 17 Standard 22 22 Standard 29 29 Standard 45 45 Standard 26 26 Standard 34 34 Standard 29 29 Standard 51 51 Standard 6 6 Standard 79 79 Standard 6 6 Standard 42 42 Standard 124 124 Standard 21 21 Standard 218 218 Standard 47 47 Standard 41 41 Standard 8 8 Standard 7 7 Standard 51 51 Standard 30 30 Standard 18 18 Standard 18 18 Standard 33 33 Standard 4 4 Standard 8 8 Standard 36 36 Standard 58 58 Standard 12 12 Standard 29 29 Standard 48 48 Standard 12 12 Standard 26 26 Standard 85 85 Standard 23 23 Standard 13 13 Standard 58 58 Standard 38 38 Standard 30 30 Standard 54 54 Standard 78 78 Standard 112 112 Standard 45 45 Standard 10 10 Standard 3 3 Standard 9 9 Standard 10 10 Standard 19 19 Standard 82 82 Standard 3 3 Standard 42 42 Standard 23 23 Standard 152 152 Standard 72 72 Standard 27 27 Standard 31 31 Standard 44 44 Standard 27 27 Standard 21 21 Standard 16 16 Standard 28 28 Standard 60 60 Standard 8 8 Standard 74 74 Standard 5 5 Standard 19 19 Standard 152 152 Standard 22 22 Standard 254 254 Standard 52 52 Standard 44 44 Standard 4 4 Standard 7 7 Standard 62 62 Standard 11 11 Standard 12 12 Standard 26 26 Standard 56 56 Standard 3 3 Standard 4 4 Standard 26 26 Standard 52 52 Standard 8 8 Standard 35 35 Standard 49 49 Standard 14 14 Standard 35 35 Standard 85 85 Standard 23 23 Standard 13 13 Standard 58 58 Standard 38 38 Standard 30 30 Standard 54 54 Standard 78 78 Standard 112 112 Standard 45 45 Standard 10 10 Standard 3 3 Standard 9 9 Standard 10 10 Standard 19 19 Standard 82 82 Standard 3 3 Standard 42 42 Standard 23 23 Standard 152 152 Standard 72 72 Standard 27 27 Standard 31 31 Standard 44 44 Standard 27 27 Standard 21 21 Standard 16 16 Standard 28 28 Standard 60 60 Standard 8 8 Standard 74 74 Standard 5 5 Standard 19 19 Standard 152 152 Standard 22 22 Standard 254 254 Standard 52 52 Standard 44 44 Standard 4 4 Standard 7 7 Standard 62 62 Standard 11 11 Standard 12 12 Standard 26 26 Standard 56 56 Standard 3 3 Standard 4 4 Standard 26 26 Standard 52 52 Standard 8 8 Standard 35 35 Standard 49 49 Standard 14 14 Standard 35 35 Standard 44 44 Standard 31 31 Standard 18 18 Standard 25 25 Standard 30 30 Standard 53 53 Standard 81 81 Standard 74 74 Standard 117 117 Standard 25 25 Standard 3 3 Standard 5 5 Standard 4 4 Standard 4 4 Standard 66 66 Standard 12 12 Standard 51 51 Standard 19 19 Standard 109 109 Standard 79 79 Standard 24 24 Standard 34 34 Standard 55 55 Standard 30 30 Standard 12 12 Standard 22 22 Standard 40 40 Standard 38 38 Standard 14 14 Standard 81 81 Standard 3 3 Standard 39 39 Standard 177 177 Standard 23 23 Standard 234 234 Standard 77 77 Standard 47 47 Standard 7 7 Standard 7 7 Standard 8 8 Standard 38 38 Standard 14 14 Standard 4 4 Standard 37 37 Standard 25 25 Standard 9 9 Standard 9 9 Standard 25 25 Standard 64 64 Standard 3 3 Standard 47 47 Standard 38 38 Standard 31 31 Standard 26 26 Standard 44 44 Standard 31 31 Standard 18 18 Standard 25 25 Standard 30 30 Standard 53 53 Standard 81 81 Standard 74 74 Standard 117 117 Standard 25 25 Standard 3 3 Standard 5 5 Standard 4 4 Standard 4 4 Standard 66 66 Standard 12 12 Standard 51 51 Standard 19 19 Standard 109 109 Standard 79 79 Standard 24 24 Standard 34 34 Standard 55 55 Standard 30 30 Standard 12 12 Standard 22 22 Standard 40 40 Standard 38 38 Standard 14 14 Standard 81 81 Standard 3 3 Standard 39 39 Standard 177 177 Standard 23 23 Standard 234 234 Standard 77 77 Standard 47 47 Standard 7 7 Standard 7 7 Standard 8 8 Standard 38 38 Standard 14 14 Standard 4 4 Standard 37 37 Standard 25 25 Standard 9 9 Standard 9 9 Standard 25 25 Standard 64 64 Standard 3 3 Standard 47 47 Standard 38 38 Standard 31 31 Standard 26 26 Standard 66 66 Standard 38 38 Standard 13 13 Standard 24 24 Standard 19 19 Standard 33 33 Standard 69 69 Standard 73 73 Standard 131 131 Standard 23 23 Standard 6 6 Standard 2 2 Standard 8 8 Standard 2 2 Standard 61 61 Standard 3 3 Standard 80 80 Standard 13 13 Standard 115 115 Standard 56 56 Standard 17 17 Standard 45 45 Standard 42 42 Standard 34 34 Standard 19 19 Standard 25 25 Standard 33 33 Standard 41 41 Standard 3 3 Standard 61 61 Standard 46 46 Standard 188 188 Standard 20 20 Standard 274 274 Standard 41 41 Standard 50 50 Standard 8 8 Standard 7 7 Standard 17 17 Standard 57 57 Standard 24 24 Standard 21 21 Standard 32 32 Standard 24 24 Standard 3 3 Standard 14 14 Standard 44 44 Standard 65 65 Standard 6 6 Standard 56 56 Standard 46 46 Standard 19 19 Standard 15 15 Standard 66 66 Standard 38 38 Standard 13 13 Standard 24 24 Standard 19 19 Standard 33 33 Standard 69 69 Standard 73 73 Standard 131 131 Standard 23 23 Standard 6 6 Standard 2 2 Standard 8 8 Standard 2 2 Standard 61 61 Standard 3 3 Standard 80 80 Standard 13 13 Standard 115 115 Standard 56 56 Standard 17 17 Standard 45 45 Standard 42 42 Standard 34 34 Standard 19 19 Standard 25 25 Standard 33 33 Standard 41 41 Standard 3 3 Standard 61 61 Standard 46 46 Standard 188 188 Standard 20 20 Standard 274 274 Standard 41 41 Standard 50 50 Standard 8 8 Standard 7 7 Standard 17 17 Standard 57 57 Standard 24 24 Standard 21 21 Standard 32 32 Standard 24 24 Standard 3 3 Standard 14 14 Standard 44 44 Standard 65 65 Standard 6 6 Standard 56 56 Standard 46 46 Standard 19 19 Standard 15 15 Standard 331 331 Standard 143 143 Standard 124 124 Standard 177 177 Standard 144 144 Standard 147 147 Standard 440 440 Standard 413 413 Standard 604 604 Standard 138 138 Standard 34 34 Standard 45 45 Standard 21 21 Standard 27 27 Standard 31 31 Standard 368 368 Standard 77 77 Standard 244 244 Standard 77 77 Standard 586 586 Standard 496 496 Standard 117 117 Standard 221 221 Standard 267 267 Standard 282 282 Standard 116 116 Standard 139 139 Standard 166 166 Standard 265 265 Standard 60 60 Standard 535 535 Standard 40 40 Standard 245 245 Standard 965 965 Standard 103 103 Standard 1469 1,469 Standard 321 321 Standard 261 261 Standard 37 37 Standard 18 18 Standard 64 64 Standard 264 264 Standard 98 98 Standard 74 74 Standard 160 160 Standard 146 146 Standard 41 41 Standard 29 29 Standard 148 148 Standard 299 299 Standard 80 80 Standard 232 232 Standard 308 308 Standard 100 100 Standard 233 233 Standard 331 331 Standard 143 143 Standard 124 124 Standard 177 177 Standard 144 144 Standard 147 147 Standard 440 440 Standard 413 413 Standard 604 604 Standard 138 138 Standard 34 34 Standard 45 45 Standard 21 21 Standard 27 27 Standard 31 31 Standard 368 368 Standard 77 77 Standard 244 244 Standard 77 77 Standard 586 586 Standard 496 496 Standard 117 117 Standard 221 221 Standard 267 267 Standard 282 282 Standard 116 116 Standard 139 139 Standard 166 166 Standard 265 265 Standard 60 60 Standard 535 535 Standard 40 40 Standard 245 245 Standard 965 965 Standard 103 103 Standard 1469 1,469 Standard 321 321 Standard 261 261 Standard 37 37 Standard 18 18 Standard 64 64 Standard 264 264 Standard 98 98 Standard 74 74 Standard 160 160 Standard 146 146 Standard 41 41 Standard 29 29 Standard 148 148 Standard 299 299 Standard 80 80 Standard 232 232 Standard 308 308 Standard 100 100 Standard 233 233 Standard 298 298 Standard 170 170 Standard 92 92 Standard 224 224 Standard 183 183 Standard 161 161 Standard 381 381 Standard 399 399 Standard 484 484 Standard 119 119 Standard 56 56 Standard 23 23 Standard 31 31 Standard 39 39 Standard 52 52 Standard 366 366 Standard 92 92 Standard 213 213 Standard 97 97 Standard 675 675 Standard 450 450 Standard 134 134 Standard 179 179 Standard 240 240 Standard 301 301 Standard 97 97 Standard 158 158 Standard 146 146 Standard 305 305 Standard 64 64 Standard 587 587 Standard 44 44 Standard 224 224 Standard 962 962 Standard 58 58 Standard 1517 1,517 Standard 326 326 Standard 194 194 Standard 25 25 Standard 36 36 Standard 45 45 Standard 255 255 Standard 61 61 Standard 70 70 Standard 118 118 Standard 178 178 Standard 46 46 Standard 27 27 Standard 166 166 Standard 326 326 Standard 62 62 Standard 200 200 Standard 315 315 Standard 147 147 Standard 181 181 Standard 298 298 Standard 170 170 Standard 92 92 Standard 224 224 Standard 183 183 Standard 161 161 Standard 381 381 Standard 399 399 Standard 484 484 Standard 119 119 Standard 56 56 Standard 23 23 Standard 31 31 Standard 39 39 Standard 52 52 Standard 366 366 Standard 92 92 Standard 213 213 Standard 97 97 Standard 675 675 Standard 450 450 Standard 134 134 Standard 179 179 Standard 240 240 Standard 301 301 Standard 97 97 Standard 158 158 Standard 146 146 Standard 305 305 Standard 64 64 Standard 587 587 Standard 44 44 Standard 224 224 Standard 962 962 Standard 58 58 Standard 1517 1,517 Standard 326 326 Standard 194 194 Standard 25 25 Standard 36 36 Standard 45 45 Standard 255 255 Standard 61 61 Standard 70 70 Standard 118 118 Standard 178 178 Standard 46 46 Standard 27 27 Standard 166 166 Standard 326 326 Standard 62 62 Standard 200 200 Standard 315 315 Standard 147 147 Standard 181 181 Standard 310 310 Standard 193 193 Standard 74 74 Standard 177 177 Standard 153 153 Standard 133 133 Standard 353 353 Standard 348 348 Standard 526 526 Standard 136 136 Standard 37 37 Standard 46 46 Standard 31 31 Standard 45 45 Standard 37 37 Standard 353 353 Standard 60 60 Standard 207 207 Standard 99 99 Standard 642 642 Standard 365 365 Standard 157 157 Standard 215 215 Standard 229 229 Standard 264 264 Standard 135 135 Standard 104 104 Standard 122 122 Standard 400 400 Standard 65 65 Standard 503 503 Standard 39 39 Standard 230 230 Standard 853 853 Standard 52 52 Standard 1351 1,351 Standard 266 266 Standard 231 231 Standard 27 27 Standard 26 26 Standard 35 35 Standard 248 248 Standard 71 71 Standard 82 82 Standard 180 180 Standard 144 144 Standard 44 44 Standard 33 33 Standard 127 127 Standard 329 329 Standard 72 72 Standard 187 187 Standard 337 337 Standard 118 118 Standard 201 201 Standard 310 310 Standard 193 193 Standard 74 74 Standard 177 177 Standard 153 153 Standard 133 133 Standard 353 353 Standard 348 348 Standard 526 526 Standard 136 136 Standard 37 37 Standard 46 46 Standard 31 31 Standard 45 45 Standard 37 37 Standard 353 353 Standard 60 60 Standard 207 207 Standard 99 99 Standard 642 642 Standard 365 365 Standard 157 157 Standard 215 215 Standard 229 229 Standard 264 264 Standard 135 135 Standard 104 104 Standard 122 122 Standard 400 400 Standard 65 65 Standard 503 503 Standard 39 39 Standard 230 230 Standard 853 853 Standard 52 52 Standard 1351 1,351 Standard 266 266 Standard 231 231 Standard 27 27 Standard 26 26 Standard 35 35 Standard 248 248 Standard 71 71 Standard 82 82 Standard 180 180 Standard 144 144 Standard 44 44 Standard 33 33 Standard 127 127 Standard 329 329 Standard 72 72 Standard 187 187 Standard 337 337 Standard 118 118 Standard 201 201 Standard 36 36 Standard 20 20 Standard 11 11 Standard 29 29 Standard 9 9 Standard 32 32 Standard 53 53 Standard 57 57 Standard 133 133 Standard 23 23 Standard 9 9 Standard 2 2 Standard 9 9 Standard 18 18 Standard 6 6 Standard 60 60 Standard 15 15 Standard 16 16 Standard 16 16 Standard 114 114 Standard 60 60 Standard 18 18 Standard 28 28 Standard 45 45 Standard 34 34 Standard 16 16 Standard 6 6 Standard 23 23 Standard 51 51 Standard 16 16 Standard 119 119 Standard 5 5 Standard 38 38 Standard 181 181 Standard 8 8 Standard 240 240 Standard 57 57 Standard 43 43 Standard 5 5 Standard 10 10 Standard 16 16 Standard 43 43 Standard 13 13 Standard 19 19 Standard 44 44 Standard 22 22 Standard 8 8 Standard 15 15 Standard 28 28 Standard 46 46 Standard 21 21 Standard 51 51 Standard 72 72 Standard 15 15 Standard 21 21 Standard 36 36 Standard 20 20 Standard 11 11 Standard 29 29 Standard 9 9 Standard 32 32 Standard 53 53 Standard 57 57 Standard 133 133 Standard 23 23 Standard 9 9 Standard 2 2 Standard 9 9 Standard 18 18 Standard 6 6 Standard 60 60 Standard 15 15 Standard 16 16 Standard 16 16 Standard 114 114 Standard 60 60 Standard 18 18 Standard 28 28 Standard 45 45 Standard 34 34 Standard 16 16 Standard 6 6 Standard 23 23 Standard 51 51 Standard 16 16 Standard 119 119 Standard 5 5 Standard 38 38 Standard 181 181 Standard 8 8 Standard 240 240 Standard 57 57 Standard 43 43 Standard 5 5 Standard 10 10 Standard 16 16 Standard 43 43 Standard 13 13 Standard 19 19 Standard 44 44 Standard 22 22 Standard 8 8 Standard 15 15 Standard 28 28 Standard 46 46 Standard 21 21 Standard 51 51 Standard 72 72 Standard 15 15 Standard 21 21 Standard 60 60 Standard 32 32 Standard 19 19 Standard 41 41 Standard 32 32 Standard 41 41 Standard 51 51 Standard 66 66 Standard 85 85 Standard 31 31 Standard 9 9 Standard 5 5 Standard 6 6 Standard 5 5 Standard 71 71 Standard 9 9 Standard 42 42 Standard 23 23 Standard 67 67 Standard 75 75 Standard 24 24 Standard 59 59 Standard 36 36 Standard 28 28 Standard 23 23 Standard 30 30 Standard 29 29 Standard 81 81 Standard 13 13 Standard 101 101 Standard 12 12 Standard 16 16 Standard 135 135 Standard 13 13 Standard 307 307 Standard 52 52 Standard 64 64 Standard 20 20 Standard 6 6 Standard 72 72 Standard 6 6 Standard 12 12 Standard 18 18 Standard 16 16 Standard 15 15 Standard 3 3 Standard 23 23 Standard 70 70 Standard 25 25 Standard 50 50 Standard 42 42 Standard 13 13 Standard 28 28 Standard 60 60 Standard 32 32 Standard 19 19 Standard 41 41 Standard 32 32 Standard 41 41 Standard 51 51 Standard 66 66 Standard 85 85 Standard 31 31 Standard 9 9 Standard 5 5 Standard 6 6 Standard 5 5 Standard 71 71 Standard 9 9 Standard 42 42 Standard 23 23 Standard 67 67 Standard 75 75 Standard 24 24 Standard 59 59 Standard 36 36 Standard 28 28 Standard 23 23 Standard 30 30 Standard 29 29 Standard 81 81 Standard 13 13 Standard 101 101 Standard 12 12 Standard 16 16 Standard 135 135 Standard 13 13 Standard 307 307 Standard 52 52 Standard 64 64 Standard 20 20 Standard 6 6 Standard 72 72 Standard 6 6 Standard 12 12 Standard 18 18 Standard 16 16 Standard 15 15 Standard 3 3 Standard 23 23 Standard 70 70 Standard 25 25 Standard 50 50 Standard 42 42 Standard 13 13 Standard 28 28 Standard 74 74 Standard 8 8 Standard 16 16 Standard 18 18 Standard 29 29 Standard 23 23 Standard 82 82 Standard 56 56 Standard 88 88 Standard 22 22 Standard 17 17 Standard 7 7 Standard 4 4 Standard 9 9 Standard 3 3 Standard 66 66 Standard 16 16 Standard 37 37 Standard 11 11 Standard 68 68 Standard 88 88 Standard 17 17 Standard 20 20 Standard 44 44 Standard 54 54 Standard 23 23 Standard 13 13 Standard 15 15 Standard 49 49 Standard 17 17 Standard 68 68 Standard 5 5 Standard 28 28 Standard 136 136 Standard 14 14 Standard 225 225 Standard 40 40 Standard 39 39 Standard 5 5 Standard 4 4 Standard 3 3 Standard 34 34 Standard 8 8 Standard 25 25 Standard 19 19 Standard 40 40 Standard 16 16 Standard 36 36 Standard 16 16 Standard 16 16 Standard 58 58 Standard 11 11 Standard 45 45 Standard 74 74 Standard 8 8 Standard 16 16 Standard 18 18 Standard 29 29 Standard 23 23 Standard 82 82 Standard 56 56 Standard 88 88 Standard 22 22 Standard 17 17 Standard 7 7 Standard 4 4 Standard 9 9 Standard 3 3 Standard 66 66 Standard 16 16 Standard 37 37 Standard 11 11 Standard 68 68 Standard 88 88 Standard 17 17 Standard 20 20 Standard 44 44 Standard 54 54 Standard 23 23 Standard 13 13 Standard 15 15 Standard 49 49 Standard 17 17 Standard 68 68 Standard 5 5 Standard 28 28 Standard 136 136 Standard 14 14 Standard 225 225 Standard 40 40 Standard 39 39 Standard 5 5 Standard 4 4 Standard 3 3 Standard 34 34 Standard 8 8 Standard 25 25 Standard 19 19 Standard 40 40 Standard 16 16 Standard 36 36 Standard 16 16 Standard 16 16 Standard 58 58 Standard 11 11 Standard 45 45 Standard 8 8 Standard 11 11 Standard 6 6 Standard 9 9 Standard 10 10 Standard 14 14 Standard 17 17 Standard 8 8 Standard 27 27 Standard 8 8 Standard 3 3 Standard 4 4 Standard 16 16 Standard 7 7 Standard 22 22 Standard 3 3 Standard 36 36 Standard 25 25 Standard 11 11 Standard 9 9 Standard 12 12 Standard 28 28 Standard 6 6 Standard 6 6 Standard 8 8 Standard 18 18 Standard 7 7 Standard 30 30 Standard 8 8 Standard 17 17 Standard 53 53 Standard 4 4 Standard 71 71 Standard 17 17 Standard 17 17 Standard 2 2 Standard 5 5 Standard 10 10 Standard 14 14 Standard 4 4 Standard 4 4 Standard 11 11 Standard 9 9 Standard 3 3 Standard 12 12 Standard 21 21 Standard 3 3 Standard 8 8 Standard 25 25 Standard 8 8 Standard 18 18 Standard 8 8 Standard 11 11 Standard 6 6 Standard 9 9 Standard 10 10 Standard 14 14 Standard 17 17 Standard 8 8 Standard 27 27 Standard 8 8 Standard 3 3 Standard 4 4 Standard 16 16 Standard 7 7 Standard 22 22 Standard 3 3 Standard 36 36 Standard 25 25 Standard 11 11 Standard 9 9 Standard 12 12 Standard 28 28 Standard 6 6 Standard 6 6 Standard 8 8 Standard 18 18 Standard 7 7 Standard 30 30 Standard 8 8 Standard 17 17 Standard 53 53 Standard 4 4 Standard 71 71 Standard 17 17 Standard 17 17 Standard 2 2 Standard 5 5 Standard 10 10 Standard 14 14 Standard 4 4 Standard 4 4 Standard 11 11 Standard 9 9 Standard 3 3 Standard 12 12 Standard 21 21 Standard 3 3 Standard 8 8 Standard 25 25 Standard 8 8 Standard 18 18 Standard 581 581 Standard 308 308 Standard 204 204 Standard 428 428 Standard 295 295 Standard 422 422 Standard 708 708 Standard 772 772 Standard 1031 1,031 Standard 293 293 Standard 86 86 Standard 87 87 Standard 63 63 Standard 55 55 Standard 73 73 Standard 669 669 Standard 159 159 Standard 470 470 Standard 149 149 Standard 1159 1,159 Standard 841 841 Standard 227 227 Standard 311 311 Standard 428 428 Standard 521 521 Standard 233 233 Standard 213 213 Standard 336 336 Standard 565 565 Standard 141 141 Standard 1093 1,093 Standard 122 122 Standard 316 316 Standard 1875 1,875 Standard 206 206 Standard 2663 2,663 Standard 572 572 Standard 488 488 Standard 77 77 Standard 69 69 Standard 83 83 Standard 514 514 Standard 170 170 Standard 161 161 Standard 292 292 Standard 330 330 Standard 57 57 Standard 58 58 Standard 348 348 Standard 591 591 Standard 168 168 Standard 309 309 Standard 571 571 Standard 231 231 Standard 399 399 Standard 581 581 Standard 308 308 Standard 204 204 Standard 428 428 Standard 295 295 Standard 422 422 Standard 708 708 Standard 772 772 Standard 1031 1,031 Standard 293 293 Standard 86 86 Standard 87 87 Standard 63 63 Standard 55 55 Standard 73 73 Standard 669 669 Standard 159 159 Standard 470 470 Standard 149 149 Standard 1159 1,159 Standard 841 841 Standard 227 227 Standard 311 311 Standard 428 428 Standard 521 521 Standard 233 233 Standard 213 213 Standard 336 336 Standard 565 565 Standard 141 141 Standard 1093 1,093 Standard 122 122 Standard 316 316 Standard 1875 1,875 Standard 206 206 Standard 2663 2,663 Standard 572 572 Standard 488 488 Standard 77 77 Standard 69 69 Standard 83 83 Standard 514 514 Standard 170 170 Standard 161 161 Standard 292 292 Standard 330 330 Standard 57 57 Standard 58 58 Standard 348 348 Standard 591 591 Standard 168 168 Standard 309 309 Standard 571 571 Standard 231 231 Standard 399 399 Standard 283 283 Standard 154 154 Standard 114 114 Standard 148 148 Standard 120 120 Standard 167 167 Standard 302 302 Standard 394 394 Standard 459 459 Standard 136 136 Standard 32 32 Standard 31 31 Standard 14 14 Standard 39 39 Standard 51 51 Standard 373 373 Standard 84 84 Standard 202 202 Standard 77 77 Standard 626 626 Standard 352 352 Standard 91 91 Standard 142 142 Standard 223 223 Standard 327 327 Standard 69 69 Standard 97 97 Standard 137 137 Standard 299 299 Standard 71 71 Standard 489 489 Standard 29 29 Standard 160 160 Standard 850 850 Standard 69 69 Standard 1107 1,107 Standard 251 251 Standard 196 196 Standard 46 46 Standard 20 20 Standard 41 41 Standard 255 255 Standard 58 58 Standard 84 84 Standard 146 146 Standard 171 171 Standard 27 27 Standard 18 18 Standard 149 149 Standard 262 262 Standard 59 59 Standard 170 170 Standard 334 334 Standard 98 98 Standard 182 182 Standard 283 283 Standard 154 154 Standard 114 114 Standard 148 148 Standard 120 120 Standard 167 167 Standard 302 302 Standard 394 394 Standard 459 459 Standard 136 136 Standard 32 32 Standard 31 31 Standard 14 14 Standard 39 39 Standard 51 51 Standard 373 373 Standard 84 84 Standard 202 202 Standard 77 77 Standard 626 626 Standard 352 352 Standard 91 91 Standard 142 142 Standard 223 223 Standard 327 327 Standard 69 69 Standard 97 97 Standard 137 137 Standard 299 299 Standard 71 71 Standard 489 489 Standard 29 29 Standard 160 160 Standard 850 850 Standard 69 69 Standard 1107 1,107 Standard 251 251 Standard 196 196 Standard 46 46 Standard 20 20 Standard 41 41 Standard 255 255 Standard 58 58 Standard 84 84 Standard 146 146 Standard 171 171 Standard 27 27 Standard 18 18 Standard 149 149 Standard 262 262 Standard 59 59 Standard 170 170 Standard 334 334 Standard 98 98 Standard 182 182 Standard 41 41 Standard 30 30 Standard 22 22 Standard 33 33 Standard 29 29 Standard 36 36 Standard 63 63 Standard 74 74 Standard 122 122 Standard 24 24 Standard 13 13 Standard 1 1 Standard 1 1 Standard 5 5 Standard 7 7 Standard 65 65 Standard 6 6 Standard 41 41 Standard 11 11 Standard 100 100 Standard 86 86 Standard 26 26 Standard 42 42 Standard 31 31 Standard 61 61 Standard 14 14 Standard 18 18 Standard 24 24 Standard 36 36 Standard 16 16 Standard 118 118 Standard 12 12 Standard 40 40 Standard 165 165 Standard 11 11 Standard 263 263 Standard 68 68 Standard 58 58 Standard 10 10 Standard 4 4 Standard 3 3 Standard 51 51 Standard 19 19 Standard 15 15 Standard 25 25 Standard 18 18 Standard 4 4 Standard 8 8 Standard 17 17 Standard 61 61 Standard 12 12 Standard 34 34 Standard 58 58 Standard 27 27 Standard 24 24 Standard 41 41 Standard 30 30 Standard 22 22 Standard 33 33 Standard 29 29 Standard 36 36 Standard 63 63 Standard 74 74 Standard 122 122 Standard 24 24 Standard 13 13 Standard 1 1 Standard 1 1 Standard 5 5 Standard 7 7 Standard 65 65 Standard 6 6 Standard 41 41 Standard 11 11 Standard 100 100 Standard 86 86 Standard 26 26 Standard 42 42 Standard 31 31 Standard 61 61 Standard 14 14 Standard 18 18 Standard 24 24 Standard 36 36 Standard 16 16 Standard 118 118 Standard 12 12 Standard 40 40 Standard 165 165 Standard 11 11 Standard 263 263 Standard 68 68 Standard 58 58 Standard 10 10 Standard 4 4 Standard 3 3 Standard 51 51 Standard 19 19 Standard 15 15 Standard 25 25 Standard 18 18 Standard 4 4 Standard 8 8 Standard 17 17 Standard 61 61 Standard 12 12 Standard 34 34 Standard 58 58 Standard 27 27 Standard 24 24 Standard 334 334 Standard 148 148 Standard 99 99 Standard 219 219 Standard 180 180 Standard 179 179 Standard 276 276 Standard 327 327 Standard 538 538 Standard 137 137 Standard 15 15 Standard 33 33 Standard 19 19 Standard 39 39 Standard 30 30 Standard 352 352 Standard 82 82 Standard 277 277 Standard 101 101 Standard 573 573 Standard 358 358 Standard 113 113 Standard 176 176 Standard 249 249 Standard 260 260 Standard 113 113 Standard 99 99 Standard 138 138 Standard 341 341 Standard 88 88 Standard 547 547 Standard 37 37 Standard 175 175 Standard 782 782 Standard 61 61 Standard 1297 1,297 Standard 319 319 Standard 240 240 Standard 40 40 Standard 16 16 Standard 37 37 Standard 247 247 Standard 104 104 Standard 102 102 Standard 165 165 Standard 144 144 Standard 17 17 Standard 24 24 Standard 134 134 Standard 242 242 Standard 105 105 Standard 170 170 Standard 315 315 Standard 116 116 Standard 162 162 Standard 334 334 Standard 148 148 Standard 99 99 Standard 219 219 Standard 180 180 Standard 179 179 Standard 276 276 Standard 327 327 Standard 538 538 Standard 137 137 Standard 15 15 Standard 33 33 Standard 19 19 Standard 39 39 Standard 30 30 Standard 352 352 Standard 82 82 Standard 277 277 Standard 101 101 Standard 573 573 Standard 358 358 Standard 113 113 Standard 176 176 Standard 249 249 Standard 260 260 Standard 113 113 Standard 99 99 Standard 138 138 Standard 341 341 Standard 88 88 Standard 547 547 Standard 37 37 Standard 175 175 Standard 782 782 Standard 61 61 Standard 1297 1,297 Standard 319 319 Standard 240 240 Standard 40 40 Standard 16 16 Standard 37 37 Standard 247 247 Standard 104 104 Standard 102 102 Standard 165 165 Standard 144 144 Standard 17 17 Standard 24 24 Standard 134 134 Standard 242 242 Standard 105 105 Standard 170 170 Standard 315 315 Standard 116 116 Standard 162 162 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 6838 6,838 Standard 3407 3,407 Standard 2469 2,469 Standard 4301 4,301 Standard 3396 3,396 Standard 4186 4,186 Standard 7870 7,870 Standard 8357 8,357 Standard 11888 11,888 Standard 3317 3,317 Standard 900 900 Standard 882 882 Standard 708 708 Standard 819 819 Standard 804 804 Standard 8006 8,006 Standard 1710 1,710 Standard 5197 5,197 Standard 1812 1,812 Standard 12885 12,885 Standard 9433 9,433 Standard 2604 2,604 Standard 4132 4,132 Standard 5004 5,004 Standard 6192 6,192 Standard 2585 2,585 Standard 2580 2,580 Standard 3310 3,310 Standard 6984 6,984 Standard 1714 1,714 Standard 11767 11,767 Standard 886 886 Standard 4400 4,400 Standard 20739 20,739 Standard 1764 1,764 Standard 30545 30,545 Standard 6884 6,884 Standard 5262 5,262 Standard 841 841 Standard 810 810 Standard 969 969 Standard 5885 5,885 Standard 1776 1,776 Standard 1813 1,813 Standard 3556 3,556 Standard 3254 3,254 Standard 833 833 Standard 815 815 Standard 3441 3,441 Standard 6782 6,782 Standard 1682 1,682 Standard 4205 4,205 Standard 6803 6,803 Standard 2477 2,477 Standard 4294 4,294 Standard 6838 6,838 Standard 3407 3,407 Standard 2469 2,469 Standard 4301 4,301 Standard 3396 3,396 Standard 4186 4,186 Standard 7870 7,870 Standard 8357 8,357 Standard 11888 11,888 Standard 3317 3,317 Standard 900 900 Standard 882 882 Standard 708 708 Standard 819 819 Standard 804 804 Standard 8006 8,006 Standard 1710 1,710 Standard 5197 5,197 Standard 1812 1,812 Standard 12885 12,885 Standard 9433 9,433 Standard 2604 2,604 Standard 4132 4,132 Standard 5004 5,004 Standard 6192 6,192 Standard 2585 2,585 Standard 2580 2,580 Standard 3310 3,310 Standard 6984 6,984 Standard 1714 1,714 Standard 11767 11,767 Standard 886 886 Standard 4400 4,400 Standard 20739 20,739 Standard 1764 1,764 Standard 30545 30,545 Standard 6884 6,884 Standard 5262 5,262 Standard 841 841 Standard 810 810 Standard 969 969 Standard 5885 5,885 Standard 1776 1,776 Standard 1813 1,813 Standard 3556 3,556 Standard 3254 3,254 Standard 833 833 Standard 815 815 Standard 3441 3,441 Standard 6782 6,782 Standard 1682 1,682 Standard 4205 4,205 Standard 6803 6,803 Standard 2477 2,477 Standard 4294 4,294 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 6838 6,838 Standard 3407 3,407 Standard 2469 2,469 Standard 4301 4,301 Standard 3396 3,396 Standard 4186 4,186 Standard 7870 7,870 Standard 8357 8,357 Standard 11888 11,888 Standard 3317 3,317 Standard 900 900 Standard 882 882 Standard 708 708 Standard 819 819 Standard 804 804 Standard 8006 8,006 Standard 1710 1,710 Standard 5197 5,197 Standard 1812 1,812 Standard 12885 12,885 Standard 9433 9,433 Standard 2604 2,604 Standard 4132 4,132 Standard 5004 5,004 Standard 6192 6,192 Standard 2585 2,585 Standard 2580 2,580 Standard 3310 3,310 Standard 6984 6,984 Standard 1714 1,714 Standard 11767 11,767 Standard 886 886 Standard 4400 4,400 Standard 20739 20,739 Standard 1764 1,764 Standard 30545 30,545 Standard 6884 6,884 Standard 5262 5,262 Standard 841 841 Standard 810 810 Standard 969 969 Standard 5885 5,885 Standard 1776 1,776 Standard 1813 1,813 Standard 3556 3,556 Standard 3254 3,254 Standard 833 833 Standard 815 815 Standard 3441 3,441 Standard 6782 6,782 Standard 1682 1,682 Standard 4205 4,205 Standard 6803 6,803 Standard 2477 2,477 Standard 4294 4,294 Standard ]]> WITH MEMBER [Measures].[COG_OQP_INT_f1]AS '1', SOLVE_ORDER = 65535 SELECT GENERATE( {[Product].[Product Category].MEMBERS}, CROSSJOIN( HEAD( {([Product].CURRENTMEMBER)}, IIF( COUNT( GENERATE( TOPCOUNT( {[Customers].[Country].MEMBERS}, 3, [Measures].[Unit Sales]), UNION( UNION( UNION( HEAD( {([Customers].CURRENTMEMBER, [Measures].[COG_OQP_INT_f1])}, IIF( COUNT( CROSSJOIN( {([Customers].CURRENTMEMBER)}, {[Measures].[Unit Sales]}), INCLUDEEMPTY)> 0, 1, IIF( COUNT( CROSSJOIN( BOTTOMCOUNT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province]), 2, [Measures].[Unit Sales]), {[Measures].[Unit Sales]}), EXCLUDEEMPTY)> 0, 1, 0))), CROSSJOIN( HEAD( {([Customers].CURRENTMEMBER)}, IIF( COUNT( CROSSJOIN( BOTTOMCOUNT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province]), 2, [Measures].[Unit Sales]), {[Measures].[Unit Sales]}), INCLUDEEMPTY)> 0, 0, 1)), {[Measures].[Unit Sales]}), ALL), CROSSJOIN( BOTTOMCOUNT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province]), 2, [Measures].[Unit Sales]), {[Measures].[Unit Sales]}), ALL), CROSSJOIN( HEAD( {([Customers].CURRENTMEMBER)}, IIF( COUNT( BOTTOMCOUNT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province]), 2, [Measures].[Unit Sales]), INCLUDEEMPTY)= 0, 1, 0)), {[Measures].[Unit Sales]}), ALL), ALL), INCLUDEEMPTY)> 0, 1, 0)), GENERATE( TOPCOUNT( {[Customers].[Country].MEMBERS}, 3, [Measures].[Unit Sales]), UNION( UNION( HEAD( {([Customers].CURRENTMEMBER, [Measures].[COG_OQP_INT_f1])}, IIF( COUNT( CROSSJOIN( {([Customers].CURRENTMEMBER)}, {[Measures].[Unit Sales]}), INCLUDEEMPTY)> 0, 1, IIF( COUNT( CROSSJOIN( BOTTOMCOUNT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province]), 2, [Measures].[Unit Sales]), {[Measures].[Unit Sales]}), EXCLUDEEMPTY)> 0, 1, 0))), CROSSJOIN( HEAD( {([Customers].CURRENTMEMBER)}, IIF( COUNT( CROSSJOIN( BOTTOMCOUNT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province]), 2, [Measures].[Unit Sales]), {[Measures].[Unit Sales]}), INCLUDEEMPTY)> 0, 0, 1)), {[Measures].[Unit Sales]}), ALL), CROSSJOIN( BOTTOMCOUNT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province]), 2, [Measures].[Unit Sales]), {[Measures].[Unit Sales]}), ALL), ALL)), ALL) ON AXIS(0) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 2 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 131074 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 131074 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 131074 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 131074 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 131074 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 131074 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 131074 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 1 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 1 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 3 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 131075 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 131075 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 131075 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 131075 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 131075 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 131075 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 131075 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 4 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 131076 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 131076 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 131076 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 131076 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 131076 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 131076 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 131076 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 1 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 1 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 45 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 45 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 45 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 1 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 45 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 4 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 131076 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 131076 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 131076 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 131076 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 131076 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 131076 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 131076 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 4 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 131076 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 131076 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 131076 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 131076 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 131076 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 131076 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 131076 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 1 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 45 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 3 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 131075 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 131075 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 131075 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 131075 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 131075 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 131075 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 131075 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 45 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 1 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 1 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 45 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 2 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 131074 [Customers].[USA].[CA] CA [Customers].[State Province] 2 45 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 131074 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 131074 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 131074 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 131074 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 131074 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 131074 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 9 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 131081 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 131081 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 131081 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 131081 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 131081 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 131081 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 131081 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 3 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 131075 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 131075 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 131075 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 131075 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 131075 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 131075 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 131075 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 2 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 131074 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 131074 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 131074 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 131074 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 131074 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 131074 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 131074 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 1 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 45 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 1 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 4 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 131076 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 131076 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 131076 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 131076 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 131076 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 131076 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 131076 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 45 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 1 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 5 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 131077 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 131077 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 131077 [Customers].[Canada] Canada [Customers].[Country] 1 65537 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 131077 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 131077 [Customers].[Mexico] Mexico [Customers].[Country] 1 65545 [Measures].[COG_OQP_INT_f1] COG_OQP_INT_f1 [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 131077 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 131077 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Store].[All Stores] All Stores [Store].[(All)] 0 3 [Store Size in SQFT].[All Store Size in SQFTs] All Store Size in SQFTs [Store Size in SQFT].[(All)] 0 21 [Store Type].[All Store Types] All Store Types [Store Type].[(All)] 0 6 [Time].[1997] 1997 [Time].[Year] 0 4 [Promotion Media].[All Media] All Media [Promotion Media].[(All)] 0 14 [Promotions].[All Promotions] All Promotions [Promotions].[(All)] 0 51 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 5 [Gender].[All Gender] All Gender [Gender].[(All)] 0 2 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 [Yearly Income].[All Yearly Incomes] All Yearly Incomes [Yearly Income].[(All)] 0 8 1 1 Standard 1680 1,680 Standard 1936 1,936 Standard 1 1 Standard 1 1 Standard 1 1 Standard 858 858 Standard 923 923 Standard 1 1 Standard 1 1 Standard 1 1 Standard 632 632 Standard 766 766 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1078 1,078 Standard 1208 1,208 Standard 1 1 Standard 1 1 Standard 1 1 Standard 817 817 Standard 989 989 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1041 1,041 Standard 1280 1,280 Standard 1 1 Standard 1 1 Standard 1 1 Standard 2013 2,013 Standard 2150 2,150 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1933 1,933 Standard 2456 2,456 Standard 1 1 Standard 1 1 Standard 1 1 Standard 2877 2,877 Standard 3343 3,343 Standard 1 1 Standard 1 1 Standard 1 1 Standard 862 862 Standard 938 938 Standard 1 1 Standard 1 1 Standard 1 1 Standard 228 228 Standard 253 253 Standard 1 1 Standard 1 1 Standard 1 1 Standard 236 236 Standard 270 270 Standard 1 1 Standard 1 1 Standard 1 1 Standard 192 192 Standard 220 220 Standard 1 1 Standard 1 1 Standard 1 1 Standard 215 215 Standard 234 234 Standard 1 1 Standard 1 1 Standard 1 1 Standard 173 173 Standard 231 231 Standard 1 1 Standard 1 1 Standard 1 1 Standard 2049 2,049 Standard 2209 2,209 Standard 1 1 Standard 1 1 Standard 1 1 Standard 432 432 Standard 476 476 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1261 1,261 Standard 1478 1,478 Standard 1 1 Standard 1 1 Standard 1 1 Standard 448 448 Standard 464 464 Standard 1 1 Standard 1 1 Standard 1 1 Standard 3131 3,131 Standard 3534 3,534 Standard 1 1 Standard 1 1 Standard 1 1 Standard 2428 2,428 Standard 2636 2,636 Standard 1 1 Standard 1 1 Standard 1 1 Standard 610 610 Standard 757 757 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1116 1,116 Standard 1119 1,119 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1143 1,143 Standard 1435 1,435 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1554 1,554 Standard 1772 1,772 Standard 1 1 Standard 1 1 Standard 1 1 Standard 669 669 Standard 726 726 Standard 1 1 Standard 1 1 Standard 1 1 Standard 645 645 Standard 662 662 Standard 1 1 Standard 1 1 Standard 1 1 Standard 796 796 Standard 940 940 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1751 1,751 Standard 1987 1,987 Standard 1 1 Standard 1 1 Standard 1 1 Standard 469 469 Standard 527 527 Standard 1 1 Standard 1 1 Standard 1 1 Standard 3008 3,008 Standard 3184 3,184 Standard 1 1 Standard 1 1 Standard 1 1 Standard 220 220 Standard 225 225 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1064 1,064 Standard 1278 1,278 Standard 1 1 Standard 1 1 Standard 1 1 Standard 5447 5,447 Standard 5906 5,906 Standard 1 1 Standard 1 1 Standard 1 1 Standard 441 441 Standard 451 451 Standard 1 1 Standard 1 1 Standard 1 1 Standard 7789 7,789 Standard 8543 8,543 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1831 1,831 Standard 1958 1,958 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1352 1,352 Standard 1448 1,448 Standard 1 1 Standard 1 1 Standard 1 1 Standard 223 223 Standard 248 248 Standard 1 1 Standard 1 1 Standard 1 1 Standard 215 215 Standard 261 261 Standard 1 1 Standard 1 1 Standard 1 1 Standard 231 231 Standard 286 286 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1493 1,493 Standard 1666 1,666 Standard 1 1 Standard 1 1 Standard 1 1 Standard 434 434 Standard 461 461 Standard 1 1 Standard 1 1 Standard 1 1 Standard 446 446 Standard 477 477 Standard 1 1 Standard 1 1 Standard 1 1 Standard 951 951 Standard 993 993 Standard 1 1 Standard 1 1 Standard 1 1 Standard 789 789 Standard 817 817 Standard 1 1 Standard 1 1 Standard 1 1 Standard 206 206 Standard 273 273 Standard 1 1 Standard 1 1 Standard 1 1 Standard 246 246 Standard 266 266 Standard 1 1 Standard 1 1 Standard 1 1 Standard 873 873 Standard 965 965 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1858 1,858 Standard 1867 1,867 Standard 1 1 Standard 1 1 Standard 1 1 Standard 467 467 Standard 493 493 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1072 1,072 Standard 1220 1,220 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1747 1,747 Standard 1837 1,837 Standard 1 1 Standard 1 1 Standard 1 1 Standard 650 650 Standard 651 651 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1063 1,063 Standard 1261 1,261 Standard 1 1 Standard 1 1 Standard ]]> WITH MEMBER [Education Level].[COG_OQP_INT_sm2] AS ' SUM( CROSSJOIN( {[Store Type].CURRENTMEMBER}, [COG_OQP_INT_s1]), IIF( ISEMPTY( ([Measures].[COG_OQP_USR_Unit Sales])), NULL, 1))', SOLVE_ORDER = 5 MEMBER [Education Level].[COG_OQP_INT_m2] AS ' SUM( CROSSJOIN( {[Store Type].CURRENTMEMBER}, [COG_OQP_INT_s1]), IIF( ISEMPTY( ([Measures].[COG_OQP_USR_Unit Sales])), NULL, 1))', SOLVE_ORDER = 65534 MEMBER [Store Type].[COG_OQP_INT_sm1] AS ' SUM( CROSSJOIN( {[Education Level].CURRENTMEMBER}, {[Store Type].MEMBERS}), ([Education Level].[COG_OQP_INT_m2], [Store Type].CURRENTMEMBER))', SOLVE_ORDER = 6 MEMBER [Measures].[COG_OQP_USR_Unit Sales] AS ' ([Measures].[COG_OQP_INT_m1])', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_m1] AS ' IIF( ([Measures].[Unit Sales])> 30, ([Measures].[Unit Sales]), NULL)', SOLVE_ORDER = 2 SET [COG_OQP_INT_s2] AS ' CROSSJOIN( {[Store Type].MEMBERS}, [COG_OQP_INT_s1])' SET [COG_OQP_INT_s1] AS ' INTERSECT( {[Education Level].MEMBERS}, {[Education Level].[Graduate Degree]})' SELECT CROSSJOIN( FILTER( UNION( CROSSJOIN( HEAD( {[Store Type].[COG_OQP_INT_sm1]}, IIF( COUNT( [COG_OQP_INT_s2], INCLUDEEMPTY)> 0, 1, 0)), HEAD( {[Education Level].[COG_OQP_INT_sm2]}, IIF( COUNT( CROSSJOIN( {[Store Type].CURRENTMEMBER}, [COG_OQP_INT_s1]), INCLUDEEMPTY)> 0, 1, 0))), GENERATE( {[Store Type].MEMBERS}, CROSSJOIN( {[Store Type].CURRENTMEMBER}, UNION( HEAD( {[Education Level].[COG_OQP_INT_sm2]}, IIF( COUNT( CROSSJOIN( {[Store Type].CURRENTMEMBER}, [COG_OQP_INT_s1]), INCLUDEEMPTY)> 0, 1, 0)), [COG_OQP_INT_s1])), ALL)), (NOT ISEMPTY( ([Measures].[COG_OQP_USR_Unit Sales]))AND ([Measures].[COG_OQP_USR_Unit Sales]) <> 0)), {[Measures].[COG_OQP_USR_Unit Sales]}) ON AXIS(0) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Store Type].[COG_OQP_INT_sm1] COG_OQP_INT_sm1 [Store Type].[(All)] 0 0 [Education Level].[COG_OQP_INT_sm2] COG_OQP_INT_sm2 [Education Level].[(All)] 0 0 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Store Type].[All Store Types] All Store Types [Store Type].[(All)] 0 6 [Education Level].[COG_OQP_INT_sm2] COG_OQP_INT_sm2 [Education Level].[(All)] 0 0 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Store Type].[All Store Types] All Store Types [Store Type].[(All)] 0 65542 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 0 [Education Level].[COG_OQP_INT_sm2] COG_OQP_INT_sm2 [Education Level].[(All)] 0 0 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 131072 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 [Education Level].[COG_OQP_INT_sm2] COG_OQP_INT_sm2 [Education Level].[(All)] 0 0 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 [Education Level].[COG_OQP_INT_sm2] COG_OQP_INT_sm2 [Education Level].[(All)] 0 0 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 [Education Level].[COG_OQP_INT_sm2] COG_OQP_INT_sm2 [Education Level].[(All)] 0 0 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 [Education Level].[COG_OQP_INT_sm2] COG_OQP_INT_sm2 [Education Level].[(All)] 0 0 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 6 6 Standard 1 1 Standard 15570 15,570 Standard 1 1 Standard 3704 3,704 Standard 1 1 Standard 1090 1,090 Standard 1 1 Standard 1203 1,203 Standard 1 1 Standard 316 316 Standard 1 1 Standard 9257 9,257 Standard ]]> WITH MEMBER [Measures].[COG_OQP_USR_Unit Sales] AS ' ([Measures].[COG_OQP_INT_m1])', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_m1] AS ' IIF( (([Measures].[Unit Sales])>= 1 AND ([Measures].[Unit Sales]) < 1000), ([Measures].[Unit Sales]), NULL)', SOLVE_ORDER = 2 MEMBER [Time].[Time].[COG_OQP_USR_Count(Quarter)] AS ' COUNT( [COG_OQP_INT_s1], EXCLUDEEMPTY)', SOLVE_ORDER = 4 SET [COG_OQP_INT_s4] AS ' CROSSJOIN( {[Marital Status].[M]}, {[Product].[Product Category].MEMBERS})' SET [COG_OQP_INT_s3] AS ' CROSSJOIN( [COG_OQP_INT_s1], {[Product].[Product Category].MEMBERS})' SET [COG_OQP_INT_s2] AS ' CROSSJOIN( {[Education Level].MEMBERS}, [COG_OQP_INT_s1])' SET [COG_OQP_INT_s1] AS ' CROSSJOIN( {[Time].[Quarter].MEMBERS}, {[Marital Status].[M]})' SELECT CROSSJOIN({[Measures].[COG_OQP_USR_Unit Sales]}, FILTER( {[Product].[Product Category].MEMBERS}, COUNT( FILTER( CROSSJOIN( {[Product].CURRENTMEMBER}, [COG_OQP_INT_s2]), (([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER)>= 1 AND ([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER) < 1000)), INCLUDEEMPTY)> 0)) ON AXIS(0), GENERATE( {[Education Level].MEMBERS}, CROSSJOIN( HEAD( {([Education Level].CURRENTMEMBER)}, IIF( (IIF(COUNT(FILTER( CROSSJOIN( {[Education Level].CURRENTMEMBER}, [COG_OQP_INT_s3]), (([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER)>= 1 AND ([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER) < 1000)), INCLUDEEMPTY)> 0, 1, 0)= 1 AND IIF( COUNT( [COG_OQP_INT_s1], INCLUDEEMPTY)> 0, 1, 0)= 1), 1, 0)), UNION( GENERATE( {[Time].[Quarter].MEMBERS}, CROSSJOIN( HEAD( {([Time].[Time].CURRENTMEMBER)}, IIF( COUNT( FILTER( CROSSJOIN( {[Education Level].CURRENTMEMBER}, [COG_OQP_INT_s4]), (([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER)>= 1 AND ([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER) < 1000)), INCLUDEEMPTY)> 0, 1, 0)), FILTER( {[Marital Status].[M]}, COUNT( FILTER( CROSSJOIN( {[Education Level].CURRENTMEMBER}, {[Product].[Product Category].MEMBERS}), (([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER)>= 1 AND ([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER) < 1000)), INCLUDEEMPTY)> 0)), ALL), HEAD( HEAD( {([Time].[COG_OQP_USR_Count(Quarter)], [Marital Status].DEFAULTMEMBER)}, IIF( COUNT( [COG_OQP_INT_s1], INCLUDEEMPTY)> 0, 1, 0)), IIF( COUNT( FILTER( CROSSJOIN( {[Education Level].CURRENTMEMBER}, [COG_OQP_INT_s3]), (([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER)>= 1 AND ([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER) < 1000)), INCLUDEEMPTY)> 0, 1, 0)), ALL)), ALL) ON AXIS(1) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 2 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 3 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 4 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 4 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 4 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 3 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 2 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 9 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 3 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 2 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 4 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 5 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 5 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Marital Status].[M] M [Marital Status].[Marital Status] 1 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 5 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 5 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 5 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Time].[COG_OQP_USR_Count(Quarter)] COG_OQP_USR_Count(Quarter) [Time].[Year] 0 0 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 65647 [Education Level].[Bachelors Degree] Bachelors Degree [Education Level].[Education Level] 1 0 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Marital Status].[M] M [Marital Status].[Marital Status] 1 0 [Education Level].[Bachelors Degree] Bachelors Degree [Education Level].[Education Level] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[Bachelors Degree] Bachelors Degree [Education Level].[Education Level] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[Bachelors Degree] Bachelors Degree [Education Level].[Education Level] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[Bachelors Degree] Bachelors Degree [Education Level].[Education Level] 1 131072 [Time].[COG_OQP_USR_Count(Quarter)] COG_OQP_USR_Count(Quarter) [Time].[Year] 0 0 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 65647 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Marital Status].[M] M [Marital Status].[Marital Status] 1 0 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Time].[COG_OQP_USR_Count(Quarter)] COG_OQP_USR_Count(Quarter) [Time].[Year] 0 0 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 65647 [Education Level].[High School Degree] High School Degree [Education Level].[Education Level] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Marital Status].[M] M [Marital Status].[Marital Status] 1 0 [Education Level].[High School Degree] High School Degree [Education Level].[Education Level] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[High School Degree] High School Degree [Education Level].[Education Level] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[High School Degree] High School Degree [Education Level].[Education Level] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[High School Degree] High School Degree [Education Level].[Education Level] 1 131072 [Time].[COG_OQP_USR_Count(Quarter)] COG_OQP_USR_Count(Quarter) [Time].[Year] 0 0 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 65647 [Education Level].[Partial College] Partial College [Education Level].[Education Level] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Marital Status].[M] M [Marital Status].[Marital Status] 1 0 [Education Level].[Partial College] Partial College [Education Level].[Education Level] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[Partial College] Partial College [Education Level].[Education Level] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[Partial College] Partial College [Education Level].[Education Level] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[Partial College] Partial College [Education Level].[Education Level] 1 131072 [Time].[COG_OQP_USR_Count(Quarter)] COG_OQP_USR_Count(Quarter) [Time].[Year] 0 0 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 65647 [Education Level].[Partial High School] Partial High School [Education Level].[Education Level] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Marital Status].[M] M [Marital Status].[Marital Status] 1 0 [Education Level].[Partial High School] Partial High School [Education Level].[Education Level] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[Partial High School] Partial High School [Education Level].[Education Level] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[Partial High School] Partial High School [Education Level].[Education Level] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Education Level].[Partial High School] Partial High School [Education Level].[Education Level] 1 131072 [Time].[COG_OQP_USR_Count(Quarter)] COG_OQP_USR_Count(Quarter) [Time].[Year] 0 0 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 833 833 Standard 432 432 Standard 320 320 Standard 495 495 Standard 440 440 Standard 483 483 Standard 981 981 Standard 430 430 Standard 88 88 Standard 101 101 Standard 85 85 Standard 105 105 Standard 132 132 Standard 198 198 Standard 654 654 Standard 199 199 Standard 267 267 Standard 497 497 Standard 668 668 Standard 730 730 Standard 348 348 Standard 292 292 Standard 357 357 Standard 817 817 Standard 154 154 Standard 79 79 Standard 510 510 Standard 188 188 Standard 871 871 Standard 669 669 Standard 76 76 Standard 141 141 Standard 129 129 Standard 724 724 Standard 202 202 Standard 222 222 Standard 447 447 Standard 376 376 Standard 144 144 Standard 73 73 Standard 441 441 Standard 814 814 Standard 203 203 Standard 494 494 Standard 867 867 Standard 288 288 Standard 525 525 Standard 857 857 Standard 415 415 Standard 274 274 Standard 506 506 Standard 425 425 Standard 467 467 Standard 896 896 Standard 975 975 Standard 372 372 Standard 132 132 Standard 133 133 Standard 76 76 Standard 89 89 Standard 76 76 Standard 956 956 Standard 182 182 Standard 664 664 Standard 223 223 Standard 281 281 Standard 500 500 Standard 545 545 Standard 747 747 Standard 322 322 Standard 353 353 Standard 432 432 Standard 758 758 Standard 203 203 Standard 75 75 Standard 504 504 Standard 180 180 Standard 695 695 Standard 645 645 Standard 99 99 Standard 106 106 Standard 89 89 Standard 666 666 Standard 184 184 Standard 218 218 Standard 404 404 Standard 366 366 Standard 103 103 Standard 116 116 Standard 431 431 Standard 751 751 Standard 178 178 Standard 510 510 Standard 737 737 Standard 324 324 Standard 546 546 Standard 869 869 Standard 457 457 Standard 289 289 Standard 436 436 Standard 438 438 Standard 468 468 Standard 972 972 Standard 982 982 Standard 383 383 Standard 117 117 Standard 101 101 Standard 90 90 Standard 77 77 Standard 87 87 Standard 949 949 Standard 259 259 Standard 571 571 Standard 215 215 Standard 291 291 Standard 525 525 Standard 630 630 Standard 730 730 Standard 293 293 Standard 361 361 Standard 394 394 Standard 861 861 Standard 233 233 Standard 130 130 Standard 491 491 Standard 211 211 Standard 931 931 Standard 699 699 Standard 125 125 Standard 61 61 Standard 101 101 Standard 748 748 Standard 200 200 Standard 255 255 Standard 454 454 Standard 419 419 Standard 100 100 Standard 103 103 Standard 424 424 Standard 876 876 Standard 212 212 Standard 463 463 Standard 892 892 Standard 272 272 Standard 555 555 Standard 924 924 Standard 462 462 Standard 350 350 Standard 542 542 Standard 403 403 Standard 591 591 Standard 437 437 Standard 125 125 Standard 108 108 Standard 63 63 Standard 89 89 Standard 94 94 Standard 997 997 Standard 228 228 Standard 728 728 Standard 252 252 Standard 385 385 Standard 548 548 Standard 624 624 Standard 770 770 Standard 352 352 Standard 303 303 Standard 483 483 Standard 889 889 Standard 222 222 Standard 106 106 Standard 688 688 Standard 278 278 Standard 926 926 Standard 721 721 Standard 95 95 Standard 116 116 Standard 116 116 Standard 772 772 Standard 233 233 Standard 243 243 Standard 442 442 Standard 451 451 Standard 87 87 Standard 79 79 Standard 412 412 Standard 888 888 Standard 222 222 Standard 499 499 Standard 911 911 Standard 347 347 Standard 524 524 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 3 3 Standard 2 2 Standard 0 0 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 3 3 Standard 4 4 Standard 4 4 Standard 4 4 Standard 0 0 Standard 0 0 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 0 0 Standard 4 4 Standard 4 4 Standard 0 0 Standard 4 4 Standard 0 0 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 202 202 Standard 117 117 Standard 76 76 Standard 108 108 Standard 114 114 Standard 120 120 Standard 219 219 Standard 313 313 Standard 358 358 Standard 86 86 Standard 12 12 Standard 32 32 Standard 18 18 Standard 29 29 Standard 33 33 Standard 269 269 Standard 47 47 Standard 158 158 Standard 68 68 Standard 464 464 Standard 308 308 Standard 70 70 Standard 120 120 Standard 162 162 Standard 157 157 Standard 79 79 Standard 72 72 Standard 113 113 Standard 199 199 Standard 32 32 Standard 368 368 Standard 17 17 Standard 103 103 Standard 633 633 Standard 50 50 Standard 282 282 Standard 212 212 Standard 25 25 Standard 42 42 Standard 37 37 Standard 172 172 Standard 60 60 Standard 65 65 Standard 114 114 Standard 72 72 Standard 30 30 Standard 22 22 Standard 132 132 Standard 166 166 Standard 41 41 Standard 115 115 Standard 232 232 Standard 64 64 Standard 126 126 Standard 272 272 Standard 133 133 Standard 60 60 Standard 102 102 Standard 121 121 Standard 115 115 Standard 246 246 Standard 307 307 Standard 411 411 Standard 110 110 Standard 36 36 Standard 55 55 Standard 20 20 Standard 28 28 Standard 30 30 Standard 242 242 Standard 33 33 Standard 209 209 Standard 80 80 Standard 462 462 Standard 253 253 Standard 93 93 Standard 157 157 Standard 166 166 Standard 202 202 Standard 88 88 Standard 78 78 Standard 141 141 Standard 214 214 Standard 42 42 Standard 376 376 Standard 23 23 Standard 148 148 Standard 673 673 Standard 63 63 Standard 961 961 Standard 184 184 Standard 176 176 Standard 18 18 Standard 28 28 Standard 23 23 Standard 198 198 Standard 61 61 Standard 76 76 Standard 117 117 Standard 97 97 Standard 38 38 Standard 27 27 Standard 131 131 Standard 230 230 Standard 38 38 Standard 144 144 Standard 197 197 Standard 95 95 Standard 158 158 Standard 220 220 Standard 109 109 Standard 90 90 Standard 106 106 Standard 122 122 Standard 87 87 Standard 231 231 Standard 284 284 Standard 412 412 Standard 93 93 Standard 33 33 Standard 30 30 Standard 28 28 Standard 19 19 Standard 28 28 Standard 231 231 Standard 85 85 Standard 110 110 Standard 47 47 Standard 356 356 Standard 256 256 Standard 82 82 Standard 126 126 Standard 173 173 Standard 195 195 Standard 93 93 Standard 93 93 Standard 96 96 Standard 224 224 Standard 87 87 Standard 368 368 Standard 37 37 Standard 110 110 Standard 698 698 Standard 45 45 Standard 946 946 Standard 213 213 Standard 194 194 Standard 30 30 Standard 31 31 Standard 39 39 Standard 206 206 Standard 50 50 Standard 74 74 Standard 110 110 Standard 98 98 Standard 16 16 Standard 25 25 Standard 103 103 Standard 246 246 Standard 54 54 Standard 125 125 Standard 230 230 Standard 57 57 Standard 136 136 Standard 238 238 Standard 141 141 Standard 80 80 Standard 147 147 Standard 104 104 Standard 164 164 Standard 268 268 Standard 282 282 Standard 397 397 Standard 143 143 Standard 42 42 Standard 29 29 Standard 15 15 Standard 7 7 Standard 26 26 Standard 278 278 Standard 62 62 Standard 207 207 Standard 73 73 Standard 422 422 Standard 339 339 Standard 69 69 Standard 136 136 Standard 177 177 Standard 190 190 Standard 85 85 Standard 64 64 Standard 167 167 Standard 255 255 Standard 62 62 Standard 400 400 Standard 30 30 Standard 166 166 Standard 610 610 Standard 76 76 Standard 994 994 Standard 218 218 Standard 209 209 Standard 27 27 Standard 36 36 Standard 19 19 Standard 213 213 Standard 45 45 Standard 46 46 Standard 94 94 Standard 123 123 Standard 24 24 Standard 29 29 Standard 69 69 Standard 222 222 Standard 59 59 Standard 146 146 Standard 281 281 Standard 98 98 Standard 171 171 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 3 3 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 52 52 Standard 6 6 Standard 17 17 Standard 30 30 Standard 10 10 Standard 15 15 Standard 73 73 Standard 69 69 Standard 125 125 Standard 10 10 Standard 9 9 Standard 5 5 Standard 6 6 Standard 12 12 Standard 7 7 Standard 96 96 Standard 17 17 Standard 36 36 Standard 15 15 Standard 94 94 Standard 58 58 Standard 9 9 Standard 31 31 Standard 50 50 Standard 45 45 Standard 18 18 Standard 20 20 Standard 9 9 Standard 55 55 Standard 8 8 Standard 71 71 Standard 3 3 Standard 32 32 Standard 189 189 Standard 16 16 Standard 208 208 Standard 61 61 Standard 33 33 Standard 5 5 Standard 5 5 Standard 5 5 Standard 43 43 Standard 9 9 Standard 13 13 Standard 9 9 Standard 16 16 Standard 8 8 Standard 17 17 Standard 46 46 Standard 3 3 Standard 8 8 Standard 76 76 Standard 12 12 Standard 47 47 Standard 35 35 Standard 21 21 Standard 22 22 Standard 51 51 Standard 22 22 Standard 42 42 Standard 42 42 Standard 47 47 Standard 84 84 Standard 21 21 Standard 5 5 Standard 11 11 Standard 16 16 Standard 7 7 Standard 4 4 Standard 78 78 Standard 7 7 Standard 23 23 Standard 22 22 Standard 96 96 Standard 58 58 Standard 18 18 Standard 23 23 Standard 34 34 Standard 30 30 Standard 18 18 Standard 23 23 Standard 27 27 Standard 47 47 Standard 19 19 Standard 66 66 Standard 2 2 Standard 16 16 Standard 135 135 Standard 13 13 Standard 208 208 Standard 23 23 Standard 30 30 Standard 5 5 Standard 50 50 Standard 2 2 Standard 13 13 Standard 28 28 Standard 20 20 Standard 9 9 Standard 5 5 Standard 22 22 Standard 35 35 Standard 16 16 Standard 41 41 Standard 40 40 Standard 33 33 Standard 36 36 Standard 74 74 Standard 20 20 Standard 13 13 Standard 24 24 Standard 23 23 Standard 44 44 Standard 54 54 Standard 54 54 Standard 90 90 Standard 30 30 Standard 2 2 Standard 7 7 Standard 10 10 Standard 48 48 Standard 24 24 Standard 42 42 Standard 17 17 Standard 107 107 Standard 90 90 Standard 21 21 Standard 36 36 Standard 33 33 Standard 55 55 Standard 15 15 Standard 32 32 Standard 33 33 Standard 54 54 Standard 10 10 Standard 62 62 Standard 16 16 Standard 157 157 Standard 16 16 Standard 204 204 Standard 57 57 Standard 74 74 Standard 21 21 Standard 10 10 Standard 56 56 Standard 17 17 Standard 21 21 Standard 27 27 Standard 42 42 Standard 9 9 Standard 37 37 Standard 50 50 Standard 18 18 Standard 35 35 Standard 53 53 Standard 16 16 Standard 27 27 Standard 34 34 Standard 19 19 Standard 39 39 Standard 42 42 Standard 16 16 Standard 27 27 Standard 60 60 Standard 49 49 Standard 112 112 Standard 8 8 Standard 11 11 Standard 4 4 Standard 7 7 Standard 2 2 Standard 63 63 Standard 12 12 Standard 41 41 Standard 6 6 Standard 118 118 Standard 65 65 Standard 11 11 Standard 42 42 Standard 42 42 Standard 41 41 Standard 11 11 Standard 25 25 Standard 23 23 Standard 50 50 Standard 3 3 Standard 71 71 Standard 6 6 Standard 37 37 Standard 189 189 Standard 8 8 Standard 204 204 Standard 36 36 Standard 44 44 Standard 13 13 Standard 5 5 Standard 5 5 Standard 25 25 Standard 24 24 Standard 23 23 Standard 21 21 Standard 22 22 Standard 3 3 Standard 2 2 Standard 36 36 Standard 55 55 Standard 15 15 Standard 25 25 Standard 43 43 Standard 34 34 Standard 21 21 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 2 2 Standard 3 3 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 3 3 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 3 3 Standard 3 3 Standard 3 3 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 3 3 Standard 3 3 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 259 259 Standard 134 134 Standard 91 91 Standard 141 141 Standard 132 132 Standard 125 125 Standard 284 284 Standard 326 326 Standard 363 363 Standard 159 159 Standard 26 26 Standard 44 44 Standard 27 27 Standard 26 26 Standard 45 45 Standard 284 284 Standard 50 50 Standard 216 216 Standard 57 57 Standard 556 556 Standard 327 327 Standard 71 71 Standard 161 161 Standard 222 222 Standard 243 243 Standard 98 98 Standard 92 92 Standard 74 74 Standard 232 232 Standard 46 46 Standard 416 416 Standard 40 40 Standard 159 159 Standard 781 781 Standard 58 58 Standard 214 214 Standard 180 180 Standard 25 25 Standard 32 32 Standard 26 26 Standard 217 217 Standard 69 69 Standard 58 58 Standard 118 118 Standard 115 115 Standard 40 40 Standard 24 24 Standard 124 124 Standard 283 283 Standard 54 54 Standard 181 181 Standard 288 288 Standard 102 102 Standard 167 167 Standard 207 207 Standard 114 114 Standard 98 98 Standard 128 128 Standard 132 132 Standard 129 129 Standard 239 239 Standard 258 258 Standard 400 400 Standard 98 98 Standard 39 39 Standard 34 34 Standard 10 10 Standard 26 26 Standard 15 15 Standard 195 195 Standard 48 48 Standard 176 176 Standard 59 59 Standard 371 371 Standard 309 309 Standard 78 78 Standard 139 139 Standard 162 162 Standard 215 215 Standard 89 89 Standard 89 89 Standard 122 122 Standard 235 235 Standard 60 60 Standard 365 365 Standard 11 11 Standard 152 152 Standard 647 647 Standard 51 51 Standard 908 908 Standard 166 166 Standard 147 147 Standard 49 49 Standard 28 28 Standard 33 33 Standard 179 179 Standard 46 46 Standard 55 55 Standard 91 91 Standard 106 106 Standard 20 20 Standard 28 28 Standard 117 117 Standard 190 190 Standard 56 56 Standard 145 145 Standard 260 260 Standard 87 87 Standard 152 152 Standard 275 275 Standard 141 141 Standard 66 66 Standard 119 119 Standard 126 126 Standard 180 180 Standard 300 300 Standard 258 258 Standard 475 475 Standard 102 102 Standard 44 44 Standard 26 26 Standard 8 8 Standard 28 28 Standard 20 20 Standard 295 295 Standard 58 58 Standard 168 168 Standard 61 61 Standard 431 431 Standard 293 293 Standard 67 67 Standard 152 152 Standard 167 167 Standard 204 204 Standard 63 63 Standard 105 105 Standard 125 125 Standard 259 259 Standard 59 59 Standard 397 397 Standard 28 28 Standard 127 127 Standard 730 730 Standard 77 77 Standard 324 324 Standard 214 214 Standard 38 38 Standard 11 11 Standard 13 13 Standard 200 200 Standard 67 67 Standard 55 55 Standard 136 136 Standard 110 110 Standard 33 33 Standard 21 21 Standard 131 131 Standard 262 262 Standard 58 58 Standard 137 137 Standard 266 266 Standard 84 84 Standard 185 185 Standard 300 300 Standard 147 147 Standard 103 103 Standard 165 165 Standard 125 125 Standard 141 141 Standard 296 296 Standard 320 320 Standard 464 464 Standard 123 123 Standard 36 36 Standard 41 41 Standard 21 21 Standard 34 34 Standard 24 24 Standard 290 290 Standard 64 64 Standard 228 228 Standard 110 110 Standard 593 593 Standard 443 443 Standard 135 135 Standard 182 182 Standard 182 182 Standard 255 255 Standard 115 115 Standard 99 99 Standard 141 141 Standard 257 257 Standard 66 66 Standard 465 465 Standard 35 35 Standard 183 183 Standard 788 788 Standard 85 85 Standard 287 287 Standard 189 189 Standard 29 29 Standard 37 37 Standard 38 38 Standard 242 242 Standard 57 57 Standard 66 66 Standard 149 149 Standard 123 123 Standard 27 27 Standard 26 26 Standard 129 129 Standard 291 291 Standard 50 50 Standard 145 145 Standard 266 266 Standard 85 85 Standard 149 149 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 1 1 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 68 68 Standard 32 32 Standard 28 28 Standard 70 70 Standard 43 43 Standard 63 63 Standard 108 108 Standard 142 142 Standard 179 179 Standard 32 32 Standard 15 15 Standard 5 5 Standard 5 5 Standard 11 11 Standard 3 3 Standard 70 70 Standard 21 21 Standard 67 67 Standard 159 159 Standard 134 134 Standard 22 22 Standard 47 47 Standard 74 74 Standard 69 69 Standard 44 44 Standard 24 24 Standard 42 42 Standard 95 95 Standard 13 13 Standard 124 124 Standard 5 5 Standard 31 31 Standard 261 261 Standard 21 21 Standard 415 415 Standard 110 110 Standard 73 73 Standard 9 9 Standard 13 13 Standard 21 21 Standard 75 75 Standard 19 19 Standard 15 15 Standard 49 49 Standard 54 54 Standard 13 13 Standard 5 5 Standard 41 41 Standard 78 78 Standard 29 29 Standard 34 34 Standard 103 103 Standard 27 27 Standard 43 43 Standard 100 100 Standard 37 37 Standard 35 35 Standard 61 61 Standard 35 35 Standard 54 54 Standard 117 117 Standard 93 93 Standard 131 131 Standard 33 33 Standard 9 9 Standard 8 8 Standard 13 13 Standard 2 2 Standard 4 4 Standard 118 118 Standard 21 21 Standard 56 56 Standard 19 19 Standard 138 138 Standard 146 146 Standard 19 19 Standard 50 50 Standard 57 57 Standard 95 95 Standard 31 31 Standard 46 46 Standard 20 20 Standard 65 65 Standard 18 18 Standard 114 114 Standard 19 19 Standard 52 52 Standard 285 285 Standard 16 16 Standard 353 353 Standard 59 59 Standard 70 70 Standard 15 15 Standard 10 10 Standard 60 60 Standard 30 30 Standard 26 26 Standard 45 45 Standard 33 33 Standard 12 12 Standard 7 7 Standard 34 34 Standard 75 75 Standard 27 27 Standard 47 47 Standard 73 73 Standard 43 43 Standard 44 44 Standard 49 49 Standard 33 33 Standard 15 15 Standard 58 58 Standard 41 41 Standard 41 41 Standard 96 96 Standard 88 88 Standard 124 124 Standard 44 44 Standard 7 7 Standard 3 3 Standard 13 13 Standard 3 3 Standard 8 8 Standard 82 82 Standard 13 13 Standard 62 62 Standard 14 14 Standard 146 146 Standard 105 105 Standard 14 14 Standard 48 48 Standard 50 50 Standard 58 58 Standard 36 36 Standard 28 28 Standard 30 30 Standard 80 80 Standard 31 31 Standard 149 149 Standard 21 21 Standard 45 45 Standard 209 209 Standard 16 16 Standard 358 358 Standard 75 75 Standard 46 46 Standard 14 14 Standard 8 8 Standard 11 11 Standard 87 87 Standard 17 17 Standard 14 14 Standard 52 52 Standard 41 41 Standard 6 6 Standard 15 15 Standard 41 41 Standard 79 79 Standard 21 21 Standard 35 35 Standard 88 88 Standard 32 32 Standard 47 47 Standard 61 61 Standard 34 34 Standard 15 15 Standard 43 43 Standard 38 38 Standard 61 61 Standard 70 70 Standard 94 94 Standard 192 192 Standard 33 33 Standard 11 11 Standard 11 11 Standard 6 6 Standard 16 16 Standard 79 79 Standard 37 37 Standard 74 74 Standard 19 19 Standard 122 122 Standard 146 146 Standard 52 52 Standard 45 45 Standard 61 61 Standard 72 72 Standard 32 32 Standard 32 32 Standard 19 19 Standard 89 89 Standard 7 7 Standard 159 159 Standard 7 7 Standard 73 73 Standard 246 246 Standard 35 35 Standard 344 344 Standard 85 85 Standard 67 67 Standard 2 2 Standard 3 3 Standard 2 2 Standard 59 59 Standard 15 15 Standard 10 10 Standard 31 31 Standard 49 49 Standard 3 3 Standard 9 9 Standard 30 30 Standard 41 41 Standard 22 22 Standard 44 44 Standard 100 100 Standard 29 29 Standard 43 43 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 3 3 Standard 4 4 Standard 4 4 Standard 4 4 Standard 3 3 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 3 3 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 252 252 Standard 143 143 Standard 108 108 Standard 146 146 Standard 141 141 Standard 160 160 Standard 297 297 Standard 279 279 Standard 373 373 Standard 143 143 Standard 26 26 Standard 15 15 Standard 29 29 Standard 27 27 Standard 44 44 Standard 308 308 Standard 63 63 Standard 177 177 Standard 59 59 Standard 431 431 Standard 385 385 Standard 95 95 Standard 138 138 Standard 160 160 Standard 216 216 Standard 109 109 Standard 84 84 Standard 119 119 Standard 236 236 Standard 55 55 Standard 446 446 Standard 14 14 Standard 185 185 Standard 749 749 Standard 43 43 Standard 204 204 Standard 171 171 Standard 12 12 Standard 49 49 Standard 40 40 Standard 217 217 Standard 45 45 Standard 71 71 Standard 157 157 Standard 119 119 Standard 53 53 Standard 22 22 Standard 127 127 Standard 241 241 Standard 76 76 Standard 156 156 Standard 168 168 Standard 83 83 Standard 142 142 Standard 243 243 Standard 110 110 Standard 59 59 Standard 164 164 Standard 115 115 Standard 127 127 Standard 252 252 Standard 270 270 Standard 420 420 Standard 110 110 Standard 43 43 Standard 25 25 Standard 17 17 Standard 26 26 Standard 23 23 Standard 323 323 Standard 73 73 Standard 200 200 Standard 43 43 Standard 436 436 Standard 268 268 Standard 73 73 Standard 131 131 Standard 126 126 Standard 205 205 Standard 96 96 Standard 117 117 Standard 122 122 Standard 197 197 Standard 64 64 Standard 381 381 Standard 20 20 Standard 136 136 Standard 654 654 Standard 37 37 Standard 991 991 Standard 263 263 Standard 222 222 Standard 17 17 Standard 35 35 Standard 33 33 Standard 179 179 Standard 45 45 Standard 48 48 Standard 123 123 Standard 110 110 Standard 24 24 Standard 49 49 Standard 127 127 Standard 221 221 Standard 41 41 Standard 133 133 Standard 167 167 Standard 66 66 Standard 156 156 Standard 251 251 Standard 154 154 Standard 105 105 Standard 129 129 Standard 126 126 Standard 116 116 Standard 291 291 Standard 298 298 Standard 405 405 Standard 114 114 Standard 31 31 Standard 42 42 Standard 41 41 Standard 20 20 Standard 21 21 Standard 293 293 Standard 79 79 Standard 189 189 Standard 76 76 Standard 450 450 Standard 346 346 Standard 107 107 Standard 163 163 Standard 207 207 Standard 218 218 Standard 86 86 Standard 103 103 Standard 110 110 Standard 244 244 Standard 46 46 Standard 525 525 Standard 44 44 Standard 193 193 Standard 748 748 Standard 57 57 Standard 262 262 Standard 171 171 Standard 22 22 Standard 11 11 Standard 28 28 Standard 199 199 Standard 49 49 Standard 91 91 Standard 129 129 Standard 128 128 Standard 45 45 Standard 33 33 Standard 112 112 Standard 239 239 Standard 61 61 Standard 131 131 Standard 255 255 Standard 83 83 Standard 160 160 Standard 291 291 Standard 121 121 Standard 113 113 Standard 145 145 Standard 120 120 Standard 198 198 Standard 320 320 Standard 336 336 Standard 453 453 Standard 130 130 Standard 25 25 Standard 27 27 Standard 17 17 Standard 25 25 Standard 42 42 Standard 287 287 Standard 53 53 Standard 178 178 Standard 44 44 Standard 542 542 Standard 405 405 Standard 118 118 Standard 143 143 Standard 162 162 Standard 212 212 Standard 109 109 Standard 83 83 Standard 133 133 Standard 238 238 Standard 84 84 Standard 416 416 Standard 28 28 Standard 229 229 Standard 772 772 Standard 74 74 Standard 300 300 Standard 212 212 Standard 24 24 Standard 35 35 Standard 52 52 Standard 233 233 Standard 92 92 Standard 98 98 Standard 147 147 Standard 134 134 Standard 30 30 Standard 13 13 Standard 148 148 Standard 279 279 Standard 76 76 Standard 139 139 Standard 221 221 Standard 101 101 Standard 140 140 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 1 1 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard 4 4 Standard ]]> WITH MEMBER [Measures].[COG_OQP_USR_Unit Sales] AS ' ([Measures].[COG_OQP_INT_m19]/2)', SOLVE_ORDER = 4 MEMBER [Measures].[COG_OQP_INT_m24] AS ' [Measures].[COG_OQP_INT_m22]', SOLVE_ORDER = 4 MEMBER [Measures].[COG_OQP_INT_m22] AS ' IIF( [Measures].[COG_OQP_INT_m21]> 0, [Measures].[COG_OQP_USR_Unit Sales], NULL)', SOLVE_ORDER = 4 MEMBER [Measures].[COG_OQP_INT_m21] AS ' COUNT( INTERSECT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City], SELF_AND_BEFORE), UNION( {[Customers].[City].[Arcadia], [Customers].[City].[Colma]}, {[Customers].[City].[Richmond], [Customers].[City].[Burbank]})), INCLUDEEMPTY)', SOLVE_ORDER = 1 MEMBER [Measures].[COG_OQP_INT_m19] AS ' IIF( [Measures].[COG_OQP_INT_m17]> 0, [Measures].[COG_OQP_INT_m18], NULL)', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_m18] AS ' ([Time].[COG_OQP_INT_m25], [Measures].[Unit Sales])', SOLVE_ORDER = 1 MEMBER [Measures].[COG_OQP_INT_m17] AS ' COUNT( INTERSECT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City], SELF_AND_BEFORE), UNION( {[Customers].[City].[Arcadia], [Customers].[City].[Colma]}, {[Customers].[City].[Richmond], [Customers].[City].[Burbank]})), INCLUDEEMPTY)', SOLVE_ORDER = 1 MEMBER [Time].[Time].[COG_OQP_INT_m25] AS ' AGGREGATE( INTERSECT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City], SELF_AND_BEFORE), UNION( {[Customers].[City].[Arcadia], [Customers].[City].[Colma]}, {[Customers].[City].[Richmond], [Customers].[City].[Burbank]})), [Time].[Time].DEFAULTMEMBER)', SOLVE_ORDER = 1 MEMBER [Customers].[COG_OQP_INT_t2]AS '1', SOLVE_ORDER = 65535 MEMBER [Customers].[COG_OQP_INT_t1]AS '1', SOLVE_ORDER = 65535 SELECT GENERATE( {[Measures].[COG_OQP_INT_m24]}, CROSSJOIN( HEAD( {([Measures].CURRENTMEMBER)}, IIF( COUNT( EXCEPT( {[Customers].[State Province].MEMBERS}, GENERATE( {[Customers].[Country].MEMBERS}, DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province]), ALL)), INCLUDEEMPTY)> 0, 1, IIF( COUNT( GENERATE( {[Customers].[Country].MEMBERS}, UNION( UNION( HEAD( {([Customers].CURRENTMEMBER)}, IIF( COUNT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province]), INCLUDEEMPTY)> 0, 0, 1)), DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province]), ALL), HEAD( {([Customers].CURRENTMEMBER)}, IIF( COUNT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province]), INCLUDEEMPTY)= 0, 1, 0)), ALL), ALL), INCLUDEEMPTY)> 0, 1, 0))), UNION( UNION( UNION( GENERATE( {[Customers].[Country].MEMBERS}, UNION( UNION( UNION( {([Customers].[COG_OQP_INT_t2])}, {([Customers].CURRENTMEMBER)}, ALL), {([Customers].[COG_OQP_INT_t1])}, ALL), DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province]), ALL), ALL), {([Customers].[COG_OQP_INT_t2])}, ALL), {([Customers].[COG_OQP_INT_t1])}, ALL), EXCEPT( {[Customers].[State Province].MEMBERS}, GENERATE( {[Customers].[Country].MEMBERS}, DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province]), ALL)), ALL)), ALL) ON AXIS(0) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Customers].[(All)] 0 0 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[Canada] Canada [Customers].[Country] 1 1 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Customers].[(All)] 0 0 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[Canada].[BC] BC [Customers].[State Province] 2 18 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Customers].[(All)] 0 0 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[Mexico] Mexico [Customers].[Country] 1 9 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Customers].[(All)] 0 0 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[Mexico].[DF] DF [Customers].[State Province] 2 4 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[Mexico].[Guerrero] Guerrero [Customers].[State Province] 2 131073 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[Mexico].[Jalisco] Jalisco [Customers].[State Province] 2 131073 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[Mexico].[Mexico] Mexico [Customers].[State Province] 2 131073 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[Mexico].[Oaxaca] Oaxaca [Customers].[State Province] 2 131073 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[Mexico].[Sinaloa] Sinaloa [Customers].[State Province] 2 131073 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[Mexico].[Veracruz] Veracruz [Customers].[State Province] 2 131073 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[Mexico].[Yucatan] Yucatan [Customers].[State Province] 2 131073 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[Mexico].[Zacatecas] Zacatecas [Customers].[State Province] 2 131074 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Customers].[(All)] 0 0 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 3 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Customers].[(All)] 0 0 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 45 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Customers].[(All)] 0 0 [Measures].[COG_OQP_INT_m24] COG_OQP_INT_m24 [Measures].[MeasuresLevel] 0 0 [Customers].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Customers].[(All)] 0 0 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 1 1 Standard 2827.5 2,828 Standard 1 1 Standard 2827.5 2,828 Standard 1 1 Standard 1 1 Standard ]]> WITH MEMBER [Measures].[COG_OQP_USR_Unit Sales] AS ' IIF( [Customers].CURRENTMEMBER.LEVEL.ORDINAL < 3, ([Time].[COG_OQP_INT_m2], [Measures].[Unit Sales]), [Measures].[COG_OQP_INT_m1])', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_m1] AS ' IIF( ([Measures].[Unit Sales]/2)> 1, [Measures].[Unit Sales], NULL)', SOLVE_ORDER = 2 MEMBER [Time].[Time].[COG_OQP_INT_m2] AS ' AGGREGATE( FILTER( INTERSECT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[City], SELF_AND_BEFORE), [COG_OQP_INT_s1]), (([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)/2)> 1), [Time].[Time].DEFAULTMEMBER)', SOLVE_ORDER = 2 SET [COG_OQP_INT_s1] AS ' UNION( UNION( UNION( {[Customers].[City].[Arcadia], [Customers].[City].[Colma]}, {[Customers].[City].[Richmond], [Customers].[City].[Burbank]}), EXCEPT( GENERATE( UNION( {[Customers].[City].[Arcadia], [Customers].[City].[Colma]}, {[Customers].[City].[Richmond], [Customers].[City].[Burbank]}), {ANCESTOR( [Customers].CURRENTMEMBER, [Customers].[State Province])}), GENERATE( UNION( {[Customers].[City].[Arcadia], [Customers].[City].[Colma]}, {[Customers].[City].[Richmond], [Customers].[City].[Burbank]}), {ANCESTOR( [Customers].CURRENTMEMBER, [Customers].[State Province])}))), EXCEPT( GENERATE( UNION( {[Customers].[City].[Arcadia], [Customers].[City].[Colma]}, {[Customers].[City].[Richmond], [Customers].[City].[Burbank]}), {ANCESTOR( [Customers].CURRENTMEMBER, [Customers].[Country])}), GENERATE( UNION( UNION( {[Customers].[City].[Arcadia], [Customers].[City].[Colma]}, {[Customers].[City].[Richmond], [Customers].[City].[Burbank]}), {[Customers].[State Province].MEMBERS}), {ANCESTOR( [Customers].CURRENTMEMBER, [Customers].[Country])})))' SELECT CROSSJOIN( FILTER( GENERATE( UNION( {[Customers].[City].[Arcadia], [Customers].[City].[Colma]}, {[Customers].[City].[Richmond], [Customers].[City].[Burbank]}), UNION( UNION( {ANCESTOR( [Customers].CURRENTMEMBER, [Customers].[Country])}, {ANCESTOR( [Customers].CURRENTMEMBER, [Customers].[State Province])}), {[Customers].CURRENTMEMBER}), ALL), NOT ISEMPTY( [Measures].[COG_OQP_USR_Unit Sales])), {[Measures].[COG_OQP_USR_Unit Sales]}) ON AXIS(0) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 65581 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA].[Arcadia] Arcadia [Customers].[City] 3 98 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 65581 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA].[Colma] Colma [Customers].[City] 3 84 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 65539 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 65581 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA].[Burbank] Burbank [Customers].[City] 3 96 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 5655 5,655 Standard 5655 5,655 Standard 2440 2,440 Standard 5655 5,655 Standard 5655 5,655 Standard 129 129 Standard 5655 5,655 Standard 5655 5,655 Standard 3086 3,086 Standard ]]> WITH MEMBER [Measures].[COG_OQP_USR_Unit Sales] AS ' ([Measures].[Unit Sales]/2)', SOLVE_ORDER = 4 MEMBER [Gender].[COG_OQP_INT_umg1] AS ' IIF( [Measures].CURRENTMEMBER IS [Measures].[Unit Sales], ([Gender].[COG_OQP_INT_m2], [Measures].[Unit Sales]), IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_USR_Unit Sales], (([Gender].[COG_OQP_INT_m2], [Measures].[Unit Sales])/2), AGGREGATE( [COG_OQP_INT_s1])))', SOLVE_ORDER = 8 MEMBER [Gender].[COG_OQP_INT_m2] AS ' AGGREGATE( [COG_OQP_INT_s1])', SOLVE_ORDER = 8 SET [COG_OQP_INT_s1] AS ' CROSSJOIN( {[Gender].MEMBERS}, {{[Product].[Product Department].[Alcoholic Beverages]}, {[Product].[Product Department].[Deli]}, {[Product].[Product Department].[Meat]}})' SELECT {[Measures].[COG_OQP_USR_Unit Sales]} ON AXIS(0), UNION( [COG_OQP_INT_s1], {([Gender].[COG_OQP_INT_umg1], [Product].DEFAULTMEMBER)}, ALL) ON AXIS(1) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Gender].[All Gender] All Gender [Gender].[(All)] 0 2 [Product].[Drink].[Alcoholic Beverages] Alcoholic Beverages [Product].[Product Department] 2 1 [Gender].[All Gender] All Gender [Gender].[(All)] 0 2 [Product].[Food].[Deli] Deli [Product].[Product Department] 2 2 [Gender].[All Gender] All Gender [Gender].[(All)] 0 65538 [Product].[Food].[Meat] Meat [Product].[Product Department] 2 131073 [Gender].[F] F [Gender].[Gender] 1 0 [Product].[Drink].[Alcoholic Beverages] Alcoholic Beverages [Product].[Product Department] 2 1 [Gender].[F] F [Gender].[Gender] 1 131072 [Product].[Food].[Deli] Deli [Product].[Product Department] 2 2 [Gender].[F] F [Gender].[Gender] 1 131072 [Product].[Food].[Meat] Meat [Product].[Product Department] 2 131073 [Gender].[M] M [Gender].[Gender] 1 131072 [Product].[Drink].[Alcoholic Beverages] Alcoholic Beverages [Product].[Product Department] 2 1 [Gender].[M] M [Gender].[Gender] 1 131072 [Product].[Food].[Deli] Deli [Product].[Product Department] 2 2 [Gender].[M] M [Gender].[Gender] 1 131072 [Product].[Food].[Meat] Meat [Product].[Product Department] 2 131073 [Gender].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Gender].[(All)] 0 0 [Product].[All Products] All Products [Product].[(All)] 0 3 3419 3,419 Standard 6018.5 6,019 Standard 857 857 Standard 1719.5 1,720 Standard 2995 2,995 Standard 420.5 421 Standard 1699.5 1,700 Standard 3023.5 3,024 Standard 436.5 437 Standard 20589 20,589 Standard ]]> WITH MEMBER [Product].[COG_OQP_INT_t2]AS '1', SOLVE_ORDER = 65535 MEMBER [Product].[COG_OQP_INT_t1]AS '1', SOLVE_ORDER = 65535 SELECT UNION( UNION( UNION( {([Product].[COG_OQP_INT_t1])}, {[Product].[Drink].[Alcoholic Beverages].[Beer and Wine]}, ALL), {([Product].[COG_OQP_INT_t2])}, ALL), {[Product].[Food].[Dairy]}, ALL) ON AXIS(0) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Product].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Product].[(All)] 0 0 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 2 [Product].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Product].[(All)] 0 0 [Product].[Food].[Dairy] Dairy [Product].[Product Department] 2 1 1 1 Standard 6838 6,838 Standard 1 1 Standard 12885 12,885 Standard ]]> WITH MEMBER [Measures].[COG_OQP_USR_Unit Sales] AS ' ([Measures].[COG_OQP_INT_m2])', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_m2] AS ' IIF( (([Measures].[Unit Sales])>= 1 AND ([Measures].[Unit Sales]) < 1000), ([Measures].[Unit Sales]), NULL)', SOLVE_ORDER = 2 MEMBER [Time].[Time].[COG_OQP_INT_umg1] AS ' COUNT( {[Time].[Quarter].MEMBERS}, EXCLUDEEMPTY)', SOLVE_ORDER = 4 SET [COG_OQP_INT_s4] AS ' CROSSJOIN( [COG_OQP_INT_s1], {[Product].[Product Category].MEMBERS})' SET [COG_OQP_INT_s3] AS ' CROSSJOIN( {[Education Level].[Graduate Degree]}, {[Product].[Product Category].MEMBERS})' SET [COG_OQP_INT_s2] AS ' CROSSJOIN( {[Store Type].MEMBERS}, [COG_OQP_INT_s1])' SET [COG_OQP_INT_s1] AS ' CROSSJOIN( {[Time].[Quarter].MEMBERS}, {[Education Level].[Graduate Degree]})' SELECT CROSSJOIN( {[Measures].[COG_OQP_USR_Unit Sales]}, FILTER( {[Marital Status].MEMBERS}, COUNT( FILTER( CROSSJOIN( {[Marital Status].CURRENTMEMBER}, [COG_OQP_INT_s2]), (([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER)>= 1 AND ([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER) < 1000)), INCLUDEEMPTY)> 0)) ON AXIS(0), GENERATE( {[Store Type].MEMBERS}, CROSSJOIN( HEAD( {([Store Type].CURRENTMEMBER)}, IIF( COUNT( FILTER( CROSSJOIN( {[Store Type].CURRENTMEMBER}, [COG_OQP_INT_s4]), (([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER)>= 1 AND ([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER) < 1000)), INCLUDEEMPTY)> 0, 1, 0)), UNION( GENERATE( {[Time].[Quarter].MEMBERS}, CROSSJOIN( HEAD( {([Time].[Time].CURRENTMEMBER)}, IIF( COUNT( FILTER( CROSSJOIN( {[Store Type].CURRENTMEMBER}, [COG_OQP_INT_s3]), (([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER)>= 1 AND ([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER) < 1000)), INCLUDEEMPTY)> 0, 1, 0)), FILTER( {[Education Level].[Graduate Degree]}, COUNT( FILTER( {[Product].[Product Category].MEMBERS}, (([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER)>= 1 AND ([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER) < 1000)), INCLUDEEMPTY)> 0)), ALL), GENERATE( {[Time].[COG_OQP_INT_umg1]}, CROSSJOIN( {([Time].[Time].CURRENTMEMBER)}, FILTER( {[Education Level].DEFAULTMEMBER}, COUNT( FILTER( {[Product].[Product Category].MEMBERS}, (([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER)>= 1 AND ([Measures].[Unit Sales], [Store Size in SQFT].DEFAULTMEMBER) < 1000)), INCLUDEEMPTY)> 0)), ALL), ALL)), ALL) ON AXIS(1) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 65647 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Marital Status].[M] M [Marital Status].[Marital Status] 1 0 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Marital Status].[S] S [Marital Status].[Marital Status] 1 131072 [Store Type].[All Store Types] All Store Types [Store Type].[(All)] 0 6 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Store Type].[All Store Types] All Store Types [Store Type].[(All)] 0 6 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[All Store Types] All Store Types [Store Type].[(All)] 0 6 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[All Store Types] All Store Types [Store Type].[(All)] 0 6 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[All Store Types] All Store Types [Store Type].[(All)] 0 65542 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 0 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 5 Standard 0 0 Standard 0 0 Standard 0 0 Standard 564 564 Standard 558 558 Standard 842 842 Standard 344 344 Standard 498 498 Standard 992 992 Standard 548 548 Standard 444 444 Standard 748 748 Standard 261 261 Standard 487 487 Standard 0 0 Standard 0 0 Standard 0 0 Standard 220 220 Standard 104 104 Standard 116 116 Standard 353 353 Standard 212 212 Standard 141 141 Standard 233 233 Standard 124 124 Standard 109 109 Standard 284 284 Standard 153 153 Standard 131 131 Standard 0 0 Standard 0 0 Standard 0 0 Standard 278 278 Standard 100 100 Standard 178 178 Standard 309 309 Standard 76 76 Standard 233 233 Standard 230 230 Standard 90 90 Standard 140 140 Standard 386 386 Standard 189 189 Standard 197 197 Standard 0 0 Standard 0 0 Standard 0 0 Standard 80 80 Standard 62 62 Standard 18 18 Standard 78 78 Standard 61 61 Standard 17 17 Standard 33 33 Standard 9 9 Standard 24 24 Standard 125 125 Standard 63 63 Standard 62 62 Standard 0 0 Standard 4 4 Standard 4 4 Standard 825 825 Standard 995 995 Standard 0 0 Standard 0 0 Standard 0 0 Standard ]]> WITH MEMBER [Product].[Product COG_OQP_INT_sfa2] AS ' IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_USR_Unit Sales], (([Product].DEFAULTMEMBER, [Measures].[Unit Sales])/2), ([Product].DEFAULTMEMBER))', SOLVE_ORDER = 5 MEMBER [Measures].[COG_OQP_USR_Unit Sales] AS ' (([Measures].[Unit Sales])/2)', SOLVE_ORDER = 4 SELECT {[Measures].[COG_OQP_USR_Unit Sales]} ON AXIS(0), { CROSSJOIN( HEAD( {([Customers].CURRENTMEMBER)}, IIF( COUNT( CROSSJOIN( {([Customers].CURRENTMEMBER)}, FILTER( {[Gender].MEMBERS}, ([Measures].[COG_OQP_USR_Unit Sales], [Customers].CURRENTMEMBER, [Product].[Product COG_OQP_INT_sfa2], [Gender].CURRENTMEMBER)> 10000)), INCLUDEEMPTY)> 0, 1, IIF( COUNT( CROSSJOIN( {([Customers].CURRENTMEMBER)}, CROSSJOIN( FILTER( {[Gender].MEMBERS}, ([Measures].[COG_OQP_USR_Unit Sales], [Customers].CURRENTMEMBER, [Product].[Product COG_OQP_INT_sfa2], [Gender].CURRENTMEMBER)> 10000), {[Product].[Product Category].[Beer and Wine], [Product].[Product Category].[Drinks], [Product].[Product Category].[Bread]})), INCLUDEEMPTY)> 0,1,0))), CROSSJOIN( FILTER( {[Gender].MEMBERS}, ([Measures].[COG_OQP_USR_Unit Sales], [Customers].CURRENTMEMBER, [Product].[Product COG_OQP_INT_sfa2], [Gender].CURRENTMEMBER)> 10000), {[Product].[Product Category].[Beer and Wine], [Product].[Product Category].[Drinks], [Product].[Product Category].[Bread]}) ) } ON AXIS(1) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[All Customers] All Customers [Customers].[(All)] 0 3 [Gender].[All Gender] All Gender [Gender].[(All)] 0 2 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 2 [Customers].[All Customers] All Customers [Customers].[(All)] 0 3 [Gender].[All Gender] All Gender [Gender].[(All)] 0 2 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 1 [Customers].[All Customers] All Customers [Customers].[(All)] 0 3 [Gender].[All Gender] All Gender [Gender].[(All)] 0 65538 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 3 [Customers].[All Customers] All Customers [Customers].[(All)] 0 3 [Gender].[F] F [Gender].[Gender] 1 0 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 2 [Customers].[All Customers] All Customers [Customers].[(All)] 0 3 [Gender].[F] F [Gender].[Gender] 1 131072 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 1 [Customers].[All Customers] All Customers [Customers].[(All)] 0 3 [Gender].[F] F [Gender].[Gender] 1 131072 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 3 [Customers].[All Customers] All Customers [Customers].[(All)] 0 3 [Gender].[M] M [Gender].[Gender] 1 131072 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 2 [Customers].[All Customers] All Customers [Customers].[(All)] 0 3 [Gender].[M] M [Gender].[Gender] 1 131072 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 1 [Customers].[All Customers] All Customers [Customers].[(All)] 0 3 [Gender].[M] M [Gender].[Gender] 1 131072 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 3 3419 3,419 Standard 1234.5 1,235 Standard 3935 3,935 Standard 1719.5 1,720 Standard 609 609 Standard 1885.5 1,886 Standard 1699.5 1,700 Standard 625.5 626 Standard 2049.5 2,050 Standard ]]> WITH MEMBER [Measures].[COG_OQP_USR_Unit Sales] AS ' ([Measures].[COG_OQP_INT_m1]/2)', SOLVE_ORDER = 4 MEMBER [Measures].[COG_OQP_INT_m3] AS ' [Measures].[COG_OQP_INT_m2]', SOLVE_ORDER = 4 MEMBER [Measures].[COG_OQP_INT_m2] AS ' IIF( ([Measures].[Unit Sales]/2)> 1, [Measures].[COG_OQP_USR_Unit Sales], NULL)', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_m1] AS ' IIF( ([Measures].[Unit Sales]/2)> 1, [Measures].[Unit Sales], NULL)', SOLVE_ORDER = 2 MEMBER [Time].[Time].[COG_OQP_INT_m5] AS ' IIF( (([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)/2)> 1, 1, NULL)', SOLVE_ORDER = 2 MEMBER [Time].[Time].[COG_OQP_INT_m4] AS ' IIF( (([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)/2)> 1, 1, NULL)', SOLVE_ORDER = 5 MEMBER [Customers].[COG_OQP_USR_Aggregate(State Province)] AS ' IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_INT_m2], (([Customers].[COG_OQP_INT_m7], [Measures].[Unit Sales])/2), IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_USR_Unit Sales], (([Customers].[COG_OQP_INT_m6], [Measures].[Unit Sales])/2), IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_INT_m1], ([Customers].[COG_OQP_INT_m7], [Measures].[Unit Sales]), IIF( [Measures].CURRENTMEMBER IS [Measures].[Unit Sales], ([Customers].[COG_OQP_INT_m7], [Measures].[Unit Sales]), IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_INT_m3], (([Customers].[COG_OQP_INT_m6], [Measures].[Unit Sales])/2), AGGREGATE( {[Customers].[State Province].MEMBERS}))))))', SOLVE_ORDER = 8 MEMBER [Customers].[COG_OQP_INT_m7] AS ' AGGREGATE( FILTER( {[Customers].[State Province].MEMBERS}, NOT ISEMPTY( [Time].[COG_OQP_INT_m5])))', SOLVE_ORDER = 8 MEMBER [Customers].[COG_OQP_INT_m6] AS ' AGGREGATE( FILTER( {[Customers].[State Province].MEMBERS}, NOT ISEMPTY( [Time].[COG_OQP_INT_m4])))', SOLVE_ORDER = 8 SELECT {[Measures].[COG_OQP_INT_m3]} ON AXIS(0), HEAD( HEAD( {[Customers].[COG_OQP_USR_Aggregate(State Province)]}, IIF( COUNT( FILTER( {[Customers].[State Province].MEMBERS}, ([Measures].[Unit Sales]/2)> 1), INCLUDEEMPTY)> 0, 1, 0)), IIF( COUNT( {[Customers].[State Province].MEMBERS}, INCLUDEEMPTY)> 0, 1, 0)) ON AXIS(1) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[COG_OQP_INT_m3] COG_OQP_INT_m3 [Measures].[MeasuresLevel] 0 0 [Customers].[COG_OQP_USR_Aggregate(State Province)] COG_OQP_USR_Aggregate(State Province) [Customers].[(All)] 0 0 133386.5 133,387 Standard ]]> WITH MEMBER [Gender].[COG_OQP_INT_m3] AS ' IIF( (([Measures].[Unit Sales], [Gender].DEFAULTMEMBER)>= 1 AND ([Measures].[Unit Sales], [Gender].DEFAULTMEMBER) < 200000), 1, NULL)', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_USR_Unit Sales] AS ' [Measures].[COG_OQP_INT_m2]', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_m2] AS ' IIF( ([Measures].[Unit Sales]>= 1 AND [Measures].[Unit Sales] < 200000), [Measures].[Unit Sales], NULL)', SOLVE_ORDER = 2 MEMBER [Time].[Time].[COG_OQP_INT_umg1] AS ' IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_INT_m2], ([Time].[COG_OQP_INT_m4], [Measures].[Unit Sales]), IIF( [Measures].CURRENTMEMBER IS [Measures].[Unit Sales], ([Time].[COG_OQP_INT_m4], [Measures].[Unit Sales]), IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_USR_Unit Sales], ([Time].[COG_OQP_INT_m4], [Measures].[Unit Sales]), AGGREGATE( {[Time].[Quarter].MEMBERS}))))', SOLVE_ORDER = 4 MEMBER [Time].[Time].[COG_OQP_INT_m4] AS ' AGGREGATE( FILTER( {[Time].[Quarter].MEMBERS}, NOT ISEMPTY( [Gender].[COG_OQP_INT_m3])))', SOLVE_ORDER = 4 SET [COG_OQP_INT_s1] AS ' CROSSJOIN( {[Marital Status].[M]}, {[Time].[Quarter].MEMBERS})' SELECT {[Measures].[COG_OQP_USR_Unit Sales]} ON AXIS(0), GENERATE( {[Store].MEMBERS}, CROSSJOIN( HEAD( {([Store].CURRENTMEMBER)}, IIF( (IIF(COUNT(FILTER( [COG_OQP_INT_s1], ([Measures].[Unit Sales]>= 1 AND [Measures].[Unit Sales] < 200000)), INCLUDEEMPTY)> 0, 1, 0)= 1 AND IIF( COUNT( [COG_OQP_INT_s1], INCLUDEEMPTY)> 0, 1, 0)= 1), 1, 0)), CROSSJOIN( HEAD( {([Marital Status].[M])}, IIF( COUNT( FILTER( {[Time].[Quarter].MEMBERS}, (([Measures].[Unit Sales], [Marital Status].[M])>= 1 AND ([Measures].[Unit Sales], [Marital Status].[M]) < 200000)), INCLUDEEMPTY)> 0, 1, 0)), UNION( FILTER( {[Time].[Quarter].MEMBERS}, (([Measures].[Unit Sales], [Marital Status].[M])>= 1 AND ([Measures].[Unit Sales], [Marital Status].[M]) < 200000)), HEAD( {([Time].[COG_OQP_INT_umg1])}, IIF( COUNT( FILTER( {[Time].[Quarter].MEMBERS}, (([Measures].[Unit Sales], [Marital Status].[M])>= 1 AND ([Measures].[Unit Sales], [Marital Status].[M]) < 200000)), INCLUDEEMPTY)> 0, 1, 0)), ALL))), ALL) ON AXIS(1) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Store].[All Stores] All Stores [Store].[(All)] 0 3 [Marital Status].[M] M [Marital Status].[Marital Status] 1 0 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[All Stores] All Stores [Store].[(All)] 0 3 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[All Stores] All Stores [Store].[(All)] 0 3 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[All Stores] All Stores [Store].[(All)] 0 3 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[All Stores] All Stores [Store].[(All)] 0 65539 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA] USA [Store].[Store Country] 1 3 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA] USA [Store].[Store Country] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA] USA [Store].[Store Country] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA] USA [Store].[Store Country] 1 131075 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA] USA [Store].[Store Country] 1 196611 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[CA] CA [Store].[Store State] 2 5 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[CA] CA [Store].[Store State] 2 131077 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[CA] CA [Store].[Store State] 2 131077 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[CA] CA [Store].[Store State] 2 131077 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[CA] CA [Store].[Store State] 2 196613 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[CA].[Beverly Hills] Beverly Hills [Store].[Store City] 3 1 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[CA].[Beverly Hills] Beverly Hills [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[Beverly Hills] Beverly Hills [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[Beverly Hills] Beverly Hills [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[Beverly Hills] Beverly Hills [Store].[Store City] 3 196609 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[CA].[Beverly Hills].[Store 6] Store 6 [Store].[Store Name] 4 0 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[CA].[Beverly Hills].[Store 6] Store 6 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[Beverly Hills].[Store 6] Store 6 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[Beverly Hills].[Store 6] Store 6 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[Beverly Hills].[Store 6] Store 6 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[CA].[Los Angeles] Los Angeles [Store].[Store City] 3 1 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[CA].[Los Angeles] Los Angeles [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[Los Angeles] Los Angeles [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[Los Angeles] Los Angeles [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[Los Angeles] Los Angeles [Store].[Store City] 3 196609 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[CA].[Los Angeles].[Store 7] Store 7 [Store].[Store Name] 4 0 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[CA].[Los Angeles].[Store 7] Store 7 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[Los Angeles].[Store 7] Store 7 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[Los Angeles].[Store 7] Store 7 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[Los Angeles].[Store 7] Store 7 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[CA].[San Diego] San Diego [Store].[Store City] 3 1 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[CA].[San Diego] San Diego [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[San Diego] San Diego [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[San Diego] San Diego [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[San Diego] San Diego [Store].[Store City] 3 196609 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[CA].[San Diego].[Store 24] Store 24 [Store].[Store Name] 4 0 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[CA].[San Diego].[Store 24] Store 24 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[San Diego].[Store 24] Store 24 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[San Diego].[Store 24] Store 24 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[San Diego].[Store 24] Store 24 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[CA].[San Francisco] San Francisco [Store].[Store City] 3 1 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[CA].[San Francisco] San Francisco [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[San Francisco] San Francisco [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[San Francisco] San Francisco [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[San Francisco] San Francisco [Store].[Store City] 3 196609 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[CA].[San Francisco].[Store 14] Store 14 [Store].[Store Name] 4 0 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[CA].[San Francisco].[Store 14] Store 14 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[San Francisco].[Store 14] Store 14 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[San Francisco].[Store 14] Store 14 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[CA].[San Francisco].[Store 14] Store 14 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[OR] OR [Store].[Store State] 2 2 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[OR] OR [Store].[Store State] 2 131074 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[OR] OR [Store].[Store State] 2 131074 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[OR] OR [Store].[Store State] 2 131074 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[OR] OR [Store].[Store State] 2 196610 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[OR].[Portland] Portland [Store].[Store City] 3 1 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[OR].[Portland] Portland [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[OR].[Portland] Portland [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[OR].[Portland] Portland [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[OR].[Portland] Portland [Store].[Store City] 3 196609 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[OR].[Portland].[Store 11] Store 11 [Store].[Store Name] 4 0 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[OR].[Portland].[Store 11] Store 11 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[OR].[Portland].[Store 11] Store 11 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[OR].[Portland].[Store 11] Store 11 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[OR].[Portland].[Store 11] Store 11 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[OR].[Salem] Salem [Store].[Store City] 3 1 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[OR].[Salem] Salem [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[OR].[Salem] Salem [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[OR].[Salem] Salem [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[OR].[Salem] Salem [Store].[Store City] 3 196609 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[OR].[Salem].[Store 13] Store 13 [Store].[Store Name] 4 0 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[OR].[Salem].[Store 13] Store 13 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[OR].[Salem].[Store 13] Store 13 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[OR].[Salem].[Store 13] Store 13 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[OR].[Salem].[Store 13] Store 13 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[WA] WA [Store].[Store State] 2 7 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[WA] WA [Store].[Store State] 2 131079 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[WA] WA [Store].[Store State] 2 131079 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[WA] WA [Store].[Store State] 2 131079 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[WA] WA [Store].[Store State] 2 196615 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[WA].[Bellingham] Bellingham [Store].[Store City] 3 1 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[WA].[Bellingham] Bellingham [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Bellingham] Bellingham [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Bellingham] Bellingham [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Bellingham] Bellingham [Store].[Store City] 3 196609 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[WA].[Bellingham].[Store 2] Store 2 [Store].[Store Name] 4 0 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[WA].[Bellingham].[Store 2] Store 2 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Bellingham].[Store 2] Store 2 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Bellingham].[Store 2] Store 2 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Bellingham].[Store 2] Store 2 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[WA].[Bremerton] Bremerton [Store].[Store City] 3 1 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[WA].[Bremerton] Bremerton [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Bremerton] Bremerton [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Bremerton] Bremerton [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Bremerton] Bremerton [Store].[Store City] 3 196609 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[WA].[Bremerton].[Store 3] Store 3 [Store].[Store Name] 4 0 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[WA].[Bremerton].[Store 3] Store 3 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Bremerton].[Store 3] Store 3 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Bremerton].[Store 3] Store 3 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Bremerton].[Store 3] Store 3 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[WA].[Seattle] Seattle [Store].[Store City] 3 1 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[WA].[Seattle] Seattle [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Seattle] Seattle [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Seattle] Seattle [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Seattle] Seattle [Store].[Store City] 3 196609 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[WA].[Seattle].[Store 15] Store 15 [Store].[Store Name] 4 0 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[WA].[Seattle].[Store 15] Store 15 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Seattle].[Store 15] Store 15 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Seattle].[Store 15] Store 15 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Seattle].[Store 15] Store 15 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[WA].[Spokane] Spokane [Store].[Store City] 3 1 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[WA].[Spokane] Spokane [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Spokane] Spokane [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Spokane] Spokane [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Spokane] Spokane [Store].[Store City] 3 196609 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[WA].[Spokane].[Store 16] Store 16 [Store].[Store Name] 4 0 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[WA].[Spokane].[Store 16] Store 16 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Spokane].[Store 16] Store 16 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Spokane].[Store 16] Store 16 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Spokane].[Store 16] Store 16 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[WA].[Tacoma] Tacoma [Store].[Store City] 3 1 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[WA].[Tacoma] Tacoma [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Tacoma] Tacoma [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Tacoma] Tacoma [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Tacoma] Tacoma [Store].[Store City] 3 196609 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[WA].[Tacoma].[Store 17] Store 17 [Store].[Store Name] 4 0 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[WA].[Tacoma].[Store 17] Store 17 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Tacoma].[Store 17] Store 17 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Tacoma].[Store 17] Store 17 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Tacoma].[Store 17] Store 17 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[WA].[Walla Walla] Walla Walla [Store].[Store City] 3 1 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[WA].[Walla Walla] Walla Walla [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Walla Walla] Walla Walla [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Walla Walla] Walla Walla [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Walla Walla] Walla Walla [Store].[Store City] 3 196609 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[WA].[Walla Walla].[Store 22] Store 22 [Store].[Store Name] 4 0 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[WA].[Walla Walla].[Store 22] Store 22 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Walla Walla].[Store 22] Store 22 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Walla Walla].[Store 22] Store 22 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Walla Walla].[Store 22] Store 22 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[WA].[Yakima] Yakima [Store].[Store City] 3 1 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[WA].[Yakima] Yakima [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Yakima] Yakima [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Yakima] Yakima [Store].[Store City] 3 131073 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Yakima] Yakima [Store].[Store City] 3 196609 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store].[USA].[WA].[Yakima].[Store 23] Store 23 [Store].[Store Name] 4 0 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Store].[USA].[WA].[Yakima].[Store 23] Store 23 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Yakima].[Store 23] Store 23 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Yakima].[Store 23] Store 23 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Store].[USA].[WA].[Yakima].[Store 23] Store 23 [Store].[Store Name] 4 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Store Size in SQFT].[All Store Size in SQFTs] All Store Size in SQFTs [Store Size in SQFT].[(All)] 0 21 [Store Type].[All Store Types] All Store Types [Store Type].[(All)] 0 6 [Product].[All Products] All Products [Product].[(All)] 0 3 [Promotion Media].[All Media] All Media [Promotion Media].[(All)] 0 14 [Promotions].[All Promotions] All Promotions [Promotions].[(All)] 0 51 [Customers].[All Customers] All Customers [Customers].[(All)] 0 3 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 5 [Gender].[All Gender] All Gender [Gender].[(All)] 0 2 [Yearly Income].[All Yearly Incomes] All Yearly Incomes [Yearly Income].[(All)] 0 8 33101 33,101 Standard 30886 30,886 Standard 32815 32,815 Standard 34994 34,994 Standard 131796 131,796 Standard 33101 33,101 Standard 30886 30,886 Standard 32815 32,815 Standard 34994 34,994 Standard 131796 131,796 Standard 8665 8,665 Standard 8797 8,797 Standard 8958 8,958 Standard 10413 10,413 Standard 36833 36,833 Standard 1838 1,838 Standard 3018 3,018 Standard 2277 2,277 Standard 3572 3,572 Standard 10705 10,705 Standard 1838 1,838 Standard 3018 3,018 Standard 2277 2,277 Standard 3572 3,572 Standard 10705 10,705 Standard 3306 3,306 Standard 2750 2,750 Standard 3199 3,199 Standard 3545 3,545 Standard 12800 12,800 Standard 3306 3,306 Standard 2750 2,750 Standard 3199 3,199 Standard 3545 3,545 Standard 12800 12,800 Standard 3273 3,273 Standard 2801 2,801 Standard 3236 3,236 Standard 3003 3,003 Standard 12313 12,313 Standard 3273 3,273 Standard 2801 2,801 Standard 3236 3,236 Standard 3003 3,003 Standard 12313 12,313 Standard 248 248 Standard 228 228 Standard 246 246 Standard 293 293 Standard 1015 1,015 Standard 248 248 Standard 228 228 Standard 246 246 Standard 293 293 Standard 1015 1,015 Standard 9108 9,108 Standard 6987 6,987 Standard 8286 8,286 Standard 7549 7,549 Standard 31930 31,930 Standard 3309 3,309 Standard 3318 3,318 Standard 2699 2,699 Standard 3306 3,306 Standard 12632 12,632 Standard 3309 3,309 Standard 3318 3,318 Standard 2699 2,699 Standard 3306 3,306 Standard 12632 12,632 Standard 5799 5,799 Standard 3669 3,669 Standard 5587 5,587 Standard 4243 4,243 Standard 19298 19,298 Standard 5799 5,799 Standard 3669 3,669 Standard 5587 5,587 Standard 4243 4,243 Standard 19298 19,298 Standard 15328 15,328 Standard 15102 15,102 Standard 15571 15,571 Standard 17032 17,032 Standard 63033 63,033 Standard 285 285 Standard 232 232 Standard 251 251 Standard 372 372 Standard 1140 1,140 Standard 285 285 Standard 232 232 Standard 251 251 Standard 372 372 Standard 1140 1,140 Standard 2838 2,838 Standard 2609 2,609 Standard 2707 2,707 Standard 2814 2,814 Standard 10968 10,968 Standard 2838 2,838 Standard 2609 2,609 Standard 2707 2,707 Standard 2814 2,814 Standard 10968 10,968 Standard 3240 3,240 Standard 3135 3,135 Standard 3235 3,235 Standard 3325 3,325 Standard 12935 12,935 Standard 3240 3,240 Standard 3135 3,135 Standard 3235 3,235 Standard 3325 3,325 Standard 12935 12,935 Standard 3087 3,087 Standard 3283 3,283 Standard 3466 3,466 Standard 3610 3,610 Standard 13446 13,446 Standard 3087 3,087 Standard 3283 3,283 Standard 3466 3,466 Standard 3610 3,610 Standard 13446 13,446 Standard 4211 4,211 Standard 4194 4,194 Standard 4451 4,451 Standard 5062 5,062 Standard 17918 17,918 Standard 4211 4,211 Standard 4194 4,194 Standard 4451 4,451 Standard 5062 5,062 Standard 17918 17,918 Standard 244 244 Standard 259 259 Standard 288 288 Standard 295 295 Standard 1086 1,086 Standard 244 244 Standard 259 259 Standard 288 288 Standard 295 295 Standard 1086 1,086 Standard 1423 1,423 Standard 1390 1,390 Standard 1173 1,173 Standard 1554 1,554 Standard 5540 5,540 Standard 1423 1,423 Standard 1390 1,390 Standard 1173 1,173 Standard 1554 1,554 Standard 5540 5,540 Standard ]]> WITH MEMBER [Measures].[COG_OQP_USR_Unit Sales] AS ' IIF( [Customers].CURRENTMEMBER.LEVEL.ORDINAL < 2, ([Time].[COG_OQP_INT_m2], [Measures].[Unit Sales]), [Measures].[COG_OQP_INT_m1])', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_m1] AS ' IIF( [Measures].[Unit Sales]> 1, [Measures].[Unit Sales], NULL)', SOLVE_ORDER = 2 MEMBER [Time].[Time].[COG_OQP_INT_m2] AS ' AGGREGATE( FILTER( INTERSECT( DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province], SELF_AND_BEFORE), INTERSECT( BOTTOMCOUNT( {[Customers].[State Province].MEMBERS}, 2, ([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)), GENERATE( TOPCOUNT( {[Customers].[Country].MEMBERS}, 3, ([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)), DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province])))), ([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)> 1), [Time].[Time].DEFAULTMEMBER)', SOLVE_ORDER = 2 SELECT CROSSJOIN( FILTER( CROSSJOIN( GENERATE( INTERSECT( TOPCOUNT( {[Customers].[State Province].MEMBERS}, 2, [Measures].[COG_OQP_INT_m1]), GENERATE( TOPCOUNT( {[Customers].[Country].MEMBERS}, 3, [Measures].[COG_OQP_INT_m1]), DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province]))), {ANCESTOR( [Customers].CURRENTMEMBER, [Customers].[Country]), [Customers].CURRENTMEMBER}, ALL), {[Product].[Product Category].MEMBERS}), NOT ISEMPTY( [Measures].[COG_OQP_USR_Unit Sales])), {[Measures].[COG_OQP_USR_Unit Sales]}) ON AXIS(0) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Customers].[USA].[WA] WA [Customers].[State Province] 2 22 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 2 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 3 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 4 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 4 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 4 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 3 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 2 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 9 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 3 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 2 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 4 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[WA] WA [Customers].[State Province] 2 131094 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 5 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 2 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 3 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 4 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 4 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 4 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 3 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 2 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 9 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 3 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 2 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 4 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 1 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 5 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 3222 3,222 Standard 1626 1,626 Standard 1071 1,071 Standard 2015 2,015 Standard 1590 1,590 Standard 1865 1,865 Standard 3707 3,707 Standard 3968 3,968 Standard 5668 5,668 Standard 1517 1,517 Standard 419 419 Standard 376 376 Standard 296 296 Standard 370 370 Standard 400 400 Standard 3748 3,748 Standard 802 802 Standard 2458 2,458 Standard 900 900 Standard 6220 6,220 Standard 4369 4,369 Standard 1237 1,237 Standard 1897 1,897 Standard 2426 2,426 Standard 2866 2,866 Standard 1190 1,190 Standard 1273 1,273 Standard 1574 1,574 Standard 3246 3,246 Standard 718 718 Standard 5575 5,575 Standard 441 441 Standard 2058 2,058 Standard 9386 9,386 Standard 872 872 Standard 14213 14,213 Standard 3095 3,095 Standard 2462 2,462 Standard 370 370 Standard 334 334 Standard 452 452 Standard 2726 2,726 Standard 881 881 Standard 890 890 Standard 1612 1,612 Standard 1648 1,648 Standard 354 354 Standard 303 303 Standard 1603 1,603 Standard 3057 3,057 Standard 722 722 Standard 1913 1,913 Standard 3219 3,219 Standard 1176 1,176 Standard 1970 1,970 Standard 1936 1,936 Standard 923 923 Standard 766 766 Standard 1208 1,208 Standard 989 989 Standard 1280 1,280 Standard 2150 2,150 Standard 2456 2,456 Standard 3343 3,343 Standard 938 938 Standard 228 228 Standard 236 236 Standard 220 220 Standard 234 234 Standard 231 231 Standard 2209 2,209 Standard 432 432 Standard 1478 1,478 Standard 448 448 Standard 3534 3,534 Standard 2636 2,636 Standard 757 757 Standard 1116 1,116 Standard 1435 1,435 Standard 1772 1,772 Standard 726 726 Standard 645 645 Standard 940 940 Standard 1987 1,987 Standard 527 527 Standard 3184 3,184 Standard 220 220 Standard 1278 1,278 Standard 5906 5,906 Standard 441 441 Standard 8543 8,543 Standard 1958 1,958 Standard 1448 1,448 Standard 223 223 Standard 261 261 Standard 286 286 Standard 1666 1,666 Standard 434 434 Standard 477 477 Standard 993 993 Standard 817 817 Standard 273 273 Standard 266 266 Standard 965 965 Standard 1867 1,867 Standard 493 493 Standard 1220 1,220 Standard 1837 1,837 Standard 651 651 Standard 1261 1,261 Standard ]]> WITH MEMBER [Position].[COG_OQP_USR_aggregate(Management Role)] AS ' COUNT( {[Position].[Position Title].MEMBERS}, EXCLUDEEMPTY)', SOLVE_ORDER = 4, FORMAT_STRING = "#" MEMBER [Position].[COG_OQP_INT_t2]AS '1', SOLVE_ORDER = 65535 MEMBER [Position].[COG_OQP_INT_t1]AS '1', SOLVE_ORDER = 65535 MEMBER [Measures].[COG_OQP_USR_Count] AS ' [Measures].[COG_OQP_INT_m2]', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_m2] AS ' IIF( [Measures].[Count]>= 1, [Measures].[Count], NULL)', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_am1] AS ' COUNT( CROSSJOIN( DESCENDANTS( [Position].CURRENTMEMBER, [Position].[Position Title]), {[Measures].[COG_OQP_INT_m2]}), EXCLUDEEMPTY)', SOLVE_ORDER = 8, FORMAT_STRING = "#" SELECT UNION( GENERATE( {[Position].[Management Role].MEMBERS}, UNION( UNION( UNION( UNION( {([Position].[COG_OQP_INT_t2])}, HEAD( {([Position].CURRENTMEMBER)}, IIF( COUNT( FILTER( DESCENDANTS( [Position].CURRENTMEMBER, [Position].[Position Title]), [Measures].[Count]>= 1), INCLUDEEMPTY)> 0, 1, 0)), ALL), FILTER( DESCENDANTS( [Position].CURRENTMEMBER, [Position].[Position Title]), [Measures].[Count]>= 1), ALL), {([Position].[COG_OQP_INT_t1])}, ALL), HEAD( HEAD( {([Position].CURRENTMEMBER)}, IIF( COUNT( DESCENDANTS( [Position].CURRENTMEMBER, [Position].[Position Title]), INCLUDEEMPTY)> 0, 1, 0)), IIF( COUNT( FILTER( DESCENDANTS( [Position].CURRENTMEMBER, [Position].[Position Title]), [Measures].[Count]>= 1), INCLUDEEMPTY)> 0, 1, 0)), ALL), ALL), HEAD( HEAD( {([Position].[COG_OQP_USR_aggregate(Management Role)])}, IIF( COUNT( {[Position].[Position Title].MEMBERS}, INCLUDEEMPTY)> 0, 1, 0)), IIF( COUNT( FILTER( {[Position].[Position Title].MEMBERS}, [Measures].[Count]>= 1), INCLUDEEMPTY)> 0, 1, 0)), ALL) DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(0), UNION( {[Measures].[COG_OQP_USR_Count]}, {[Measures].[COG_OQP_INT_am1]}, ALL) DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(1) FROM [HR] CELL PROPERTIES VALUE, FORMAT_STRING, LANGUAGE ${catalog} ${data.source.info} ${format} TupleFormat ]]> HR [Position].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Position].[(All)] 0 0 0 0 [Position].[Middle Management] Middle Management [Position].[Management Role] 1 65540 0 4 [Position].[All Position] [Position].[Middle Management].[HQ Information Systems] HQ Information Systems [Position].[Position Title] 2 0 1 0 [Position].[Middle Management] [Position].[Middle Management].[HQ Marketing] HQ Marketing [Position].[Position Title] 2 131072 1 0 [Position].[Middle Management] [Position].[Middle Management].[HQ Human Resources] HQ Human Resources [Position].[Position Title] 2 131072 1 0 [Position].[Middle Management] [Position].[Middle Management].[HQ Finance and Accounting] HQ Finance and Accounting [Position].[Position Title] 2 131072 1 0 [Position].[Middle Management] [Position].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Position].[(All)] 0 0 0 0 [Position].[Middle Management] Middle Management [Position].[Management Role] 1 4 0 4 [Position].[All Position] [Position].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Position].[(All)] 0 0 0 0 [Position].[Senior Management] Senior Management [Position].[Management Role] 1 65541 0 5 [Position].[All Position] [Position].[Senior Management].[President] President [Position].[Position Title] 2 0 1 0 [Position].[Senior Management] [Position].[Senior Management].[VP Country Manager] VP Country Manager [Position].[Position Title] 2 131072 1 0 [Position].[Senior Management] [Position].[Senior Management].[VP Information Systems] VP Information Systems [Position].[Position Title] 2 131072 1 0 [Position].[Senior Management] [Position].[Senior Management].[VP Human Resources] VP Human Resources [Position].[Position Title] 2 131072 1 0 [Position].[Senior Management] [Position].[Senior Management].[VP Finance] VP Finance [Position].[Position Title] 2 131072 1 0 [Position].[Senior Management] [Position].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Position].[(All)] 0 0 0 0 [Position].[Senior Management] Senior Management [Position].[Management Role] 1 5 0 5 [Position].[All Position] [Position].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Position].[(All)] 0 0 0 0 [Position].[Store Full Time Staf] Store Full Time Staf [Position].[Management Role] 1 65541 0 5 [Position].[All Position] [Position].[Store Full Time Staf].[Store Information Systems] Store Information Systems [Position].[Position Title] 2 0 1 0 [Position].[Store Full Time Staf] [Position].[Store Full Time Staf].[Store Permanent Checker] Store Permanent Checker [Position].[Position Title] 2 131072 1 0 [Position].[Store Full Time Staf] [Position].[Store Full Time Staf].[Store Temporary Checker] Store Temporary Checker [Position].[Position Title] 2 131072 1 0 [Position].[Store Full Time Staf] [Position].[Store Full Time Staf].[Store Permanent Stocker] Store Permanent Stocker [Position].[Position Title] 2 131072 1 0 [Position].[Store Full Time Staf] [Position].[Store Full Time Staf].[Store Permanent Butcher] Store Permanent Butcher [Position].[Position Title] 2 131072 1 0 [Position].[Store Full Time Staf] [Position].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Position].[(All)] 0 0 0 0 [Position].[Store Full Time Staf] Store Full Time Staf [Position].[Management Role] 1 5 0 5 [Position].[All Position] [Position].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Position].[(All)] 0 0 0 0 [Position].[Store Management] Store Management [Position].[Management Role] 1 65539 0 3 [Position].[All Position] [Position].[Store Management].[Store Manager] Store Manager [Position].[Position Title] 2 0 1 0 [Position].[Store Management] [Position].[Store Management].[Store Assistant Manager] Store Assistant Manager [Position].[Position Title] 2 131072 1 0 [Position].[Store Management] [Position].[Store Management].[Store Shift Supervisor] Store Shift Supervisor [Position].[Position Title] 2 131072 1 0 [Position].[Store Management] [Position].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Position].[(All)] 0 0 0 0 [Position].[Store Management] Store Management [Position].[Management Role] 1 3 0 3 [Position].[All Position] [Position].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Position].[(All)] 0 0 0 0 [Position].[Store Temp Staff] Store Temp Staff [Position].[Management Role] 1 65537 0 1 [Position].[All Position] [Position].[Store Temp Staff].[Store Temporary Stocker] Store Temporary Stocker [Position].[Position Title] 2 0 1 0 [Position].[Store Temp Staff] [Position].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Position].[(All)] 0 0 0 0 [Position].[Store Temp Staff] Store Temp Staff [Position].[Management Role] 1 1 0 1 [Position].[All Position] [Position].[COG_OQP_USR_aggregate(Management Role)] COG_OQP_USR_aggregate(Management Role) [Position].[(All)] 0 0 0 0 [Measures].[COG_OQP_USR_Count] COG_OQP_USR_Count [Measures].[MeasuresLevel] 0 0 0 0 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 0 0 1 #,# 108 #,# 24 #,# 24 #,# 12 #,# 48 #,# 1 #,# 108 #,# 1 #,# 96 #,# 12 #,# 48 #,# 12 #,# 12 #,# 12 #,# 1 #,# 96 #,# 1 #,# 4860 #,# 108 #,# 1428 #,# 1716 #,# 1392 #,# 216 #,# 1 #,# 4860 #,# 1 #,# 648 #,# 156 #,# 156 #,# 336 #,# 1 #,# 648 #,# 1 #,# 1680 #,# 1680 #,# 1 #,# 1680 #,# 18 # 1 # 4 # 1 # 1 # 1 # 1 # 1 # 4 # 1 # 5 # 1 # 1 # 1 # 1 # 1 # 1 # 5 # 1 # 5 # 1 # 1 # 1 # 1 # 1 # 1 # 5 # 1 # 3 # 1 # 1 # 1 # 1 # 3 # 1 # 1 # 1 # 1 # 1 # 0 # ]]> WITH MEMBER [Position].[COG_OQP_USR_aggregate(Management Role)] AS ' SUM( {[Position].[Position Title].MEMBERS})', SOLVE_ORDER = 4 MEMBER [Position].[COG_OQP_INT_t2]AS '1', SOLVE_ORDER = 65535 MEMBER [Position].[COG_OQP_INT_t1]AS '1', SOLVE_ORDER = 65535 MEMBER [Measures].[COG_OQP_USR_Count] AS ' [Measures].[COG_OQP_INT_m2]', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_m2] AS ' IIF( [Measures].[Count]>= 66, [Measures].[Count], NULL)', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_am1] AS ' SUM( DESCENDANTS( [Position].CURRENTMEMBER, [Position].[Position Title]), [Measures].[COG_OQP_INT_m2])', SOLVE_ORDER = 8 SELECT UNION( GENERATE( {[Position].[Management Role].MEMBERS}, UNION( UNION( UNION( UNION( {([Position].[COG_OQP_INT_t2])}, HEAD( {([Position].CURRENTMEMBER)}, IIF( COUNT( FILTER( DESCENDANTS( [Position].CURRENTMEMBER, [Position].[Position Title]), [Measures].[Count]>= 66), INCLUDEEMPTY)> 0, 1, 0)), ALL), FILTER( DESCENDANTS( [Position].CURRENTMEMBER, [Position].[Position Title]), [Measures].[Count]>= 66), ALL), {([Position].[COG_OQP_INT_t1])}, ALL), HEAD( HEAD( {([Position].CURRENTMEMBER)}, IIF( COUNT( DESCENDANTS( [Position].CURRENTMEMBER, [Position].[Position Title]), INCLUDEEMPTY)> 0, 1, 0)), IIF( COUNT( FILTER( DESCENDANTS( [Position].CURRENTMEMBER, [Position].[Position Title]), [Measures].[Count]>= 66), INCLUDEEMPTY)> 0, 1, 0)), ALL), ALL), HEAD( HEAD( {([Position].[COG_OQP_USR_aggregate(Management Role)])}, IIF( COUNT( {[Position].[Position Title].MEMBERS}, INCLUDEEMPTY)> 0, 1, 0)), IIF( COUNT( FILTER( {[Position].[Position Title].MEMBERS}, [Measures].[Count]>= 66), INCLUDEEMPTY)> 0, 1, 0)), ALL) DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(0), UNION( {[Measures].[COG_OQP_USR_Count]}, {[Measures].[COG_OQP_INT_am1]}, ALL) DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(1) FROM [HR] CELL PROPERTIES VALUE, FORMAT_STRING, LANGUAGE ${catalog} ${data.source.info} ${format} TupleFormat ]]> HR [Position].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Position].[(All)] 0 0 0 0 [Position].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Position].[(All)] 0 0 0 0 [Position].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Position].[(All)] 0 0 0 0 [Position].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Position].[(All)] 0 0 0 0 [Position].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Position].[(All)] 0 0 0 0 [Position].[Store Full Time Staf] Store Full Time Staf [Position].[Management Role] 1 65541 0 5 [Position].[All Position] [Position].[Store Full Time Staf].[Store Information Systems] Store Information Systems [Position].[Position Title] 2 0 1 0 [Position].[Store Full Time Staf] [Position].[Store Full Time Staf].[Store Permanent Checker] Store Permanent Checker [Position].[Position Title] 2 131072 1 0 [Position].[Store Full Time Staf] [Position].[Store Full Time Staf].[Store Temporary Checker] Store Temporary Checker [Position].[Position Title] 2 131072 1 0 [Position].[Store Full Time Staf] [Position].[Store Full Time Staf].[Store Permanent Stocker] Store Permanent Stocker [Position].[Position Title] 2 131072 1 0 [Position].[Store Full Time Staf] [Position].[Store Full Time Staf].[Store Permanent Butcher] Store Permanent Butcher [Position].[Position Title] 2 131072 1 0 [Position].[Store Full Time Staf] [Position].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Position].[(All)] 0 0 0 0 [Position].[Store Full Time Staf] Store Full Time Staf [Position].[Management Role] 1 5 0 5 [Position].[All Position] [Position].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Position].[(All)] 0 0 0 0 [Position].[Store Management] Store Management [Position].[Management Role] 1 65539 0 3 [Position].[All Position] [Position].[Store Management].[Store Manager] Store Manager [Position].[Position Title] 2 0 1 0 [Position].[Store Management] [Position].[Store Management].[Store Assistant Manager] Store Assistant Manager [Position].[Position Title] 2 131072 1 0 [Position].[Store Management] [Position].[Store Management].[Store Shift Supervisor] Store Shift Supervisor [Position].[Position Title] 2 131072 1 0 [Position].[Store Management] [Position].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Position].[(All)] 0 0 0 0 [Position].[Store Management] Store Management [Position].[Management Role] 1 3 0 3 [Position].[All Position] [Position].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Position].[(All)] 0 0 0 0 [Position].[Store Temp Staff] Store Temp Staff [Position].[Management Role] 1 65537 0 1 [Position].[All Position] [Position].[Store Temp Staff].[Store Temporary Stocker] Store Temporary Stocker [Position].[Position Title] 2 0 1 0 [Position].[Store Temp Staff] [Position].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Position].[(All)] 0 0 0 0 [Position].[Store Temp Staff] Store Temp Staff [Position].[Management Role] 1 1 0 1 [Position].[All Position] [Position].[COG_OQP_USR_aggregate(Management Role)] COG_OQP_USR_aggregate(Management Role) [Position].[(All)] 0 0 0 0 [Measures].[COG_OQP_USR_Count] COG_OQP_USR_Count [Measures].[MeasuresLevel] 0 0 0 0 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 0 0 1 #,# 1 #,# 1 #,# 1 #,# 1 #,# 4860 #,# 108 #,# 1428 #,# 1716 #,# 1392 #,# 216 #,# 1 #,# 4860 #,# 1 #,# 648 #,# 156 #,# 156 #,# 336 #,# 1 #,# 648 #,# 1 #,# 1680 #,# 1680 #,# 1 #,# 1680 #,# 7188 #,# 1 #,# 1 #,# 1 #,# 1 #,# 1 #,# 4860 #,# 108 #,# 1428 #,# 1716 #,# 1392 #,# 216 #,# 1 #,# 4860 #,# 1 #,# 648 #,# 156 #,# 156 #,# 336 #,# 1 #,# 648 #,# 1 #,# 1680 #,# 1680 #,# 1 #,# 1680 #,# ]]> WITH MEMBER [Time].[Time].[COG_OQP_USR_aggregate(Year)] AS ' IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_INT_m12], ([Time].[COG_OQP_INT_m15], [Measures].[Profit]), IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_INT_m11], ([Time].[COG_OQP_INT_m15], [Measures].[Unit Sales]), IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_USR_Profit], ([Time].[COG_OQP_INT_m15], [Measures].[Profit]), IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_USR_Unit Sales], ([Time].[COG_OQP_INT_m15], [Measures].[Unit Sales]), IIF( [Measures].CURRENTMEMBER IS [Measures].[Profit], ([Time].[COG_OQP_INT_m15], [Measures].[Profit]), IIF( [Measures].CURRENTMEMBER IS [Measures].[Unit Sales], ([Time].[COG_OQP_INT_m15], [Measures].[Unit Sales]), AGGREGATE( GENERATE( INTERSECT( {[Time].[Year].MEMBERS}, GENERATE( {[Time].[1997].[Q2], [Time].[1997].[Q4]}, {ANCESTOR( [Time].[Time].CURRENTMEMBER, [Time].[Year])})), INTERSECT( DESCENDANTS( [Time].[Time].CURRENTMEMBER, [Time].[Quarter]), {[Time].[1997].[Q2], [Time].[1997].[Q4]}), ALL))))))))', SOLVE_ORDER = 8 MEMBER [Time].[Time].[COG_OQP_INT_t7]AS '1', SOLVE_ORDER = 65535 MEMBER [Time].[Time].[COG_OQP_INT_t6]AS '1', SOLVE_ORDER = 65535 MEMBER [Time].[Time].[COG_OQP_INT_t5]AS '1', SOLVE_ORDER = 65535 MEMBER [Time].[Time].[COG_OQP_INT_m18] AS ' AGGREGATE( FILTER( [COG_OQP_INT_s2], [Measures].[Unit Sales]>= 1))', SOLVE_ORDER = 9 MEMBER [Time].[Time].[COG_OQP_INT_m15] AS ' AGGREGATE( FILTER( GENERATE( INTERSECT( {[Time].[Year].MEMBERS}, GENERATE( {[Time].[1997].[Q2], [Time].[1997].[Q4]}, {ANCESTOR( [Time].[Time].CURRENTMEMBER, [Time].[Year])})), INTERSECT( DESCENDANTS( [Time].[Time].CURRENTMEMBER, [Time].[Quarter]), {[Time].[1997].[Q2], [Time].[1997].[Q4]}), ALL), NOT ISEMPTY( [Product].[COG_OQP_INT_m13])))', SOLVE_ORDER = 8 MEMBER [Store Type].[COG_OQP_USR_aggregate(Store Type)] AS ' IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_INT_m12], ([Store Type].[COG_OQP_INT_m14], [Measures].[Profit]), IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_INT_m11], ([Store Type].[COG_OQP_INT_m14], [Measures].[Unit Sales]), IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_USR_Profit], ([Store Type].[COG_OQP_INT_m14], [Measures].[Profit]), IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_USR_Unit Sales], ([Store Type].[COG_OQP_INT_m14], [Measures].[Unit Sales]), IIF( [Measures].CURRENTMEMBER IS [Measures].[Profit], ([Store Type].[COG_OQP_INT_m14], [Measures].[Profit]), IIF( [Measures].CURRENTMEMBER IS [Measures].[Unit Sales], ([Store Type].[COG_OQP_INT_m14], [Measures].[Unit Sales]), AGGREGATE( {[Store Type].[Store Type].MEMBERS})))))))', SOLVE_ORDER = 4 MEMBER [Store Type].[COG_OQP_INT_m17] AS ' AGGREGATE( FILTER( CROSSJOIN( INTERSECT( DESCENDANTS( [Time].[Time].CURRENTMEMBER, [Time].[Quarter]), {[Time].[1997].[Q2], [Time].[1997].[Q4]}), {[Store Type].[Store Type].MEMBERS}), [Measures].[Unit Sales]>= 1))', SOLVE_ORDER = 13 MEMBER [Store Type].[COG_OQP_INT_m14] AS ' AGGREGATE( FILTER( {[Store Type].[Store Type].MEMBERS}, NOT ISEMPTY( [Product].[COG_OQP_INT_m13])))', SOLVE_ORDER = 4 MEMBER [Product].[COG_OQP_INT_m16] AS ' AGGREGATE( FILTER( INTERSECT( DESCENDANTS( [Time].[Time].CURRENTMEMBER, [Time].[Quarter]), {[Time].[1997].[Q2], [Time].[1997].[Q4]}), ([Measures].[Unit Sales], [Product].DEFAULTMEMBER)>= 1), [Product].DEFAULTMEMBER)', SOLVE_ORDER = 12 MEMBER [Product].[COG_OQP_INT_m13] AS ' IIF( ([Measures].[Unit Sales], [Product].DEFAULTMEMBER)>= 1, 1, NULL)', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_USR_Unit Sales] AS ' [Measures].[COG_OQP_INT_m11]', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_USR_Profit] AS ' [Measures].[COG_OQP_INT_m12]', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_t4]AS '1', SOLVE_ORDER = 65535 MEMBER [Measures].[COG_OQP_INT_t3]AS '1', SOLVE_ORDER = 65535 MEMBER [Measures].[COG_OQP_INT_t2]AS '1', SOLVE_ORDER = 65535 MEMBER [Measures].[COG_OQP_INT_t1]AS '1', SOLVE_ORDER = 65535 MEMBER [Measures].[COG_OQP_INT_m12] AS ' IIF( [Measures].[Unit Sales]>= 1, [Measures].[Profit], NULL)', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_m11] AS ' IIF( [Measures].[Unit Sales]>= 1, [Measures].[Unit Sales], NULL)', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_am8] AS ' ([Time].[COG_OQP_INT_m18], [Measures].[Unit Sales])', SOLVE_ORDER = 9 MEMBER [Measures].[COG_OQP_INT_am6] AS ' ([Store Type].[COG_OQP_INT_m17], [Measures].[Profit])', SOLVE_ORDER = 13 MEMBER [Measures].[COG_OQP_INT_am4] AS ' ([Store Type].[COG_OQP_INT_m17], [Measures].[Unit Sales])', SOLVE_ORDER = 13 MEMBER [Measures].[COG_OQP_INT_am2] AS ' ([Product].[COG_OQP_INT_m16], [Measures].[Profit])', SOLVE_ORDER = 12 MEMBER [Measures].[COG_OQP_INT_am1] AS ' ([Product].[COG_OQP_INT_m16], [Measures].[Unit Sales])', SOLVE_ORDER = 12 MEMBER [Measures].[COG_OQP_INT_am10] AS ' ([Time].[COG_OQP_INT_m18], [Measures].[Profit])', SOLVE_ORDER = 9 SET [COG_OQP_INT_s2] AS ' CROSSJOIN( GENERATE( INTERSECT( {[Time].[Year].MEMBERS}, GENERATE( {[Time].[1997].[Q2], [Time].[1997].[Q4]}, {ANCESTOR( [Time].[Time].CURRENTMEMBER, [Time].[Year])})), INTERSECT( DESCENDANTS( [Time].[Time].CURRENTMEMBER, [Time].[Quarter]), {[Time].[1997].[Q2], [Time].[1997].[Q4]}), ALL), {[Store Type].[Store Type].MEMBERS})' SET [COG_OQP_INT_s1] AS ' CROSSJOIN( {[Store Type].[Store Type].MEMBERS}, UNION( {[Measures].[Unit Sales]}, {[Measures].[Profit]}, ALL))' SELECT UNION( GENERATE( {[Store Type].[Store Type].MEMBERS}, CROSSJOIN( HEAD( {([Store Type].CURRENTMEMBER)}, IIF( COUNT( FILTER( GENERATE( INTERSECT( {[Time].[Year].MEMBERS}, GENERATE( {[Time].[1997].[Q2], [Time].[1997].[Q4]}, {ANCESTOR( [Time].[Time].CURRENTMEMBER, [Time].[Year])})), INTERSECT( DESCENDANTS( [Time].[Time].CURRENTMEMBER, [Time].[Quarter]), {[Time].[1997].[Q2], [Time].[1997].[Q4]}), ALL), [Measures].[Unit Sales]>= 1), INCLUDEEMPTY)> 0, 1, 0)), UNION( UNION( UNION( UNION( UNION( UNION( UNION( UNION( UNION( {([Measures].[COG_OQP_INT_t1])}, {[Measures].[COG_OQP_USR_Unit Sales]}, ALL), {[Measures].[COG_OQP_INT_am1]}, ALL), {[Measures].[COG_OQP_INT_am4]}, ALL), {[Measures].[COG_OQP_INT_am8]}, ALL), {([Measures].[COG_OQP_INT_t2])}, ALL), {[Measures].[COG_OQP_USR_Profit]}, ALL), {[Measures].[COG_OQP_INT_am2]}, ALL), {[Measures].[COG_OQP_INT_am6]}, ALL), {[Measures].[COG_OQP_INT_am10]}, ALL)), ALL), CROSSJOIN( HEAD( {([Store Type].[COG_OQP_USR_aggregate(Store Type)])}, IIF( COUNT( [COG_OQP_INT_s1], INCLUDEEMPTY)> 0, 1, 0)), UNION( UNION( UNION( UNION( UNION( UNION( UNION( UNION( UNION( {([Measures].[COG_OQP_INT_t3])}, {[Measures].[COG_OQP_USR_Unit Sales]}, ALL), {[Measures].[COG_OQP_INT_am1]}, ALL), {[Measures].[COG_OQP_INT_am4]}, ALL), {[Measures].[COG_OQP_INT_am8]}, ALL), {([Measures].[COG_OQP_INT_t4])}, ALL), {[Measures].[COG_OQP_USR_Profit]}, ALL), {[Measures].[COG_OQP_INT_am2]}, ALL), {[Measures].[COG_OQP_INT_am6]}, ALL), {[Measures].[COG_OQP_INT_am10]}, ALL)), ALL) DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(0), UNION( UNION( GENERATE( INTERSECT( {[Time].[Year].MEMBERS}, GENERATE( {[Time].[1997].[Q2], [Time].[1997].[Q4]}, {ANCESTOR( [Time].[Time].CURRENTMEMBER, [Time].[Year])})), UNION( UNION( UNION( UNION( {([Time].[COG_OQP_INT_t6])}, HEAD( {([Time].[Time].CURRENTMEMBER)}, IIF( COUNT( FILTER( CROSSJOIN( INTERSECT( DESCENDANTS( [Time].[Time].CURRENTMEMBER, [Time].[Quarter]), {[Time].[1997].[Q2], [Time].[1997].[Q4]}), {[Store Type].[Store Type].MEMBERS}), ([Measures].[Unit Sales], [Product].DEFAULTMEMBER)>= 1), INCLUDEEMPTY)> 0, 1, 0)), ALL), FILTER( INTERSECT( DESCENDANTS( [Time].[Time].CURRENTMEMBER, [Time].[Quarter]), {[Time].[1997].[Q2], [Time].[1997].[Q4]}), COUNT( FILTER( {[Store Type].[Store Type].MEMBERS}, ([Measures].[Unit Sales], [Product].DEFAULTMEMBER)>= 1), INCLUDEEMPTY)> 0), ALL), {([Time].[COG_OQP_INT_t5])}, ALL), HEAD( HEAD( {([Time].[Time].CURRENTMEMBER)}, IIF( COUNT( INTERSECT( DESCENDANTS( [Time].[Time].CURRENTMEMBER, [Time].[Quarter]), {[Time].[1997].[Q2], [Time].[1997].[Q4]}), INCLUDEEMPTY)> 0, 1, 0)), IIF( COUNT( FILTER( CROSSJOIN( INTERSECT( DESCENDANTS( [Time].[Time].CURRENTMEMBER, [Time].[Quarter]), {[Time].[1997].[Q2], [Time].[1997].[Q4]}), {[Store Type].[Store Type].MEMBERS}), ([Measures].[Unit Sales], [Product].DEFAULTMEMBER)>= 1), INCLUDEEMPTY)> 0, 1, 0)), ALL), ALL), {([Time].[COG_OQP_INT_t7])}, ALL), HEAD( HEAD( {([Time].[COG_OQP_USR_aggregate(Year)])}, IIF( COUNT( GENERATE( INTERSECT( {[Time].[Year].MEMBERS}, GENERATE( {[Time].[1997].[Q2], [Time].[1997].[Q4]}, {ANCESTOR( [Time].[Time].CURRENTMEMBER, [Time].[Year])})), INTERSECT( DESCENDANTS( [Time].[Time].CURRENTMEMBER, [Time].[Quarter]), {[Time].[1997].[Q2], [Time].[1997].[Q4]}), ALL), INCLUDEEMPTY)> 0, 1, 0)), IIF( COUNT( FILTER( [COG_OQP_INT_s2], ([Measures].[Unit Sales], [Product].DEFAULTMEMBER)>= 1), INCLUDEEMPTY)> 0, 1, 0)), ALL) DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(1) FROM [Sales] CELL PROPERTIES VALUE, FORMAT_STRING, LANGUAGE ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 0 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am4] COG_OQP_INT_am4 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am8] COG_OQP_INT_am8 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_USR_Profit] COG_OQP_USR_Profit [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am2] COG_OQP_INT_am2 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am6] COG_OQP_INT_am6 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Deluxe Supermarket] Deluxe Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am10] COG_OQP_INT_am10 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am4] COG_OQP_INT_am4 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am8] COG_OQP_INT_am8 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_USR_Profit] COG_OQP_USR_Profit [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am2] COG_OQP_INT_am2 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am6] COG_OQP_INT_am6 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Gourmet Supermarket] Gourmet Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am10] COG_OQP_INT_am10 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am4] COG_OQP_INT_am4 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am8] COG_OQP_INT_am8 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_USR_Profit] COG_OQP_USR_Profit [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am2] COG_OQP_INT_am2 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am6] COG_OQP_INT_am6 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Mid-Size Grocery] Mid-Size Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am10] COG_OQP_INT_am10 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am4] COG_OQP_INT_am4 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am8] COG_OQP_INT_am8 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_USR_Profit] COG_OQP_USR_Profit [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am2] COG_OQP_INT_am2 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am6] COG_OQP_INT_am6 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Small Grocery] Small Grocery [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am10] COG_OQP_INT_am10 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am4] COG_OQP_INT_am4 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am8] COG_OQP_INT_am8 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_USR_Profit] COG_OQP_USR_Profit [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am2] COG_OQP_INT_am2 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am6] COG_OQP_INT_am6 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[Supermarket] Supermarket [Store Type].[Store Type] 1 131072 0 0 [Store Type].[All Store Types] [Measures].[COG_OQP_INT_am10] COG_OQP_INT_am10 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[COG_OQP_USR_aggregate(Store Type)] COG_OQP_USR_aggregate(Store Type) [Store Type].[(All)] 0 0 0 0 [Measures].[COG_OQP_INT_t3] COG_OQP_INT_t3 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[COG_OQP_USR_aggregate(Store Type)] COG_OQP_USR_aggregate(Store Type) [Store Type].[(All)] 0 0 0 0 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[COG_OQP_USR_aggregate(Store Type)] COG_OQP_USR_aggregate(Store Type) [Store Type].[(All)] 0 0 0 0 [Measures].[COG_OQP_INT_am1] COG_OQP_INT_am1 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[COG_OQP_USR_aggregate(Store Type)] COG_OQP_USR_aggregate(Store Type) [Store Type].[(All)] 0 0 0 0 [Measures].[COG_OQP_INT_am4] COG_OQP_INT_am4 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[COG_OQP_USR_aggregate(Store Type)] COG_OQP_USR_aggregate(Store Type) [Store Type].[(All)] 0 0 0 0 [Measures].[COG_OQP_INT_am8] COG_OQP_INT_am8 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[COG_OQP_USR_aggregate(Store Type)] COG_OQP_USR_aggregate(Store Type) [Store Type].[(All)] 0 0 0 0 [Measures].[COG_OQP_INT_t4] COG_OQP_INT_t4 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[COG_OQP_USR_aggregate(Store Type)] COG_OQP_USR_aggregate(Store Type) [Store Type].[(All)] 0 0 0 0 [Measures].[COG_OQP_USR_Profit] COG_OQP_USR_Profit [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[COG_OQP_USR_aggregate(Store Type)] COG_OQP_USR_aggregate(Store Type) [Store Type].[(All)] 0 0 0 0 [Measures].[COG_OQP_INT_am2] COG_OQP_INT_am2 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[COG_OQP_USR_aggregate(Store Type)] COG_OQP_USR_aggregate(Store Type) [Store Type].[(All)] 0 0 0 0 [Measures].[COG_OQP_INT_am6] COG_OQP_INT_am6 [Measures].[MeasuresLevel] 0 0 0 0 [Store Type].[COG_OQP_USR_aggregate(Store Type)] COG_OQP_USR_aggregate(Store Type) [Store Type].[(All)] 0 0 0 0 [Measures].[COG_OQP_INT_am10] COG_OQP_INT_am10 [Measures].[MeasuresLevel] 0 0 0 0 [Time].[COG_OQP_INT_t6] COG_OQP_INT_t6 [Time].[Year] 0 0 0 0 [Time].[1997] 1997 [Time].[Year] 0 65540 0 4 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 3 0 3 [Time].[1997] [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 0 3 [Time].[1997] [Time].[COG_OQP_INT_t5] COG_OQP_INT_t5 [Time].[Year] 0 0 0 0 [Time].[1997] 1997 [Time].[Year] 0 4 0 4 [Time].[COG_OQP_INT_t7] COG_OQP_INT_t7 [Time].[Year] 0 0 0 0 [Time].[COG_OQP_USR_aggregate(Year)] COG_OQP_USR_aggregate(Year) [Time].[Year] 0 0 0 0 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 76837 Standard 35629 Standard 134634 Standard 134634 Standard 1 Standard 97279.40209999999 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 21333 Standard 12787 Standard 134634 Standard 134634 Standard 1 Standard 27483.7996 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 11491 Standard 5945 Standard 134634 Standard 134634 Standard 1 Standard 14615.417 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 6557 Standard 3537 Standard 134634 Standard 134634 Standard 1 Standard 8330.507099999999 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 150555 Standard 76736 Standard 134634 Standard 134634 Standard 1 Standard 191901.7706 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 266773 Standard 134634 Standard 134634 Standard 134634 Standard 1 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 16371 Standard 16371 Standard 62610 Standard 134634 Standard 1 Standard 20720.392499999998 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 5837 Standard 5837 Standard 62610 Standard 134634 Standard 1 Standard 7565.7489 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 2860 Standard 2860 Standard 62610 Standard 134634 Standard 1 Standard 3640.4385999999995 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 1588 Standard 1588 Standard 62610 Standard 134634 Standard 1 Standard 1991.0770999999997 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 35954 Standard 35954 Standard 62610 Standard 134634 Standard 1 Standard 45784.3881 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 62610 Standard 62610 Standard 62610 Standard 134634 Standard 1 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 19258 Standard 19258 Standard 72024 Standard 134634 Standard 1 Standard 24515.908299999996 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 6950 Standard 6950 Standard 72024 Standard 134634 Standard 1 Standard 8896.134399999999 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 3085 Standard 3085 Standard 72024 Standard 134634 Standard 1 Standard 3960.7004 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 1949 Standard 1949 Standard 72024 Standard 134634 Standard 1 Standard 2482.1777 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 40782 Standard 40782 Standard 72024 Standard 134634 Standard 1 Standard 51810.8003 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 72024 Standard 72024 Standard 72024 Standard 134634 Standard 1 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 76837 Standard 35629 Standard 134634 Standard 134634 Standard 1 Standard 97279.40209999999 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 21333 Standard 12787 Standard 134634 Standard 134634 Standard 1 Standard 27483.7996 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 11491 Standard 5945 Standard 134634 Standard 134634 Standard 1 Standard 14615.417 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 6557 Standard 3537 Standard 134634 Standard 134634 Standard 1 Standard 8330.507099999999 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 150555 Standard 76736 Standard 134634 Standard 134634 Standard 1 Standard 191901.7706 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 266773 Standard 134634 Standard 134634 Standard 134634 Standard 1 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 $#,##0.00 1 $#,##0.00 1 $#,##0.00 1 Standard 35629 Standard 134634 Standard 1 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 12787 Standard 134634 Standard 1 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 5945 Standard 134634 Standard 1 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 3537 Standard 134634 Standard 1 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 76736 Standard 134634 Standard 1 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 1 Standard 134634 Standard 134634 Standard 1 Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context Standard mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context $#,##0.00 ]]> WITH MEMBER [Store].[COG_OQP_USR_aggregate(Store Country)] AS ' SUM( [COG_OQP_INT_s2])', SOLVE_ORDER = 8 MEMBER [Product].[COG_OQP_USR_aggregate(Product Family)] AS ' SUM( INTERSECT( {[Product].[Product Family].MEMBERS}, {[Product].[Drink], [Product].[Non-Consumable]}))', SOLVE_ORDER = 4 MEMBER [Measures].[COG_OQP_USR_Unit Sales] AS ' [Measures].[COG_OQP_INT_m1]', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_m1] AS ' IIF( [Measures].[Unit Sales]>= 1, [Measures].[Unit Sales], NULL)', SOLVE_ORDER = 2 MEMBER [Marital Status].[COG_OQP_USR_aggregate(Marital Status)] AS ' SUM( {[Marital Status].[Marital Status].MEMBERS})', SOLVE_ORDER = 16 MEMBER [Gender].[COG_OQP_USR_aggregate(Gender)] AS ' SUM( [COG_OQP_INT_s1])', SOLVE_ORDER = 12 SET [COG_OQP_INT_s6] AS ' CROSSJOIN( {[Marital Status].[Marital Status].MEMBERS}, INTERSECT( {[Product].[Product Family].MEMBERS}, {[Product].[Drink], [Product].[Non-Consumable]}))' SET [COG_OQP_INT_s5] AS ' CROSSJOIN( [COG_OQP_INT_s1], INTERSECT( {[Product].[Product Family].MEMBERS}, {[Product].[Drink], [Product].[All Products].[Non-Consumable]}))' SET [COG_OQP_INT_s4] AS ' CROSSJOIN( [COG_OQP_INT_s2], INTERSECT( {[Product].[Product Family].MEMBERS}, {[Product].[All Products].[Drink], [Product].[All Products].[Non-Consumable]}))' SET [COG_OQP_INT_s3] AS ' CROSSJOIN( INTERSECT( {[Product].[Product Family].MEMBERS}, {[Product].[All Products].[Drink], [Product].[All Products].[Non-Consumable]}), [COG_OQP_INT_s2])' SET [COG_OQP_INT_s2] AS ' CROSSJOIN( {[Store].[Store Country].MEMBERS}, [COG_OQP_INT_s1])' SET [COG_OQP_INT_s1] AS ' CROSSJOIN( {[Gender].[Gender].MEMBERS}, {[Marital Status].[Marital Status].MEMBERS})' SELECT UNION( FILTER( INTERSECT( {[Product].[Product Family].MEMBERS}, {[Product].[All Products].[Drink], [Product].[All Products].[Non-Consumable]}), COUNT( FILTER( [COG_OQP_INT_s2], ([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)>= 1), INCLUDEEMPTY)> 0), HEAD( HEAD( {([Product].[COG_OQP_USR_aggregate(Product Family)])}, IIF( COUNT( INTERSECT( {[Product].[Product Family].MEMBERS}, {[Product].[All Products].[Drink], [Product].[All Products].[Non-Consumable]}), INCLUDEEMPTY)> 0, 1, 0)), IIF( COUNT( FILTER( [COG_OQP_INT_s3], ([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)>= 1), INCLUDEEMPTY)> 0, 1, 0)), ALL) ON AXIS(0), UNION( GENERATE( {[Store].[Store Country].MEMBERS}, CROSSJOIN( HEAD( {([Store].CURRENTMEMBER)}, IIF( (IIF(COUNT(FILTER( [COG_OQP_INT_s5], ([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)>= 1), INCLUDEEMPTY)> 0, 1, 0)= 1 AND IIF( COUNT( [COG_OQP_INT_s1], INCLUDEEMPTY)> 0, 1, 0)= 1), 1, 0)), UNION( GENERATE( {[Gender].[Gender].MEMBERS}, CROSSJOIN( HEAD( {([Gender].CURRENTMEMBER)}, IIF( COUNT( FILTER( [COG_OQP_INT_s6], ([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)>= 1), INCLUDEEMPTY)> 0, 1, 0)), UNION( FILTER( {[Marital Status].[Marital Status].MEMBERS}, COUNT( FILTER( INTERSECT( {[Product].[Product Family].MEMBERS}, {[Product].[All Products].[Drink], [Product].[All Products].[Non-Consumable]}), ([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)>= 1), INCLUDEEMPTY)> 0), HEAD( {([Marital Status].[COG_OQP_USR_aggregate(Marital Status)])}, IIF( COUNT( FILTER( [COG_OQP_INT_s6], ([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)>= 1), INCLUDEEMPTY)> 0, 1, 0)), ALL)), ALL), HEAD( HEAD( {([Gender].[COG_OQP_USR_aggregate(Gender)], [Marital Status].DEFAULTMEMBER)}, IIF( COUNT( [COG_OQP_INT_s1], INCLUDEEMPTY)> 0, 1, 0)), IIF( COUNT( FILTER( [COG_OQP_INT_s5], ([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)>= 1), INCLUDEEMPTY)> 0, 1, 0)), ALL)), ALL), HEAD( HEAD( {([Store].[COG_OQP_USR_aggregate(Store Country)], [Gender].DEFAULTMEMBER, [Marital Status].DEFAULTMEMBER)}, IIF( COUNT( [COG_OQP_INT_s2], INCLUDEEMPTY)> 0, 1, 0)), IIF( COUNT( FILTER( [COG_OQP_INT_s4], ([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)>= 1), INCLUDEEMPTY)> 0, 1, 0)), ALL) ON AXIS(1), {[Measures].[COG_OQP_USR_Unit Sales]} ON AXIS(2) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Product].[Drink] Drink [Product].[Product Family] 1 3 [Product].[Non-Consumable] Non-Consumable [Product].[Product Family] 1 131077 [Product].[COG_OQP_USR_aggregate(Product Family)] COG_OQP_USR_aggregate(Product Family) [Product].[(All)] 0 0 [Store].[USA] USA [Store].[Store Country] 1 3 [Gender].[F] F [Gender].[Gender] 1 0 [Marital Status].[M] M [Marital Status].[Marital Status] 1 0 [Store].[USA] USA [Store].[Store Country] 1 131075 [Gender].[F] F [Gender].[Gender] 1 131072 [Marital Status].[S] S [Marital Status].[Marital Status] 1 131072 [Store].[USA] USA [Store].[Store Country] 1 131075 [Gender].[F] F [Gender].[Gender] 1 131072 [Marital Status].[COG_OQP_USR_aggregate(Marital Status)] COG_OQP_USR_aggregate(Marital Status) [Marital Status].[(All)] 0 0 [Store].[USA] USA [Store].[Store Country] 1 131075 [Gender].[M] M [Gender].[Gender] 1 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 0 [Store].[USA] USA [Store].[Store Country] 1 131075 [Gender].[M] M [Gender].[Gender] 1 131072 [Marital Status].[S] S [Marital Status].[Marital Status] 1 131072 [Store].[USA] USA [Store].[Store Country] 1 131075 [Gender].[M] M [Gender].[Gender] 1 131072 [Marital Status].[COG_OQP_USR_aggregate(Marital Status)] COG_OQP_USR_aggregate(Marital Status) [Marital Status].[(All)] 0 0 [Store].[USA] USA [Store].[Store Country] 1 131075 [Gender].[COG_OQP_USR_aggregate(Gender)] COG_OQP_USR_aggregate(Gender) [Gender].[(All)] 0 0 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 [Store].[COG_OQP_USR_aggregate(Store Country)] COG_OQP_USR_aggregate(Store Country) [Store].[(All)] 0 0 [Gender].[All Gender] All Gender [Gender].[(All)] 0 2 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 6207 6,207 Standard 11942 11,942 Standard 18149 18,149 Standard 5995 5,995 Standard 12600 12,600 Standard 18595 18,595 Standard 12202 12,202 Standard 24542 24,542 Standard 36744 36,744 Standard 5969 5,969 Standard 12749 12,749 Standard 18718 18,718 Standard 6426 6,426 Standard 12945 12,945 Standard 19371 19,371 Standard 12395 12,395 Standard 25694 25,694 Standard 38089 38,089 Standard 24597 24,597 Standard 50236 50,236 Standard 74833 74,833 Standard 24597 24,597 Standard 50236 50,236 Standard 74833 74,833 Standard ]]> WITH MEMBER [Store].[COG_OQP_USR_aggregate(Store Country)] AS 'SUM( CROSSJOIN( FILTER( {[Store].[Store Country].MEMBERS}, ([Measures].[Unit Sales], [Product].[COG_OQP_INT_sfa1], [Gender].[COG_OQP_INT_sfa2], [Marital Status].[COG_OQP_INT_sfa3])>= 500),[COG_OQP_INT_s7]))',SOLVE_ORDER = 8 MEMBER [Product].[COG_OQP_USR_aggregate(Product Family)] AS 'SUM( [COG_OQP_INT_s1])', SOLVE_ORDER = 4 MEMBER [Product].[COG_OQP_INT_sfa1] AS ' SUM( [COG_OQP_INT_s6])', SOLVE_ORDER = 2 MEMBER [Marital Status].[COG_OQP_USR_aggregate(Marital Status)] AS 'SUM( {[Marital Status].[Marital Status].MEMBERS})', SOLVE_ORDER = 16 MEMBER [Marital Status].[COG_OQP_INT_sfa3] AS ' SUM( {[Marital Status].[Marital Status].MEMBERS})',SOLVE_ORDER = 2 MEMBER [Gender].[COG_OQP_USR_aggregate(Gender)] AS 'SUM( [COG_OQP_INT_s7])', SOLVE_ORDER = 12 MEMBER [Gender].[COG_OQP_INT_sfa2] AS 'SUM( {[Gender].[Gender].MEMBERS})', SOLVE_ORDER = 2 SET [COG_OQP_INT_s8] AS 'CROSSJOIN( {[Gender].[Gender].MEMBERS}, UNION( {[Marital Status].[Marital Status].MEMBERS}, {([Marital Status].[COG_OQP_USR_aggregate(Marital Status)])}, ALL))' SET [COG_OQP_INT_s7] AS 'CROSSJOIN( {[Gender].[Gender].MEMBERS},{[Marital Status].[Marital Status].MEMBERS})' SET [COG_OQP_INT_s6] AS 'EXCEPT( {[Product].[Product Family].MEMBERS}, {[Product].[Food]})' SET [COG_OQP_INT_s5] AS 'EXCEPT( {[Product].[Product Family].MEMBERS}, {[Product].[Food]})' SET [COG_OQP_INT_s3] AS ' EXCEPT( {[Product].[Product Family].MEMBERS}, {[Product].[Food]})' SET [COG_OQP_INT_s1] AS 'EXCEPT( {[Product].[Product Family].MEMBERS}, {[Product].[Food]})' SELECT UNION( [COG_OQP_INT_s5], HEAD( {([Product].[COG_OQP_USR_aggregate(Product Family)])}, IIF( COUNT( [COG_OQP_INT_s3], INCLUDEEMPTY)> 0,1,0)),ALL) ON AXIS(0), UNION( GENERATE( FILTER( {[Store].[Store Country].MEMBERS}, ([Measures].[Unit Sales], [Product].[COG_OQP_INT_sfa1], [Gender].[COG_OQP_INT_sfa2], [Marital Status].[COG_OQP_INT_sfa3])>= 500), CROSSJOIN( HEAD( {([Store].CURRENTMEMBER)}, IIF( COUNT( [COG_OQP_INT_s7], INCLUDEEMPTY)> 0,1,0)), UNION( [COG_OQP_INT_s8], HEAD( HEAD( {([Gender].[COG_OQP_USR_aggregate(Gender)], [Marital Status].DEFAULTMEMBER)}, IIF( COUNT( [COG_OQP_INT_s7], INCLUDEEMPTY)> 0,1,0)), IIF( COUNT( [COG_OQP_INT_s7], INCLUDEEMPTY)> 0,1,0)), ALL)), ALL), HEAD( HEAD( {([Store].[COG_OQP_USR_aggregate(Store Country)], [Gender].DEFAULTMEMBER, [Marital Status].DEFAULTMEMBER)},IIF( COUNT( CROSSJOIN( FILTER( {[Store].[Store Country].MEMBERS}, ([Measures].[Unit Sales], [Product].[COG_OQP_INT_sfa1], [Gender].[COG_OQP_INT_sfa2],[Marital Status].[COG_OQP_INT_sfa3])>= 500), [COG_OQP_INT_s7]), INCLUDEEMPTY)> 0,1,0)), IIF( COUNT( CROSSJOIN( FILTER( {[Store].[Store Country].MEMBERS},([Measures].[Unit Sales], [Product].[COG_OQP_INT_sfa1], [Gender].[COG_OQP_INT_sfa2], [Marital Status].[COG_OQP_INT_sfa3])>= 500),[COG_OQP_INT_s7]),INCLUDEEMPTY)>0,1,0)), ALL) ON AXIS(1), {[Measures].[Unit Sales]} ON AXIS(2) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Product].[Drink] Drink [Product].[Product Family] 1 3 [Product].[Non-Consumable] Non-Consumable [Product].[Product Family] 1 131077 [Product].[COG_OQP_USR_aggregate(Product Family)] COG_OQP_USR_aggregate(Product Family) [Product].[(All)] 0 0 [Store].[USA] USA [Store].[Store Country] 1 3 [Gender].[F] F [Gender].[Gender] 1 0 [Marital Status].[M] M [Marital Status].[Marital Status] 1 0 [Store].[USA] USA [Store].[Store Country] 1 131075 [Gender].[F] F [Gender].[Gender] 1 131072 [Marital Status].[S] S [Marital Status].[Marital Status] 1 131072 [Store].[USA] USA [Store].[Store Country] 1 131075 [Gender].[F] F [Gender].[Gender] 1 131072 [Marital Status].[COG_OQP_USR_aggregate(Marital Status)] COG_OQP_USR_aggregate(Marital Status) [Marital Status].[(All)] 0 0 [Store].[USA] USA [Store].[Store Country] 1 131075 [Gender].[M] M [Gender].[Gender] 1 131072 [Marital Status].[M] M [Marital Status].[Marital Status] 1 0 [Store].[USA] USA [Store].[Store Country] 1 131075 [Gender].[M] M [Gender].[Gender] 1 131072 [Marital Status].[S] S [Marital Status].[Marital Status] 1 131072 [Store].[USA] USA [Store].[Store Country] 1 131075 [Gender].[M] M [Gender].[Gender] 1 131072 [Marital Status].[COG_OQP_USR_aggregate(Marital Status)] COG_OQP_USR_aggregate(Marital Status) [Marital Status].[(All)] 0 0 [Store].[USA] USA [Store].[Store Country] 1 131075 [Gender].[COG_OQP_USR_aggregate(Gender)] COG_OQP_USR_aggregate(Gender) [Gender].[(All)] 0 0 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 [Store].[COG_OQP_USR_aggregate(Store Country)] COG_OQP_USR_aggregate(Store Country) [Store].[(All)] 0 0 [Gender].[All Gender] All Gender [Gender].[(All)] 0 2 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 6207 6,207 Standard 11942 11,942 Standard 18149 18,149 Standard 5995 5,995 Standard 12600 12,600 Standard 18595 18,595 Standard 12202 12,202 Standard 24542 24,542 Standard 36744 36,744 Standard 5969 5,969 Standard 12749 12,749 Standard 18718 18,718 Standard 6426 6,426 Standard 12945 12,945 Standard 19371 19,371 Standard 12395 12,395 Standard 25694 25,694 Standard 38089 38,089 Standard 24597 24,597 Standard 50236 50,236 Standard 74833 74,833 Standard 24597 24,597 Standard 50236 50,236 Standard 74833 74,833 Standard ]]> WITH MEMBER [Time].[Time].[COG_OQP_INT_umg1] AS ' COUNT( [COG_OQP_INT_s1], EXCLUDEEMPTY)', SOLVE_ORDER = 4 SET [COG_OQP_INT_s1] AS ' CROSSJOIN( {[Time].[Quarter].MEMBERS}, {[Education Level].[Graduate Degree]})' SELECT CROSSJOIN({[Measures].[Unit Sales]},{[Gender].MEMBERS}) ON AXIS(0), GENERATE( {[Product].[Product Department].MEMBERS}, CROSSJOIN( HEAD( {([Product].CURRENTMEMBER)}, IIF( COUNT( [COG_OQP_INT_s1], INCLUDEEMPTY)> 0, 1, 0)), UNION( [COG_OQP_INT_s1], {([Time].[COG_OQP_INT_umg1], [Education Level].DEFAULTMEMBER)}, ALL)), ALL) ON AXIS(1) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Gender].[All Gender] All Gender [Gender].[(All)] 0 65538 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Gender].[F] F [Gender].[Gender] 1 0 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Gender].[M] M [Gender].[Gender] 1 131072 [Product].[Drink].[Alcoholic Beverages] Alcoholic Beverages [Product].[Product Department] 2 1 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Drink].[Alcoholic Beverages] Alcoholic Beverages [Product].[Product Department] 2 131073 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Alcoholic Beverages] Alcoholic Beverages [Product].[Product Department] 2 131073 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Alcoholic Beverages] Alcoholic Beverages [Product].[Product Department] 2 131073 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Alcoholic Beverages] Alcoholic Beverages [Product].[Product Department] 2 131073 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Alcoholic Beverages] Alcoholic Beverages [Product].[Product Department] 2 131073 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Alcoholic Beverages] Alcoholic Beverages [Product].[Product Department] 2 131073 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Alcoholic Beverages] Alcoholic Beverages [Product].[Product Department] 2 131073 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Alcoholic Beverages] Alcoholic Beverages [Product].[Product Department] 2 131073 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Drink].[Beverages] Beverages [Product].[Product Department] 2 131076 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Drink].[Beverages] Beverages [Product].[Product Department] 2 131076 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Beverages] Beverages [Product].[Product Department] 2 131076 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Beverages] Beverages [Product].[Product Department] 2 131076 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Beverages] Beverages [Product].[Product Department] 2 131076 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Beverages] Beverages [Product].[Product Department] 2 131076 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Beverages] Beverages [Product].[Product Department] 2 131076 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Beverages] Beverages [Product].[Product Department] 2 131076 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Beverages] Beverages [Product].[Product Department] 2 131076 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Drink].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Drink].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Drink].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Food].[Baked Goods] Baked Goods [Product].[Product Department] 2 1 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Food].[Baked Goods] Baked Goods [Product].[Product Department] 2 131073 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Baked Goods] Baked Goods [Product].[Product Department] 2 131073 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Baked Goods] Baked Goods [Product].[Product Department] 2 131073 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Baked Goods] Baked Goods [Product].[Product Department] 2 131073 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Baked Goods] Baked Goods [Product].[Product Department] 2 131073 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Baked Goods] Baked Goods [Product].[Product Department] 2 131073 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Baked Goods] Baked Goods [Product].[Product Department] 2 131073 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Baked Goods] Baked Goods [Product].[Product Department] 2 131073 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Food].[Baking Goods] Baking Goods [Product].[Product Department] 2 131074 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Food].[Baking Goods] Baking Goods [Product].[Product Department] 2 131074 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Baking Goods] Baking Goods [Product].[Product Department] 2 131074 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Baking Goods] Baking Goods [Product].[Product Department] 2 131074 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Baking Goods] Baking Goods [Product].[Product Department] 2 131074 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Baking Goods] Baking Goods [Product].[Product Department] 2 131074 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Baking Goods] Baking Goods [Product].[Product Department] 2 131074 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Baking Goods] Baking Goods [Product].[Product Department] 2 131074 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Baking Goods] Baking Goods [Product].[Product Department] 2 131074 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Food].[Breakfast Foods] Breakfast Foods [Product].[Product Department] 2 131073 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Food].[Breakfast Foods] Breakfast Foods [Product].[Product Department] 2 131073 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Breakfast Foods] Breakfast Foods [Product].[Product Department] 2 131073 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Breakfast Foods] Breakfast Foods [Product].[Product Department] 2 131073 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Breakfast Foods] Breakfast Foods [Product].[Product Department] 2 131073 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Breakfast Foods] Breakfast Foods [Product].[Product Department] 2 131073 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Breakfast Foods] Breakfast Foods [Product].[Product Department] 2 131073 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Breakfast Foods] Breakfast Foods [Product].[Product Department] 2 131073 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Breakfast Foods] Breakfast Foods [Product].[Product Department] 2 131073 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Food].[Canned Foods] Canned Foods [Product].[Product Department] 2 131080 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Food].[Canned Foods] Canned Foods [Product].[Product Department] 2 131080 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Canned Foods] Canned Foods [Product].[Product Department] 2 131080 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Canned Foods] Canned Foods [Product].[Product Department] 2 131080 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Canned Foods] Canned Foods [Product].[Product Department] 2 131080 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Canned Foods] Canned Foods [Product].[Product Department] 2 131080 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Canned Foods] Canned Foods [Product].[Product Department] 2 131080 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Canned Foods] Canned Foods [Product].[Product Department] 2 131080 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Canned Foods] Canned Foods [Product].[Product Department] 2 131080 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Food].[Canned Products] Canned Products [Product].[Product Department] 2 131073 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Food].[Canned Products] Canned Products [Product].[Product Department] 2 131073 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Canned Products] Canned Products [Product].[Product Department] 2 131073 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Canned Products] Canned Products [Product].[Product Department] 2 131073 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Canned Products] Canned Products [Product].[Product Department] 2 131073 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Canned Products] Canned Products [Product].[Product Department] 2 131073 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Canned Products] Canned Products [Product].[Product Department] 2 131073 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Canned Products] Canned Products [Product].[Product Department] 2 131073 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Canned Products] Canned Products [Product].[Product Department] 2 131073 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Food].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Food].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Dairy] Dairy [Product].[Product Department] 2 131073 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Food].[Deli] Deli [Product].[Product Department] 2 131074 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Food].[Deli] Deli [Product].[Product Department] 2 131074 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Deli] Deli [Product].[Product Department] 2 131074 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Deli] Deli [Product].[Product Department] 2 131074 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Deli] Deli [Product].[Product Department] 2 131074 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Deli] Deli [Product].[Product Department] 2 131074 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Deli] Deli [Product].[Product Department] 2 131074 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Deli] Deli [Product].[Product Department] 2 131074 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Deli] Deli [Product].[Product Department] 2 131074 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Food].[Eggs] Eggs [Product].[Product Department] 2 131073 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Food].[Eggs] Eggs [Product].[Product Department] 2 131073 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Eggs] Eggs [Product].[Product Department] 2 131073 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Eggs] Eggs [Product].[Product Department] 2 131073 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Eggs] Eggs [Product].[Product Department] 2 131073 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Eggs] Eggs [Product].[Product Department] 2 131073 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Eggs] Eggs [Product].[Product Department] 2 131073 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Eggs] Eggs [Product].[Product Department] 2 131073 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Eggs] Eggs [Product].[Product Department] 2 131073 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Food].[Frozen Foods] Frozen Foods [Product].[Product Department] 2 131078 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Food].[Frozen Foods] Frozen Foods [Product].[Product Department] 2 131078 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Frozen Foods] Frozen Foods [Product].[Product Department] 2 131078 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Frozen Foods] Frozen Foods [Product].[Product Department] 2 131078 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Frozen Foods] Frozen Foods [Product].[Product Department] 2 131078 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Frozen Foods] Frozen Foods [Product].[Product Department] 2 131078 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Frozen Foods] Frozen Foods [Product].[Product Department] 2 131078 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Frozen Foods] Frozen Foods [Product].[Product Department] 2 131078 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Frozen Foods] Frozen Foods [Product].[Product Department] 2 131078 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Food].[Meat] Meat [Product].[Product Department] 2 131073 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Food].[Meat] Meat [Product].[Product Department] 2 131073 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Meat] Meat [Product].[Product Department] 2 131073 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Meat] Meat [Product].[Product Department] 2 131073 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Meat] Meat [Product].[Product Department] 2 131073 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Meat] Meat [Product].[Product Department] 2 131073 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Meat] Meat [Product].[Product Department] 2 131073 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Meat] Meat [Product].[Product Department] 2 131073 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Meat] Meat [Product].[Product Department] 2 131073 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Food].[Produce] Produce [Product].[Product Department] 2 131076 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Food].[Produce] Produce [Product].[Product Department] 2 131076 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Produce] Produce [Product].[Product Department] 2 131076 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Produce] Produce [Product].[Product Department] 2 131076 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Produce] Produce [Product].[Product Department] 2 131076 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Produce] Produce [Product].[Product Department] 2 131076 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Produce] Produce [Product].[Product Department] 2 131076 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Produce] Produce [Product].[Product Department] 2 131076 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Produce] Produce [Product].[Product Department] 2 131076 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Food].[Seafood] Seafood [Product].[Product Department] 2 131073 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Food].[Seafood] Seafood [Product].[Product Department] 2 131073 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Seafood] Seafood [Product].[Product Department] 2 131073 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Seafood] Seafood [Product].[Product Department] 2 131073 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Seafood] Seafood [Product].[Product Department] 2 131073 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Seafood] Seafood [Product].[Product Department] 2 131073 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Seafood] Seafood [Product].[Product Department] 2 131073 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Seafood] Seafood [Product].[Product Department] 2 131073 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Seafood] Seafood [Product].[Product Department] 2 131073 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Food].[Snack Foods] Snack Foods [Product].[Product Department] 2 131073 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Food].[Snack Foods] Snack Foods [Product].[Product Department] 2 131073 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Snack Foods] Snack Foods [Product].[Product Department] 2 131073 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Snack Foods] Snack Foods [Product].[Product Department] 2 131073 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Snack Foods] Snack Foods [Product].[Product Department] 2 131073 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Snack Foods] Snack Foods [Product].[Product Department] 2 131073 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Snack Foods] Snack Foods [Product].[Product Department] 2 131073 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Snack Foods] Snack Foods [Product].[Product Department] 2 131073 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Snack Foods] Snack Foods [Product].[Product Department] 2 131073 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Food].[Snacks] Snacks [Product].[Product Department] 2 131073 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Food].[Snacks] Snacks [Product].[Product Department] 2 131073 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Snacks] Snacks [Product].[Product Department] 2 131073 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Snacks] Snacks [Product].[Product Department] 2 131073 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Snacks] Snacks [Product].[Product Department] 2 131073 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Snacks] Snacks [Product].[Product Department] 2 131073 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Snacks] Snacks [Product].[Product Department] 2 131073 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Snacks] Snacks [Product].[Product Department] 2 131073 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Snacks] Snacks [Product].[Product Department] 2 131073 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Food].[Starchy Foods] Starchy Foods [Product].[Product Department] 2 131073 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Food].[Starchy Foods] Starchy Foods [Product].[Product Department] 2 131073 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Starchy Foods] Starchy Foods [Product].[Product Department] 2 131073 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Starchy Foods] Starchy Foods [Product].[Product Department] 2 131073 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Starchy Foods] Starchy Foods [Product].[Product Department] 2 131073 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Starchy Foods] Starchy Foods [Product].[Product Department] 2 131073 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Starchy Foods] Starchy Foods [Product].[Product Department] 2 131073 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Starchy Foods] Starchy Foods [Product].[Product Department] 2 131073 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Food].[Starchy Foods] Starchy Foods [Product].[Product Department] 2 131073 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Non-Consumable].[Carousel] Carousel [Product].[Product Department] 2 1 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Non-Consumable].[Carousel] Carousel [Product].[Product Department] 2 131073 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Carousel] Carousel [Product].[Product Department] 2 131073 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Carousel] Carousel [Product].[Product Department] 2 131073 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Carousel] Carousel [Product].[Product Department] 2 131073 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Carousel] Carousel [Product].[Product Department] 2 131073 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Carousel] Carousel [Product].[Product Department] 2 131073 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Carousel] Carousel [Product].[Product Department] 2 131073 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Carousel] Carousel [Product].[Product Department] 2 131073 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Non-Consumable].[Checkout] Checkout [Product].[Product Department] 2 131074 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Non-Consumable].[Checkout] Checkout [Product].[Product Department] 2 131074 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Checkout] Checkout [Product].[Product Department] 2 131074 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Checkout] Checkout [Product].[Product Department] 2 131074 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Checkout] Checkout [Product].[Product Department] 2 131074 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Checkout] Checkout [Product].[Product Department] 2 131074 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Checkout] Checkout [Product].[Product Department] 2 131074 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Checkout] Checkout [Product].[Product Department] 2 131074 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Checkout] Checkout [Product].[Product Department] 2 131074 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Non-Consumable].[Health and Hygiene] Health and Hygiene [Product].[Product Department] 2 131077 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Non-Consumable].[Health and Hygiene] Health and Hygiene [Product].[Product Department] 2 131077 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Health and Hygiene] Health and Hygiene [Product].[Product Department] 2 131077 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Health and Hygiene] Health and Hygiene [Product].[Product Department] 2 131077 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Health and Hygiene] Health and Hygiene [Product].[Product Department] 2 131077 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Health and Hygiene] Health and Hygiene [Product].[Product Department] 2 131077 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Health and Hygiene] Health and Hygiene [Product].[Product Department] 2 131077 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Health and Hygiene] Health and Hygiene [Product].[Product Department] 2 131077 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Health and Hygiene] Health and Hygiene [Product].[Product Department] 2 131077 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Non-Consumable].[Household] Household [Product].[Product Department] 2 131080 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Non-Consumable].[Household] Household [Product].[Product Department] 2 131080 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Household] Household [Product].[Product Department] 2 131080 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Household] Household [Product].[Product Department] 2 131080 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Household] Household [Product].[Product Department] 2 131080 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Household] Household [Product].[Product Department] 2 131080 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Household] Household [Product].[Product Department] 2 131080 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Household] Household [Product].[Product Department] 2 131080 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Household] Household [Product].[Product Department] 2 131080 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 65541 [Product].[Non-Consumable].[Periodicals] Periodicals [Product].[Product Department] 2 131073 [Time].[1997].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 0 [Product].[Non-Consumable].[Periodicals] Periodicals [Product].[Product Department] 2 131073 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Periodicals] Periodicals [Product].[Product Department] 2 131073 [Time].[1997].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Periodicals] Periodicals [Product].[Product Department] 2 131073 [Time].[1997].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Periodicals] Periodicals [Product].[Product Department] 2 131073 [Time].[1998].[Q1] Q1 [Time].[Quarter] 1 3 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Periodicals] Periodicals [Product].[Product Department] 2 131073 [Time].[1998].[Q2] Q2 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Periodicals] Periodicals [Product].[Product Department] 2 131073 [Time].[1998].[Q3] Q3 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Periodicals] Periodicals [Product].[Product Department] 2 131073 [Time].[1998].[Q4] Q4 [Time].[Quarter] 1 131075 [Education Level].[Graduate Degree] Graduate Degree [Education Level].[Education Level] 1 131072 [Product].[Non-Consumable].[Periodicals] Periodicals [Product].[Product Department] 2 131073 [Time].[COG_OQP_INT_umg1] COG_OQP_INT_umg1 [Time].[Year] 0 0 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 5 83 83 Standard 39 39 Standard 44 44 Standard 84 84 Standard 35 35 Standard 49 49 Standard 116 116 Standard 75 75 Standard 41 41 Standard 69 69 Standard 38 38 Standard 31 31 Standard 4 4 Standard 4 4 Standard 4 4 Standard 153 153 Standard 81 81 Standard 72 72 Standard 214 214 Standard 117 117 Standard 97 97 Standard 140 140 Standard 80 80 Standard 60 60 Standard 223 223 Standard 124 124 Standard 99 99 Standard 4 4 Standard 4 4 Standard 4 4 Standard 38 38 Standard 13 13 Standard 25 25 Standard 76 76 Standard 45 45 Standard 31 31 Standard 64 64 Standard 37 37 Standard 27 27 Standard 65 65 Standard 35 35 Standard 30 30 Standard 4 4 Standard 4 4 Standard 4 4 Standard 105 105 Standard 48 48 Standard 57 57 Standard 107 107 Standard 62 62 Standard 45 45 Standard 134 134 Standard 86 86 Standard 48 48 Standard 154 154 Standard 95 95 Standard 59 59 Standard 4 4 Standard 4 4 Standard 4 4 Standard 289 289 Standard 154 154 Standard 135 135 Standard 313 313 Standard 159 159 Standard 154 154 Standard 297 297 Standard 171 171 Standard 126 126 Standard 379 379 Standard 188 188 Standard 191 191 Standard 4 4 Standard 4 4 Standard 4 4 Standard 22 22 Standard 11 11 Standard 11 11 Standard 45 45 Standard 26 26 Standard 19 19 Standard 49 49 Standard 33 33 Standard 16 16 Standard 28 28 Standard 17 17 Standard 11 11 Standard 4 4 Standard 4 4 Standard 4 4 Standard 326 326 Standard 175 175 Standard 151 151 Standard 290 290 Standard 175 175 Standard 115 115 Standard 239 239 Standard 135 135 Standard 104 104 Standard 347 347 Standard 176 176 Standard 171 171 Standard 4 4 Standard 4 4 Standard 4 4 Standard 38 38 Standard 17 17 Standard 21 21 Standard 35 35 Standard 16 16 Standard 19 19 Standard 23 23 Standard 15 15 Standard 8 8 Standard 18 18 Standard 10 10 Standard 8 8 Standard 4 4 Standard 4 4 Standard 4 4 Standard 192 192 Standard 90 90 Standard 102 102 Standard 212 212 Standard 116 116 Standard 96 96 Standard 217 217 Standard 152 152 Standard 65 65 Standard 201 201 Standard 117 117 Standard 84 84 Standard 4 4 Standard 4 4 Standard 4 4 Standard 153 153 Standard 58 58 Standard 95 95 Standard 140 140 Standard 61 61 Standard 79 79 Standard 194 194 Standard 134 134 Standard 60 60 Standard 198 198 Standard 100 100 Standard 98 98 Standard 4 4 Standard 4 4 Standard 4 4 Standard 49 49 Standard 19 19 Standard 30 30 Standard 60 60 Standard 34 34 Standard 26 26 Standard 65 65 Standard 35 35 Standard 30 30 Standard 73 73 Standard 35 35 Standard 38 38 Standard 4 4 Standard 4 4 Standard 4 4 Standard 358 358 Standard 191 191 Standard 167 167 Standard 367 367 Standard 173 173 Standard 194 194 Standard 383 383 Standard 238 238 Standard 145 145 Standard 432 432 Standard 215 215 Standard 217 217 Standard 4 4 Standard 4 4 Standard 4 4 Standard 26 26 Standard 9 9 Standard 17 17 Standard 33 33 Standard 19 19 Standard 14 14 Standard 24 24 Standard 5 5 Standard 19 19 Standard 18 18 Standard 10 10 Standard 8 8 Standard 4 4 Standard 4 4 Standard 4 4 Standard 557 557 Standard 276 276 Standard 281 281 Standard 527 527 Standard 292 292 Standard 235 235 Standard 477 477 Standard 283 283 Standard 194 194 Standard 597 597 Standard 314 314 Standard 283 283 Standard 4 4 Standard 4 4 Standard 4 4 Standard 25 25 Standard 19 19 Standard 6 6 Standard 27 27 Standard 17 17 Standard 10 10 Standard 28 28 Standard 12 12 Standard 16 16 Standard 21 21 Standard 12 12 Standard 9 9 Standard 4 4 Standard 4 4 Standard 4 4 Standard 363 363 Standard 201 201 Standard 162 162 Standard 473 473 Standard 260 260 Standard 213 213 Standard 428 428 Standard 217 217 Standard 211 211 Standard 436 436 Standard 233 233 Standard 203 203 Standard 4 4 Standard 4 4 Standard 4 4 Standard 127 127 Standard 38 38 Standard 89 89 Standard 59 59 Standard 33 33 Standard 26 26 Standard 106 106 Standard 47 47 Standard 59 59 Standard 86 86 Standard 39 39 Standard 47 47 Standard 4 4 Standard 4 4 Standard 4 4 Standard 53 53 Standard 23 23 Standard 30 30 Standard 67 67 Standard 32 32 Standard 35 35 Standard 99 99 Standard 55 55 Standard 44 44 Standard 66 66 Standard 45 45 Standard 21 21 Standard 4 4 Standard 4 4 Standard 4 4 Standard 7 7 Standard 7 7 Standard 2 2 Standard 2 2 Standard 21 21 Standard 7 7 Standard 14 14 Standard 23 23 Standard 16 16 Standard 7 7 Standard 4 4 Standard 3 3 Standard 3 3 Standard 31 31 Standard 15 15 Standard 16 16 Standard 15 15 Standard 6 6 Standard 9 9 Standard 18 18 Standard 10 10 Standard 8 8 Standard 16 16 Standard 5 5 Standard 11 11 Standard 4 4 Standard 4 4 Standard 4 4 Standard 183 183 Standard 82 82 Standard 101 101 Standard 288 288 Standard 128 128 Standard 160 160 Standard 241 241 Standard 146 146 Standard 95 95 Standard 283 283 Standard 151 151 Standard 132 132 Standard 4 4 Standard 4 4 Standard 4 4 Standard 385 385 Standard 200 200 Standard 185 185 Standard 381 381 Standard 231 231 Standard 150 150 Standard 372 372 Standard 174 174 Standard 198 198 Standard 469 469 Standard 255 255 Standard 214 214 Standard 4 4 Standard 4 4 Standard 4 4 Standard 74 74 Standard 50 50 Standard 24 24 Standard 65 65 Standard 21 21 Standard 44 44 Standard 64 64 Standard 39 39 Standard 25 25 Standard 52 52 Standard 24 24 Standard 28 28 Standard 4 4 Standard 4 4 Standard 4 4 Standard ]]> WITH MEMBER [Measures].[COG_OQP_USR_Unit Sales] AS ' [Measures].[COG_OQP_INT_m1]', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_m1] AS ' IIF( [Measures].[Unit Sales]> 1, [Measures].[Unit Sales], NULL)', SOLVE_ORDER = 2 MEMBER [Time].[Time].[COG_OQP_INT_m2] AS ' IIF( ([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)> 1, 1, NULL)', SOLVE_ORDER = 2 MEMBER [Customers].[COG_OQP_USR_Aggregate(TC0)] AS ' IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_USR_Unit Sales], ([Customers].[COG_OQP_INT_m3], [Measures].[Unit Sales]), IIF( [Measures].CURRENTMEMBER IS [Measures].[COG_OQP_INT_m1], ([Customers].[COG_OQP_INT_m3], [Measures].[Unit Sales]), IIF( [Measures].CURRENTMEMBER IS [Measures].[Unit Sales], ([Customers].[COG_OQP_INT_m3], [Measures].[Unit Sales]), AGGREGATE( BOTTOMCOUNT( filter( {[Customers].[State Province].MEMBERS}, [Measures].[Unit Sales] > 0), 2, [Measures].[COG_OQP_INT_m1])))))', SOLVE_ORDER = 4 MEMBER [Customers].[COG_OQP_INT_m3] AS ' AGGREGATE( FILTER( BOTTOMCOUNT( filter( {[Customers].[State Province].MEMBERS}, [Measures].[Unit Sales] > 0), 2, [Measures].[COG_OQP_INT_m1]), NOT ISEMPTY( [Time].[COG_OQP_INT_m2])))', SOLVE_ORDER = 4 SELECT FILTER( {[Product].[Product Category].MEMBERS}, COUNT( FILTER( BOTTOMCOUNT( filter( {[Customers].[State Province].MEMBERS}, [Measures].[Unit Sales] > 0), 2, ([Measures].[COG_OQP_INT_m1], [Customers].CURRENTMEMBER, [Product].DEFAULTMEMBER)), ([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)> 1), INCLUDEEMPTY)> 0) ON AXIS(0), CROSSJOIN({[Measures].[COG_OQP_USR_Unit Sales]},HEAD( HEAD( {[Customers].[COG_OQP_USR_Aggregate(TC0)]}, IIF( COUNT( FILTER( CROSSJOIN( BOTTOMCOUNT( filter( {[Customers].[State Province].MEMBERS}, [Measures].[Unit Sales] > 0), 2, [Measures].[Unit Sales]), {[Product].[Product Category].MEMBERS}), ([Measures].[Unit Sales], [Time].[Time].DEFAULTMEMBER)> 1), INCLUDEEMPTY)> 0, 1, 0)), IIF( COUNT( BOTTOMCOUNT( filter( {[Customers].[State Province].MEMBERS}, [Measures].[Unit Sales] > 0), 2, [Measures].[COG_OQP_INT_m1]), INCLUDEEMPTY)> 0, 1, 0))) ON AXIS(1) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 2 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 1 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 1 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 3 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 4 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 1 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 1 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 1 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 4 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 4 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 1 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 3 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 1 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 1 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 2 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 9 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 3 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 2 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 1 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 1 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 4 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 1 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 5 [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[COG_OQP_USR_Aggregate(TC0)] COG_OQP_USR_Aggregate(TC0) [Customers].[(All)] 0 0 [Store].[All Stores] All Stores [Store].[(All)] 0 3 [Store Size in SQFT].[All Store Size in SQFTs] All Store Size in SQFTs [Store Size in SQFT].[(All)] 0 21 [Store Type].[All Store Types] All Store Types [Store Type].[(All)] 0 6 [Time].[1997] 1997 [Time].[Year] 0 4 [Promotion Media].[All Media] All Media [Promotion Media].[(All)] 0 14 [Promotions].[All Promotions] All Promotions [Promotions].[(All)] 0 51 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 5 [Gender].[All Gender] All Gender [Gender].[(All)] 0 2 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 [Yearly Income].[All Yearly Incomes] All Yearly Incomes [Yearly Income].[(All)] 0 8 3616 3,616 Standard 1781 1,781 Standard 1398 1,398 Standard 2286 2,286 Standard 1806 1,806 Standard 2321 2,321 Standard 4163 4,163 Standard 4389 4,389 Standard 6220 6,220 Standard 1800 1,800 Standard 481 481 Standard 506 506 Standard 412 412 Standard 449 449 Standard 404 404 Standard 4258 4,258 Standard 908 908 Standard 2739 2,739 Standard 912 912 Standard 6665 6,665 Standard 5064 5,064 Standard 1367 1,367 Standard 2235 2,235 Standard 2578 2,578 Standard 3326 3,326 Standard 1395 1,395 Standard 1307 1,307 Standard 1736 1,736 Standard 3738 3,738 Standard 996 996 Standard 6192 6,192 Standard 445 445 Standard 2342 2,342 Standard 11353 11,353 Standard 892 892 Standard 16332 16,332 Standard 3789 3,789 Standard 2800 2,800 Standard 471 471 Standard 476 476 Standard 517 517 Standard 3159 3,159 Standard 895 895 Standard 923 923 Standard 1944 1,944 Standard 1606 1,606 Standard 479 479 Standard 512 512 Standard 1838 1,838 Standard 3725 3,725 Standard 960 960 Standard 2292 2,292 Standard 3584 3,584 Standard 1301 1,301 Standard 2324 2,324 Standard ]]> WITH MEMBER [Customers].[COG_OQP_USR_Aggregate(TC0)] AS ' IIF( [Measures].CURRENTMEMBER IS [Measures].[Unit Sales], ([Customers].[COG_OQP_INT_m1], [Measures].[Unit Sales]), AGGREGATE( BOTTOMCOUNT( filter( {[Customers].[State Province].MEMBERS}, [Measures].[Unit Sales] > 0), 2, [Measures].[Unit Sales])))', SOLVE_ORDER = 4 MEMBER [Customers].[COG_OQP_INT_m1] AS ' AGGREGATE( BOTTOMCOUNT( filter( {[Customers].[State Province].MEMBERS}, [Measures].[Unit Sales] > 0), 2, [Measures].[Unit Sales]))', SOLVE_ORDER = 4 SELECT CROSSJOIN({measures.[unit sales]},{[Product].[Product Category].MEMBERS}) ON AXIS(0), HEAD( {[Customers].[COG_OQP_USR_Aggregate(TC0)]}, IIF( COUNT( BOTTOMCOUNT( filter( {[Customers].[State Province].MEMBERS}, [Measures].[Unit Sales] > 0), 2, [Measures].[Unit Sales]), INCLUDEEMPTY)> 0, 1, 0)) ON AXIS(1) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 9 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 5 [Customers].[COG_OQP_USR_Aggregate(TC0)] COG_OQP_USR_Aggregate(TC0) [Customers].[(All)] 0 0 3616 3,616 Standard 1781 1,781 Standard 1398 1,398 Standard 2286 2,286 Standard 1806 1,806 Standard 2321 2,321 Standard 4163 4,163 Standard 4389 4,389 Standard 6220 6,220 Standard 1800 1,800 Standard 481 481 Standard 506 506 Standard 412 412 Standard 449 449 Standard 404 404 Standard 4258 4,258 Standard 908 908 Standard 2739 2,739 Standard 912 912 Standard 6665 6,665 Standard 5064 5,064 Standard 1367 1,367 Standard 2235 2,235 Standard 2578 2,578 Standard 3326 3,326 Standard 1395 1,395 Standard 1307 1,307 Standard 1736 1,736 Standard 3738 3,738 Standard 996 996 Standard 6192 6,192 Standard 445 445 Standard 2342 2,342 Standard 11353 11,353 Standard 892 892 Standard 16332 16,332 Standard 3789 3,789 Standard 2800 2,800 Standard 471 471 Standard 476 476 Standard 517 517 Standard 3159 3,159 Standard 895 895 Standard 923 923 Standard 1944 1,944 Standard 1606 1,606 Standard 479 479 Standard 512 512 Standard 1838 1,838 Standard 3725 3,725 Standard 960 960 Standard 2292 2,292 Standard 3584 3,584 Standard 1301 1,301 Standard 2324 2,324 Standard ]]> SELECT CROSSJOIN( CROSSJOIN( GENERATE( INTERSECT( BOTTOMCOUNT( filter( {[Customers].[State Province].MEMBERS}, [Measures].[Unit Sales] > 0), 2, [Measures].[Unit Sales]), GENERATE( TOPCOUNT( {[Customers].[Country].MEMBERS}, 3, [Measures].[Unit Sales]), DESCENDANTS( [Customers].CURRENTMEMBER, [Customers].[State Province]))), {ANCESTOR( [Customers].CURRENTMEMBER, [Customers].[Country]), [Customers].CURRENTMEMBER}, ALL), {[Product].[Product Category].MEMBERS}), {[Measures].[Unit Sales]}) ON AXIS(0) FROM [Sales] ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Customers].[USA] USA [Customers].[Country] 1 3 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 9 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 196611 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 5 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 11 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 9 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[OR] OR [Customers].[State Province] 2 131083 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 5 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 3 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 9 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA] USA [Customers].[Country] 1 196611 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 5 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 45 [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] Beer and Wine [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Drink].[Beverages].[Carbonated Beverages] Carbonated Beverages [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Drink].[Beverages].[Drinks] Drinks [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Drink].[Beverages].[Hot Beverages] Hot Beverages [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Drink].[Beverages].[Pure Juice Beverages] Pure Juice Beverages [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Drink].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Baked Goods].[Bread] Bread [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Baking Goods].[Baking Goods] Baking Goods [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Baking Goods].[Jams and Jellies] Jams and Jellies [Product].[Product Category] 3 131076 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Breakfast Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Foods].[Canned Anchovies] Canned Anchovies [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Foods].[Canned Clams] Canned Clams [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Foods].[Canned Oysters] Canned Oysters [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Foods].[Canned Sardines] Canned Sardines [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Foods].[Canned Shrimp] Canned Shrimp [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Foods].[Canned Soup] Canned Soup [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Foods].[Canned Tuna] Canned Tuna [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Canned Products].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Dairy].[Dairy] Dairy [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Deli].[Meat] Meat [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Deli].[Side Dishes] Side Dishes [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Eggs].[Eggs] Eggs [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Frozen Foods].[Breakfast Foods] Breakfast Foods [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Frozen Foods].[Frozen Desserts] Frozen Desserts [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Frozen Foods].[Frozen Entrees] Frozen Entrees [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Frozen Foods].[Meat] Meat [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Frozen Foods].[Pizza] Pizza [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Frozen Foods].[Vegetables] Vegetables [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Meat].[Meat] Meat [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Produce].[Fruit] Fruit [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Produce].[Packaged Vegetables] Packaged Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Produce].[Specialty] Specialty [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Produce].[Vegetables] Vegetables [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Seafood].[Seafood] Seafood [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Snack Foods].[Snack Foods] Snack Foods [Product].[Product Category] 3 9 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Snacks].[Candy] Candy [Product].[Product Category] 3 3 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Food].[Starchy Foods].[Starchy Foods] Starchy Foods [Product].[Product Category] 3 2 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Carousel].[Specialty] Specialty [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Checkout].[Hardware] Hardware [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Checkout].[Miscellaneous] Miscellaneous [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 4 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] Cold Remedies [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] Decongestants [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] Hygiene [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] Pain Relievers [Product].[Product Category] 3 131075 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Household].[Bathroom Products] Bathroom Products [Product].[Product Category] 3 1 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Household].[Candles] Candles [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Household].[Cleaning Supplies] Cleaning Supplies [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Household].[Electrical] Electrical [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Household].[Hardware] Hardware [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Household].[Kitchen Products] Kitchen Products [Product].[Product Category] 3 131076 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Household].[Paper Products] Paper Products [Product].[Product Category] 3 131074 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Household].[Plastic Products] Plastic Products [Product].[Product Category] 3 131073 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Customers].[USA].[CA] CA [Customers].[State Province] 2 131117 [Product].[Non-Consumable].[Periodicals].[Magazines] Magazines [Product].[Product Category] 3 5 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 6838 6,838 Standard 3407 3,407 Standard 2469 2,469 Standard 4301 4,301 Standard 3396 3,396 Standard 4186 4,186 Standard 7870 7,870 Standard 8357 8,357 Standard 11888 11,888 Standard 3317 3,317 Standard 900 900 Standard 882 882 Standard 708 708 Standard 819 819 Standard 804 804 Standard 8006 8,006 Standard 1710 1,710 Standard 5197 5,197 Standard 1812 1,812 Standard 12885 12,885 Standard 9433 9,433 Standard 2604 2,604 Standard 4132 4,132 Standard 5004 5,004 Standard 6192 6,192 Standard 2585 2,585 Standard 2580 2,580 Standard 3310 3,310 Standard 6984 6,984 Standard 1714 1,714 Standard 11767 11,767 Standard 886 886 Standard 4400 4,400 Standard 20739 20,739 Standard 1764 1,764 Standard 30545 30,545 Standard 6884 6,884 Standard 5262 5,262 Standard 841 841 Standard 810 810 Standard 969 969 Standard 5885 5,885 Standard 1776 1,776 Standard 1813 1,813 Standard 3556 3,556 Standard 3254 3,254 Standard 833 833 Standard 815 815 Standard 3441 3,441 Standard 6782 6,782 Standard 1682 1,682 Standard 4205 4,205 Standard 6803 6,803 Standard 2477 2,477 Standard 4294 4,294 Standard 1680 1,680 Standard 858 858 Standard 632 632 Standard 1078 1,078 Standard 817 817 Standard 1041 1,041 Standard 2013 2,013 Standard 1933 1,933 Standard 2877 2,877 Standard 862 862 Standard 253 253 Standard 270 270 Standard 192 192 Standard 215 215 Standard 173 173 Standard 2049 2,049 Standard 476 476 Standard 1261 1,261 Standard 464 464 Standard 3131 3,131 Standard 2428 2,428 Standard 610 610 Standard 1119 1,119 Standard 1143 1,143 Standard 1554 1,554 Standard 669 669 Standard 662 662 Standard 796 796 Standard 1751 1,751 Standard 469 469 Standard 3008 3,008 Standard 225 225 Standard 1064 1,064 Standard 5447 5,447 Standard 451 451 Standard 7789 7,789 Standard 1831 1,831 Standard 1352 1,352 Standard 248 248 Standard 215 215 Standard 231 231 Standard 1493 1,493 Standard 461 461 Standard 446 446 Standard 951 951 Standard 789 789 Standard 206 206 Standard 246 246 Standard 873 873 Standard 1858 1,858 Standard 467 467 Standard 1072 1,072 Standard 1747 1,747 Standard 650 650 Standard 1063 1,063 Standard 6838 6,838 Standard 3407 3,407 Standard 2469 2,469 Standard 4301 4,301 Standard 3396 3,396 Standard 4186 4,186 Standard 7870 7,870 Standard 8357 8,357 Standard 11888 11,888 Standard 3317 3,317 Standard 900 900 Standard 882 882 Standard 708 708 Standard 819 819 Standard 804 804 Standard 8006 8,006 Standard 1710 1,710 Standard 5197 5,197 Standard 1812 1,812 Standard 12885 12,885 Standard 9433 9,433 Standard 2604 2,604 Standard 4132 4,132 Standard 5004 5,004 Standard 6192 6,192 Standard 2585 2,585 Standard 2580 2,580 Standard 3310 3,310 Standard 6984 6,984 Standard 1714 1,714 Standard 11767 11,767 Standard 886 886 Standard 4400 4,400 Standard 20739 20,739 Standard 1764 1,764 Standard 30545 30,545 Standard 6884 6,884 Standard 5262 5,262 Standard 841 841 Standard 810 810 Standard 969 969 Standard 5885 5,885 Standard 1776 1,776 Standard 1813 1,813 Standard 3556 3,556 Standard 3254 3,254 Standard 833 833 Standard 815 815 Standard 3441 3,441 Standard 6782 6,782 Standard 1682 1,682 Standard 4205 4,205 Standard 6803 6,803 Standard 2477 2,477 Standard 4294 4,294 Standard 1936 1,936 Standard 923 923 Standard 766 766 Standard 1208 1,208 Standard 989 989 Standard 1280 1,280 Standard 2150 2,150 Standard 2456 2,456 Standard 3343 3,343 Standard 938 938 Standard 228 228 Standard 236 236 Standard 220 220 Standard 234 234 Standard 231 231 Standard 2209 2,209 Standard 432 432 Standard 1478 1,478 Standard 448 448 Standard 3534 3,534 Standard 2636 2,636 Standard 757 757 Standard 1116 1,116 Standard 1435 1,435 Standard 1772 1,772 Standard 726 726 Standard 645 645 Standard 940 940 Standard 1987 1,987 Standard 527 527 Standard 3184 3,184 Standard 220 220 Standard 1278 1,278 Standard 5906 5,906 Standard 441 441 Standard 8543 8,543 Standard 1958 1,958 Standard 1448 1,448 Standard 223 223 Standard 261 261 Standard 286 286 Standard 1666 1,666 Standard 434 434 Standard 477 477 Standard 993 993 Standard 817 817 Standard 273 273 Standard 266 266 Standard 965 965 Standard 1867 1,867 Standard 493 493 Standard 1220 1,220 Standard 1837 1,837 Standard 651 651 Standard 1261 1,261 Standard ]]> WITH MEMBER [Promotion Media].[COG_OQP_INT_m2] AS 'AGGREGATE(FILTER(DESCENDANTS([Store].CURRENTMEMBER,[Store].[Store State]), ([Measures].[Unit Sales], [Promotion Media].DEFAULTMEMBER)> 1), [Promotion Media].DEFAULTMEMBER)', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_USR_Unit Sales] AS ' IIF( [Store].CURRENTMEMBER.LEVEL.ORDINAL < 2, ([Promotion Media].[COG_OQP_INT_m2], [Measures].[Unit Sales]), [Measures].[COG_OQP_INT_m1])', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_m1] AS ' IIF( [Measures].[Unit Sales]> 1, [Measures].[Unit Sales], NULL)', SOLVE_ORDER = 2 SET [COG_OQP_INT_s1] AS ' CROSSJOIN( CROSSJOIN( CROSSJOIN( CROSSJOIN( CROSSJOIN( HIERARCHIZE( UNION( {[Store].[(All)].MEMBERS}, {[Store].[Store State].MEMBERS}, ALL)), {[Time].[Year].MEMBERS}), {[Product].[(All)].MEMBERS}), {[Yearly Income].[(All)].MEMBERS}), {[Customers].[Country].MEMBERS}), {[Gender].[Gender].MEMBERS})' SELECT CROSSJOIN( FILTER( [COG_OQP_INT_s1], NOT ISEMPTY( [Measures].[COG_OQP_USR_Unit Sales])), {[Measures].[COG_OQP_USR_Unit Sales]}) DIMENSION PROPERTIES PARENT_LEVEL, CHILDREN_CARDINALITY, PARENT_UNIQUE_NAME ON AXIS(0) FROM [Sales] CELL PROPERTIES VALUE, FORMAT_STRING, LANGUAGE ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Store].[All Stores] All Stores [Store].[(All)] 0 3 0 3 [Time].[1997] 1997 [Time].[Year] 0 4 0 4 [Product].[All Products] All Products [Product].[(All)] 0 3 0 3 [Yearly Income].[All Yearly Incomes] All Yearly Incomes [Yearly Income].[(All)] 0 8 0 8 [Customers].[USA] USA [Customers].[Country] 1 3 0 3 [Customers].[All Customers] [Gender].[F] F [Gender].[Gender] 1 0 0 0 [Gender].[All Gender] [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 0 0 [Store].[All Stores] All Stores [Store].[(All)] 0 3 0 3 [Time].[1997] 1997 [Time].[Year] 0 4 0 4 [Product].[All Products] All Products [Product].[(All)] 0 3 0 3 [Yearly Income].[All Yearly Incomes] All Yearly Incomes [Yearly Income].[(All)] 0 8 0 8 [Customers].[USA] USA [Customers].[Country] 1 131075 0 3 [Customers].[All Customers] [Gender].[M] M [Gender].[Gender] 1 131072 0 0 [Gender].[All Gender] [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 0 0 [Store].[USA].[CA] CA [Store].[Store State] 2 5 1 5 [Store].[USA] [Time].[1997] 1997 [Time].[Year] 0 4 0 4 [Product].[All Products] All Products [Product].[(All)] 0 3 0 3 [Yearly Income].[All Yearly Incomes] All Yearly Incomes [Yearly Income].[(All)] 0 8 0 8 [Customers].[USA] USA [Customers].[Country] 1 131075 0 3 [Customers].[All Customers] [Gender].[F] F [Gender].[Gender] 1 131072 0 0 [Gender].[All Gender] [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 0 0 [Store].[USA].[CA] CA [Store].[Store State] 2 131077 1 5 [Store].[USA] [Time].[1997] 1997 [Time].[Year] 0 4 0 4 [Product].[All Products] All Products [Product].[(All)] 0 3 0 3 [Yearly Income].[All Yearly Incomes] All Yearly Incomes [Yearly Income].[(All)] 0 8 0 8 [Customers].[USA] USA [Customers].[Country] 1 131075 0 3 [Customers].[All Customers] [Gender].[M] M [Gender].[Gender] 1 131072 0 0 [Gender].[All Gender] [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 0 0 [Store].[USA].[OR] OR [Store].[Store State] 2 131074 1 2 [Store].[USA] [Time].[1997] 1997 [Time].[Year] 0 4 0 4 [Product].[All Products] All Products [Product].[(All)] 0 3 0 3 [Yearly Income].[All Yearly Incomes] All Yearly Incomes [Yearly Income].[(All)] 0 8 0 8 [Customers].[USA] USA [Customers].[Country] 1 131075 0 3 [Customers].[All Customers] [Gender].[F] F [Gender].[Gender] 1 131072 0 0 [Gender].[All Gender] [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 0 0 [Store].[USA].[OR] OR [Store].[Store State] 2 131074 1 2 [Store].[USA] [Time].[1997] 1997 [Time].[Year] 0 4 0 4 [Product].[All Products] All Products [Product].[(All)] 0 3 0 3 [Yearly Income].[All Yearly Incomes] All Yearly Incomes [Yearly Income].[(All)] 0 8 0 8 [Customers].[USA] USA [Customers].[Country] 1 131075 0 3 [Customers].[All Customers] [Gender].[M] M [Gender].[Gender] 1 131072 0 0 [Gender].[All Gender] [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 0 0 [Store].[USA].[WA] WA [Store].[Store State] 2 131079 1 7 [Store].[USA] [Time].[1997] 1997 [Time].[Year] 0 4 0 4 [Product].[All Products] All Products [Product].[(All)] 0 3 0 3 [Yearly Income].[All Yearly Incomes] All Yearly Incomes [Yearly Income].[(All)] 0 8 0 8 [Customers].[USA] USA [Customers].[Country] 1 131075 0 3 [Customers].[All Customers] [Gender].[F] F [Gender].[Gender] 1 131072 0 0 [Gender].[All Gender] [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 0 0 [Store].[USA].[WA] WA [Store].[Store State] 2 131079 1 7 [Store].[USA] [Time].[1997] 1997 [Time].[Year] 0 4 0 4 [Product].[All Products] All Products [Product].[(All)] 0 3 0 3 [Yearly Income].[All Yearly Incomes] All Yearly Incomes [Yearly Income].[(All)] 0 8 0 8 [Customers].[USA] USA [Customers].[Country] 1 131075 0 3 [Customers].[All Customers] [Gender].[M] M [Gender].[Gender] 1 131072 0 0 [Gender].[All Gender] [Measures].[COG_OQP_USR_Unit Sales] COG_OQP_USR_Unit Sales [Measures].[MeasuresLevel] 0 0 0 0 131558 Standard 135215 Standard 36759 Standard 37989 Standard 33036 Standard 34623 Standard 61763 Standard 62603 Standard ]]> WITH MEMBER [Measures].[COG_OQP_INT_t2] AS '1', SOLVE_ORDER = 65535 MEMBER [Measures].[COG_OQP_INT_t1] AS '1', SOLVE_ORDER = 65535 SET [COG_OQP_INT_s1] AS 'CROSSJOIN({[Store].[Store State].MEMBERS}, {[Gender].[Gender].MEMBERS})' SELECT {{([Measures].[COG_OQP_INT_t1])}, {[Measures].[Customer Count]}, {([Measures].[COG_OQP_INT_t2])}, {[Measures].[Store Sales]}} DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(0), [COG_OQP_INT_s1] DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(1) FROM [Sales] CELL PROPERTIES VALUE, FORMAT_STRING ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Measures].[MeasuresLevel] 0 0 0 [Measures].[Customer Count] Customer Count [Measures].[MeasuresLevel] 0 0 0 [Measures].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Measures].[MeasuresLevel] 0 0 0 [Measures].[Store Sales] Store Sales [Measures].[MeasuresLevel] 0 0 0 [Store].[Canada].[BC] BC [Store].[Store State] 2 2 1 [Store].[Canada] [Gender].[F] F [Gender].[Gender] 1 0 0 [Gender].[All Gender] [Store].[Canada].[BC] BC [Store].[Store State] 2 131074 1 [Store].[Canada] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[Mexico].[DF] DF [Store].[Store State] 2 2 1 [Store].[Mexico] [Gender].[F] F [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[Mexico].[DF] DF [Store].[Store State] 2 131074 1 [Store].[Mexico] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[Mexico].[Guerrero] Guerrero [Store].[Store State] 2 131073 1 [Store].[Mexico] [Gender].[F] F [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[Mexico].[Guerrero] Guerrero [Store].[Store State] 2 131073 1 [Store].[Mexico] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[Mexico].[Jalisco] Jalisco [Store].[Store State] 2 131073 1 [Store].[Mexico] [Gender].[F] F [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[Mexico].[Jalisco] Jalisco [Store].[Store State] 2 131073 1 [Store].[Mexico] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[Mexico].[Veracruz] Veracruz [Store].[Store State] 2 131073 1 [Store].[Mexico] [Gender].[F] F [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[Mexico].[Veracruz] Veracruz [Store].[Store State] 2 131073 1 [Store].[Mexico] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[Mexico].[Yucatan] Yucatan [Store].[Store State] 2 131073 1 [Store].[Mexico] [Gender].[F] F [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[Mexico].[Yucatan] Yucatan [Store].[Store State] 2 131073 1 [Store].[Mexico] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[Mexico].[Zacatecas] Zacatecas [Store].[Store State] 2 131074 1 [Store].[Mexico] [Gender].[F] F [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[Mexico].[Zacatecas] Zacatecas [Store].[Store State] 2 131074 1 [Store].[Mexico] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[USA].[CA] CA [Store].[Store State] 2 5 1 [Store].[USA] [Gender].[F] F [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[USA].[CA] CA [Store].[Store State] 2 131077 1 [Store].[USA] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[USA].[OR] OR [Store].[Store State] 2 131074 1 [Store].[USA] [Gender].[F] F [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[USA].[OR] OR [Store].[Store State] 2 131074 1 [Store].[USA] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[USA].[WA] WA [Store].[Store State] 2 131079 1 [Store].[USA] [Gender].[F] F [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Store].[USA].[WA] WA [Store].[Store State] 2 131079 1 [Store].[USA] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1327 #,### 1 Standard 79050.79 #,###.00 1 Standard 1389 #,### 1 Standard 80117.05 #,###.00 1 Standard 501 #,### 1 Standard 69825.44 #,###.00 1 Standard 536 #,### 1 Standard 72451.63 #,###.00 1 Standard 927 #,### 1 Standard 131349.98 #,###.00 1 Standard 901 #,### 1 Standard 132443.24 #,###.00 ]]> WITH MEMBER [Measures].[COG_OQP_INT_t3] AS '1', SOLVE_ORDER = 65535 MEMBER [Measures].[COG_OQP_INT_t2] AS '1', SOLVE_ORDER = -65 MEMBER [Measures].[COG_OQP_INT_t1] AS '1', SOLVE_ORDER = 100 SELECT {{([Measures].[COG_OQP_INT_t1])}, {[Measures].[Store Cost]} , {([Measures].[COG_OQP_INT_t2])}, {[Measures].[Store Sales]}, {([Measures].[COG_OQP_INT_t3])}, {[Measures].[Sales Count]}} DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(0), {[Customers].[(All)].MEMBERS} DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(1) FROM [Sales] CELL PROPERTIES VALUE, FORMAT_STRING ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Measures].[MeasuresLevel] 0 0 0 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 0 0 [Measures].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Measures].[MeasuresLevel] 0 0 0 [Measures].[Store Sales] Store Sales [Measures].[MeasuresLevel] 0 0 0 [Measures].[COG_OQP_INT_t3] COG_OQP_INT_t3 [Measures].[MeasuresLevel] 0 0 0 [Measures].[Sales Count] Sales Count [Measures].[MeasuresLevel] 0 0 0 [Customers].[All Customers] All Customers [Customers].[(All)] 0 3 0 1 Standard 225627.2336 #,###.00 1 Standard 565238.13 #,###.00 1 Standard 86837 #,### ]]> WITH MEMBER [Measures].[COG_OQP_INT_t2] AS '1', SOLVE_ORDER = 65535 MEMBER [Measures].[COG_OQP_INT_t1] AS '1', SOLVE_ORDER = 65535 SELECT {{([Measures].[COG_OQP_INT_t1])}, {[Measures].[Store Sales]}, {([Measures].[COG_OQP_INT_t2])}, {[Measures].[Store Cost]}} DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(0), {[Store].[Store State].MEMBERS} DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(1) FROM [Sales] CELL PROPERTIES VALUE, FORMAT_STRING ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[COG_OQP_INT_t1] COG_OQP_INT_t1 [Measures].[MeasuresLevel] 0 0 0 [Measures].[Store Sales] Store Sales [Measures].[MeasuresLevel] 0 0 0 [Measures].[COG_OQP_INT_t2] COG_OQP_INT_t2 [Measures].[MeasuresLevel] 0 0 0 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 0 0 [Store].[Canada].[BC] BC [Store].[Store State] 2 2 1 [Store].[Canada] [Store].[Mexico].[DF] DF [Store].[Store State] 2 2 1 [Store].[Mexico] [Store].[Mexico].[Guerrero] Guerrero [Store].[Store State] 2 131073 1 [Store].[Mexico] [Store].[Mexico].[Jalisco] Jalisco [Store].[Store State] 2 131073 1 [Store].[Mexico] [Store].[Mexico].[Veracruz] Veracruz [Store].[Store State] 2 131073 1 [Store].[Mexico] [Store].[Mexico].[Yucatan] Yucatan [Store].[Store State] 2 131073 1 [Store].[Mexico] [Store].[Mexico].[Zacatecas] Zacatecas [Store].[Store State] 2 131074 1 [Store].[Mexico] [Store].[USA].[CA] CA [Store].[Store State] 2 5 1 [Store].[USA] [Store].[USA].[OR] OR [Store].[Store State] 2 131074 1 [Store].[USA] [Store].[USA].[WA] WA [Store].[Store State] 2 131079 1 [Store].[USA] 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 1 Standard 159167.84 #,###.00 1 Standard 63530.4251 #,###.00 1 Standard 142277.07 #,###.00 1 Standard 56772.5006 #,###.00 1 Standard 263793.22 #,###.00 1 Standard 105324.3079 #,###.00 ]]> SELECT {[Measures].[Customer Count]} DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(0), {[Gender].[Gender].MEMBERS} DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(1) FROM [Sales] CELL PROPERTIES VALUE, FORMAT_STRING ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[Customer Count] Customer Count [Measures].[MeasuresLevel] 0 0 0 [Gender].[F] F [Gender].[Gender] 1 0 0 [Gender].[All Gender] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] 2755 #,### 2826 #,### ]]> WITH SET [COG_OQP_INT_s1] AS 'CROSSJOIN({[Gender].[Gender].MEMBERS}, {[Marital Status].[Marital Status].MEMBERS})' SELECT {[Measures].[Customer Count]} DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(0), [COG_OQP_INT_s1] DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(1) FROM [Sales] CELL PROPERTIES VALUE, FORMAT_STRING ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[Customer Count] Customer Count [Measures].[MeasuresLevel] 0 0 0 [Gender].[F] F [Gender].[Gender] 1 0 0 [Gender].[All Gender] [Marital Status].[M] M [Marital Status].[Marital Status] 1 0 0 [Marital Status].[All Marital Status] [Gender].[F] F [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Marital Status].[S] S [Marital Status].[Marital Status] 1 131072 0 [Marital Status].[All Marital Status] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 0 [Marital Status].[All Marital Status] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Marital Status].[S] S [Marital Status].[Marital Status] 1 131072 0 [Marital Status].[All Marital Status] 1365 #,### 1390 #,### 1376 #,### 1450 #,### ]]> WITH SET [COG_OQP_INT_s1] AS 'CROSSJOIN({[Gender].[Gender].MEMBERS}, {[Marital Status].[Marital Status].MEMBERS})' SELECT {[Measures].[Customer Count]} DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(0), [COG_OQP_INT_s1] DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(1), {[Time].[1997]} DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(2) FROM [Sales] CELL PROPERTIES VALUE, FORMAT_STRING ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[Customer Count] Customer Count [Measures].[MeasuresLevel] 0 0 0 [Gender].[F] F [Gender].[Gender] 1 0 0 [Gender].[All Gender] [Marital Status].[M] M [Marital Status].[Marital Status] 1 0 0 [Marital Status].[All Marital Status] [Gender].[F] F [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Marital Status].[S] S [Marital Status].[Marital Status] 1 131072 0 [Marital Status].[All Marital Status] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 0 [Marital Status].[All Marital Status] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Marital Status].[S] S [Marital Status].[Marital Status] 1 131072 0 [Marital Status].[All Marital Status] [Time].[1997] 1997 [Time].[Year] 0 4 0 1365 #,### 1390 #,### 1376 #,### 1450 #,### ]]> SELECT {[Measures].[Customer Count]} DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(0), GENERATE({[Gender].[Gender].MEMBERS}, CROSSJOIN(HEAD({([Gender].CURRENTMEMBER)}, IIF(COUNT(ORDER({[Marital Status].[Marital Status].MEMBERS}, ([Measures].[Customer Count]), BDESC), INCLUDEEMPTY) > 0, 1, 0)), ORDER({[Marital Status].[Marital Status].MEMBERS}, ([Measures].[Customer Count]), BDESC)), ALL) DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(1) FROM [Sales] CELL PROPERTIES VALUE, FORMAT_STRING ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[Customer Count] Customer Count [Measures].[MeasuresLevel] 0 0 0 [Gender].[F] F [Gender].[Gender] 1 0 0 [Gender].[All Gender] [Marital Status].[S] S [Marital Status].[Marital Status] 1 0 0 [Marital Status].[All Marital Status] [Gender].[F] F [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 0 [Marital Status].[All Marital Status] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Marital Status].[S] S [Marital Status].[Marital Status] 1 131072 0 [Marital Status].[All Marital Status] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Marital Status].[M] M [Marital Status].[Marital Status] 1 131072 0 [Marital Status].[All Marital Status] 1390 #,### 1365 #,### 1450 #,### 1376 #,### ]]> WITH MEMBER [Measures].[COG_OQP_USR_Customer Count] AS '[Measures].[COG_OQP_INT_m1]', SOLVE_ORDER = 2 MEMBER [Measures].[COG_OQP_INT_m1] AS 'IIF([Measures].[Customer Count] > 1380, [Measures].[Customer Count], NULL)', SOLVE_ORDER = 2 SELECT {[Measures].[COG_OQP_USR_Customer Count]} DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(0), GENERATE({[Gender].[Gender].MEMBERS}, CROSSJOIN(HEAD({([Gender].CURRENTMEMBER)}, IIF(COUNT(FILTER({[Marital Status].[Marital Status].MEMBERS}, [Measures].[Customer Count] > 1380), INCLUDEEMPTY) > 0, 1, 0)), FILTER({[Marital Status].[Marital Status].MEMBERS}, [Measures].[Customer Count] > 1380)), ALL) DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(1) FROM [Sales] CELL PROPERTIES VALUE, FORMAT_STRING ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[COG_OQP_USR_Customer Count] COG_OQP_USR_Customer Count [Measures].[MeasuresLevel] 0 0 0 [Gender].[F] F [Gender].[Gender] 1 0 0 [Gender].[All Gender] [Marital Status].[S] S [Marital Status].[Marital Status] 1 0 0 [Marital Status].[All Marital Status] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Marital Status].[S] S [Marital Status].[Marital Status] 1 131072 0 [Marital Status].[All Marital Status] 1390 #,### 1450 #,### ]]> WITH MEMBER [Marital Status].[COG_OQP_USR_Total(Marital Status)] AS 'SUM({[Marital Status].DEFAULTMEMBER})', SOLVE_ORDER = 8 SET [COG_OQP_INT_s2] AS 'CROSSJOIN({[Gender].[Gender].MEMBERS}, {{[Marital Status].[Marital Status].MEMBERS}, {([Marital Status].[COG_OQP_USR_Total(Marital Status)])}})' SET [COG_OQP_INT_s1] AS 'CROSSJOIN({[Gender].[Gender].MEMBERS}, {[Marital Status].[Marital Status].MEMBERS})' SELECT {[Measures].[Unit Sales]} DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(0), {[COG_OQP_INT_s2], HEAD({([Gender].DEFAULTMEMBER, [Marital Status].DEFAULTMEMBER)}, IIF(COUNT([COG_OQP_INT_s1], INCLUDEEMPTY) > 0, 1, 0))} DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(1) FROM [Sales] CELL PROPERTIES VALUE, FORMAT_STRING ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 0 [Gender].[F] F [Gender].[Gender] 1 0 0 [Gender].[All Gender] [Marital Status].[M] M [Marital Status].[Marital Status] 1 0 0 [Marital Status].[All Marital Status] [Gender].[F] F [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Marital Status].[S] S [Marital Status].[Marital Status] 1 131072 0 [Marital Status].[All Marital Status] [Gender].[F] F [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Marital Status].[COG_OQP_USR_Total(Marital Status)] COG_OQP_USR_Total(Marital Status) [Marital Status].[(All)] 0 0 0 [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Marital Status].[M] M [Marital Status].[Marital Status] 1 0 0 [Marital Status].[All Marital Status] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Marital Status].[S] S [Marital Status].[Marital Status] 1 131072 0 [Marital Status].[All Marital Status] [Gender].[M] M [Gender].[Gender] 1 131072 0 [Gender].[All Gender] [Marital Status].[COG_OQP_USR_Total(Marital Status)] COG_OQP_USR_Total(Marital Status) [Marital Status].[(All)] 0 0 0 [Gender].[All Gender] All Gender [Gender].[(All)] 0 2 0 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 0 65336 Standard 66222 Standard 131558 Standard 66460 Standard 68755 Standard 135215 Standard 266773 Standard ]]> mondrian-3.4.1/testsrc/main/mondrian/xmla/XmlaBaseTestCase.java0000644000175000017500000006477211735330606024424 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2012 Pentaho and others // All Rights Reserved. */ package mondrian.xmla; import mondrian.olap.*; import mondrian.rolap.RolapConnectionProperties; import mondrian.test.*; import mondrian.tui.*; import mondrian.util.LockBox; import junit.framework.AssertionFailedError; import org.olap4j.metadata.XmlaConstants; import org.custommonkey.xmlunit.XMLAssert; import org.w3c.dom.*; import org.xml.sax.SAXException; import java.io.*; import java.util.*; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Extends FoodMartTestCase, adding support for testing XMLA specific * functionality, for example LAST_SCHEMA_UPDATE * * @author mkambol */ public abstract class XmlaBaseTestCase extends FoodMartTestCase { protected static final String LAST_SCHEMA_UPDATE_DATE = "xxxx-xx-xxTxx:xx:xx"; private static final String LAST_SCHEMA_UPDATE_NODE_NAME = "LAST_SCHEMA_UPDATE"; protected SortedMap catalogNameUrls = null; private static int sessionIdCounter = 1000; private static Map sessionIdMap = new HashMap(); // session id property public static final String SESSION_ID_PROP = "session.id"; // request.type public static final String REQUEST_TYPE_PROP = "request.type";// data.source.info public static final String DATA_SOURCE_INFO_PROP = "data.source.info"; public static final String DATA_SOURCE_INFO = "FoodMart";// catalog public static final String CATALOG_PROP = "catalog"; public static final String CATALOG_NAME_PROP = "catalog.name"; public static final String CATALOG = "FoodMart";// cube public static final String CUBE_NAME_PROP = "cube.name"; public static final String SALES_CUBE = "Sales";// format public static final String FORMAT_PROP = "format"; public static final String FORMAT_MULTI_DIMENSIONAL = "Multidimensional"; public static final String ROLE_PROP = "Role"; public static final String LOCALE_PROP = "locale"; protected static final boolean DEBUG = false; /** * Cache servlet instances between test invocations. Prevents creation * of many spurious MondrianServer instances. */ private final HashMap, Servlet> SERVLET_CACHE = new HashMap, Servlet>(); /** * Cache servlet instances between test invocations. Prevents creation * of many spurious MondrianServer instances. */ private final HashMap, MondrianServer> SERVER_CACHE = new HashMap, MondrianServer>(); protected void tearDown() throws Exception { super.tearDown(); for (MondrianServer server : SERVER_CACHE.values()) { server.shutdown(); } SERVER_CACHE.clear(); for (Servlet servlet : SERVLET_CACHE.values()) { servlet.destroy(); } SERVLET_CACHE.clear(); } protected String generateExpectedString(Properties props) throws Exception { String expectedStr = fileToString("response"); if (props != null) { // YES, duplicate the above String sessionId = getSessionId(Action.QUERY); if (sessionId != null) { props.put(SESSION_ID_PROP, sessionId); } expectedStr = Util.replaceProperties( expectedStr, Util.toMap(props)); } return expectedStr; } protected String generateRequestString(Properties props) throws Exception { String requestText = fileToString("request"); if (props != null) { String sessionId = getSessionId(Action.QUERY); if (sessionId != null) { props.put(SESSION_ID_PROP, sessionId); } requestText = Util.replaceProperties( requestText, Util.toMap(props)); } if (DEBUG) { System.out.println("requestText=" + requestText); } return requestText; } protected void validate( byte[] bytes, Document expectedDoc, TestContext testContext, boolean replace) throws Exception { if (XmlUtil.supportsValidation()) { if (XmlaSupport.validateSoapXmlaUsingXpath(bytes)) { if (DEBUG) { System.out.println("XML Data is Valid"); } } } Document gotDoc = XmlUtil.parse(bytes); gotDoc = replaceLastSchemaUpdateDate(gotDoc); String gotStr = XmlUtil.toString(gotDoc, true); gotStr = Util.maskVersion(gotStr); gotStr = testContext.upgradeActual(gotStr); if (expectedDoc == null) { if (replace) { getDiffRepos().amend("${response}", gotStr); } return; } expectedDoc = replaceLastSchemaUpdateDate(expectedDoc); String expectedStr = XmlUtil.toString(expectedDoc, true); try { XMLAssert.assertXMLEqual(expectedStr, gotStr); } catch (AssertionFailedError e) { // Let DiffRepository do the comparison. It will output // a textual difference, and will update the logfile, // XmlaBasicTest.log.xml. If you agree with the change, // copy this file to XmlaBasicTest.ref.xml. if (replace) { gotStr = gotStr.replaceAll( " SessionId=\"[^\"]*\" ", " SessionId=\"\\${session.id}\" "); getDiffRepos().assertEquals( "response", "${response}", gotStr); } else { throw e; } } } public void doTest(Properties props) throws Exception { String requestText = generateRequestString(props); Document reqDoc = XmlUtil.parseString(requestText); Servlet servlet = getServlet(getTestContext()); byte[] bytes = XmlaSupport.processSoapXmla(reqDoc, servlet); String expectedStr = generateExpectedString(props); Document expectedDoc = XmlUtil.parseString(expectedStr); validate(bytes, expectedDoc, TestContext.instance(), true); } protected void doTest( MockHttpServletRequest req, Properties props) throws Exception { String requestText = generateRequestString(props); MockHttpServletResponse res = new MockHttpServletResponse(); res.setCharacterEncoding("UTF-8"); Servlet servlet = getServlet(getTestContext()); servlet.service(req, res); int statusCode = res.getStatusCode(); if (statusCode == HttpServletResponse.SC_OK) { byte[] bytes = res.toByteArray(); String expectedStr = generateExpectedString(props); Document expectedDoc = XmlUtil.parseString(expectedStr); validate(bytes, expectedDoc, TestContext.instance(), true); } else if (statusCode == HttpServletResponse.SC_CONTINUE) { // remove the Expect header from request and try again if (DEBUG) { System.out.println("Got CONTINUE"); } req.clearHeader(XmlaRequestCallback.EXPECT); req.setBodyContent(requestText); servlet.service(req, res); statusCode = res.getStatusCode(); if (statusCode == HttpServletResponse.SC_OK) { byte[] bytes = res.toByteArray(); String expectedStr = generateExpectedString(props); Document expectedDoc = XmlUtil.parseString(expectedStr); validate(bytes, expectedDoc, TestContext.instance(), true); } else { fail("Bad status code: " + statusCode); } } else { fail("Bad status code: " + statusCode); } } protected void helperTestExpect(boolean doSessionId) { if (doSessionId) { Util.discard(getSessionId(Action.CREATE)); } MockHttpServletRequest req = new MockHttpServletRequest(); req.setMethod("POST"); req.setContentType("text/xml"); req.setHeader( XmlaRequestCallback.EXPECT, XmlaRequestCallback.EXPECT_100_CONTINUE); Properties props = new Properties(); try { doTest(req, props); } catch (Exception e) { throw new RuntimeException(e); } } protected void helperTest(boolean doSessionId) { if (doSessionId) { getSessionId(Action.CREATE); } Properties props = new Properties(); try { doTest(props); } catch (Exception e) { throw new RuntimeException(e); } } static class CallBack implements XmlaRequestCallback { public CallBack() { } public void init(ServletConfig servletConfig) throws ServletException { } public boolean processHttpHeader( HttpServletRequest request, HttpServletResponse response, Map context) throws Exception { return true; } public void preAction( HttpServletRequest request, Element[] requestSoapParts, Map context) throws Exception { } public String generateSessionId(Map context) { return null; } public void postAction( HttpServletRequest request, HttpServletResponse response, byte[][] responseSoapParts, Map context) throws Exception { } } public XmlaBaseTestCase() { } public XmlaBaseTestCase(String name) { super(name); } protected abstract DiffRepository getDiffRepos(); protected String fileToString(String filename) throws Exception { String var = "${" + filename + "}"; String s = getDiffRepos().expand(null, var); if (s.startsWith("$")) { getDiffRepos().amend(var, "\n\n"); } // Give derived class a chance to change the content. s = filter(getDiffRepos().getCurrentTestCaseName(true), filename, s); return s; } protected Document replaceLastSchemaUpdateDate(Document doc) { NodeList elements = doc.getElementsByTagName(LAST_SCHEMA_UPDATE_NODE_NAME); for (int i = 0; i < elements.getLength(); i++) { Node node = elements.item(i); node.getFirstChild().setNodeValue( LAST_SCHEMA_UPDATE_DATE); } return doc; } private String ignoreLastUpdateDate(String document) { return document.replaceAll( "\"LAST_SCHEMA_UPDATE\": \"....-..-..T..:..:..\"", "\"LAST_SCHEMA_UPDATE\": \"" + LAST_SCHEMA_UPDATE_DATE + "\""); } protected Map getCatalogNameUrls(TestContext testContext) { if (catalogNameUrls == null) { catalogNameUrls = new TreeMap(); String connectString = testContext.getConnectString(); Util.PropertyList connectProperties = Util.parseConnectString(connectString); String catalog = connectProperties.get( RolapConnectionProperties.Catalog.name()); catalogNameUrls.put("FoodMart", catalog); } return catalogNameUrls; } protected Servlet getServlet(TestContext testContext) throws IOException, ServletException, SAXException { getSessionId(Action.CLEAR); String connectString = testContext.getConnectString(); Map catalogNameUrls = getCatalogNameUrls(testContext); return XmlaSupport.makeServlet( connectString, catalogNameUrls, getServletCallbackClass().getName(), SERVLET_CACHE); } protected abstract Class getServletCallbackClass(); protected Properties getDefaultRequestProperties(String requestType) { Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CATALOG_NAME_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, SALES_CUBE); props.setProperty(FORMAT_PROP, FORMAT_MULTI_DIMENSIONAL); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); return props; } protected Document fileToDocument(String filename) throws IOException, SAXException { final String var = "${" + filename + "}"; String s = getDiffRepos().expand(null, var); if (s.equals(filename)) { s = ""; getDiffRepos().amend(var, s); } // Give derived class a chance to change the content. s = filter(getDiffRepos().getCurrentTestCaseName(true), filename, s); return XmlUtil.parse(new ByteArrayInputStream(s.getBytes())); } /** * Filters the content of a test resource. The default implementation * returns the content unchanged, but a derived class might override this * method to change the content. * * @param testCaseName Name of current test case, e.g. "testFoo" * @param filename Name of requested content, e.g. "${request}" * @param content Content * @return Modified content */ protected String filter( String testCaseName, String filename, String content) { Util.discard(testCaseName); // might be used by derived class Util.discard(filename); // might be used by derived class return content; } /** * Executes an XMLA request, reading the text of the request and the * response from attributes in {@link #getDiffRepos()}. * * @param requestType Request type: "DISCOVER_DATASOURCES", "EXECUTE", etc. * @param props Properties for request * @param testContext Test context * @throws Exception on error */ public void doTest( String requestType, Properties props, TestContext testContext) throws Exception { doTest(requestType, props, testContext, null); } public void doTest( String requestType, Properties props, TestContext testContext, Role role) throws Exception { String requestText = fileToString("request"); requestText = testContext.upgradeQuery(requestText); doTestInline( requestType, requestText, "response", props, testContext, role); } public void doTestInline( String requestType, String requestText, String respFileName, Properties props, TestContext testContext) throws Exception { doTestInline( requestType, requestText, respFileName, props, testContext, null); } public void doTestInline( String requestType, String requestText, String respFileName, Properties props, TestContext testContext, Role role) throws Exception { String connectString = testContext.getConnectString(); Map catalogNameUrls = getCatalogNameUrls(testContext); boolean xml = !requestText.contains("application/json"); if (!xml) { String responseStr = (respFileName != null) ? fileToString(respFileName) : null; doTestsJson( requestText, props, testContext, connectString, catalogNameUrls, responseStr, XmlaConstants.Content.Data, role); return; } final Document responseDoc = (respFileName != null) ? fileToDocument(respFileName) : null; Document expectedDoc; // Test 'schemadata' first, so that if it fails, we will be able to // amend the ref file with the fullest XML response. final String ns = "cxmla"; expectedDoc = (responseDoc != null) ? XmlaSupport.transformSoapXmla( responseDoc, new String[][] {{"content", "schemadata"}}, ns) : null; doTests( requestText, props, testContext, connectString, catalogNameUrls, expectedDoc, XmlaConstants.Content.SchemaData, role, true); if (requestType.equals("EXECUTE")) { return; } expectedDoc = (responseDoc != null) ? XmlaSupport.transformSoapXmla( responseDoc, new String[][] {{"content", "none"}}, ns) : null; doTests( requestText, props, testContext, connectString, catalogNameUrls, expectedDoc, XmlaConstants.Content.None, role, false); expectedDoc = (responseDoc != null) ? XmlaSupport.transformSoapXmla( responseDoc, new String[][] {{"content", "data"}}, ns) : null; doTests( requestText, props, testContext, connectString, catalogNameUrls, expectedDoc, XmlaConstants.Content.Data, role, false); expectedDoc = (responseDoc != null) ? XmlaSupport.transformSoapXmla( responseDoc, new String[][] {{"content", "schema"}}, ns) : null; doTests( requestText, props, testContext, connectString, catalogNameUrls, expectedDoc, XmlaConstants.Content.Schema, role, false); } /** * Executes a SOAP request. * * @param soapRequestText SOAP request * @param props Name/value pairs to substitute in the request * @param testContext Test context * @param connectString Connect string * @param catalogNameUrls Map from catalog names to URL * @param expectedDoc Expected SOAP output * @param content Content type * @param role Role in which to execute query, or null * @param replace Whether to generate a replacement reference log into * TestName.log.xml if there is an exception. If you are running the same * request with different content types and the same reference log, you * should pass {@code true} for the content type that has the most * information (generally * {@link org.olap4j.metadata.XmlaConstants.Content#SchemaData}) * @throws Exception on error */ protected void doTests( String soapRequestText, Properties props, TestContext testContext, String connectString, Map catalogNameUrls, Document expectedDoc, XmlaConstants.Content content, Role role, boolean replace) throws Exception { if (content != null) { props.setProperty(XmlaBasicTest.CONTENT_PROP, content.name()); } // Even though it is not used, it is important that entry is in scope // until after request has returned. Prevents role's lock box entry from // being garbage collected. LockBox.Entry entry = null; if (role != null) { final MondrianServer mondrianServer = MondrianServer.forConnection(testContext.getConnection()); entry = mondrianServer.getLockBox().register(role); connectString += "; Role='" + entry.getMoniker() + "'"; props.setProperty(XmlaBaseTestCase.ROLE_PROP, entry.getMoniker()); } soapRequestText = Util.replaceProperties( soapRequestText, Util.toMap(props)); Document soapReqDoc = XmlUtil.parseString(soapRequestText); Document xmlaReqDoc = XmlaSupport.extractBodyFromSoap(soapReqDoc); // do XMLA byte[] bytes = XmlaSupport.processXmla( xmlaReqDoc, connectString, catalogNameUrls, role, SERVER_CACHE); if (XmlUtil.supportsValidation()) { if (XmlaSupport.validateXmlaUsingXpath(bytes)) { if (DEBUG) { System.out.println( "XmlaBaseTestCase.doTests: XML Data is Valid"); } } } // do SOAP-XMLA String callBackClassName = CallBack.class.getName(); bytes = XmlaSupport.processSoapXmla( soapReqDoc, connectString, catalogNameUrls, callBackClassName, role, SERVLET_CACHE); if (DEBUG) { System.out.println( "XmlaBaseTestCase.doTests: soap response=" + new String(bytes)); } validate(bytes, expectedDoc, testContext, replace); Util.discard(entry); } protected void doTestsJson( String soapRequestText, Properties props, TestContext testContext, String connectString, Map catalogNameUrls, String expectedStr, XmlaConstants.Content content, Role role) throws Exception { if (content != null) { props.setProperty(XmlaBasicTest.CONTENT_PROP, content.name()); } if (role != null) { final MondrianServer mondrianServer = MondrianServer.forConnection(testContext.getConnection()); final LockBox.Entry entry = mondrianServer.getLockBox().register(role); props.setProperty(XmlaBaseTestCase.ROLE_PROP, entry.getMoniker()); } soapRequestText = Util.replaceProperties( soapRequestText, Util.toMap(props)); Document soapReqDoc = XmlUtil.parseString(soapRequestText); Document xmlaReqDoc = XmlaSupport.extractBodyFromSoap(soapReqDoc); // do XMLA byte[] bytes = XmlaSupport.processXmla( xmlaReqDoc, connectString, catalogNameUrls, role, SERVER_CACHE); if (XmlUtil.supportsValidation()) { if (XmlaSupport.validateXmlaUsingXpath(bytes)) { if (DEBUG) { System.out.println( "XmlaBaseTestCase.doTests: XML Data is Valid"); } } } // do SOAP-XMLA String callBackClassName = CallBack.class.getName(); bytes = XmlaSupport.processSoapXmla( soapReqDoc, connectString, catalogNameUrls, callBackClassName, role, SERVLET_CACHE); if (DEBUG) { System.out.println( "XmlaBaseTestCase.doTests: soap response=" + new String(bytes)); } if (XmlUtil.supportsValidation()) { if (XmlaSupport.validateSoapXmlaUsingXpath(bytes)) { if (DEBUG) { System.out.println( "XmlaBaseTestCase.doTests: XML Data is Valid"); } } } String gotStr = new String(bytes); gotStr = ignoreLastUpdateDate(gotStr); gotStr = Util.maskVersion(gotStr); gotStr = testContext.upgradeActual(gotStr); if (expectedStr != null) { // Let DiffRepository do the comparison. It will output // a textual difference, and will update the logfile, // XmlaBasicTest.log.xml. If you agree with the change, // copy this file to XmlaBasicTest.ref.xml. getDiffRepos().assertEquals( "response", "${response}", gotStr); } } enum Action { CREATE, QUERY, CLEAR } /** * Creates, retrieves or clears the session id for this test. * * @param action Action to perform * @return Session id for create, query; null for clear */ protected abstract String getSessionId(Action action); protected static String getSessionId(String name, Action action) { switch (action) { case CLEAR: sessionIdMap.put(name, null); return null; case QUERY: return sessionIdMap.get(name); case CREATE: String sessionId = sessionIdMap.get(name); if (sessionId == null) { int id = sessionIdCounter++; StringBuilder buf = new StringBuilder(); buf.append(name); buf.append("-"); buf.append(id); buf.append("-foo"); sessionId = buf.toString(); sessionIdMap.put(name, sessionId); } return sessionId; default: throw new UnsupportedOperationException(); } } protected static abstract class XmlaRequestCallbackImpl implements XmlaRequestCallback { private static final String MY_SESSION_ID = "my_session_id"; private final String name; protected XmlaRequestCallbackImpl(String name) { this.name = name; } public void init(ServletConfig servletConfig) throws ServletException { } public boolean processHttpHeader( HttpServletRequest request, HttpServletResponse response, Map context) throws Exception { String expect = request.getHeader(XmlaRequestCallback.EXPECT); if ((expect != null) && expect.equalsIgnoreCase( XmlaRequestCallback.EXPECT_100_CONTINUE)) { Helper.generatedExpectResponse( request, response, context); return false; } else { return true; } } public void preAction( HttpServletRequest request, Element[] requestSoapParts, Map context) throws Exception { } private void setSessionId(Map context) { context.put( MY_SESSION_ID, getSessionId(name, Action.CREATE)); } public String generateSessionId(Map context) { setSessionId(context); return (String) context.get(MY_SESSION_ID); } public void postAction( HttpServletRequest request, HttpServletResponse response, byte[][] responseSoapParts, Map context) throws Exception { } } } // End XmlaBaseTestCase.java mondrian-3.4.1/testsrc/main/mondrian/xmla/XmlaBasicTest.java0000644000175000017500000011121611735330606023761 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.xmla; import mondrian.olap.*; import mondrian.olap4j.MondrianOlap4jDriver; import mondrian.spi.Dialect; import mondrian.test.DiffRepository; import mondrian.test.TestContext; import mondrian.tui.XmlUtil; import mondrian.tui.XmlaSupport; import org.olap4j.metadata.XmlaConstants; import org.w3c.dom.Document; import java.util.*; /** * Test XML/A functionality. * * @author Richard M. Emberson */ public class XmlaBasicTest extends XmlaBaseTestCase { public static final String FORMAT_TABLULAR = "Tabular"; // unique name public static final String UNIQUE_NAME_ELEMENT = "unique.name.element"; // dimension unique name public static final String UNIQUE_NAME_PROP = "unique.name"; public static final String RESTRICTION_NAME_PROP = "restriction.name"; public static final String RESTRICTION_VALUE_PROP = "restriction.value"; // content public static final String CONTENT_PROP = "content"; public XmlaBasicTest() { } public XmlaBasicTest(String name) { super(name); } @Override protected void setUp() throws Exception { super.setUp(); Class.forName(MondrianOlap4jDriver.class.getName()); } protected DiffRepository getDiffRepos() { return DiffRepository.lookup(XmlaBasicTest.class); } protected Class getServletCallbackClass() { return null; } protected String extractSoapResponse( Document responseDoc, XmlaConstants.Content content) { Document partialDoc = null; switch (content) { case None: // return soap and no content break; case Schema: // return soap plus scheam content break; case Data: // return soap plus data content break; case SchemaData: // return everything partialDoc = responseDoc; break; } String responseText = XmlUtil.toString(responseDoc, false); return responseText; } ///////////////////////////////////////////////////////////////////////// // DISCOVER ///////////////////////////////////////////////////////////////////////// public void testDDatasource() throws Exception { String requestType = "DISCOVER_DATASOURCES"; doTestRT(requestType, TestContext.instance()); } public void testDEnumerators() throws Exception { String requestType = "DISCOVER_ENUMERATORS"; doTestRT(requestType, TestContext.instance()); } public void testDKeywords() throws Exception { String requestType = "DISCOVER_KEYWORDS"; doTestRT(requestType, TestContext.instance()); } public void testDLiterals() throws Exception { String requestType = "DISCOVER_LITERALS"; doTestRT(requestType, TestContext.instance()); } public void testDProperties() throws Exception { String requestType = "DISCOVER_PROPERTIES"; doTestRT(requestType, TestContext.instance()); } public void testDSchemaRowsets() throws Exception { String requestType = "DISCOVER_SCHEMA_ROWSETS"; doTestRT(requestType, TestContext.instance()); } ///////////////////////////////////////////////////////////////////////// // DBSCHEMA ///////////////////////////////////////////////////////////////////////// public void testDBCatalogs() throws Exception { String requestType = "DBSCHEMA_CATALOGS"; doTestRT(requestType, TestContext.instance()); } public void testDBSchemata() throws Exception { String requestType = "DBSCHEMA_SCHEMATA"; doTestRT(requestType, TestContext.instance()); } // passes 2/25 - I think that this is good but not sure public void _testDBColumns() throws Exception { String requestType = "DBSCHEMA_COLUMNS"; doTestRT(requestType, TestContext.instance()); } // passes 2/25 - I think that this is good but not sure public void _testDBProviderTypes() throws Exception { String requestType = "DBSCHEMA_PROVIDER_TYPES"; doTestRT(requestType, TestContext.instance()); } // passes 2/25 - I think that this is good but not sure // Should this even be here public void _testDBTablesInfo() throws Exception { String requestType = "DBSCHEMA_TABLES_INFO"; doTestRT(requestType, TestContext.instance()); } // passes 2/25 - I think that this is good but not sure public void testDBTables() throws Exception { String requestType = "DBSCHEMA_TABLES"; doTestRT(requestType, TestContext.instance()); } ///////////////////////////////////////////////////////////////////////// // MDSCHEMA ///////////////////////////////////////////////////////////////////////// public void testMDActions() throws Exception { String requestType = "MDSCHEMA_ACTIONS"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); doTest(requestType, props, TestContext.instance()); } public void testMDCubes() throws Exception { String requestType = "MDSCHEMA_CUBES"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); doTest(requestType, props, TestContext.instance()); } public void testMDCubesJson() throws Exception { String requestType = "MDSCHEMA_CUBES"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); doTest(requestType, props, TestContext.instance()); } public void testMDCubesDeep() throws Exception { String requestType = "MDSCHEMA_CUBES"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, "HR"); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); doTest(requestType, props, TestContext.instance()); } public void testMDCubesDeepJson() throws Exception { String requestType = "MDSCHEMA_CUBES"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, "HR"); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); doTest(requestType, props, TestContext.instance()); } public void testMDCubesLocale() throws Exception { String requestType = "MDSCHEMA_CUBES"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, "Sales"); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); props.setProperty(LOCALE_PROP, Locale.GERMANY.toString()); doTest(requestType, props, TestContext.instance()); } public void testMDCubesLcid() throws Exception { String requestType = "MDSCHEMA_CUBES"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, "Sales"); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); props.setProperty(LOCALE_PROP, 0x040c + ""); // LCID code for FRENCH doTest(requestType, props, TestContext.instance()); } public void testMDSets() throws Exception { String requestType = "MDSCHEMA_SETS"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); doTest(requestType, props, TestContext.instance()); } public void testMDDimensions() throws Exception { String requestType = "MDSCHEMA_DIMENSIONS"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); doTest(requestType, props, TestContext.instance()); } public void testMDDimensionsShared() throws Exception { String requestType = "MDSCHEMA_DIMENSIONS"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, ""); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); doTest(requestType, props, TestContext.instance()); } public void testMDFunction() throws Exception { String requestType = "MDSCHEMA_FUNCTIONS"; String restrictionName = "FUNCTION_NAME"; String restrictionValue = "Item"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(RESTRICTION_NAME_PROP, restrictionName); props.setProperty(RESTRICTION_VALUE_PROP, restrictionValue); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); doTest(requestType, props, TestContext.instance()); } /** * Tests the output of the MDSCHEMA_FUNCTIONS call in JDK 1.5 or later. In * JDK 1.4, does nothing and trivially succeeds. * See {@link #testMDFunctionsJdk14()}. * * @throws Exception on error */ public void testMDFunctions() throws Exception { if (Util.PreJdk15 || Util.Retrowoven) { // MDSCHEMA_FUNCTIONS produces different output in JDK 1.4. return; } if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { // .CurrentMember function exists if // SsasCompatibleNaming=false. return; } String requestType = "MDSCHEMA_FUNCTIONS"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); doTest(requestType, props, TestContext.instance()); } /** * Tests the output of the MDSCHEMA_FUNCTIONS call in JDK 1.4, which is * different because metadata such as function name and description is * encoded using Java annotations, and hence is not available until * JDK 1.5. In JDK 1.5 and later, does nothing and trivially succeeds. * See {@link #testMDFunctions()}. * * @throws Exception on error */ public void testMDFunctionsJdk14() throws Exception { if (!(Util.PreJdk15 || Util.Retrowoven)) { // MDSCHEMA_FUNCTIONS produces different output in JDK 1.4. return; } String requestType = "MDSCHEMA_FUNCTIONS"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); doTest(requestType, props, TestContext.instance()); } // good 2/25 : (partial implementation) public void testMDHierarchies() throws Exception { if (!MondrianProperties.instance().FilterChildlessSnowflakeMembers .get()) { return; } String requestType = "MDSCHEMA_HIERARCHIES"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, SALES_CUBE); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); doTest(requestType, props, TestContext.instance()); } public void testMDLevels() throws Exception { String requestType = "MDSCHEMA_LEVELS"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CATALOG_NAME_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, SALES_CUBE); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); props.setProperty(UNIQUE_NAME_PROP, "[Customers]"); props.setProperty(UNIQUE_NAME_ELEMENT, "DIMENSION_UNIQUE_NAME"); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); doTest(requestType, props, TestContext.instance()); } public void testMDLevelsAccessControlled() throws Exception { String requestType = "MDSCHEMA_LEVELS"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CATALOG_NAME_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, SALES_CUBE); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); props.setProperty(UNIQUE_NAME_PROP, "[Customers]"); props.setProperty(UNIQUE_NAME_ELEMENT, "DIMENSION_UNIQUE_NAME"); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); // TestContext which operates in a different Role. TestContext testContext = TestContext.instance().withRole("California manager"); doTest(requestType, props, testContext); } public void testMDMeasures() throws Exception { String requestType = "MDSCHEMA_MEASURES"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CATALOG_NAME_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, SALES_CUBE); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); // not used here props.setProperty(UNIQUE_NAME_PROP, "[Customers]"); props.setProperty(UNIQUE_NAME_ELEMENT, "MEASURE_UNIQUE_NAME"); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); doTest(requestType, props, TestContext.instance()); } public void testMDMembers() throws Exception { String requestType = "MDSCHEMA_MEMBERS"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CATALOG_NAME_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, SALES_CUBE); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); props.setProperty(UNIQUE_NAME_PROP, "[Gender]"); props.setProperty(UNIQUE_NAME_ELEMENT, "HIERARCHY_UNIQUE_NAME"); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); doTest(requestType, props, TestContext.instance()); } public void testMDMembersMulti() throws Exception { String requestType = "MDSCHEMA_MEMBERS"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CATALOG_NAME_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, SALES_CUBE); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); doTest(requestType, props, TestContext.instance()); } public void testMDMembersTreeop() throws Exception { String requestType = "MDSCHEMA_MEMBERS"; // Treeop 34 = Ancestors | Siblings // MEMBER_UNIQUE_NAME = [USA].[OR] // Hence should return {[All], [USA], [USA].[CA], [USA].[WA]} Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CATALOG_NAME_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, SALES_CUBE); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); doTest(requestType, props, TestContext.instance()); } public void testMDProperties() throws Exception { String requestType = "MDSCHEMA_PROPERTIES"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); doTest(requestType, props, TestContext.instance()); } public void testApproxRowCountOverridesCountCallsToDatabase() throws Exception { String requestType = "MDSCHEMA_LEVELS"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CATALOG_NAME_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, SALES_CUBE); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); props.setProperty(UNIQUE_NAME_PROP, "[Marital Status]"); props.setProperty(UNIQUE_NAME_ELEMENT, "DIMENSION_UNIQUE_NAME"); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); doTest(requestType, props, TestContext.instance()); } public void testApproxRowCountInHierarchyOverridesCountCallsToDatabase() throws Exception { String requestType = "MDSCHEMA_HIERARCHIES"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CATALOG_NAME_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, SALES_CUBE); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); props.setProperty(UNIQUE_NAME_PROP, "[Marital Status]"); props.setProperty(UNIQUE_NAME_ELEMENT, "DIMENSION_UNIQUE_NAME"); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); doTest(requestType, props, TestContext.instance()); } /** * Tests an 'DRILLTHROUGH SELECT' statement with a 'MAXROWS' clause. * * @throws Exception on error */ public void testDrillThroughMaxRows() throws Exception { // NOTE: this test uses the filter method to adjust the expected result // for different databases if (!MondrianProperties.instance().EnableTotalCount.booleanValue()) { return; } String requestType = "EXECUTE"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CATALOG_NAME_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, SALES_CUBE); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); doTest(requestType, props, TestContext.instance()); } /** * Tests an 'DRILLTHROUGH SELECT' statement with no 'MAXROWS' clause. * * @throws Exception on error */ public void testDrillThrough() throws Exception { // NOTE: this test uses the filter method to adjust the expected result // for different databases if (!MondrianProperties.instance().EnableTotalCount.booleanValue()) { return; } String requestType = "EXECUTE"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CATALOG_NAME_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, SALES_CUBE); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); doTest(requestType, props, TestContext.instance()); } /** * Tests an 'DRILLTHROUGH SELECT' statement with a zero-dimensional query, * that is, a query with 'SELECT FROM', and no axes. * * @throws Exception on error */ public void testDrillThroughZeroDimensionalQuery() throws Exception { // NOTE: this test uses the filter method to adjust the expected result // for different databases if (!MondrianProperties.instance().EnableTotalCount.booleanValue()) { return; } String requestType = "EXECUTE"; Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(CATALOG_PROP, CATALOG); props.setProperty(CATALOG_NAME_PROP, CATALOG); props.setProperty(CUBE_NAME_PROP, SALES_CUBE); props.setProperty(FORMAT_PROP, FORMAT_TABLULAR); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); doTest(requestType, props, TestContext.instance()); } protected String filter( String testCaseName, String filename, String content) { if (testCaseName.startsWith("testDrillThrough") && filename.equals("response")) { // Different databases have slightly different column types, which // results in slightly different inferred xml schema for the drill- // through result. Dialect dialect = TestContext.instance().getDialect(); switch (dialect.getDatabaseProduct()) { case ORACLE: content = Util.replace( content, " type=\"xsd:double\"", " type=\"xsd:decimal\""); content = Util.replace( content, " type=\"xsd:integer\"", " type=\"xsd:decimal\""); break; case POSTGRESQL: content = Util.replace( content, " sql:field=\"Store Sqft\" type=\"xsd:double\"", " sql:field=\"Store Sqft\" type=\"xsd:integer\""); content = Util.replace( content, " sql:field=\"Unit Sales\" type=\"xsd:double\"", " sql:field=\"Unit Sales\" type=\"xsd:decimal\""); break; case DERBY: case HSQLDB: case INFOBRIGHT: case LUCIDDB: case MYSQL: case NEOVIEW: case NETEZZA: case TERADATA: content = Util.replace( content, " sql:field=\"Store Sqft\" type=\"xsd:double\"", " sql:field=\"Store Sqft\" type=\"xsd:integer\""); content = Util.replace( content, " sql:field=\"Unit Sales\" type=\"xsd:double\"", " sql:field=\"Unit Sales\" type=\"xsd:string\""); content = Util.replace( content, " sql:field=\"Week\" type=\"xsd:decimal\"", " sql:field=\"Week\" type=\"xsd:integer\""); content = Util.replace( content, " sql:field=\"Day\" type=\"xsd:decimal\"", " sql:field=\"Day\" type=\"xsd:integer\""); break; case ACCESS: content = Util.replace( content, " sql:field=\"Week\" type=\"xsd:decimal\"", " sql:field=\"Week\" type=\"xsd:double\""); content = Util.replace( content, " sql:field=\"Day\" type=\"xsd:decimal\"", " sql:field=\"Day\" type=\"xsd:integer\""); break; } } return content; } public void testExecuteSlicer() throws Exception { String requestType = "EXECUTE"; Properties props = getDefaultRequestProperties(requestType); doTest(requestType, props, TestContext.instance()); } public void testExecuteSlicerJson() throws Exception { String requestType = "EXECUTE"; Properties props = getDefaultRequestProperties(requestType); doTest(requestType, props, TestContext.instance()); } public void testExecuteSlicer_ContentDataOmitDefaultSlicer() throws Exception { doTestExecuteContent(XmlaConstants.Content.DataOmitDefaultSlicer); } public void testExecuteNoSlicer_ContentDataOmitDefaultSlicer() throws Exception { doTestExecuteContent(XmlaConstants.Content.DataOmitDefaultSlicer); } public void testExecuteSlicer_ContentDataIncludeDefaultSlicer() throws Exception { if (MondrianProperties.instance().SsasCompatibleNaming.get()) { // slight differences in reference log, viz [Time.Weekly] return; } doTestExecuteContent(XmlaConstants.Content.DataIncludeDefaultSlicer); } public void testExecuteNoSlicer_ContentDataIncludeDefaultSlicer() throws Exception { if (MondrianProperties.instance().SsasCompatibleNaming.get()) { // slight differences in reference log, viz [Time.Weekly] return; } doTestExecuteContent(XmlaConstants.Content.DataIncludeDefaultSlicer); } public void testExecuteEmptySlicer_ContentDataIncludeDefaultSlicer() throws Exception { if (MondrianProperties.instance().SsasCompatibleNaming.get()) { // slight differences in reference log, viz [Time.Weekly] return; } doTestExecuteContent(XmlaConstants.Content.DataIncludeDefaultSlicer); } public void testExecuteEmptySlicer_ContentDataOmitDefaultSlicer() throws Exception { doTestExecuteContent(XmlaConstants.Content.DataOmitDefaultSlicer); } public void testExecuteWithoutCellProperties() throws Exception { String requestType = "EXECUTE"; Properties props = getDefaultRequestProperties(requestType); doTest(requestType, props, TestContext.instance()); } public void testExecuteWithCellProperties() throws Exception { String requestType = "EXECUTE"; Properties props = getDefaultRequestProperties(requestType); doTest(requestType, props, TestContext.instance()); } public void testExecuteWithMemberKeyDimensionPropertyForMemberWithoutKey() throws Exception { String requestType = "EXECUTE"; Properties props = getDefaultRequestProperties(requestType); doTest(requestType, props, TestContext.instance()); } public void testExecuteWithMemberKeyDimensionPropertyForMemberWithKey() throws Exception { String requestType = "EXECUTE"; Properties props = getDefaultRequestProperties(requestType); doTest(requestType, props, TestContext.instance()); } public void testExecuteWithMemberKeyDimensionPropertyForAllMember() throws Exception { String requestType = "EXECUTE"; final Properties props = getDefaultRequestProperties(requestType); doTest(requestType, props, TestContext.instance()); } public void testExecuteWithKeyDimensionProperty() throws Exception { String requestType = "EXECUTE"; Properties props = getDefaultRequestProperties(requestType); doTest(requestType, props, TestContext.instance()); } public void testExecuteWithDimensionProperties() throws Exception { String requestType = "EXECUTE"; Properties props = getDefaultRequestProperties(requestType); doTest(requestType, props, TestContext.instance()); } /** * Testcase for bug * MONDRIAN-257, "Crossjoin gives 'Execute unparse results' error in * XMLA". */ public void testExecuteCrossjoin() throws Exception { if (!MondrianProperties.instance().FilterChildlessSnowflakeMembers .get()) { return; } String requestType = "EXECUTE"; String query = "SELECT CrossJoin({[Product].[All Products].children}, " + "{[Customers].[All Customers].children}) ON columns FROM Sales"; String request = "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + query + "\n" + " \n" + " \n" + " \n" + " \n" + " ${catalog}\n" + " ${data.source.info}\n" + " ${format}\n" + " TupleFormat\n" + " \n" + " \n" + "\n" + "\n" + ""; Properties props = getDefaultRequestProperties(requestType); doTestInline( requestType, request, "response", props, TestContext.instance()); } /** * This test returns the same result as testExecuteCrossjoin above * except that the Role used disables accessing * [Customers].[All Customers].[Mexico]. */ public void testExecuteCrossjoinRole() throws Exception { if (!MondrianProperties.instance().FilterChildlessSnowflakeMembers .get()) { return; } String requestType = "EXECUTE"; String query = "SELECT CrossJoin({[Product].[All Products].children}, " + "{[Customers].[All Customers].children}) ON columns FROM Sales"; String request = "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + query + "\n" + " \n" + " \n" + " \n" + " \n" + " ${catalog}\n" + " ${data.source.info}\n" + " ${format}\n" + " ${format}\n" + " TupleFormat\n" + " \n" + " \n" + "\n" + "\n" + ""; class RR implements Role { public RR() { } public Access getAccess(Cube cube) { return Access.ALL; } public Access getAccess(NamedSet set) { return Access.ALL; } public boolean canAccess(OlapElement olapElement) { return true; } public Access getAccess(Schema schema) { return Access.ALL; } public Access getAccess(Dimension dimension) { return Access.ALL; } public Access getAccess(Hierarchy hierarchy) { String mname = "[Customers]"; if (hierarchy.getUniqueName().equals(mname)) { return Access.CUSTOM; } else { return Access.ALL; } } public HierarchyAccess getAccessDetails(Hierarchy hierarchy) { String hname = "[Customers]"; if (hierarchy.getUniqueName().equals(hname)) { return new HierarchyAccess() { public Access getAccess(Member member) { String mname = "[Customers].[Mexico]"; if (member.getUniqueName().equals(mname)) { return Access.NONE; } else { return Access.ALL; } } public int getTopLevelDepth() { return 0; } public int getBottomLevelDepth() { return 4; } public RollupPolicy getRollupPolicy() { return RollupPolicy.FULL; } public boolean hasInaccessibleDescendants( Member member) { return false; } }; } else { return RoleImpl.createAllAccess(hierarchy); } } public Access getAccess(Level level) { return Access.ALL; } public Access getAccess(Member member) { String mname = "[Customers].[All Customers]"; if (member.getUniqueName().equals(mname)) { return Access.ALL; } else { return Access.ALL; } } } Role role = new RR(); Properties props = getDefaultRequestProperties(requestType); doTestInline( requestType, request, "response", props, TestContext.instance(), role); } public void testExecuteBugMondrian762() throws Exception { String requestType = "EXECUTE"; Properties props = getDefaultRequestProperties(requestType); propSaver.set( MondrianProperties.instance().EnableRolapCubeMemberCache, false); doTest(requestType, props, TestContext.instance()); } public void doTestRT(String requestType, TestContext testContext) throws Exception { Properties props = new Properties(); props.setProperty(REQUEST_TYPE_PROP, requestType); props.setProperty(DATA_SOURCE_INFO_PROP, DATA_SOURCE_INFO); doTest(requestType, props, testContext); } private void doTestExecuteContent( XmlaConstants.Content content) throws Exception { String requestType = "EXECUTE"; Properties props = getDefaultRequestProperties(requestType); String requestText = fileToString("request"); TestContext testContext = TestContext.instance(); requestText = testContext.upgradeQuery(requestText); Document responseDoc = fileToDocument("response"); String connectString = testContext.getConnectString(); Map catalogNameUrls = getCatalogNameUrls(testContext); Document expectedDoc; final String ns = "cxmla"; expectedDoc = (responseDoc != null) ? XmlaSupport.transformSoapXmla( responseDoc, new String[][] {{"content", content.name()}}, ns) : null; doTests( requestText, props, testContext, connectString, catalogNameUrls, expectedDoc, content, null, true); } protected String getSessionId(Action action) { throw new UnsupportedOperationException(); } } // End XmlaBasicTest.java mondrian-3.4.1/testsrc/main/mondrian/xmla/XmlaExcel2007Test.ref.xml0000644000175000017500000043626711735330606024763 0ustar drazzibdrazzib
WITH SET [XL_Row_Dim_0] as 'VisualTotals(Distinct(Hierarchize({ Ascendants([Customers].[USA].[CA].[Beverly Hills].[Ari Tweten]), Descendants([Customers].[USA].[CA].[Beverly Hills].[Ari Tweten]), Ascendants([Customers].[Mexico]), Descendants([Customers].[Mexico]) })))' select NON EMPTY Hierarchize( Intersect( DrilldownMember( {{DrilldownMember( {{DrilldownLevel( {[Customers].[All Customers]})}}, {[Customers].[USA]})}}, {[Customers].[USA].[CA]}), [XL_Row_Dim_0])) DIMENSION PROPERTIES PARENT_UNIQUE_NAME ON COLUMNS from [Sales] where [Measures].[Unit Sales] FoodMart FoodMart Multidimensional TupleFormat Data ]]> Sales [Customers].[All Customers] All Customers [Customers].[(All)] 0 65539 [Customers].[USA] USA [Customers].[Country] 1 65539 [Customers].[All Customers] [Customers].[USA].[CA] CA [Customers].[State Province] 2 65581 [Customers].[USA] [Customers].[USA].[CA].[Beverly Hills] Beverly Hills [Customers].[City] 3 94 [Customers].[USA].[CA] [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 12 12 Standard 12 12 Standard 12 12 Standard 12 12 Standard ]]>
SELECT NON EMPTY Hierarchize(DrilldownMember({{DrilldownMember({{DrilldownMember({{DrilldownLevel({[Store].[All Stores]})}}, {[Store].[USA]})}}, {[Store].[USA].[CA]})}}, {[Store].[USA].[CA].[Beverly Hills],[Store].[USA].[CA].[Los Angeles]})) DIMENSION PROPERTIES PARENT_UNIQUE_NAME,[Store].[Store Name].[Store Type],[Store].[Store Name].[Store Manager],[Store].[Store Name].[Store Sqft],[Store].[Store Name].[Grocery Sqft],[Store].[Store Name].[Frozen Sqft],[Store].[Store Name].[Meat Sqft],[Store].[Store Name].[Has coffee bar],[Store].[Store Name].[Street address] ON COLUMNS FROM [Sales] WHERE ([Measures].[Customer Count],[Gender].[M]) CELL PROPERTIES VALUE Multidimensional TupleFormat FoodMart 2057 FoodMart 1 ]]>
Sales [Store].[All Stores] All Stores [Store].[(All)] 0 65539 [Store].[USA] USA [Store].[Store Country] 1 65539 [Store].[All Stores] [Store].[USA].[CA] CA [Store].[Store State] 2 65541 [Store].[USA] [Store].[USA].[CA].[Beverly Hills] Beverly Hills [Store].[Store City] 3 65537 [Store].[USA].[CA] [Store].[USA].[CA].[Beverly Hills].[Store 6] Store 6 [Store].[Store Name] 4 0 [Store].[USA].[CA].[Beverly Hills] Gourmet Supermarket Maris 23688 15337 5011 3340 1 5495 Mitchell Canyon Road [Store].[USA].[CA].[Los Angeles] Los Angeles [Store].[Store City] 3 65537 [Store].[USA].[CA] [Store].[USA].[CA].[Los Angeles].[Store 7] Store 7 [Store].[Store Name] 4 0 [Store].[USA].[CA].[Los Angeles] Supermarket White 23598 14210 5633 3755 0 1077 Wharf Drive [Store].[USA].[CA].[San Diego] San Diego [Store].[Store City] 3 1 [Store].[USA].[CA] [Store].[USA].[CA].[San Francisco] San Francisco [Store].[Store City] 3 131073 [Store].[USA].[CA] [Store].[USA].[OR] OR [Store].[Store State] 2 2 [Store].[USA] [Store].[USA].[WA] WA [Store].[Store State] 2 131079 [Store].[USA] [Measures].[Customer Count] Customer Count [Measures].[MeasuresLevel] 0 0 [Gender].[M] M [Gender].[Gender] 1 0 2826 2826 1389 540 540 596 596 488 150 536 901 ]]>
MDSCHEMA_PROPERTIES 1 1033 FoodMart FoodMart Tabular ]]>
FoodMart FoodMart HR [Store] [Store] [Store].[Store Name] Store Type Store Type 1 130 0 HR Cube - Store Hierarchy - Store Name Level - Store Type Property FoodMart FoodMart HR [Store] [Store] [Store].[Store Name] Store Manager Store Manager 1 130 0 HR Cube - Store Hierarchy - Store Name Level - Store Manager Property FoodMart FoodMart HR [Store] [Store] [Store].[Store Name] Store Sqft Store Sqft 1 5 0 HR Cube - Store Hierarchy - Store Name Level - Store Sqft Property FoodMart FoodMart HR [Store] [Store] [Store].[Store Name] Grocery Sqft Grocery Sqft 1 5 0 HR Cube - Store Hierarchy - Store Name Level - Grocery Sqft Property FoodMart FoodMart HR [Store] [Store] [Store].[Store Name] Frozen Sqft Frozen Sqft 1 5 0 HR Cube - Store Hierarchy - Store Name Level - Frozen Sqft Property FoodMart FoodMart HR [Store] [Store] [Store].[Store Name] Meat Sqft Meat Sqft 1 5 0 HR Cube - Store Hierarchy - Store Name Level - Meat Sqft Property FoodMart FoodMart HR [Store] [Store] [Store].[Store Name] Has coffee bar Has coffee bar 1 11 0 HR Cube - Store Hierarchy - Store Name Level - Has coffee bar Property FoodMart FoodMart HR [Store] [Store] [Store].[Store Name] Street address Street address 1 130 0 HR Cube - Store Hierarchy - Store Name Level - Street address Property FoodMart FoodMart HR [Employees] [Employees] [Employees].[Employee Id] Marital Status Marital Status 1 130 0 HR Cube - Employees Hierarchy - Employee Id Level - Marital Status Property FoodMart FoodMart HR [Employees] [Employees] [Employees].[Employee Id] Position Title Position Title 1 130 0 HR Cube - Employees Hierarchy - Employee Id Level - Position Title Property FoodMart FoodMart HR [Employees] [Employees] [Employees].[Employee Id] Gender Gender 1 130 0 HR Cube - Employees Hierarchy - Employee Id Level - Gender Property FoodMart FoodMart HR [Employees] [Employees] [Employees].[Employee Id] Salary Salary 1 130 0 HR Cube - Employees Hierarchy - Employee Id Level - Salary Property FoodMart FoodMart HR [Employees] [Employees] [Employees].[Employee Id] Education Level Education Level 1 130 0 HR Cube - Employees Hierarchy - Employee Id Level - Education Level Property FoodMart FoodMart HR [Employees] [Employees] [Employees].[Employee Id] Management Role Management Role 1 130 0 HR Cube - Employees Hierarchy - Employee Id Level - Management Role Property FoodMart FoodMart Sales [Store] [Store] [Store].[Store Name] Store Type Store Type 1 130 0 Sales Cube - Store Hierarchy - Store Name Level - Store Type Property FoodMart FoodMart Sales [Store] [Store] [Store].[Store Name] Store Manager Store Manager 1 130 0 Sales Cube - Store Hierarchy - Store Name Level - Store Manager Property FoodMart FoodMart Sales [Store] [Store] [Store].[Store Name] Store Sqft Store Sqft 1 5 0 Sales Cube - Store Hierarchy - Store Name Level - Store Sqft Property FoodMart FoodMart Sales [Store] [Store] [Store].[Store Name] Grocery Sqft Grocery Sqft 1 5 0 Sales Cube - Store Hierarchy - Store Name Level - Grocery Sqft Property FoodMart FoodMart Sales [Store] [Store] [Store].[Store Name] Frozen Sqft Frozen Sqft 1 5 0 Sales Cube - Store Hierarchy - Store Name Level - Frozen Sqft Property FoodMart FoodMart Sales [Store] [Store] [Store].[Store Name] Meat Sqft Meat Sqft 1 5 0 Sales Cube - Store Hierarchy - Store Name Level - Meat Sqft Property FoodMart FoodMart Sales [Store] [Store] [Store].[Store Name] Has coffee bar Has coffee bar 1 11 0 Sales Cube - Store Hierarchy - Store Name Level - Has coffee bar Property FoodMart FoodMart Sales [Store] [Store] [Store].[Store Name] Street address Street address 1 130 0 Sales Cube - Store Hierarchy - Store Name Level - Street address Property FoodMart FoodMart Sales [Customers] [Customers] [Customers].[Name] Gender Gender 1 130 0 Sales Cube - Customers Hierarchy - Name Level - Gender Property FoodMart FoodMart Sales [Customers] [Customers] [Customers].[Name] Marital Status Marital Status 1 130 0 Sales Cube - Customers Hierarchy - Name Level - Marital Status Property FoodMart FoodMart Sales [Customers] [Customers] [Customers].[Name] Education Education 1 130 0 Sales Cube - Customers Hierarchy - Name Level - Education Property FoodMart FoodMart Sales [Customers] [Customers] [Customers].[Name] Yearly Income Yearly Income 1 130 0 Sales Cube - Customers Hierarchy - Name Level - Yearly Income Property FoodMart FoodMart Sales Ragged [Store] [Store] [Store].[Store Name] Store Type Store Type 1 130 0 Sales Ragged Cube - Store Hierarchy - Store Name Level - Store Type Property FoodMart FoodMart Sales Ragged [Store] [Store] [Store].[Store Name] Store Manager Store Manager 1 130 0 Sales Ragged Cube - Store Hierarchy - Store Name Level - Store Manager Property FoodMart FoodMart Sales Ragged [Store] [Store] [Store].[Store Name] Store Sqft Store Sqft 1 5 0 Sales Ragged Cube - Store Hierarchy - Store Name Level - Store Sqft Property FoodMart FoodMart Sales Ragged [Store] [Store] [Store].[Store Name] Grocery Sqft Grocery Sqft 1 5 0 Sales Ragged Cube - Store Hierarchy - Store Name Level - Grocery Sqft Property FoodMart FoodMart Sales Ragged [Store] [Store] [Store].[Store Name] Frozen Sqft Frozen Sqft 1 5 0 Sales Ragged Cube - Store Hierarchy - Store Name Level - Frozen Sqft Property FoodMart FoodMart Sales Ragged [Store] [Store] [Store].[Store Name] Meat Sqft Meat Sqft 1 5 0 Sales Ragged Cube - Store Hierarchy - Store Name Level - Meat Sqft Property FoodMart FoodMart Sales Ragged [Store] [Store] [Store].[Store Name] Has coffee bar Has coffee bar 1 11 0 Sales Ragged Cube - Store Hierarchy - Store Name Level - Has coffee bar Property FoodMart FoodMart Sales Ragged [Store] [Store] [Store].[Store Name] Street address Street address 1 130 0 Sales Ragged Cube - Store Hierarchy - Store Name Level - Street address Property FoodMart FoodMart Sales Ragged [Customers] [Customers] [Customers].[Name] Gender Gender 1 130 0 Sales Ragged Cube - Customers Hierarchy - Name Level - Gender Property FoodMart FoodMart Sales Ragged [Customers] [Customers] [Customers].[Name] Marital Status Marital Status 1 130 0 Sales Ragged Cube - Customers Hierarchy - Name Level - Marital Status Property FoodMart FoodMart Sales Ragged [Customers] [Customers] [Customers].[Name] Education Education 1 130 0 Sales Ragged Cube - Customers Hierarchy - Name Level - Education Property FoodMart FoodMart Sales Ragged [Customers] [Customers] [Customers].[Name] Yearly Income Yearly Income 1 130 0 Sales Ragged Cube - Customers Hierarchy - Name Level - Yearly Income Property FoodMart FoodMart Store [Store] [Store] [Store].[Store Name] Store Type Store Type 1 130 0 Store Cube - Store Hierarchy - Store Name Level - Store Type Property FoodMart FoodMart Store [Store] [Store] [Store].[Store Name] Store Manager Store Manager 1 130 0 Store Cube - Store Hierarchy - Store Name Level - Store Manager Property FoodMart FoodMart Store [Store] [Store] [Store].[Store Name] Store Sqft Store Sqft 1 5 0 Store Cube - Store Hierarchy - Store Name Level - Store Sqft Property FoodMart FoodMart Store [Store] [Store] [Store].[Store Name] Grocery Sqft Grocery Sqft 1 5 0 Store Cube - Store Hierarchy - Store Name Level - Grocery Sqft Property FoodMart FoodMart Store [Store] [Store] [Store].[Store Name] Frozen Sqft Frozen Sqft 1 5 0 Store Cube - Store Hierarchy - Store Name Level - Frozen Sqft Property FoodMart FoodMart Store [Store] [Store] [Store].[Store Name] Meat Sqft Meat Sqft 1 5 0 Store Cube - Store Hierarchy - Store Name Level - Meat Sqft Property FoodMart FoodMart Store [Store] [Store] [Store].[Store Name] Has coffee bar Has coffee bar 1 11 0 Store Cube - Store Hierarchy - Store Name Level - Has coffee bar Property FoodMart FoodMart Store [Store] [Store] [Store].[Store Name] Street address Street address 1 130 0 Store Cube - Store Hierarchy - Store Name Level - Street address Property FoodMart FoodMart Warehouse [Store] [Store] [Store].[Store Name] Store Type Store Type 1 130 0 Warehouse Cube - Store Hierarchy - Store Name Level - Store Type Property FoodMart FoodMart Warehouse [Store] [Store] [Store].[Store Name] Store Manager Store Manager 1 130 0 Warehouse Cube - Store Hierarchy - Store Name Level - Store Manager Property FoodMart FoodMart Warehouse [Store] [Store] [Store].[Store Name] Store Sqft Store Sqft 1 5 0 Warehouse Cube - Store Hierarchy - Store Name Level - Store Sqft Property FoodMart FoodMart Warehouse [Store] [Store] [Store].[Store Name] Grocery Sqft Grocery Sqft 1 5 0 Warehouse Cube - Store Hierarchy - Store Name Level - Grocery Sqft Property FoodMart FoodMart Warehouse [Store] [Store] [Store].[Store Name] Frozen Sqft Frozen Sqft 1 5 0 Warehouse Cube - Store Hierarchy - Store Name Level - Frozen Sqft Property FoodMart FoodMart Warehouse [Store] [Store] [Store].[Store Name] Meat Sqft Meat Sqft 1 5 0 Warehouse Cube - Store Hierarchy - Store Name Level - Meat Sqft Property FoodMart FoodMart Warehouse [Store] [Store] [Store].[Store Name] Has coffee bar Has coffee bar 1 11 0 Warehouse Cube - Store Hierarchy - Store Name Level - Has coffee bar Property FoodMart FoodMart Warehouse [Store] [Store] [Store].[Store Name] Street address Street address 1 130 0 Warehouse Cube - Store Hierarchy - Store Name Level - Street address Property FoodMart FoodMart Warehouse and Sales [Customers] [Customers] [Customers].[Name] Gender Gender 1 130 0 Warehouse and Sales Cube - Customers Hierarchy - Name Level - Gender Property FoodMart FoodMart Warehouse and Sales [Customers] [Customers] [Customers].[Name] Marital Status Marital Status 1 130 0 Warehouse and Sales Cube - Customers Hierarchy - Name Level - Marital Status Property FoodMart FoodMart Warehouse and Sales [Customers] [Customers] [Customers].[Name] Education Education 1 130 0 Warehouse and Sales Cube - Customers Hierarchy - Name Level - Education Property FoodMart FoodMart Warehouse and Sales [Customers] [Customers] [Customers].[Name] Yearly Income Yearly Income 1 130 0 Warehouse and Sales Cube - Customers Hierarchy - Name Level - Yearly Income Property FoodMart FoodMart Warehouse and Sales [Store] [Store] [Store].[Store Name] Store Type Store Type 1 130 0 Warehouse and Sales Cube - Store Hierarchy - Store Name Level - Store Type Property FoodMart FoodMart Warehouse and Sales [Store] [Store] [Store].[Store Name] Store Manager Store Manager 1 130 0 Warehouse and Sales Cube - Store Hierarchy - Store Name Level - Store Manager Property FoodMart FoodMart Warehouse and Sales [Store] [Store] [Store].[Store Name] Store Sqft Store Sqft 1 5 0 Warehouse and Sales Cube - Store Hierarchy - Store Name Level - Store Sqft Property FoodMart FoodMart Warehouse and Sales [Store] [Store] [Store].[Store Name] Grocery Sqft Grocery Sqft 1 5 0 Warehouse and Sales Cube - Store Hierarchy - Store Name Level - Grocery Sqft Property FoodMart FoodMart Warehouse and Sales [Store] [Store] [Store].[Store Name] Frozen Sqft Frozen Sqft 1 5 0 Warehouse and Sales Cube - Store Hierarchy - Store Name Level - Frozen Sqft Property FoodMart FoodMart Warehouse and Sales [Store] [Store] [Store].[Store Name] Meat Sqft Meat Sqft 1 5 0 Warehouse and Sales Cube - Store Hierarchy - Store Name Level - Meat Sqft Property FoodMart FoodMart Warehouse and Sales [Store] [Store] [Store].[Store Name] Has coffee bar Has coffee bar 1 11 0 Warehouse and Sales Cube - Store Hierarchy - Store Name Level - Has coffee bar Property FoodMart FoodMart Warehouse and Sales [Store] [Store] [Store].[Store Name] Street address Street address 1 130 0 Warehouse and Sales Cube - Store Hierarchy - Store Name Level - Street address Property ]]>
MDSCHEMA_PROPERTIES 2 ]]>
BACK_COLOR BACK_COLOR 2 130 CELL_EVALUATION_LIST CELL_EVALUATION_LIST 2 130 CELL_ORDINAL CELL_ORDINAL 2 19 FORE_COLOR FORE_COLOR 2 130 FONT_NAME FONT_NAME 2 130 FONT_SIZE FONT_SIZE 2 130 FONT_FLAGS FONT_FLAGS 2 19 FORMATTED_VALUE FORMATTED_VALUE 2 130 FORMAT_STRING FORMAT_STRING 2 130 NON_EMPTY_BEHAVIOR NON_EMPTY_BEHAVIOR 2 130 SOLVE_ORDER SOLVE_ORDER 2 3 VALUE VALUE 2 12 DATATYPE DATATYPE 2 130 LANGUAGE LANGUAGE 2 19 ACTION_TYPE ACTION_TYPE 2 1009 UPDATEABLE UPDATEABLE 2 19 ]]>
SELECT FROM Sales WHERE [Measures].[Unit sales] CELL PROPERTIES VALUE, FORMAT_STRING, LANGUAGE, BACK_COLOR, FORE_COLOR, FONT_FLAGS Multidimensional TupleFormat FoodMart 2057 FoodMart 1 ]]>
Sales [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 266773 Standard ]]>
WITH SET [XL_Row_Dim_0] AS 'VisualTotals(Distinct(Hierarchize( {Ascendants([Store].[USA].[WA].[Yakima]), Descendants([Store].[USA].[WA].[Yakima]), Ascendants([Store].[USA].[WA].[Walla Walla]), Descendants([Store].[USA].[WA].[Walla Walla]), Ascendants([Store].[USA].[WA].[Tacoma]), Descendants([Store].[USA].[WA].[Tacoma]), Ascendants([Store].[USA].[WA].[Spokane]), Descendants([Store].[USA].[WA].[Spokane]), Ascendants([Store].[USA].[WA].[Seattle]), Descendants([Store].[USA].[WA].[Seattle]), Ascendants([Store].[USA].[WA].[Bremerton]), Descendants([Store].[USA].[WA].[Bremerton]), Ascendants([Store].[USA].[OR]), Descendants([Store].[USA].[OR])})))' SELECT NON EMPTY Hierarchize(Intersect(DrilldownMember({{DrilldownMember({{DrilldownMember({{DrilldownLevel({[Store].[All Stores]})}}, {[Store].[USA]})}}, {[Store].[USA].[WA]})}}, {[Store].[USA].[WA].[Bremerton]}), [XL_Row_Dim_0])) DIMENSION PROPERTIES PARENT_UNIQUE_NAME,[Store].[Store Name].[Store Type],[Store].[Store Name].[Store Manager],[Store].[Store Name].[Store Sqft],[Store].[Store Name].[Grocery Sqft],[Store].[Store Name].[Frozen Sqft],[Store].[Store Name].[Meat Sqft],[Store].[Store Name].[Has coffee bar],[Store].[Store Name].[Street address] ON COLUMNS FROM [HR] WHERE ([Measures].[Number of Employees]) CELL PROPERTIES VALUE, FORMAT_STRING, LANGUAGE, BACK_COLOR, FORE_COLOR, FONT_FLAGS FoodMart FoodMart Multidimensional TupleFormat Data ]]> HR [Store].[All Stores] All Stores [Store].[(All)] 0 65539 [Store].[USA] USA [Store].[Store Country] 1 65539 [Store].[All Stores] [Store].[USA].[OR] OR [Store].[Store State] 2 2 [Store].[USA] [Store].[USA].[WA] WA [Store].[Store State] 2 196615 [Store].[USA] [Store].[USA].[WA].[Bremerton] Bremerton [Store].[Store City] 3 65537 [Store].[USA].[WA] [Store].[USA].[WA].[Bremerton].[Store 3] Store 3 [Store].[Store Name] 4 0 [Store].[USA].[WA].[Bremerton] Supermarket Davis 39696 24390 9184 6122 0 1501 Ramsey Circle [Store].[USA].[WA].[Seattle] Seattle [Store].[Store City] 3 1 [Store].[USA].[WA] [Store].[USA].[WA].[Spokane] Spokane [Store].[Store City] 3 131073 [Store].[USA].[WA] [Store].[USA].[WA].[Tacoma] Tacoma [Store].[Store City] 3 131073 [Store].[USA].[WA] [Store].[USA].[WA].[Walla Walla] Walla Walla [Store].[Store City] 3 131073 [Store].[USA].[WA] [Store].[USA].[WA].[Yakima] Yakima [Store].[Store City] 3 131073 [Store].[USA].[WA] [Measures].[Number of Employees] Number of Employees [Measures].[MeasuresLevel] 0 0 419 #,# 419 #,# 136 #,# 283 #,# 62 #,# 62 #,# 62 #,# 62 #,# 74 #,# 4 #,# 19 #,# ]]> mondrian-3.4.1/testsrc/main/mondrian/xmla/XmlaTabularTest.java0000644000175000017500000000344111735330606024332 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho // All Rights Reserved. */ package mondrian.xmla; import mondrian.test.DiffRepository; import mondrian.test.TestContext; /** * Test XMLA output in tabular (flattened) format. * * @author Julio Caubín, jhyde */ public class XmlaTabularTest extends XmlaBaseTestCase { public XmlaTabularTest() { } public XmlaTabularTest(String name) { super(name); } public void testTabularOneByOne() throws Exception { executeMDX(); } public void testTabularOneByTwo() throws Exception { executeMDX(); } public void testTabularTwoByOne() throws Exception { executeMDX(); } public void testTabularTwoByTwo() throws Exception { executeMDX(); } public void testTabularZeroByZero() throws Exception { executeMDX(); } public void testTabularVoid() throws Exception { executeMDX(); } public void testTabularThreeAxes() throws Exception { executeMDX(); } private void executeMDX() throws Exception { String requestType = "EXECUTE"; doTest( requestType, getDefaultRequestProperties(requestType), TestContext.instance()); } protected DiffRepository getDiffRepos() { return DiffRepository.lookup(XmlaTabularTest.class); } protected Class getServletCallbackClass() { return null; } protected String getSessionId(Action action) { return null; } } // End XmlaTabularTest.java mondrian-3.4.1/testsrc/main/mondrian/xmla/XmlaTests.java0000644000175000017500000002365411735330606023212 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho and others // All Rights Reserved. */ package mondrian.xmla; import mondrian.olap.Util; import mondrian.test.FoodMartTestCase; import junit.framework.Assert; import java.math.BigDecimal; import java.math.BigInteger; // Only in Java5 and above //import java.math.MathContext; /** * Extends FoodMartTestCase, adding support for testing XMLA Utility * functionality. * * @author Richard M. Emberson * @since Jul 12 2007 */ public class XmlaTests extends FoodMartTestCase { public XmlaTests() { } public XmlaTests(String name) { super(name); } public void testXmlaUtilNormalizeNumericString() throws Exception { String vin = "1.0E10"; String expected = "1.0E10"; doXmlaUtilNormalizeNumericString(vin, expected); vin = "1.0E1"; expected = "1.0E1"; doXmlaUtilNormalizeNumericString(vin, expected); vin = "1.0E11"; expected = "1.0E11"; doXmlaUtilNormalizeNumericString(vin, expected); vin = "1.40"; expected = "1.4"; doXmlaUtilNormalizeNumericString(vin, expected); vin = "1.400"; expected = "1.4"; doXmlaUtilNormalizeNumericString(vin, expected); vin = "1.4040"; expected = "1.404"; doXmlaUtilNormalizeNumericString(vin, expected); vin = "1.0"; expected = "1"; doXmlaUtilNormalizeNumericString(vin, expected); vin = "1"; expected = "1"; doXmlaUtilNormalizeNumericString(vin, expected); vin = "10"; expected = "10"; doXmlaUtilNormalizeNumericString(vin, expected); } public void testXmlaHandlerGetValueTypeHint() throws Exception { String dataType = "Integer"; doXmlaHandlerGetValueTypeHint(dataType, XmlaHandler.XSD_INT); dataType = "Numeric"; doXmlaHandlerGetValueTypeHint(dataType, XmlaHandler.XSD_DOUBLE); dataType = "FOO"; doXmlaHandlerGetValueTypeHint(dataType, XmlaHandler.XSD_STRING); dataType = null; doXmlaHandlerGetValueTypeHint(dataType, null); } public void testXmlaHandlerValueInfo() throws Exception { // Integer or null String dataType = "Integer"; Object inputValue = new Integer(4); String valueType = XmlaHandler.XSD_INT; Object value = inputValue; boolean isDecimal = false; doXmlaHandlerValueInfo( dataType, inputValue, valueType, value, isDecimal); doXmlaHandlerValueInfo( null, inputValue, valueType, value, isDecimal); dataType = "Integer"; inputValue = new Long((long)XmlaHandler.XSD_INT_MAX_INCLUSIVE + 1); valueType = XmlaHandler.XSD_LONG; value = inputValue; isDecimal = false; doXmlaHandlerValueInfo( dataType, inputValue, valueType, value, isDecimal); doXmlaHandlerValueInfo( null, inputValue, valueType, value, isDecimal); dataType = "Integer"; inputValue = new Long((long)XmlaHandler.XSD_INT_MIN_INCLUSIVE - 1); valueType = XmlaHandler.XSD_LONG; value = inputValue; isDecimal = false; doXmlaHandlerValueInfo( dataType, inputValue, valueType, value, isDecimal); doXmlaHandlerValueInfo( null, inputValue, valueType, value, isDecimal); dataType = "Integer"; inputValue = new BigInteger("9223372036854775807"); valueType = XmlaHandler.XSD_LONG; value = new Long(XmlaHandler.XSD_LONG_MAX_INCLUSIVE); isDecimal = false; doXmlaHandlerValueInfo( dataType, inputValue, valueType, value, isDecimal); doXmlaHandlerValueInfo( null, inputValue, valueType, value, isDecimal); dataType = "Integer"; inputValue = new BigInteger("-9223372036854775808"); valueType = XmlaHandler.XSD_LONG; value = new Long(XmlaHandler.XSD_LONG_MIN_INCLUSIVE); isDecimal = false; doXmlaHandlerValueInfo( dataType, inputValue, valueType, value, isDecimal); doXmlaHandlerValueInfo( null, inputValue, valueType, value, isDecimal); // one more than XSD_LONG_MAX_INCLUSIVE dataType = "Integer"; inputValue = new BigInteger("9223372036854775808"); valueType = XmlaHandler.XSD_INTEGER; value = inputValue; isDecimal = false; doXmlaHandlerValueInfo( dataType, inputValue, valueType, value, isDecimal); doXmlaHandlerValueInfo( null, inputValue, valueType, value, isDecimal); // one less than XSD_LONG_MIN_INCLUSIVE dataType = "Integer"; inputValue = new BigInteger("-9223372036854775809"); valueType = XmlaHandler.XSD_INTEGER; value = inputValue; isDecimal = false; doXmlaHandlerValueInfo( dataType, inputValue, valueType, value, isDecimal); doXmlaHandlerValueInfo( null, inputValue, valueType, value, isDecimal); dataType = "Integer"; inputValue = new BigDecimal("9223372036854775807.0"); valueType = XmlaHandler.XSD_LONG; value = new Long(XmlaHandler.XSD_LONG_MAX_INCLUSIVE); isDecimal = false; doXmlaHandlerValueInfo( dataType, inputValue, valueType, value, isDecimal); valueType = XmlaHandler.XSD_DECIMAL; value = inputValue; isDecimal = true; doXmlaHandlerValueInfo( null, inputValue, valueType, value, isDecimal); dataType = "Integer"; inputValue = new BigDecimal("-9223372036854775808.0"); valueType = XmlaHandler.XSD_LONG; value = new Long(XmlaHandler.XSD_LONG_MIN_INCLUSIVE); isDecimal = false; doXmlaHandlerValueInfo( dataType, inputValue, valueType, value, isDecimal); valueType = (Util.Retrowoven) ? XmlaHandler.XSD_DOUBLE : XmlaHandler.XSD_DECIMAL; value = (Util.Retrowoven) ? Double.valueOf("-9.223372036854776E18") : inputValue; isDecimal = true; doXmlaHandlerValueInfo( null, inputValue, valueType, value, isDecimal); // Numeric or null dataType = "Numeric"; inputValue = new Double(4.0); valueType = XmlaHandler.XSD_DOUBLE; value = inputValue; isDecimal = true; doXmlaHandlerValueInfo( dataType, inputValue, valueType, value, isDecimal); doXmlaHandlerValueInfo( null, inputValue, valueType, value, isDecimal); dataType = "Numeric"; inputValue = new Integer(4); valueType = XmlaHandler.XSD_DOUBLE; value = new Double(4.0); isDecimal = true; doXmlaHandlerValueInfo( dataType, inputValue, valueType, value, isDecimal); dataType = "Numeric"; inputValue = new Long(4); valueType = XmlaHandler.XSD_DOUBLE; value = new Double(4.0); isDecimal = true; doXmlaHandlerValueInfo( dataType, inputValue, valueType, value, isDecimal); // MAX_VALUE = 1.7976931348623157e+308 // one less decimal point than max value if (! Util.Retrowoven) { dataType = "Numeric"; inputValue = new BigDecimal("1.797693134862315e+308"); valueType = XmlaHandler.XSD_DOUBLE; value = Double.valueOf("1.797693134862315e+308"); isDecimal = true; doXmlaHandlerValueInfo( dataType, inputValue, valueType, value, isDecimal); } /* does not work - BigDecimal converts double 4.9E-323 to 4.940656458412465E-323 // MIN_VALUE = 4.9e-324 // slightly larger dataType = "Numeric"; inputValue = new BigDecimal("4.9e-323"); //MathContext.DECIMAL32); //MathContext.DECIMAL64); valueType = XmlaHandler.XSD_DOUBLE; value = Double.valueOf("4.9e-323"); System.out.println(" value=" +value); isDecimal = true; doXmlaHandlerValueInfo(dataType, inputValue, valueType, value, isDecimal); */ dataType = "Numeric"; inputValue = new BigDecimal("1.9e+500"); valueType = XmlaHandler.XSD_DECIMAL; value = inputValue; isDecimal = true; doXmlaHandlerValueInfo( dataType, inputValue, valueType, value, isDecimal); dataType = "Numeric"; inputValue = new BigInteger("4"); valueType = XmlaHandler.XSD_INT; value = new Integer(4); isDecimal = false; doXmlaHandlerValueInfo( dataType, inputValue, valueType, value, isDecimal); } // Helper methods protected void doXmlaUtilNormalizeNumericString( final String vin, final String expected) throws Exception { String actual = XmlaUtil.normalizeNumericString(vin); Assert.assertEquals(expected, actual); } protected void doXmlaHandlerGetValueTypeHint( final String dataType, final String expected) throws Exception { String actual = XmlaHandler.ValueInfo.getValueTypeHint(dataType); Assert.assertEquals(expected, actual); } protected void doXmlaHandlerValueInfo( final String dataType, final Object inputValue, final String valueType, final Object value, final boolean isDecimal) throws Exception { XmlaHandler.ValueInfo vi = new XmlaHandler.ValueInfo(dataType, inputValue); Assert.assertEquals("valueType:", valueType, vi.valueType); Assert.assertEquals("value:", value, vi.value); Assert.assertEquals("isDecimal:", isDecimal, vi.isDecimal); } } // End XmlaTests.java mondrian-3.4.1/testsrc/main/mondrian/xmla/XmlaCognosTest.java0000644000175000017500000001633111735330606024172 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2010 Pentaho and others // All Rights Reserved. */ package mondrian.xmla; import mondrian.olap.MondrianProperties; import mondrian.olap.Util; import mondrian.spi.Dialect; import mondrian.test.DiffRepository; import mondrian.test.TestContext; import mondrian.util.Bug; import org.eigenbase.util.property.BooleanProperty; /** * Test suite for compatibility of Mondrian XMLA with Cognos8.2 connected via * Simba O2X bridge. * * @author Thiyagu, Shishir */ public class XmlaCognosTest extends XmlaBaseTestCase { public XmlaCognosTest() { } public XmlaCognosTest(String name) { super(name); } @Override protected String filter( String testCaseName, String filename, String content) { if ("testWithFilter".equals(testCaseName) && filename.equals("response")) { Dialect dialect = TestContext.instance().getDialect(); switch (dialect.getDatabaseProduct()) { case DERBY: content = Util.replace( content, "", ""); break; } } return content; } public void testCognosMDXSuiteHR_001() throws Exception { Dialect dialect = TestContext.instance().getDialect(); switch (dialect.getDatabaseProduct()) { case DERBY: // Derby gives right answer, but many cells have wrong xsi:type. return; } executeMDX(); } public void testCognosMDXSuiteHR_002() throws Exception { Dialect dialect = TestContext.instance().getDialect(); switch (dialect.getDatabaseProduct()) { case DERBY: // Derby gives right answer, but many cells have wrong xsi:type. return; } executeMDX(); } public void testCognosMDXSuiteSales_001() throws Exception { executeMDX(); } public void testCognosMDXSuiteSales_002() throws Exception { executeMDX(); } public void testCognosMDXSuiteSales_003() throws Exception { executeMDX(); } public void testCognosMDXSuiteSales_004() throws Exception { executeMDX(); } public void testCognosMDXSuiteConvertedAdventureWorksToFoodMart_003() throws Exception { executeMDX(); } public void testCognosMDXSuiteConvertedAdventureWorksToFoodMart_005() throws Exception { executeMDX(); } public void testCognosMDXSuiteConvertedAdventureWorksToFoodMart_006() throws Exception { executeMDX(); } public void testCognosMDXSuiteConvertedAdventureWorksToFoodMart_007() throws Exception { executeMDX(); } // disabled because runs out of memory/hangs public void _testCognosMDXSuiteConvertedAdventureWorksToFoodMart_009() throws Exception { executeMDX(); } // disabled because runs out of memory/hangs public void _testCognosMDXSuiteConvertedAdventureWorksToFoodMart_012() throws Exception { executeMDX(); } public void testCognosMDXSuiteConvertedAdventureWorksToFoodMart_013() throws Exception { executeMDX(); } // disabled because runs out of memory/hangs public void _testCognosMDXSuiteConvertedAdventureWorksToFoodMart_014() throws Exception { executeMDX(); } public void testCognosMDXSuiteConvertedAdventureWorksToFoodMart_015() throws Exception { executeMDX(); } public void testCognosMDXSuiteConvertedAdventureWorksToFoodMart_016() throws Exception { executeMDX(); } public void testCognosMDXSuiteConvertedAdventureWorksToFoodMart_017() throws Exception { executeMDX(); } public void testCognosMDXSuiteConvertedAdventureWorksToFoodMart_020() throws Exception { executeMDX(); } public void testCognosMDXSuiteConvertedAdventureWorksToFoodMart_021() throws Exception { executeMDX(); } public void testCognosMDXSuiteConvertedAdventureWorksToFoodMart_024() throws Exception { executeMDX(); } public void testCognosMDXSuiteConvertedAdventureWorksToFoodMart_028() throws Exception { executeMDX(); } public void testCognosMDXSuiteConvertedAdventureWorksToFoodMart_029() throws Exception { executeMDX(); } public void testDimensionPropertyForPercentageIssue() throws Exception { executeMDX(); } public void testNegativeSolveOrder() throws Exception { executeMDX(); } public void testNonEmptyWithCognosCalcOneLiteral() throws Exception { final BooleanProperty enableNonEmptyOnAllAxes = MondrianProperties.instance().EnableNonEmptyOnAllAxis; boolean nonEmptyAllAxesCurrentState = enableNonEmptyOnAllAxes.get(); final BooleanProperty enableNativeNonEmpty = MondrianProperties.instance().EnableNativeNonEmpty; boolean nativeNonemptyCurrentState = enableNativeNonEmpty.get(); try { enableNonEmptyOnAllAxes.set(true); enableNativeNonEmpty.set(false); executeMDX(); if (Bug.BugMondrian446Fixed) { enableNativeNonEmpty.set(true); executeMDX(); } } finally { enableNativeNonEmpty.set(nativeNonemptyCurrentState); enableNonEmptyOnAllAxes.set(nonEmptyAllAxesCurrentState); } } public void testCellProperties() throws Exception { executeMDX(); } public void testCrossJoin() throws Exception { executeMDX(); } public void testWithFilterOn3rdAxis() throws Exception { executeMDX(); } public void testWithSorting() throws Exception { executeMDX(); } public void testWithFilter() throws Exception { if (getTestContext().getDialect().getDatabaseProduct() == Dialect.DatabaseProduct.ACCESS) { // Disabled because of bug on access: generates query with // distinct-count even though access does not support it. Bug // 2685902, "Mondrian generates invalid count distinct on access" // logged. return; } executeMDX(); } public void testWithAggregation() throws Exception { executeMDX(); } private void executeMDX() throws Exception { String requestType = "EXECUTE"; doTest( requestType, getDefaultRequestProperties(requestType), TestContext.instance()); } protected DiffRepository getDiffRepos() { return DiffRepository.lookup(XmlaCognosTest.class); } protected Class getServletCallbackClass() { return null; } protected String getSessionId(Action action) { return null; } } // End XmlaCognosTest.java mondrian-3.4.1/testsrc/main/mondrian/xmla/XmlaBasicTest.ref.xml0000644000175000017500000522202611735330606024422 0ustar drazzibdrazzib ]]> ${request.type} ${data.source.info} ${catalog} ${format} ${content} ]]> FoodMart FoodMart HR CUBE xxxx-xx-xxTxx:xx:xx true false false false HR FoodMart Schema - HR Cube FoodMart FoodMart Sales CUBE xxxx-xx-xxTxx:xx:xx true false false false Sales FoodMart Schema - Sales Cube FoodMart FoodMart Sales 2 CUBE xxxx-xx-xxTxx:xx:xx true false false false Sales 2 FoodMart Schema - Sales 2 Cube FoodMart FoodMart Sales Ragged CUBE xxxx-xx-xxTxx:xx:xx true false false false Sales Ragged FoodMart Schema - Sales Ragged Cube FoodMart FoodMart Store CUBE xxxx-xx-xxTxx:xx:xx true false false false Store FoodMart Schema - Store Cube FoodMart FoodMart Warehouse CUBE xxxx-xx-xxTxx:xx:xx true false false false Warehouse FoodMart Schema - Warehouse Cube FoodMart FoodMart Warehouse and Sales VIRTUAL CUBE xxxx-xx-xxTxx:xx:xx true false false false Warehouse and Sales FoodMart Schema - Warehouse and Sales Cube ]]> ${request.type} ${data.source.info} ${catalog} ${format} ${content} ]]> FoodMart FoodMart Sales CUBE xxxx-xx-xxTxx:xx:xx true false false false Verkaufen Cube Verkaufen ]]> ${request.type} ${cube.name} ${data.source.info} ${catalog} ${format} ${content} ${locale} ]]> FoodMart FoodMart Sales CUBE xxxx-xx-xxTxx:xx:xx true false false false Ventes Cube des ventes ]]> ${request.type} ${cube.name} ${data.source.info} ${catalog} ${format} ${content} ${locale} ]]> FoodMart FoodMart HR CUBE xxxx-xx-xxTxx:xx:xx true false false false HR FoodMart Schema - HR Cube FoodMart FoodMart HR Measures [Measures] Measures 0 2 6 [Measures] HR Cube - Measures Dimension false false 0 true FoodMart FoodMart HR [Measures] Measures [Measures] Measures 2 5 [Measures].[Org Salary] HR Cube - Measures Hierarchy 0 false false 0 true true 0 true false FoodMart FoodMart HR [Measures] [Measures] MeasuresLevel [Measures].[MeasuresLevel] MeasuresLevel 0 5 0 0 0 true HR Cube - Measures Hierarchy - MeasuresLevel Level FoodMart FoodMart HR Time [Time] Time 1 1 25 [Time] HR Cube - Time Dimension false false 0 true FoodMart FoodMart HR [Time] Time [Time] Time 1 34 [Time].[1997] HR Cube - Time Hierarchy 0 false false 0 true true 1 true false FoodMart FoodMart HR [Time] [Time] Year [Time].[Year] Year 0 2 20 0 1 true HR Cube - Time Hierarchy - Year Level FoodMart FoodMart HR [Time] [Time] Quarter [Time].[Quarter] Quarter 1 8 68 0 0 true HR Cube - Time Hierarchy - Quarter Level FoodMart FoodMart HR [Time] [Time] Month [Time].[Month] Month 2 24 132 0 0 true HR Cube - Time Hierarchy - Month Level FoodMart FoodMart HR Store [Store] Store 2 3 26 [Store] HR Cube - Store Dimension false false 0 true FoodMart FoodMart HR [Store] Store [Store] Store 3 63 [Store].[All Stores] [Store].[All Stores] HR Cube - Store Hierarchy 0 false false 0 true true 2 true false FoodMart FoodMart HR [Store] [Store] (All) [Store].[(All)] (All) 0 1 1 0 3 true HR Cube - Store Hierarchy - (All) Level FoodMart FoodMart HR [Store] [Store] Store Country [Store].[Store Country] Store Country 1 3 0 0 1 true HR Cube - Store Hierarchy - Store Country Level FoodMart FoodMart HR [Store] [Store] Store State [Store].[Store State] Store State 2 10 0 0 1 true HR Cube - Store Hierarchy - Store State Level FoodMart FoodMart HR [Store] [Store] Store City [Store].[Store City] Store City 3 24 0 0 0 true HR Cube - Store Hierarchy - Store City Level FoodMart FoodMart HR [Store] [Store] Store Name [Store].[Store Name] Store Name 4 25 0 0 1 true HR Cube - Store Hierarchy - Store Name Level FoodMart FoodMart HR Pay Type [Pay Type] Pay Type 3 3 3 [Pay Type] HR Cube - Pay Type Dimension false false 0 true FoodMart FoodMart HR [Pay Type] Pay Type [Pay Type] Pay Type 3 3 [Pay Type].[All Pay Types] [Pay Type].[All Pay Types] HR Cube - Pay Type Hierarchy 0 false false 0 true true 3 true false FoodMart FoodMart HR [Pay Type] [Pay Type] (All) [Pay Type].[(All)] (All) 0 1 1 0 3 true HR Cube - Pay Type Hierarchy - (All) Level FoodMart FoodMart HR [Pay Type] [Pay Type] Pay Type [Pay Type].[Pay Type] Pay Type 1 2 0 0 1 true HR Cube - Pay Type Hierarchy - Pay Type Level FoodMart FoodMart HR Store Type [Store Type] Store Type 4 3 7 [Store Type] HR Cube - Store Type Dimension false false 0 true FoodMart FoodMart HR [Store Type] Store Type [Store Type] Store Type 3 7 [Store Type].[All Store Types] [Store Type].[All Store Types] HR Cube - Store Type Hierarchy 0 false false 0 true true 4 true false FoodMart FoodMart HR [Store Type] [Store Type] (All) [Store Type].[(All)] (All) 0 1 1 0 3 true HR Cube - Store Type Hierarchy - (All) Level FoodMart FoodMart HR [Store Type] [Store Type] Store Type [Store Type].[Store Type] Store Type 1 6 0 0 1 true HR Cube - Store Type Hierarchy - Store Type Level FoodMart FoodMart HR Position [Position] Position 5 3 19 [Position] HR Cube - Position Dimension false false 0 true FoodMart FoodMart HR [Position] Position [Position] Position 3 24 [Position].[All Position] [Position].[All Position] HR Cube - Position Hierarchy 0 false false 0 true true 5 true false FoodMart FoodMart HR [Position] [Position] (All) [Position].[(All)] (All) 0 1 1 0 3 true HR Cube - Position Hierarchy - (All) Level FoodMart FoodMart HR [Position] [Position] Management Role [Position].[Management Role] Management Role 1 5 0 0 1 true HR Cube - Position Hierarchy - Management Role Level FoodMart FoodMart HR [Position] [Position] Position Title [Position].[Position Title] Position Title 2 18 0 0 0 true HR Cube - Position Hierarchy - Position Title Level FoodMart FoodMart HR Department [Department] Department 6 3 13 [Department] HR Cube - Department Dimension false false 0 true FoodMart FoodMart HR [Department] Department [Department] Department 3 13 [Department].[All Departments] [Department].[All Departments] HR Cube - Department Hierarchy 0 false false 0 true true 6 true false FoodMart FoodMart HR [Department] [Department] (All) [Department].[(All)] (All) 0 1 1 0 3 true HR Cube - Department Hierarchy - (All) Level FoodMart FoodMart HR [Department] [Department] Department Description [Department].[Department Description] Department Description 1 12 0 0 1 true HR Cube - Department Hierarchy - Department Description Level FoodMart FoodMart HR Employees [Employees] Employees 7 3 1156 [Employees] HR Cube - Employees Dimension false false 0 true FoodMart FoodMart HR [Employees] Employees [Employees] Employees 3 1156 [Employees].[All Employees] [Employees].[All Employees] HR Cube - Employees Hierarchy 0 false false 0 true true 7 true true FoodMart FoodMart HR [Employees] [Employees] (All) [Employees].[(All)] (All) 0 1 1 0 3 true HR Cube - Employees Hierarchy - (All) Level FoodMart FoodMart HR [Employees] [Employees] Employee Id [Employees].[Employee Id] Employee Id 1 1155 0 0 1 true HR Cube - Employees Hierarchy - Employee Id Level FoodMart FoodMart HR Org Salary [Measures].[Org Salary] Org Salary 1 5 true [Time].[Month],[Store].[Store Name],[Pay Type].[Pay Type],[Store Type].[Store Type],[Position].[Position Title],[Department].[Department Description],[Employees].[Employee Id] HR Cube - Org Salary Member FoodMart FoodMart HR Count [Measures].[Count] Count 2 3 true [Time].[Month],[Store].[Store Name],[Pay Type].[Pay Type],[Store Type].[Store Type],[Position].[Position Title],[Department].[Department Description],[Employees].[Employee Id] HR Cube - Count Member FoodMart FoodMart HR Number of Employees [Measures].[Number of Employees] Number of Employees 0 3 true [Time].[Month],[Store].[Store Name],[Pay Type].[Pay Type],[Store Type].[Store Type],[Position].[Position Title],[Department].[Department Description],[Employees].[Employee Id] HR Cube - Number of Employees Member FoodMart FoodMart HR Employee Salary [Measures].[Employee Salary] Employee Salary 127 130 true HR Cube - Employee Salary Member FoodMart FoodMart HR Avg Salary [Measures].[Avg Salary] Avg Salary 127 130 true HR Cube - Avg Salary Member ]]> ${request.type} ${catalog} ${cube.name} ${data.source.info} ${catalog} ${format} ${content} application/soap+xml true ]]> ${request.type} ${catalog} ${cube.name} ${data.source.info} ${catalog} ${format} ${content} application/json true ]]> FoodMart FoodMart Warehouse [Top Sellers] 1 ]]> ${request.type} ${data.source.info} ${catalog} ${format} ${content} ]]> FoodMart HR:Department:(All) SYSTEM TABLE FoodMart - HR Cube - Department Hierarchy - (All) Level FoodMart HR:Department:Department Description SYSTEM TABLE FoodMart - HR Cube - Department Hierarchy - Department Description Level FoodMart HR:Employees:(All) SYSTEM TABLE FoodMart - HR Cube - Employees Hierarchy - (All) Level FoodMart HR:Employees:Employee Id SYSTEM TABLE FoodMart - HR Cube - Employees Hierarchy - Employee Id Level FoodMart HR:Pay Type:(All) SYSTEM TABLE FoodMart - HR Cube - Pay Type Hierarchy - (All) Level FoodMart HR:Pay Type:Pay Type SYSTEM TABLE FoodMart - HR Cube - Pay Type Hierarchy - Pay Type Level FoodMart HR:Position:(All) SYSTEM TABLE FoodMart - HR Cube - Position Hierarchy - (All) Level FoodMart HR:Position:Management Role SYSTEM TABLE FoodMart - HR Cube - Position Hierarchy - Management Role Level FoodMart HR:Position:Position Title SYSTEM TABLE FoodMart - HR Cube - Position Hierarchy - Position Title Level FoodMart HR:Store Type:(All) SYSTEM TABLE FoodMart - HR Cube - Store Type Hierarchy - (All) Level FoodMart HR:Store Type:Store Type SYSTEM TABLE FoodMart - HR Cube - Store Type Hierarchy - Store Type Level FoodMart HR:Store:(All) SYSTEM TABLE FoodMart - HR Cube - Store Hierarchy - (All) Level FoodMart HR:Store:Store City SYSTEM TABLE FoodMart - HR Cube - Store Hierarchy - Store City Level FoodMart HR:Store:Store Country SYSTEM TABLE FoodMart - HR Cube - Store Hierarchy - Store Country Level FoodMart HR:Store:Store Name SYSTEM TABLE FoodMart - HR Cube - Store Hierarchy - Store Name Level FoodMart HR:Store:Store State SYSTEM TABLE FoodMart - HR Cube - Store Hierarchy - Store State Level FoodMart HR:Time:Month SYSTEM TABLE FoodMart - HR Cube - Time Hierarchy - Month Level FoodMart HR:Time:Quarter SYSTEM TABLE FoodMart - HR Cube - Time Hierarchy - Quarter Level FoodMart HR:Time:Year SYSTEM TABLE FoodMart - HR Cube - Time Hierarchy - Year Level FoodMart Sales 2:Gender:(All) SYSTEM TABLE FoodMart - Sales 2 Cube - Gender Hierarchy - (All) Level FoodMart Sales 2:Gender:Gender SYSTEM TABLE FoodMart - Sales 2 Cube - Gender Hierarchy - Gender Level FoodMart Sales 2:Product:(All) SYSTEM TABLE FoodMart - Sales 2 Cube - Product Hierarchy - (All) Level FoodMart Sales 2:Product:Brand Name SYSTEM TABLE FoodMart - Sales 2 Cube - Product Hierarchy - Brand Name Level FoodMart Sales 2:Product:Product Category SYSTEM TABLE FoodMart - Sales 2 Cube - Product Hierarchy - Product Category Level FoodMart Sales 2:Product:Product Department SYSTEM TABLE FoodMart - Sales 2 Cube - Product Hierarchy - Product Department Level FoodMart Sales 2:Product:Product Family SYSTEM TABLE FoodMart - Sales 2 Cube - Product Hierarchy - Product Family Level FoodMart Sales 2:Product:Product Name SYSTEM TABLE FoodMart - Sales 2 Cube - Product Hierarchy - Product Name Level FoodMart Sales 2:Product:Product Subcategory SYSTEM TABLE FoodMart - Sales 2 Cube - Product Hierarchy - Product Subcategory Level FoodMart Sales 2:Time.Weekly:(All) SYSTEM TABLE FoodMart - Sales 2 Cube - Time.Weekly Hierarchy - (All) Level FoodMart Sales 2:Time.Weekly:Day SYSTEM TABLE FoodMart - Sales 2 Cube - Time.Weekly Hierarchy - Day Level FoodMart Sales 2:Time.Weekly:Week SYSTEM TABLE FoodMart - Sales 2 Cube - Time.Weekly Hierarchy - Week Level FoodMart Sales 2:Time.Weekly:Year SYSTEM TABLE FoodMart - Sales 2 Cube - Time.Weekly Hierarchy - Year Level FoodMart Sales 2:Time:Month SYSTEM TABLE FoodMart - Sales 2 Cube - Time Hierarchy - Month Level FoodMart Sales 2:Time:Quarter SYSTEM TABLE FoodMart - Sales 2 Cube - Time Hierarchy - Quarter Level FoodMart Sales 2:Time:Year SYSTEM TABLE FoodMart - Sales 2 Cube - Time Hierarchy - Year Level FoodMart Sales Ragged:Customers:(All) SYSTEM TABLE FoodMart - Sales Ragged Cube - Customers Hierarchy - (All) Level FoodMart Sales Ragged:Customers:City SYSTEM TABLE FoodMart - Sales Ragged Cube - Customers Hierarchy - City Level FoodMart Sales Ragged:Customers:Country SYSTEM TABLE FoodMart - Sales Ragged Cube - Customers Hierarchy - Country Level FoodMart Sales Ragged:Customers:Name SYSTEM TABLE FoodMart - Sales Ragged Cube - Customers Hierarchy - Name Level FoodMart Sales Ragged:Customers:State Province SYSTEM TABLE FoodMart - Sales Ragged Cube - Customers Hierarchy - State Province Level FoodMart Sales Ragged:Education Level:(All) SYSTEM TABLE FoodMart - Sales Ragged Cube - Education Level Hierarchy - (All) Level FoodMart Sales Ragged:Education Level:Education Level SYSTEM TABLE FoodMart - Sales Ragged Cube - Education Level Hierarchy - Education Level Level FoodMart Sales Ragged:Gender:(All) SYSTEM TABLE FoodMart - Sales Ragged Cube - Gender Hierarchy - (All) Level FoodMart Sales Ragged:Gender:Gender SYSTEM TABLE FoodMart - Sales Ragged Cube - Gender Hierarchy - Gender Level FoodMart Sales Ragged:Geography:(All) SYSTEM TABLE FoodMart - Sales Ragged Cube - Geography Hierarchy - (All) Level FoodMart Sales Ragged:Geography:City SYSTEM TABLE FoodMart - Sales Ragged Cube - Geography Hierarchy - City Level FoodMart Sales Ragged:Geography:Country SYSTEM TABLE FoodMart - Sales Ragged Cube - Geography Hierarchy - Country Level FoodMart Sales Ragged:Geography:State SYSTEM TABLE FoodMart - Sales Ragged Cube - Geography Hierarchy - State Level FoodMart Sales Ragged:Marital Status:(All) SYSTEM TABLE FoodMart - Sales Ragged Cube - Marital Status Hierarchy - (All) Level FoodMart Sales Ragged:Marital Status:Marital Status SYSTEM TABLE FoodMart - Sales Ragged Cube - Marital Status Hierarchy - Marital Status Level FoodMart Sales Ragged:Product:(All) SYSTEM TABLE FoodMart - Sales Ragged Cube - Product Hierarchy - (All) Level FoodMart Sales Ragged:Product:Brand Name SYSTEM TABLE FoodMart - Sales Ragged Cube - Product Hierarchy - Brand Name Level FoodMart Sales Ragged:Product:Product Category SYSTEM TABLE FoodMart - Sales Ragged Cube - Product Hierarchy - Product Category Level FoodMart Sales Ragged:Product:Product Department SYSTEM TABLE FoodMart - Sales Ragged Cube - Product Hierarchy - Product Department Level FoodMart Sales Ragged:Product:Product Family SYSTEM TABLE FoodMart - Sales Ragged Cube - Product Hierarchy - Product Family Level FoodMart Sales Ragged:Product:Product Name SYSTEM TABLE FoodMart - Sales Ragged Cube - Product Hierarchy - Product Name Level FoodMart Sales Ragged:Product:Product Subcategory SYSTEM TABLE FoodMart - Sales Ragged Cube - Product Hierarchy - Product Subcategory Level FoodMart Sales Ragged:Promotion Media:(All) SYSTEM TABLE FoodMart - Sales Ragged Cube - Promotion Media Hierarchy - (All) Level FoodMart Sales Ragged:Promotion Media:Media Type SYSTEM TABLE FoodMart - Sales Ragged Cube - Promotion Media Hierarchy - Media Type Level FoodMart Sales Ragged:Promotions:(All) SYSTEM TABLE FoodMart - Sales Ragged Cube - Promotions Hierarchy - (All) Level FoodMart Sales Ragged:Promotions:Promotion Name SYSTEM TABLE FoodMart - Sales Ragged Cube - Promotions Hierarchy - Promotion Name Level FoodMart Sales Ragged:Store Size in SQFT:(All) SYSTEM TABLE FoodMart - Sales Ragged Cube - Store Size in SQFT Hierarchy - (All) Level FoodMart Sales Ragged:Store Size in SQFT:Store Sqft SYSTEM TABLE FoodMart - Sales Ragged Cube - Store Size in SQFT Hierarchy - Store Sqft Level FoodMart Sales Ragged:Store Type:(All) SYSTEM TABLE FoodMart - Sales Ragged Cube - Store Type Hierarchy - (All) Level FoodMart Sales Ragged:Store Type:Store Type SYSTEM TABLE FoodMart - Sales Ragged Cube - Store Type Hierarchy - Store Type Level FoodMart Sales Ragged:Store:(All) SYSTEM TABLE FoodMart - Sales Ragged Cube - Store Hierarchy - (All) Level FoodMart Sales Ragged:Store:Store City SYSTEM TABLE FoodMart - Sales Ragged Cube - Store Hierarchy - Store City Level FoodMart Sales Ragged:Store:Store Country SYSTEM TABLE FoodMart - Sales Ragged Cube - Store Hierarchy - Store Country Level FoodMart Sales Ragged:Store:Store Name SYSTEM TABLE FoodMart - Sales Ragged Cube - Store Hierarchy - Store Name Level FoodMart Sales Ragged:Store:Store State SYSTEM TABLE FoodMart - Sales Ragged Cube - Store Hierarchy - Store State Level FoodMart Sales Ragged:Time.Weekly:(All) SYSTEM TABLE FoodMart - Sales Ragged Cube - Time.Weekly Hierarchy - (All) Level FoodMart Sales Ragged:Time.Weekly:Day SYSTEM TABLE FoodMart - Sales Ragged Cube - Time.Weekly Hierarchy - Day Level FoodMart Sales Ragged:Time.Weekly:Week SYSTEM TABLE FoodMart - Sales Ragged Cube - Time.Weekly Hierarchy - Week Level FoodMart Sales Ragged:Time.Weekly:Year SYSTEM TABLE FoodMart - Sales Ragged Cube - Time.Weekly Hierarchy - Year Level FoodMart Sales Ragged:Time:Month SYSTEM TABLE FoodMart - Sales Ragged Cube - Time Hierarchy - Month Level FoodMart Sales Ragged:Time:Quarter SYSTEM TABLE FoodMart - Sales Ragged Cube - Time Hierarchy - Quarter Level FoodMart Sales Ragged:Time:Year SYSTEM TABLE FoodMart - Sales Ragged Cube - Time Hierarchy - Year Level FoodMart Sales Ragged:Yearly Income:(All) SYSTEM TABLE FoodMart - Sales Ragged Cube - Yearly Income Hierarchy - (All) Level FoodMart Sales Ragged:Yearly Income:Yearly Income SYSTEM TABLE FoodMart - Sales Ragged Cube - Yearly Income Hierarchy - Yearly Income Level FoodMart Sales:Customers:(All) SYSTEM TABLE FoodMart - Sales Cube - Customers Hierarchy - (All) Level FoodMart Sales:Customers:City SYSTEM TABLE FoodMart - Sales Cube - Customers Hierarchy - City Level FoodMart Sales:Customers:Country SYSTEM TABLE FoodMart - Sales Cube - Customers Hierarchy - Country Level FoodMart Sales:Customers:Name SYSTEM TABLE FoodMart - Sales Cube - Customers Hierarchy - Name Level FoodMart Sales:Customers:State Province SYSTEM TABLE FoodMart - Sales Cube - Customers Hierarchy - State Province Level FoodMart Sales:Education Level:(All) SYSTEM TABLE FoodMart - Sales Cube - Education Level Hierarchy - (All) Level FoodMart Sales:Education Level:Education Level SYSTEM TABLE FoodMart - Sales Cube - Education Level Hierarchy - Education Level Level FoodMart Sales:Gender:(All) SYSTEM TABLE FoodMart - Sales Cube - Gender Hierarchy - (All) Level FoodMart Sales:Gender:Gender SYSTEM TABLE FoodMart - Sales Cube - Gender Hierarchy - Gender Level FoodMart Sales:Marital Status:(All) SYSTEM TABLE FoodMart - Sales Cube - Marital Status Hierarchy - (All) Level FoodMart Sales:Marital Status:Marital Status SYSTEM TABLE FoodMart - Sales Cube - Marital Status Hierarchy - Marital Status Level FoodMart Sales:Product:(All) SYSTEM TABLE FoodMart - Sales Cube - Product Hierarchy - (All) Level FoodMart Sales:Product:Brand Name SYSTEM TABLE FoodMart - Sales Cube - Product Hierarchy - Brand Name Level FoodMart Sales:Product:Product Category SYSTEM TABLE FoodMart - Sales Cube - Product Hierarchy - Product Category Level FoodMart Sales:Product:Product Department SYSTEM TABLE FoodMart - Sales Cube - Product Hierarchy - Product Department Level FoodMart Sales:Product:Product Family SYSTEM TABLE FoodMart - Sales Cube - Product Hierarchy - Product Family Level FoodMart Sales:Product:Product Name SYSTEM TABLE FoodMart - Sales Cube - Product Hierarchy - Product Name Level FoodMart Sales:Product:Product Subcategory SYSTEM TABLE FoodMart - Sales Cube - Product Hierarchy - Product Subcategory Level FoodMart Sales:Promotion Media:(All) SYSTEM TABLE FoodMart - Sales Cube - Promotion Media Hierarchy - (All) Level FoodMart Sales:Promotion Media:Media Type SYSTEM TABLE FoodMart - Sales Cube - Promotion Media Hierarchy - Media Type Level FoodMart Sales:Promotions:(All) SYSTEM TABLE FoodMart - Sales Cube - Promotions Hierarchy - (All) Level FoodMart Sales:Promotions:Promotion Name SYSTEM TABLE FoodMart - Sales Cube - Promotions Hierarchy - Promotion Name Level FoodMart Sales:Store Size in SQFT:(All) SYSTEM TABLE FoodMart - Sales Cube - Store Size in SQFT Hierarchy - (All) Level FoodMart Sales:Store Size in SQFT:Store Sqft SYSTEM TABLE FoodMart - Sales Cube - Store Size in SQFT Hierarchy - Store Sqft Level FoodMart Sales:Store Type:(All) SYSTEM TABLE FoodMart - Sales Cube - Store Type Hierarchy - (All) Level FoodMart Sales:Store Type:Store Type SYSTEM TABLE FoodMart - Sales Cube - Store Type Hierarchy - Store Type Level FoodMart Sales:Store:(All) SYSTEM TABLE FoodMart - Sales Cube - Store Hierarchy - (All) Level FoodMart Sales:Store:Store City SYSTEM TABLE FoodMart - Sales Cube - Store Hierarchy - Store City Level FoodMart Sales:Store:Store Country SYSTEM TABLE FoodMart - Sales Cube - Store Hierarchy - Store Country Level FoodMart Sales:Store:Store Name SYSTEM TABLE FoodMart - Sales Cube - Store Hierarchy - Store Name Level FoodMart Sales:Store:Store State SYSTEM TABLE FoodMart - Sales Cube - Store Hierarchy - Store State Level FoodMart Sales:Time.Weekly:(All) SYSTEM TABLE FoodMart - Sales Cube - Time.Weekly Hierarchy - (All) Level FoodMart Sales:Time.Weekly:Day SYSTEM TABLE FoodMart - Sales Cube - Time.Weekly Hierarchy - Day Level FoodMart Sales:Time.Weekly:Week SYSTEM TABLE FoodMart - Sales Cube - Time.Weekly Hierarchy - Week Level FoodMart Sales:Time.Weekly:Year SYSTEM TABLE FoodMart - Sales Cube - Time.Weekly Hierarchy - Year Level FoodMart Sales:Time:Month SYSTEM TABLE FoodMart - Sales Cube - Time Hierarchy - Month Level FoodMart Sales:Time:Quarter SYSTEM TABLE FoodMart - Sales Cube - Time Hierarchy - Quarter Level FoodMart Sales:Time:Year SYSTEM TABLE FoodMart - Sales Cube - Time Hierarchy - Year Level FoodMart Sales:Yearly Income:(All) SYSTEM TABLE FoodMart - Sales Cube - Yearly Income Hierarchy - (All) Level FoodMart Sales:Yearly Income:Yearly Income SYSTEM TABLE FoodMart - Sales Cube - Yearly Income Hierarchy - Yearly Income Level FoodMart Store:Has coffee bar:(All) SYSTEM TABLE FoodMart - Store Cube - Has coffee bar Hierarchy - (All) Level FoodMart Store:Has coffee bar:Has coffee bar SYSTEM TABLE FoodMart - Store Cube - Has coffee bar Hierarchy - Has coffee bar Level FoodMart Store:Store Type:(All) SYSTEM TABLE FoodMart - Store Cube - Store Type Hierarchy - (All) Level FoodMart Store:Store Type:Store Type SYSTEM TABLE FoodMart - Store Cube - Store Type Hierarchy - Store Type Level FoodMart Store:Store:(All) SYSTEM TABLE FoodMart - Store Cube - Store Hierarchy - (All) Level FoodMart Store:Store:Store City SYSTEM TABLE FoodMart - Store Cube - Store Hierarchy - Store City Level FoodMart Store:Store:Store Country SYSTEM TABLE FoodMart - Store Cube - Store Hierarchy - Store Country Level FoodMart Store:Store:Store Name SYSTEM TABLE FoodMart - Store Cube - Store Hierarchy - Store Name Level FoodMart Store:Store:Store State SYSTEM TABLE FoodMart - Store Cube - Store Hierarchy - Store State Level FoodMart Warehouse and Sales:Customers:(All) SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Customers Hierarchy - (All) Level FoodMart Warehouse and Sales:Customers:City SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Customers Hierarchy - City Level FoodMart Warehouse and Sales:Customers:Country SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Customers Hierarchy - Country Level FoodMart Warehouse and Sales:Customers:Name SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Customers Hierarchy - Name Level FoodMart Warehouse and Sales:Customers:State Province SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Customers Hierarchy - State Province Level FoodMart Warehouse and Sales:Education Level:(All) SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Education Level Hierarchy - (All) Level FoodMart Warehouse and Sales:Education Level:Education Level SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Education Level Hierarchy - Education Level Level FoodMart Warehouse and Sales:Gender:(All) SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Gender Hierarchy - (All) Level FoodMart Warehouse and Sales:Gender:Gender SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Gender Hierarchy - Gender Level FoodMart Warehouse and Sales:Marital Status:(All) SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Marital Status Hierarchy - (All) Level FoodMart Warehouse and Sales:Marital Status:Marital Status SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Marital Status Hierarchy - Marital Status Level FoodMart Warehouse and Sales:Product:(All) SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Product Hierarchy - (All) Level FoodMart Warehouse and Sales:Product:Brand Name SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Product Hierarchy - Brand Name Level FoodMart Warehouse and Sales:Product:Product Category SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Product Hierarchy - Product Category Level FoodMart Warehouse and Sales:Product:Product Department SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Product Hierarchy - Product Department Level FoodMart Warehouse and Sales:Product:Product Family SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Product Hierarchy - Product Family Level FoodMart Warehouse and Sales:Product:Product Name SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Product Hierarchy - Product Name Level FoodMart Warehouse and Sales:Product:Product Subcategory SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Product Hierarchy - Product Subcategory Level FoodMart Warehouse and Sales:Promotion Media:(All) SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Promotion Media Hierarchy - (All) Level FoodMart Warehouse and Sales:Promotion Media:Media Type SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Promotion Media Hierarchy - Media Type Level FoodMart Warehouse and Sales:Promotions:(All) SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Promotions Hierarchy - (All) Level FoodMart Warehouse and Sales:Promotions:Promotion Name SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Promotions Hierarchy - Promotion Name Level FoodMart Warehouse and Sales:Store:(All) SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Store Hierarchy - (All) Level FoodMart Warehouse and Sales:Store:Store City SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Store Hierarchy - Store City Level FoodMart Warehouse and Sales:Store:Store Country SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Store Hierarchy - Store Country Level FoodMart Warehouse and Sales:Store:Store Name SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Store Hierarchy - Store Name Level FoodMart Warehouse and Sales:Store:Store State SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Store Hierarchy - Store State Level FoodMart Warehouse and Sales:Time.Weekly:(All) SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Time.Weekly Hierarchy - (All) Level FoodMart Warehouse and Sales:Time.Weekly:Day SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Time.Weekly Hierarchy - Day Level FoodMart Warehouse and Sales:Time.Weekly:Week SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Time.Weekly Hierarchy - Week Level FoodMart Warehouse and Sales:Time.Weekly:Year SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Time.Weekly Hierarchy - Year Level FoodMart Warehouse and Sales:Time:Month SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Time Hierarchy - Month Level FoodMart Warehouse and Sales:Time:Quarter SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Time Hierarchy - Quarter Level FoodMart Warehouse and Sales:Time:Year SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Time Hierarchy - Year Level FoodMart Warehouse and Sales:Warehouse:(All) SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Warehouse Hierarchy - (All) Level FoodMart Warehouse and Sales:Warehouse:City SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Warehouse Hierarchy - City Level FoodMart Warehouse and Sales:Warehouse:Country SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Warehouse Hierarchy - Country Level FoodMart Warehouse and Sales:Warehouse:State Province SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Warehouse Hierarchy - State Province Level FoodMart Warehouse and Sales:Warehouse:Warehouse Name SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Warehouse Hierarchy - Warehouse Name Level FoodMart Warehouse and Sales:Yearly Income:(All) SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Yearly Income Hierarchy - (All) Level FoodMart Warehouse and Sales:Yearly Income:Yearly Income SYSTEM TABLE FoodMart - Warehouse and Sales Cube - Yearly Income Hierarchy - Yearly Income Level FoodMart Warehouse:Product:(All) SYSTEM TABLE FoodMart - Warehouse Cube - Product Hierarchy - (All) Level FoodMart Warehouse:Product:Brand Name SYSTEM TABLE FoodMart - Warehouse Cube - Product Hierarchy - Brand Name Level FoodMart Warehouse:Product:Product Category SYSTEM TABLE FoodMart - Warehouse Cube - Product Hierarchy - Product Category Level FoodMart Warehouse:Product:Product Department SYSTEM TABLE FoodMart - Warehouse Cube - Product Hierarchy - Product Department Level FoodMart Warehouse:Product:Product Family SYSTEM TABLE FoodMart - Warehouse Cube - Product Hierarchy - Product Family Level FoodMart Warehouse:Product:Product Name SYSTEM TABLE FoodMart - Warehouse Cube - Product Hierarchy - Product Name Level FoodMart Warehouse:Product:Product Subcategory SYSTEM TABLE FoodMart - Warehouse Cube - Product Hierarchy - Product Subcategory Level FoodMart Warehouse:Store Size in SQFT:(All) SYSTEM TABLE FoodMart - Warehouse Cube - Store Size in SQFT Hierarchy - (All) Level FoodMart Warehouse:Store Size in SQFT:Store Sqft SYSTEM TABLE FoodMart - Warehouse Cube - Store Size in SQFT Hierarchy - Store Sqft Level FoodMart Warehouse:Store Type:(All) SYSTEM TABLE FoodMart - Warehouse Cube - Store Type Hierarchy - (All) Level FoodMart Warehouse:Store Type:Store Type SYSTEM TABLE FoodMart - Warehouse Cube - Store Type Hierarchy - Store Type Level FoodMart Warehouse:Store:(All) SYSTEM TABLE FoodMart - Warehouse Cube - Store Hierarchy - (All) Level FoodMart Warehouse:Store:Store City SYSTEM TABLE FoodMart - Warehouse Cube - Store Hierarchy - Store City Level FoodMart Warehouse:Store:Store Country SYSTEM TABLE FoodMart - Warehouse Cube - Store Hierarchy - Store Country Level FoodMart Warehouse:Store:Store Name SYSTEM TABLE FoodMart - Warehouse Cube - Store Hierarchy - Store Name Level FoodMart Warehouse:Store:Store State SYSTEM TABLE FoodMart - Warehouse Cube - Store Hierarchy - Store State Level FoodMart Warehouse:Time.Weekly:(All) SYSTEM TABLE FoodMart - Warehouse Cube - Time.Weekly Hierarchy - (All) Level FoodMart Warehouse:Time.Weekly:Day SYSTEM TABLE FoodMart - Warehouse Cube - Time.Weekly Hierarchy - Day Level FoodMart Warehouse:Time.Weekly:Week SYSTEM TABLE FoodMart - Warehouse Cube - Time.Weekly Hierarchy - Week Level FoodMart Warehouse:Time.Weekly:Year SYSTEM TABLE FoodMart - Warehouse Cube - Time.Weekly Hierarchy - Year Level FoodMart Warehouse:Time:Month SYSTEM TABLE FoodMart - Warehouse Cube - Time Hierarchy - Month Level FoodMart Warehouse:Time:Quarter SYSTEM TABLE FoodMart - Warehouse Cube - Time Hierarchy - Quarter Level FoodMart Warehouse:Time:Year SYSTEM TABLE FoodMart - Warehouse Cube - Time Hierarchy - Year Level FoodMart Warehouse:Warehouse:(All) SYSTEM TABLE FoodMart - Warehouse Cube - Warehouse Hierarchy - (All) Level FoodMart Warehouse:Warehouse:City SYSTEM TABLE FoodMart - Warehouse Cube - Warehouse Hierarchy - City Level FoodMart Warehouse:Warehouse:Country SYSTEM TABLE FoodMart - Warehouse Cube - Warehouse Hierarchy - Country Level FoodMart Warehouse:Warehouse:State Province SYSTEM TABLE FoodMart - Warehouse Cube - Warehouse Hierarchy - State Province Level FoodMart Warehouse:Warehouse:Warehouse Name SYSTEM TABLE FoodMart - Warehouse Cube - Warehouse Hierarchy - Warehouse Name Level FoodMart HR TABLE FoodMart - HR Cube FoodMart Sales TABLE FoodMart - Sales Cube FoodMart Sales 2 TABLE FoodMart - Sales 2 Cube FoodMart Sales Ragged TABLE FoodMart - Sales Ragged Cube FoodMart Store TABLE FoodMart - Store Cube FoodMart Warehouse TABLE FoodMart - Warehouse Cube FoodMart Warehouse and Sales TABLE FoodMart - Warehouse and Sales Cube ]]> ${request.type} ${data.source.info} ${content} ]]> FoodMart FoodMart Product [Product] Product 0 3 1561 [Product] Cube - Product Dimension false false 0 true FoodMart FoodMart Store [Store] Store 1 3 26 [Store] Cube - Store Dimension false false 0 true FoodMart FoodMart Store Size in SQFT [Store Size in SQFT] Store Size in SQFT 2 3 22 [Store Size in SQFT] Cube - Store Size in SQFT Dimension false false 0 true FoodMart FoodMart Store Type [Store Type] Store Type 3 3 7 [Store Type] Cube - Store Type Dimension false false 0 true FoodMart FoodMart Time [Time] Time 4 1 25 [Time] Cube - Time Dimension false false 0 true FoodMart FoodMart Warehouse [Warehouse] Warehouse 5 3 25 [Warehouse] Cube - Warehouse Dimension false false 0 true FoodMart FoodMart HR Department [Department] Department 6 3 13 [Department] HR Cube - Department Dimension false false 0 true FoodMart FoodMart HR Employees [Employees] Employees 7 3 1156 [Employees] HR Cube - Employees Dimension false false 0 true FoodMart FoodMart HR Measures [Measures] Measures 0 2 6 [Measures] HR Cube - Measures Dimension false false 0 true FoodMart FoodMart HR Pay Type [Pay Type] Pay Type 3 3 3 [Pay Type] HR Cube - Pay Type Dimension false false 0 true FoodMart FoodMart HR Position [Position] Position 5 3 19 [Position] HR Cube - Position Dimension false false 0 true FoodMart FoodMart HR Store [Store] Store 2 3 26 [Store] HR Cube - Store Dimension false false 0 true FoodMart FoodMart HR Store Type [Store Type] Store Type 4 3 7 [Store Type] HR Cube - Store Type Dimension false false 0 true FoodMart FoodMart HR Time [Time] Time 1 1 25 [Time] HR Cube - Time Dimension false false 0 true FoodMart FoodMart Sales Customers [Customers] Customers 8 3 10282 [Customers] Sales Cube - Customers Dimension false false 0 true FoodMart FoodMart Sales Education Level [Education Level] Education Level 9 3 6 [Education Level] Sales Cube - Education Level Dimension false false 0 true FoodMart FoodMart Sales Gender [Gender] Gender 10 3 3 [Gender] Sales Cube - Gender Dimension false false 0 true FoodMart FoodMart Sales Marital Status [Marital Status] Marital Status 11 3 112 [Marital Status] Sales Cube - Marital Status Dimension false false 0 true FoodMart FoodMart Sales Measures [Measures] Measures 0 2 10 [Measures] Sales Cube - Measures Dimension false false 0 true FoodMart FoodMart Sales Product [Product] Product 5 3 1561 [Product] Sales Cube - Product Dimension false false 0 true FoodMart FoodMart Sales Promotion Media [Promotion Media] Promotion Media 6 3 15 [Promotion Media] Sales Cube - Promotion Media Dimension false false 0 true FoodMart FoodMart Sales Promotions [Promotions] Promotions 7 3 52 [Promotions] Sales Cube - Promotions Dimension false false 0 true FoodMart FoodMart Sales Store [Store] Store 1 3 26 [Store] Sales Cube - Store Dimension false false 0 true FoodMart FoodMart Sales Store Size in SQFT [Store Size in SQFT] Store Size in SQFT 2 3 22 [Store Size in SQFT] Sales Cube - Store Size in SQFT Dimension false false 0 true FoodMart FoodMart Sales Store Type [Store Type] Store Type 3 3 7 [Store Type] Sales Cube - Store Type Dimension false false 0 true FoodMart FoodMart Sales Time [Time] Time 4 1 25 [Time] Sales Cube - Time Dimension false false 0 true FoodMart FoodMart Sales Yearly Income [Yearly Income] Yearly Income 12 3 9 [Yearly Income] Sales Cube - Yearly Income Dimension false false 0 true FoodMart FoodMart Sales 2 Gender [Gender] Gender 3 3 3 [Gender] Sales 2 Cube - Gender Dimension false false 0 true FoodMart FoodMart Sales 2 Measures [Measures] Measures 0 2 8 [Measures] Sales 2 Cube - Measures Dimension false false 0 true FoodMart FoodMart Sales 2 Product [Product] Product 2 3 1561 [Product] Sales 2 Cube - Product Dimension false false 0 true FoodMart FoodMart Sales 2 Time [Time] Time 1 1 25 [Time] Sales 2 Cube - Time Dimension false false 0 true FoodMart FoodMart Sales Ragged Customers [Customers] Customers 9 3 10182 [Customers] Sales Ragged Cube - Customers Dimension false false 0 true FoodMart FoodMart Sales Ragged Education Level [Education Level] Education Level 10 3 6 [Education Level] Sales Ragged Cube - Education Level Dimension false false 0 true FoodMart FoodMart Sales Ragged Gender [Gender] Gender 11 3 3 [Gender] Sales Ragged Cube - Gender Dimension false false 0 true FoodMart FoodMart Sales Ragged Geography [Geography] Geography 2 3 25 [Geography] Sales Ragged Cube - Geography Dimension false false 0 true FoodMart FoodMart Sales Ragged Marital Status [Marital Status] Marital Status 12 3 3 [Marital Status] Sales Ragged Cube - Marital Status Dimension false false 0 true FoodMart FoodMart Sales Ragged Measures [Measures] Measures 0 2 6 [Measures] Sales Ragged Cube - Measures Dimension false false 0 true FoodMart FoodMart Sales Ragged Product [Product] Product 6 3 1561 [Product] Sales Ragged Cube - Product Dimension false false 0 true FoodMart FoodMart Sales Ragged Promotion Media [Promotion Media] Promotion Media 7 3 15 [Promotion Media] Sales Ragged Cube - Promotion Media Dimension false false 0 true FoodMart FoodMart Sales Ragged Promotions [Promotions] Promotions 8 3 52 [Promotions] Sales Ragged Cube - Promotions Dimension false false 0 true FoodMart FoodMart Sales Ragged Store [Store] Store 1 3 26 [Store] Sales Ragged Cube - Store Dimension false false 0 true FoodMart FoodMart Sales Ragged Store Size in SQFT [Store Size in SQFT] Store Size in SQFT 3 3 22 [Store Size in SQFT] Sales Ragged Cube - Store Size in SQFT Dimension false false 0 true FoodMart FoodMart Sales Ragged Store Type [Store Type] Store Type 4 3 7 [Store Type] Sales Ragged Cube - Store Type Dimension false false 0 true FoodMart FoodMart Sales Ragged Time [Time] Time 5 1 25 [Time] Sales Ragged Cube - Time Dimension false false 0 true FoodMart FoodMart Sales Ragged Yearly Income [Yearly Income] Yearly Income 13 3 9 [Yearly Income] Sales Ragged Cube - Yearly Income Dimension false false 0 true FoodMart FoodMart Store Has coffee bar [Has coffee bar] Has coffee bar 3 3 3 [Has coffee bar] Store Cube - Has coffee bar Dimension false false 0 true FoodMart FoodMart Store Measures [Measures] Measures 0 2 4 [Measures] Store Cube - Measures Dimension false false 0 true FoodMart FoodMart Store Store [Store] Store 2 3 26 [Store] Store Cube - Store Dimension false false 0 true FoodMart FoodMart Store Store Type [Store Type] Store Type 1 3 7 [Store Type] Store Cube - Store Type Dimension false false 0 true FoodMart FoodMart Warehouse Measures [Measures] Measures 0 2 10 [Measures] Warehouse Cube - Measures Dimension false false 0 true FoodMart FoodMart Warehouse Product [Product] Product 5 3 1561 [Product] Warehouse Cube - Product Dimension false false 0 true FoodMart FoodMart Warehouse Store [Store] Store 1 3 26 [Store] Warehouse Cube - Store Dimension false false 0 true FoodMart FoodMart Warehouse Store Size in SQFT [Store Size in SQFT] Store Size in SQFT 2 3 22 [Store Size in SQFT] Warehouse Cube - Store Size in SQFT Dimension false false 0 true FoodMart FoodMart Warehouse Store Type [Store Type] Store Type 3 3 7 [Store Type] Warehouse Cube - Store Type Dimension false false 0 true FoodMart FoodMart Warehouse Time [Time] Time 4 1 25 [Time] Warehouse Cube - Time Dimension false false 0 true FoodMart FoodMart Warehouse Warehouse [Warehouse] Warehouse 6 3 25 [Warehouse] Warehouse Cube - Warehouse Dimension false false 0 true FoodMart FoodMart Warehouse and Sales Customers [Customers] Customers 1 3 10282 [Customers] Warehouse and Sales Cube - Customers Dimension false false 0 true FoodMart FoodMart Warehouse and Sales Education Level [Education Level] Education Level 2 3 6 [Education Level] Warehouse and Sales Cube - Education Level Dimension false false 0 true FoodMart FoodMart Warehouse and Sales Gender [Gender] Gender 3 3 3 [Gender] Warehouse and Sales Cube - Gender Dimension false false 0 true FoodMart FoodMart Warehouse and Sales Marital Status [Marital Status] Marital Status 4 3 112 [Marital Status] Warehouse and Sales Cube - Marital Status Dimension false false 0 true FoodMart FoodMart Warehouse and Sales Measures [Measures] Measures 0 2 16 [Measures] Warehouse and Sales Cube - Measures Dimension false false 0 true FoodMart FoodMart Warehouse and Sales Product [Product] Product 5 3 1561 [Product] Warehouse and Sales Cube - Product Dimension false false 0 true FoodMart FoodMart Warehouse and Sales Promotion Media [Promotion Media] Promotion Media 6 3 15 [Promotion Media] Warehouse and Sales Cube - Promotion Media Dimension false false 0 true FoodMart FoodMart Warehouse and Sales Promotions [Promotions] Promotions 7 3 52 [Promotions] Warehouse and Sales Cube - Promotions Dimension false false 0 true FoodMart FoodMart Warehouse and Sales Store [Store] Store 8 3 26 [Store] Warehouse and Sales Cube - Store Dimension false false 0 true FoodMart FoodMart Warehouse and Sales Time [Time] Time 9 1 25 [Time] Warehouse and Sales Cube - Time Dimension false false 0 true FoodMart FoodMart Warehouse and Sales Warehouse [Warehouse] Warehouse 11 3 25 [Warehouse] Warehouse and Sales Cube - Warehouse Dimension false false 0 true FoodMart FoodMart Warehouse and Sales Yearly Income [Yearly Income] Yearly Income 10 3 9 [Yearly Income] Warehouse and Sales Cube - Yearly Income Dimension false false 0 true ]]> ${request.type} ${data.source.info} ${catalog} ${format} ${content} ]]> FoodMart FoodMart Product [Product] Product 0 3 1561 [Product] Cube - Product Dimension false false 0 true FoodMart FoodMart Store [Store] Store 1 3 26 [Store] Cube - Store Dimension false false 0 true FoodMart FoodMart Store Size in SQFT [Store Size in SQFT] Store Size in SQFT 2 3 22 [Store Size in SQFT] Cube - Store Size in SQFT Dimension false false 0 true FoodMart FoodMart Store Type [Store Type] Store Type 3 3 7 [Store Type] Cube - Store Type Dimension false false 0 true FoodMart FoodMart Time [Time] Time 4 1 25 [Time] Cube - Time Dimension false false 0 true FoodMart FoodMart Warehouse [Warehouse] Warehouse 5 3 25 [Warehouse] Cube - Warehouse Dimension false false 0 true ]]> ${request.type} ${cube.name} ${data.source.info} ${catalog} ${format} ${content} ]]> Item Returns a member from the tuple specified in <Tuple>. The member to be returned is specified by the zero-based position of the member in the set in <Index>. Tuple, Numeric Expression 12 1 Item Item Returns a tuple from the set specified in <Set>. The tuple to be returned is specified by the zero-based position of the tuple in the set in <Index>. Set, Numeric Expression 12 1 Item Item Returns a tuple from the set specified in <Set>. The tuple to be returned is specified by the member name (or names) in <String>. (none) 1 1 Item ]]> ${request.type} <${restriction.name}>${restriction.value} ${data.source.info} ${content} ]]> * Multiplies two numbers. Numeric Expression, Numeric Expression 5 1 * * Returns the cross product of two sets. Set, Set 12 1 * * Returns the cross product of two sets. Member, Set 12 1 * * Returns the cross product of two sets. Set, Member 12 1 * * Returns the cross product of two sets. Member, Member 12 1 * + Adds two numbers. Numeric Expression, Numeric Expression 5 1 + - Subtracts two numbers. Numeric Expression, Numeric Expression 5 1 - - Returns the negative of a number. Numeric Expression 5 1 - / Divides two numbers. Numeric Expression, Numeric Expression 5 1 / : Infix colon operator returns the set of members between a given pair of members. Member, Member 12 1 : < Returns whether an expression is less than another. Numeric Expression, Numeric Expression 11 1 < < Returns whether an expression is less than another. String, String 11 1 < <= Returns whether an expression is less than or equal to another. Numeric Expression, Numeric Expression 11 1 <= <= Returns whether an expression is less than or equal to another. String, String 11 1 <= <> Returns whether two expressions are not equal. Numeric Expression, Numeric Expression 11 1 <> <> Returns whether two expressions are not equal. String, String 11 1 <> = Returns whether two expressions are equal. Numeric Expression, Numeric Expression 11 1 = = Returns whether two expressions are equal. String, String 11 1 = > Returns whether an expression is greater than another. Numeric Expression, Numeric Expression 11 1 > > Returns whether an expression is greater than another. String, String 11 1 > >= Returns whether an expression is greater than or equal to another. Numeric Expression, Numeric Expression 11 1 >= >= Returns whether an expression is greater than or equal to another. String, String 11 1 >= _CaseMatch Evaluates various expressions, and returns the corresponding expression for the first which matches a particular value. (none) 1 1 _CaseMatch _CaseTest Evaluates various conditions, and returns the corresponding expression for the first which evaluates to true. (none) 1 1 _CaseTest Abs Returns a value of the same type that is passed to it specifying the absolute value of a number. Numeric Expression 5 1 Abs Acos Returns the arccosine, or inverse cosine, of a number. The arccosine is the angle whose cosine is Arg1. The returned angle is given in radians in the range 0 (zero) to pi. Numeric Expression 5 1 Acos Acosh Returns the inverse hyperbolic cosine of a number. Number must be greater than or equal to 1. The inverse hyperbolic cosine is the value whose hyperbolic cosine is Arg1, so Acosh(Cosh(number)) equals Arg1. Numeric Expression 5 1 Acosh AddCalculatedMembers Adds calculated members to a set. Set 12 1 AddCalculatedMembers Aggregate Returns a calculated value using the appropriate aggregate function, based on the context of the query. Set 5 1 Aggregate Aggregate Returns a calculated value using the appropriate aggregate function, based on the context of the query. Set, Numeric Expression 5 1 Aggregate AllMembers Returns a set that contains all members, including calculated members, of the specified hierarchy. Hierarchy 12 1 AllMembers AllMembers Returns a set that contains all members, including calculated members, of the specified level. Level 12 1 AllMembers Ancestor Returns the ancestor of a member at a specified level. Member, Level 12 1 Ancestor Ancestor Returns the ancestor of a member at a specified level. Member, Numeric Expression 12 1 Ancestor AND Returns the conjunction of two conditions. Logical Expression, Logical Expression 11 1 AND AS (none) 1 1 AS Asc Returns an Integer representing the character code corresponding to the first letter in a string. String 2 1 Asc AscB See Asc. String 2 1 AscB Ascendants Returns the set of the ascendants of a specified member. Member 12 1 Ascendants AscW See Asc. String 2 1 AscW Asin Returns the arcsine, or inverse sine, of a number. The arcsine is the angle whose sine is Arg1. The returned angle is given in radians in the range -pi/2 to pi/2. Numeric Expression 5 1 Asin Asinh Returns the inverse hyperbolic sine of a number. The inverse hyperbolic sine is the value whose hyperbolic sine is Arg1, so Asinh(Sinh(number)) equals Arg1. Numeric Expression 5 1 Asinh Atan2 Returns the arctangent, or inverse tangent, of the specified x- and y-coordinates. The arctangent is the angle from the x-axis to a line containing the origin (0, 0) and a point with coordinates (x_num, y_num). The angle is given in radians between -pi and pi, excluding -pi. Numeric Expression, Numeric Expression 5 1 Atan2 Atanh Returns the inverse hyperbolic tangent of a number. Number must be between -1 and 1 (excluding -1 and 1). Numeric Expression 5 1 Atanh Atn Returns a Double specifying the arctangent of a number. Numeric Expression 5 1 Atn Avg Returns the average value of a numeric expression evaluated over a set. Set 5 1 Avg Avg Returns the average value of a numeric expression evaluated over a set. Set, Numeric Expression 5 1 Avg BottomCount Returns a specified number of items from the bottom of a set, optionally ordering the set first. Set, Numeric Expression, Numeric Expression 12 1 BottomCount BottomCount Returns a specified number of items from the bottom of a set, optionally ordering the set first. Set, Numeric Expression 12 1 BottomCount BottomPercent Sorts a set and returns the bottom N elements whose cumulative total is at least a specified percentage. Set, Numeric Expression, Numeric Expression 12 1 BottomPercent BottomSum Sorts a set and returns the bottom N elements whose cumulative total is at least a specified value. Set, Numeric Expression, Numeric Expression 12 1 BottomSum Cache Evaluates and returns its sole argument, applying statement-level caching (none) 1 1 Cache CalculatedChild Returns an existing calculated child member with name <String> from the specified <Member>. Member, String 12 1 CalculatedChild Caption Returns the caption of a dimension. Dimension 8 1 Caption Caption Returns the caption of a hierarchy. Hierarchy 8 1 Caption Caption Returns the caption of a level. Level 8 1 Caption Caption Returns the caption of a member. Member 8 1 Caption Cast Converts values to another type. (none) 1 1 Cast CBool Returns an expression that has been converted to a Variant of subtype Boolean. Value 11 1 CBool CByte Returns an expression that has been converted to a Variant of subtype Byte. Value 2 1 CByte CDate Returns an expression that has been converted to a Variant of subtype Date. Value 7 1 CDate CDbl Returns an expression that has been converted to a Variant of subtype Double. Value 5 1 CDbl Children Returns the children of a member. Member 12 1 Children Chr Returns a String containing the character associated with the specified character code. Integer 8 1 Chr ChrB See Chr. Integer 8 1 ChrB ChrW See Chr. Integer 8 1 ChrW CInt Returns an expression that has been converted to a Variant of subtype Integer. Value 2 1 CInt ClosingPeriod Returns the last descendant of a member at a level. 12 1 ClosingPeriod ClosingPeriod Returns the last descendant of a member at a level. Level 12 1 ClosingPeriod ClosingPeriod Returns the last descendant of a member at a level. Level, Member 12 1 ClosingPeriod ClosingPeriod Returns the last descendant of a member at a level. Member 12 1 ClosingPeriod CoalesceEmpty Coalesces an empty cell value to a different value. All of the expressions must be of the same type (number or string). (none) 1 1 CoalesceEmpty Correlation Returns the correlation of two series evaluated over a set. Set, Numeric Expression 5 1 Correlation Correlation Returns the correlation of two series evaluated over a set. Set, Numeric Expression, Numeric Expression 5 1 Correlation Cos Returns a Double specifying the cosine of an angle. Numeric Expression 5 1 Cos Cosh Returns the hyperbolic cosine of a number. Numeric Expression 5 1 Cosh Count Returns the number of tuples in a set, empty cells included unless the optional EXCLUDEEMPTY flag is used. Set 5 1 Count Count Returns the number of tuples in a set, empty cells included unless the optional EXCLUDEEMPTY flag is used. Set, Symbol 5 1 Count Count Returns the number of tuples in a set including empty cells. Set 5 1 Count Cousin Returns the member with the same relative position under <ancestor member> as the member specified. Member, Member 12 1 Cousin Covariance Returns the covariance of two series evaluated over a set (biased). Set, Numeric Expression 5 1 Covariance Covariance Returns the covariance of two series evaluated over a set (biased). Set, Numeric Expression, Numeric Expression 5 1 Covariance CovarianceN Returns the covariance of two series evaluated over a set (unbiased). Set, Numeric Expression 5 1 CovarianceN CovarianceN Returns the covariance of two series evaluated over a set (unbiased). Set, Numeric Expression, Numeric Expression 5 1 CovarianceN Crossjoin Returns the cross product of two sets. Set, Set 12 1 Crossjoin Current Returns the current member or tuple of a named set. Set 12 1 Current CurrentDateMember Hierarchy, String, Symbol 12 1 CurrentDateMember CurrentDateMember Hierarchy, String 12 1 CurrentDateMember CurrentDateString String 8 1 CurrentDateString CurrentMember Returns the current member along a hierarchy during an iteration. Hierarchy 12 1 CurrentMember CurrentOrdinal Returns the ordinal of the current iteration through a named set. Set 2 1 CurrentOrdinal DataMember Returns the system-generated data member that is associated with a nonleaf member of a dimension. Member 12 1 DataMember Date Returns a Variant (Date) containing the current system date. 7 1 Date DateAdd Returns a Variant (Date) containing a date to which a specified time interval has been added. String, Numeric Expression, DateTime 7 1 DateAdd DateDiff Returns a Variant (Long) specifying the number of time intervals between two specified dates. String, DateTime, DateTime, Integer, Integer 5 1 DateDiff DateDiff Returns a Variant (Long) specifying the number of time intervals between two specified dates. String, DateTime, DateTime, Integer 5 1 DateDiff DateDiff Returns a Variant (Long) specifying the number of time intervals between two specified dates. String, DateTime, DateTime 5 1 DateDiff DatePart Returns a Variant (Integer) containing the specified part of a given date. String, DateTime, Integer, Integer 2 1 DatePart DatePart Returns a Variant (Integer) containing the specified part of a given date. String, DateTime, Integer 2 1 DatePart DatePart Returns a Variant (Integer) containing the specified part of a given date. String, DateTime 2 1 DatePart DateSerial Returns a Variant (Date) for a specified year, month, and day. Integer, Integer, Integer 7 1 DateSerial DateValue Returns a Variant (Date). DateTime 7 1 DateValue Day Returns a Variant (Integer) specifying a whole number between 1 and 31, inclusive, representing the day of the month. DateTime 2 1 Day DDB Returns a Double specifying the depreciation of an asset for a specific time period using the double-declining balance method or some other method you specify. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 DDB DDB Returns a Double specifying the depreciation of an asset for a specific time period using the double-declining balance method or some other method you specify. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 DDB DefaultMember Returns the default member of a hierarchy. Hierarchy 12 1 DefaultMember Degrees Converts radians to degrees. Numeric Expression 5 1 Degrees Descendants Returns the set of descendants of a member at a specified level, optionally including or excluding descendants in other levels. Member 12 1 Descendants Descendants Returns the set of descendants of a member at a specified level, optionally including or excluding descendants in other levels. Member, Level 12 1 Descendants Descendants Returns the set of descendants of a member at a specified level, optionally including or excluding descendants in other levels. Member, Level, Symbol 12 1 Descendants Descendants Returns the set of descendants of a member at a specified level, optionally including or excluding descendants in other levels. Member, Numeric Expression 12 1 Descendants Descendants Returns the set of descendants of a member at a specified level, optionally including or excluding descendants in other levels. Member, Numeric Expression, Symbol 12 1 Descendants Descendants Returns the set of descendants of a member at a specified level, optionally including or excluding descendants in other levels. Member, Empty, Symbol 12 1 Descendants Descendants Returns the set of descendants of a set of members at a specified level, optionally including or excluding descendants in other levels. Set 12 1 Descendants Descendants Returns the set of descendants of a set of members at a specified level, optionally including or excluding descendants in other levels. Set, Level 12 1 Descendants Descendants Returns the set of descendants of a set of members at a specified level, optionally including or excluding descendants in other levels. Set, Level, Symbol 12 1 Descendants Descendants Returns the set of descendants of a set of members at a specified level, optionally including or excluding descendants in other levels. Set, Numeric Expression 12 1 Descendants Descendants Returns the set of descendants of a set of members at a specified level, optionally including or excluding descendants in other levels. Set, Numeric Expression, Symbol 12 1 Descendants Descendants Returns the set of descendants of a set of members at a specified level, optionally including or excluding descendants in other levels. Set, Empty, Symbol 12 1 Descendants Dimension Returns the dimension that contains a specified hierarchy. Dimension 12 1 Dimension Dimension Returns the dimension that contains a specified hierarchy. Hierarchy 12 1 Dimension Dimension Returns the dimension that contains a specified level. Level 12 1 Dimension Dimension Returns the dimension that contains a specified member. Member 12 1 Dimension Dimensions Returns the hierarchy whose zero-based position within the cube is specified by a numeric expression. Numeric Expression 12 1 Dimensions Dimensions Returns the hierarchy whose name is specified by a string. String 12 1 Dimensions Distinct Eliminates duplicate tuples from a set. Set 12 1 Distinct DrilldownLevel Drills down the members of a set, at a specified level, to one level below. Alternatively, drills down on a specified dimension in the set. Set 12 1 DrilldownLevel DrilldownLevel Drills down the members of a set, at a specified level, to one level below. Alternatively, drills down on a specified dimension in the set. Set, Level 12 1 DrilldownLevel DrilldownLevel Drills down the members of a set, at a specified level, to one level below. Alternatively, drills down on a specified dimension in the set. Set, Empty, Numeric Expression 12 1 DrilldownLevel DrilldownLevelBottom Drills down the bottommost members of a set, at a specified level, to one level below. Set, Numeric Expression 12 1 DrilldownLevelBottom DrilldownLevelBottom Drills down the bottommost members of a set, at a specified level, to one level below. Set, Numeric Expression, Level 12 1 DrilldownLevelBottom DrilldownLevelBottom Drills down the bottommost members of a set, at a specified level, to one level below. Set, Numeric Expression, Level, Numeric Expression 12 1 DrilldownLevelBottom DrilldownLevelBottom Drills down the bottommost members of a set, at a specified level, to one level below. Set, Numeric Expression, Empty, Numeric Expression 12 1 DrilldownLevelBottom DrilldownLevelTop Drills down the topmost members of a set, at a specified level, to one level below. Set, Numeric Expression 12 1 DrilldownLevelTop DrilldownLevelTop Drills down the topmost members of a set, at a specified level, to one level below. Set, Numeric Expression, Level 12 1 DrilldownLevelTop DrilldownLevelTop Drills down the topmost members of a set, at a specified level, to one level below. Set, Numeric Expression, Level, Numeric Expression 12 1 DrilldownLevelTop DrilldownLevelTop Drills down the topmost members of a set, at a specified level, to one level below. Set, Numeric Expression, Empty, Numeric Expression 12 1 DrilldownLevelTop DrilldownMember Drills down the members in a set that are present in a second specified set. Set, Set 12 1 DrilldownMember DrilldownMember Drills down the members in a set that are present in a second specified set. Set, Set, Symbol 12 1 DrilldownMember Except Finds the difference between two sets, optionally retaining duplicates. Set, Set 12 1 Except Except Finds the difference between two sets, optionally retaining duplicates. Set, Set, Symbol 12 1 Except Exists Returns the the set of tuples of the first set that exist with one or more tuples of the second set. Set, Set 12 1 Exists Exp Returns a Double specifying e (the base of natural logarithms) raised to a power. Numeric Expression 5 1 Exp Extract Returns a set of tuples from extracted hierarchy elements. The opposite of Crossjoin. (none) 1 1 Extract Filter Returns the set resulting from filtering a set based on a search condition. Set, Logical Expression 12 1 Filter FirstChild Returns the first child of a member. Member 12 1 FirstChild FirstQ Returns the 1st quartile value of a numeric expression evaluated over a set. Set 5 1 FirstQ FirstQ Returns the 1st quartile value of a numeric expression evaluated over a set. Set, Numeric Expression 5 1 FirstQ FirstSibling Returns the first child of the parent of a member. Member 12 1 FirstSibling Fix Returns the integer portion of a number. If negative, returns the negative number greater than or equal to the number. Value 2 1 Fix Format Formats a number or date to a string. Member, String 8 1 Format Format Formats a number or date to a string. Numeric Expression, String 8 1 Format Format Formats a number or date to a string. DateTime, String 8 1 Format FormatCurrency Returns an expression formatted as a currency value using the currency symbol defined in the system control panel. Value, Integer, Integer, Integer, Integer 8 1 FormatCurrency FormatCurrency Returns an expression formatted as a currency value using the currency symbol defined in the system control panel. Value, Integer, Integer, Integer 8 1 FormatCurrency FormatCurrency Returns an expression formatted as a currency value using the currency symbol defined in the system control panel. Value, Integer, Integer 8 1 FormatCurrency FormatCurrency Returns an expression formatted as a currency value using the currency symbol defined in the system control panel. Value, Integer 8 1 FormatCurrency FormatCurrency Returns an expression formatted as a currency value using the currency symbol defined in the system control panel. Value 8 1 FormatCurrency FormatDateTime Returns an expression formatted as a date or time. DateTime, Integer 8 1 FormatDateTime FormatDateTime Returns an expression formatted as a date or time. DateTime 8 1 FormatDateTime FormatNumber Returns an expression formatted as a number. Value, Integer, Integer, Integer, Integer 8 1 FormatNumber FormatNumber Returns an expression formatted as a number. Value, Integer, Integer, Integer 8 1 FormatNumber FormatNumber Returns an expression formatted as a number. Value, Integer, Integer 8 1 FormatNumber FormatNumber Returns an expression formatted as a number. Value, Integer 8 1 FormatNumber FormatNumber Returns an expression formatted as a number. Value 8 1 FormatNumber FormatPercent Returns an expression formatted as a percentage (multipled by 100) with a trailing % character. Value, Integer, Integer, Integer, Integer 8 1 FormatPercent FormatPercent Returns an expression formatted as a percentage (multipled by 100) with a trailing % character. Value, Integer, Integer, Integer 8 1 FormatPercent FormatPercent Returns an expression formatted as a percentage (multipled by 100) with a trailing % character. Value, Integer, Integer 8 1 FormatPercent FormatPercent Returns an expression formatted as a percentage (multipled by 100) with a trailing % character. Value, Integer 8 1 FormatPercent FormatPercent Returns an expression formatted as a percentage (multipled by 100) with a trailing % character. Value 8 1 FormatPercent FV Returns a Double specifying the future value of an annuity based on periodic, fixed payments and a fixed interest rate. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Logical Expression 5 1 FV FV Returns a Double specifying the future value of an annuity based on periodic, fixed payments and a fixed interest rate. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 FV FV Returns a Double specifying the future value of an annuity based on periodic, fixed payments and a fixed interest rate. Numeric Expression, Numeric Expression, Numeric Expression 5 1 FV Generate Applies a set to each member of another set and joins the resulting sets by union. Set, Set 12 1 Generate Generate Applies a set to each member of another set and joins the resulting sets by union. Set, Set, Symbol 12 1 Generate Generate Applies a set to a string expression and joins resulting sets by string concatenation. Set, String 8 1 Generate Generate Applies a set to a string expression and joins resulting sets by string concatenation. Set, String, String 8 1 Generate Head Returns the first specified number of elements in a set. Set 12 1 Head Head Returns the first specified number of elements in a set. Set, Numeric Expression 12 1 Head Hex Returns a String representing the hexadecimal value of a number. Value 8 1 Hex Hierarchize Orders the members of a set in a hierarchy. Set 12 1 Hierarchize Hierarchize Orders the members of a set in a hierarchy. Set, Symbol 12 1 Hierarchize Hierarchy Returns a level's hierarchy. Level 12 1 Hierarchy Hierarchy Returns a member's hierarchy. Member 12 1 Hierarchy Hour Returns a Variant (Integer) specifying a whole number between 0 and 23, inclusive, representing the hour of the day. DateTime 2 1 Hour IIf Returns one of two tuples determined by a logical test. Logical Expression, Tuple, Tuple 12 1 IIf IIf Returns one of two dimension values determined by a logical test. Logical Expression, Dimension, Dimension 12 1 IIf IIf Returns one of two hierarchy values determined by a logical test. Logical Expression, Hierarchy, Hierarchy 12 1 IIf IIf Returns one of two level values determined by a logical test. Logical Expression, Level, Level 12 1 IIf IIf Returns boolean determined by a logical test. Logical Expression, Logical Expression, Logical Expression 11 1 IIf IIf Returns one of two member values determined by a logical test. Logical Expression, Member, Member 12 1 IIf IIf Returns one of two numeric values determined by a logical test. Logical Expression, Numeric Expression, Numeric Expression 5 1 IIf IIf Returns one of two set values determined by a logical test. Logical Expression, Set, Set 12 1 IIf IIf Returns one of two string values determined by a logical test. Logical Expression, String, String 8 1 IIf IN Member, Set 11 1 IN InStr Returns the position of an occurrence of one string within another. Integer, String, String, Integer 2 1 InStr InStr Returns the position of an occurrence of one string within another. Integer, String, String 2 1 InStr InStr Returns a Variant (Long) specifying the position of the first occurrence of one string within another. String, String 2 1 InStr InStrRev Returns the position of an occurrence of one string within another, from the end of string. String, String, Integer, Integer 2 1 InStrRev InStrRev Returns the position of an occurrence of one string within another, from the end of string. String, String, Integer 2 1 InStrRev InStrRev Returns the position of an occurrence of one string within another, from the end of string. String, String 2 1 InStrRev Int Returns the integer portion of a number. If negative, returns the negative number less than or equal to the number. Value 2 1 Int Intersect Returns the intersection of two input sets, optionally retaining duplicates. Set, Set, Symbol 12 1 Intersect Intersect Returns the intersection of two input sets, optionally retaining duplicates. Set, Set 12 1 Intersect InverseNormal Numeric Expression 5 1 InverseNormal IPmt Returns a Double specifying the interest payment for a given period of an annuity based on periodic, fixed payments and a fixed interest rate. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Logical Expression 5 1 IPmt IPmt Returns a Double specifying the interest payment for a given period of an annuity based on periodic, fixed payments and a fixed interest rate. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 IPmt IPmt Returns a Double specifying the interest payment for a given period of an annuity based on periodic, fixed payments and a fixed interest rate. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 IPmt IRR Returns a Double specifying the internal rate of return for a series of periodic cash flows (payments and receipts). Array, Numeric Expression 5 1 IRR IRR Returns a Double specifying the internal rate of return for a series of periodic cash flows (payments and receipts). Array 5 1 IRR IS Returns whether two objects are the same Member, Member 11 1 IS IS Returns whether two objects are the same Level, Level 11 1 IS IS Returns whether two objects are the same Hierarchy, Hierarchy 11 1 IS IS Returns whether two objects are the same Dimension, Dimension 11 1 IS IS Returns whether two objects are the same Tuple, Tuple 11 1 IS IS EMPTY Determines if an expression evaluates to the empty cell value. Member 11 1 IS EMPTY IS EMPTY Determines if an expression evaluates to the empty cell value. Tuple 11 1 IS EMPTY IS NULL Returns whether an object is null Member 11 1 IS NULL IS NULL Returns whether an object is null Level 11 1 IS NULL IS NULL Returns whether an object is null Hierarchy 11 1 IS NULL IS NULL Returns whether an object is null Dimension 11 1 IS NULL IsDate Returns a Boolean value indicating whether an expression can be converted to a date. Value 11 1 IsDate IsEmpty Determines if an expression evaluates to the empty cell value. String 11 1 IsEmpty IsEmpty Determines if an expression evaluates to the empty cell value. Numeric Expression 11 1 IsEmpty Item Returns a member from the tuple specified in <Tuple>. The member to be returned is specified by the zero-based position of the member in the set in <Index>. Tuple, Numeric Expression 12 1 Item Item Returns a tuple from the set specified in <Set>. The tuple to be returned is specified by the zero-based position of the tuple in the set in <Index>. Set, Numeric Expression 12 1 Item Item Returns a tuple from the set specified in <Set>. The tuple to be returned is specified by the member name (or names) in <String>. (none) 1 1 Item Lag Returns a member further along the specified member's dimension. Member, Numeric Expression 12 1 Lag LastChild Returns the last child of a member. Member 12 1 LastChild LastNonEmpty Set, Member 12 1 LastNonEmpty LastPeriods Returns a set of members prior to and including a specified member. Numeric Expression 12 1 LastPeriods LastPeriods Returns a set of members prior to and including a specified member. Numeric Expression, Member 12 1 LastPeriods LastSibling Returns the last child of the parent of a member. Member 12 1 LastSibling LCase Returns a String that has been converted to lowercase. String 8 1 LCase Lead Returns a member further along the specified member's dimension. Member, Numeric Expression 12 1 Lead Left Returns a specified number of characters from the left side of a string. String, Integer 8 1 Left Len Returns the number of characters in a string String 5 1 Len Level Returns a member's level. Member 12 1 Level Levels Returns the level whose position in a hierarchy is specified by a numeric expression. Hierarchy, Numeric Expression 12 1 Levels Levels Returns the level whose name is specified by a string expression. Hierarchy, String 12 1 Levels Levels Returns the level whose name is specified by a string expression. String 12 1 Levels LinRegIntercept Calculates the linear regression of a set and returns the value of b in the regression line y = ax + b. Set, Numeric Expression 5 1 LinRegIntercept LinRegIntercept Calculates the linear regression of a set and returns the value of b in the regression line y = ax + b. Set, Numeric Expression, Numeric Expression 5 1 LinRegIntercept LinRegPoint Calculates the linear regression of a set and returns the value of y in the regression line y = ax + b. Numeric Expression, Set, Numeric Expression 5 1 LinRegPoint LinRegPoint Calculates the linear regression of a set and returns the value of y in the regression line y = ax + b. Numeric Expression, Set, Numeric Expression, Numeric Expression 5 1 LinRegPoint LinRegR2 Calculates the linear regression of a set and returns R2 (the coefficient of determination). Set, Numeric Expression 5 1 LinRegR2 LinRegR2 Calculates the linear regression of a set and returns R2 (the coefficient of determination). Set, Numeric Expression, Numeric Expression 5 1 LinRegR2 LinRegSlope Calculates the linear regression of a set and returns the value of a in the regression line y = ax + b. Set, Numeric Expression 5 1 LinRegSlope LinRegSlope Calculates the linear regression of a set and returns the value of a in the regression line y = ax + b. Set, Numeric Expression, Numeric Expression 5 1 LinRegSlope LinRegVariance Calculates the linear regression of a set and returns the variance associated with the regression line y = ax + b. Set, Numeric Expression 5 1 LinRegVariance LinRegVariance Calculates the linear regression of a set and returns the variance associated with the regression line y = ax + b. Set, Numeric Expression, Numeric Expression 5 1 LinRegVariance Log Returns a Double specifying the natural logarithm of a number. Numeric Expression 5 1 Log Log10 Returns the base-10 logarithm of a number. Numeric Expression 5 1 Log10 LTrim Returns a Variant (String) containing a copy of a specified string without leading spaces. String 8 1 LTrim MATCHES String, String 11 1 MATCHES Max Returns the maximum value of a numeric expression evaluated over a set. Set 5 1 Max Max Returns the maximum value of a numeric expression evaluated over a set. Set, Numeric Expression 5 1 Max Median Returns the median value of a numeric expression evaluated over a set. Set 5 1 Median Median Returns the median value of a numeric expression evaluated over a set. Set, Numeric Expression 5 1 Median Members Returns the set of members in a hierarchy. Hierarchy 12 1 Members Members Returns the set of members in a level. Level 12 1 Members Members Returns the member whose name is specified by a string expression. String 12 1 Members Mid Returns a specified number of characters from a string. String, Integer, Integer 8 1 Mid Mid Returns a specified number of characters from a string. String, Integer 8 1 Mid Min Returns the minimum value of a numeric expression evaluated over a set. Set 5 1 Min Min Returns the minimum value of a numeric expression evaluated over a set. Set, Numeric Expression 5 1 Min Minute Returns a Variant (Integer) specifying a whole number between 0 and 59, inclusive, representing the minute of the hour. DateTime 2 1 Minute MIRR Returns a Double specifying the modified internal rate of return for a series of periodic cash flows (payments and receipts). Array, Numeric Expression, Numeric Expression 5 1 MIRR Mod Returns the remainder of dividing n by d. Value, Value 5 1 Mod Month Returns a Variant (Integer) specifying a whole number between 1 and 12, inclusive, representing the month of the year. DateTime 2 1 Month MonthName Returns a string indicating the specified month. Integer, Logical Expression 8 1 MonthName Mtd A shortcut function for the PeriodsToDate function that specifies the level to be Month. 12 1 Mtd Mtd A shortcut function for the PeriodsToDate function that specifies the level to be Month. Member 12 1 Mtd Name Returns the name of a dimension. Dimension 8 1 Name Name Returns the name of a hierarchy. Hierarchy 8 1 Name Name Returns the name of a level. Level 8 1 Name Name Returns the name of a member. Member 8 1 Name NativizeSet Tries to natively evaluate <Set>. Set 12 1 NativizeSet NextMember Returns the next member in the level that contains a specified member. Member 12 1 NextMember NonEmptyCrossJoin Returns the cross product of two sets, excluding empty tuples and tuples without associated fact table data. Set, Set 12 1 NonEmptyCrossJoin NOT Returns the negation of a condition. Logical Expression 11 1 NOT Now Returns a Variant (Date) specifying the current date and time according your computer's system date and time. 7 1 Now NPer Returns a Double specifying the number of periods for an annuity based on periodic, fixed payments and a fixed interest rate. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Logical Expression 5 1 NPer NPV Returns a Double specifying the net present value of an investment based on a series of periodic cash flows (payments and receipts) and a discount rate. Numeric Expression, Array 5 1 NPV NullValue 5 1 NullValue Oct Returns a Variant (String) representing the octal value of a number. Value 8 1 Oct OpeningPeriod Returns the first descendant of a member at a level. 12 1 OpeningPeriod OpeningPeriod Returns the first descendant of a member at a level. Level 12 1 OpeningPeriod OpeningPeriod Returns the first descendant of a member at a level. Level, Member 12 1 OpeningPeriod OR Returns the disjunction of two conditions. Logical Expression, Logical Expression 11 1 OR Order Arranges members of a set, optionally preserving or breaking the hierarchy. (none) 1 1 Order OrderKey Returns the member order key. Member 12 1 OrderKey Ordinal Returns the zero-based ordinal value associated with a level. Level 5 1 Ordinal ParallelPeriod Returns a member from a prior period in the same relative position as a specified member. 12 1 ParallelPeriod ParallelPeriod Returns a member from a prior period in the same relative position as a specified member. Level 12 1 ParallelPeriod ParallelPeriod Returns a member from a prior period in the same relative position as a specified member. Level, Numeric Expression 12 1 ParallelPeriod ParallelPeriod Returns a member from a prior period in the same relative position as a specified member. Level, Numeric Expression, Member 12 1 ParallelPeriod Parameter Returns default value of parameter. String, Symbol, String, String 8 1 Parameter Parameter Returns default value of parameter. String, Symbol, String 8 1 Parameter Parameter Returns default value of parameter. String, Symbol, Numeric Expression, String 5 1 Parameter Parameter Returns default value of parameter. String, Symbol, Numeric Expression 5 1 Parameter Parameter Returns default value of parameter. String, Hierarchy, Member, String 12 1 Parameter Parameter Returns default value of parameter. String, Hierarchy, Member 12 1 Parameter Parameter Returns default value of parameter. String, Hierarchy, Set, String 12 1 Parameter Parameter Returns default value of parameter. String, Hierarchy, Set 12 1 Parameter ParamRef Returns the current value of this parameter. If it is null, returns the default value. String 12 1 ParamRef Parent Returns the parent of a member. Member 12 1 Parent Percentile Returns the value of the tuple that is at a given percentile of a set. Set, Numeric Expression, Numeric Expression 5 1 Percentile PeriodsToDate Returns a set of periods (members) from a specified level starting with the first period and ending with a specified member. 12 1 PeriodsToDate PeriodsToDate Returns a set of periods (members) from a specified level starting with the first period and ending with a specified member. Level 12 1 PeriodsToDate PeriodsToDate Returns a set of periods (members) from a specified level starting with the first period and ending with a specified member. Level, Member 12 1 PeriodsToDate Pi Returns the number 3.14159265358979, the mathematical constant pi, accurate to 15 digits. 5 1 Pi Pmt Returns a Double specifying the payment for an annuity based on periodic, fixed payments and a fixed interest rate. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Logical Expression 5 1 Pmt Power Returns the result of a number raised to a power. Numeric Expression, Numeric Expression 5 1 Power PPmt Returns a Double specifying the principal payment for a given period of an annuity based on periodic, fixed payments and a fixed interest rate. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Logical Expression 5 1 PPmt PPmt Returns a Double specifying the principal payment for a given period of an annuity based on periodic, fixed payments and a fixed interest rate. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 PPmt PPmt Returns a Double specifying the principal payment for a given period of an annuity based on periodic, fixed payments and a fixed interest rate. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 PPmt PrevMember Returns the previous member in the level that contains a specified member. Member 12 1 PrevMember Properties Returns the value of a member property. (none) 1 1 Properties PV Returns a Double specifying the present value of an annuity based on periodic, fixed payments to be paid in the future and a fixed interest rate. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Logical Expression 5 1 PV Qtd A shortcut function for the PeriodsToDate function that specifies the level to be Quarter. 12 1 Qtd Qtd A shortcut function for the PeriodsToDate function that specifies the level to be Quarter. Member 12 1 Qtd Radians Converts degrees to radians. Numeric Expression 5 1 Radians Rank Returns the one-based rank of a tuple in a set. Tuple, Set 2 1 Rank Rank Returns the one-based rank of a tuple in a set. Tuple, Set, Numeric Expression 2 1 Rank Rank Returns the one-based rank of a tuple in a set. Member, Set 2 1 Rank Rank Returns the one-based rank of a tuple in a set. Member, Set, Numeric Expression 2 1 Rank Rate Returns a Double specifying the interest rate per period for an annuity. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Logical Expression, Numeric Expression 5 1 Rate Rate Returns a Double specifying the interest rate per period for an annuity. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Logical Expression 5 1 Rate Rate Returns a Double specifying the interest rate per period for an annuity. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 Rate Rate Returns a Double specifying the interest rate per period for an annuity. Numeric Expression, Numeric Expression, Numeric Expression 5 1 Rate Replace Returns a string in which a specified substring has been replaced with another substring a specified number of times. String, String, String, Integer, Integer, Integer 8 1 Replace Replace Returns a string in which a specified substring has been replaced with another substring a specified number of times. String, String, String, Integer, Integer 8 1 Replace Replace Returns a string in which a specified substring has been replaced with another substring a specified number of times. String, String, String, Integer 8 1 Replace Replace String, String, String 8 1 Replace Right Returns a Variant (String) containing a specified number of characters from the right side of a string. String, Integer 8 1 Right Round Returns a number rounded to a specified number of decimal places. Numeric Expression, Integer 5 1 Round Round Returns a number rounded to a specified number of decimal places. Numeric Expression 5 1 Round RTrim Returns a Variant (String) containing a copy of a specified string without trailing spaces. String 8 1 RTrim Second Returns a Variant (Integer) specifying a whole number between 0 and 59, inclusive, representing the second of the minute. DateTime 2 1 Second SetToStr Constructs a string from a set. Set 8 1 SetToStr Sgn Returns a Variant (Integer) indicating the sign of a number. Numeric Expression 2 1 Sgn Siblings Returns the siblings of a specified member, including the member itself. Member 12 1 Siblings Sin Returns a Double specifying the sine of an angle. Numeric Expression 5 1 Sin Sinh Returns the hyperbolic sine of a number. Numeric Expression 5 1 Sinh SLN Returns a Double specifying the straight-line depreciation of an asset for a single period. Numeric Expression, Numeric Expression, Numeric Expression 5 1 SLN Space Returns a Variant (String) consisting of the specified number of spaces. Integer 8 1 Space Sqr Returns a Double specifying the square root of a number. Numeric Expression 5 1 Sqr SqrtPi Returns the square root of (number * pi). Numeric Expression 5 1 SqrtPi Stddev Alias for Stdev. Set 5 1 Stddev Stddev Alias for Stdev. Set, Numeric Expression 5 1 Stddev StddevP Alias for StdevP. Set 5 1 StddevP StddevP Alias for StdevP. Set, Numeric Expression 5 1 StddevP Stdev Returns the standard deviation of a numeric expression evaluated over a set (unbiased). Set 5 1 Stdev Stdev Returns the standard deviation of a numeric expression evaluated over a set (unbiased). Set, Numeric Expression 5 1 Stdev StdevP Returns the standard deviation of a numeric expression evaluated over a set (biased). Set 5 1 StdevP StdevP Returns the standard deviation of a numeric expression evaluated over a set (biased). Set, Numeric Expression 5 1 StdevP Str Returns a Variant (String) representation of a number. Value 8 1 Str StrComp Returns a Variant (Integer) indicating the result of a string comparison. String, String, Integer 2 1 StrComp StrComp Returns a Variant (Integer) indicating the result of a string comparison. String, String 2 1 StrComp String Integer, String 8 1 String StripCalculatedMembers Removes calculated members from a set. Set 12 1 StripCalculatedMembers StrReverse Returns a string in which the character order of a specified string is reversed. String 8 1 StrReverse StrToMember Returns a member from a unique name String in MDX format. String 12 1 StrToMember StrToSet Constructs a set from a string expression. String 12 1 StrToSet StrToTuple Constructs a tuple from a string. String 12 1 StrToTuple Subset Returns a subset of elements from a set. Set, Numeric Expression 12 1 Subset Subset Returns a subset of elements from a set. Set, Numeric Expression, Numeric Expression 12 1 Subset Sum Returns the sum of a numeric expression evaluated over a set. Set 5 1 Sum Sum Returns the sum of a numeric expression evaluated over a set. Set, Numeric Expression 5 1 Sum SYD Returns a Double specifying the sum-of-years' digits depreciation of an asset for a specified period. Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 SYD Tail Returns a subset from the end of a set. Set 12 1 Tail Tail Returns a subset from the end of a set. Set, Numeric Expression 12 1 Tail Tan Returns a Double specifying the tangent of an angle. Numeric Expression 5 1 Tan Tanh Returns the hyperbolic tangent of a number. Numeric Expression 5 1 Tanh ThirdQ Returns the 3rd quartile value of a numeric expression evaluated over a set. Set 5 1 ThirdQ ThirdQ Returns the 3rd quartile value of a numeric expression evaluated over a set. Set, Numeric Expression 5 1 ThirdQ Time Returns a Variant (Date) indicating the current system time. 7 1 Time Timer Returns a Single representing the number of seconds elapsed since midnight. 5 1 Timer TimeSerial Returns a Variant (Date) containing the time for a specific hour, minute, and second. Integer, Integer, Integer 7 1 TimeSerial TimeValue Returns a Variant (Date) containing the time. DateTime 7 1 TimeValue ToggleDrillState Toggles the drill state of members. This function is a combination of DrillupMember and DrilldownMember. Set, Set 12 1 ToggleDrillState ToggleDrillState Toggles the drill state of members. This function is a combination of DrillupMember and DrilldownMember. Set, Set, Symbol 12 1 ToggleDrillState TopCount Returns a specified number of items from the top of a set, optionally ordering the set first. Set, Numeric Expression, Numeric Expression 12 1 TopCount TopCount Returns a specified number of items from the top of a set, optionally ordering the set first. Set, Numeric Expression 12 1 TopCount TopPercent Sorts a set and returns the top N elements whose cumulative total is at least a specified percentage. Set, Numeric Expression, Numeric Expression 12 1 TopPercent TopSum Sorts a set and returns the top N elements whose cumulative total is at least a specified value. Set, Numeric Expression, Numeric Expression 12 1 TopSum Trim Returns a Variant (String) containing a copy of a specified string without leading and trailing spaces. String 8 1 Trim TupleToStr Constructs a string from a tuple. Tuple 8 1 TupleToStr TypeName Returns a String that provides information about a variable. Value 8 1 TypeName UCase Returns a string that has been converted to uppercase String 8 1 UCase Union Returns the union of two sets, optionally retaining duplicates. Set, Set 12 1 Union Union Returns the union of two sets, optionally retaining duplicates. Set, Set, Symbol 12 1 Union UniqueName Returns the unique name of a dimension. Dimension 8 1 UniqueName UniqueName Returns the unique name of a hierarchy. Hierarchy 8 1 UniqueName UniqueName Returns the unique name of a level. Level 8 1 UniqueName UniqueName Returns the unique name of a member. Member 8 1 UniqueName Unorder Removes any enforced ordering from a specified set. Set 12 1 Unorder Val Numeric Expression 5 1 Val Val Returns the numbers contained in a string as a numeric value of appropriate type. String 5 1 Val ValidMeasure Returns a valid measure in a virtual cube by forcing inapplicable dimensions to their top level. Tuple 5 1 ValidMeasure Value Returns the value of a measure. Member 5 1 Value Var Returns the variance of a numeric expression evaluated over a set (unbiased). Set 5 1 Var Var Returns the variance of a numeric expression evaluated over a set (unbiased). Set, Numeric Expression 5 1 Var Variance Alias for Var. Set 5 1 Variance Variance Alias for Var. Set, Numeric Expression 5 1 Variance VarianceP Alias for VarP. Set 5 1 VarianceP VarianceP Alias for VarP. Set, Numeric Expression 5 1 VarianceP VarP Returns the variance of a numeric expression evaluated over a set (biased). Set 5 1 VarP VarP Returns the variance of a numeric expression evaluated over a set (biased). Set, Numeric Expression 5 1 VarP VisualTotals Dynamically totals child members specified in a set using a pattern for the total label in the result set. Set 12 1 VisualTotals VisualTotals Dynamically totals child members specified in a set using a pattern for the total label in the result set. Set, String 12 1 VisualTotals Weekday Returns a Variant (Integer) containing a whole number representing the day of the week. DateTime, Integer 2 1 Weekday Weekday Returns a Variant (Integer) containing a whole number representing the day of the week. DateTime 2 1 Weekday WeekdayName Returns a string indicating the specified day of the week. Integer, Logical Expression, Integer 8 1 WeekdayName Wtd A shortcut function for the PeriodsToDate function that specifies the level to be Week. 12 1 Wtd Wtd A shortcut function for the PeriodsToDate function that specifies the level to be Week. Member 12 1 Wtd XOR Returns whether two conditions are mutually exclusive. Logical Expression, Logical Expression 11 1 XOR Year Returns a Variant (Integer) containing a whole number representing the year. DateTime 2 1 Year Ytd A shortcut function for the PeriodsToDate function that specifies the level to be Year. 12 1 Ytd Ytd A shortcut function for the PeriodsToDate function that specifies the level to be Year. Member 12 1 Ytd {} Brace operator constructs a set. (none) 1 1 {} || Concatenates two strings. String, String 8 1 || ]]> ${request.type} ${data.source.info} ${content} ]]> FoodMart FoodMart Sales [Customers] Customers [Customers] Customers 3 10407 [Customers].[All Customers] [Customers].[All Customers] Sales Cube - Customers Hierarchy 0 false false 0 true true 9 true false FoodMart FoodMart Sales [Education Level] Education Level [Education Level] Education Level 3 6 [Education Level].[All Education Levels] [Education Level].[All Education Levels] Sales Cube - Education Level Hierarchy 0 false false 0 true true 10 true false FoodMart FoodMart Sales [Gender] Gender [Gender] Gender 3 3 [Gender].[All Gender] [Gender].[All Gender] Sales Cube - Gender Hierarchy 0 false false 0 true true 11 true false FoodMart FoodMart Sales [Marital Status] Marital Status [Marital Status] Marital Status 3 112 [Marital Status].[All Marital Status] [Marital Status].[All Marital Status] Sales Cube - Marital Status Hierarchy 0 false false 0 true true 12 true false FoodMart FoodMart Sales [Measures] Measures [Measures] Measures 2 9 [Measures].[Unit Sales] Sales Cube - Measures Hierarchy 0 false false 0 true true 0 true false FoodMart FoodMart Sales [Product] Product [Product] Product 3 2256 [Product].[All Products] [Product].[All Products] Sales Cube - Product Hierarchy 0 false false 0 true true 6 true false FoodMart FoodMart Sales [Promotion Media] Promotion Media [Promotion Media] Promotion Media 3 15 [Promotion Media].[All Media] [Promotion Media].[All Media] Sales Cube - Promotion Media Hierarchy 0 false false 0 true true 7 true false FoodMart FoodMart Sales [Promotions] Promotions [Promotions] Promotions 3 52 [Promotions].[All Promotions] [Promotions].[All Promotions] Sales Cube - Promotions Hierarchy 0 false false 0 true true 8 true false FoodMart FoodMart Sales [Store Size in SQFT] Store Size in SQFT [Store Size in SQFT] Store Size in SQFT 3 22 [Store Size in SQFT].[All Store Size in SQFTs] [Store Size in SQFT].[All Store Size in SQFTs] Sales Cube - Store Size in SQFT Hierarchy 0 false false 0 true true 2 true false FoodMart FoodMart Sales [Store Type] Store Type [Store Type] Store Type 3 7 [Store Type].[All Store Types] [Store Type].[All Store Types] Sales Cube - Store Type Hierarchy 0 false false 0 true true 3 true false FoodMart FoodMart Sales [Store] Store [Store] Store 3 63 [Store].[All Stores] [Store].[All Stores] Sales Cube - Store Hierarchy 0 false false 0 true true 1 true false FoodMart FoodMart Sales [Time] Time [Time] Time 1 34 [Time].[1997] Sales Cube - Time Hierarchy 0 false false 0 true true 4 true false FoodMart FoodMart Sales [Time] Weekly [Time].[Weekly] Weekly 1 837 [Time].[Weekly].[All Weeklys] [Time].[Weekly].[All Weeklys] Sales Cube - Time.Weekly Hierarchy 0 false false 0 true true 5 true false FoodMart FoodMart Sales [Yearly Income] Yearly Income [Yearly Income] Yearly Income 3 9 [Yearly Income].[All Yearly Incomes] [Yearly Income].[All Yearly Incomes] Sales Cube - Yearly Income Hierarchy 0 false false 0 true true 13 true false ]]> ${request.type} ${catalog} ${cube.name} ${data.source.info} ${content} ]]> FoodMart FoodMart Sales [Customers] [Customers] (All) [Customers].[(All)] (All) 0 1 1 0 3 true Sales Cube - Customers Hierarchy - (All) Level FoodMart FoodMart Sales [Customers] [Customers] Country [Customers].[Country] Country 1 3 0 0 1 true Sales Cube - Customers Hierarchy - Country Level FoodMart FoodMart Sales [Customers] [Customers] State Province [Customers].[State Province] State Province 2 13 0 0 1 true Sales Cube - Customers Hierarchy - State Province Level FoodMart FoodMart Sales [Customers] [Customers] City [Customers].[City] City 3 109 0 0 0 true Sales Cube - Customers Hierarchy - City Level FoodMart FoodMart Sales [Customers] [Customers] Name [Customers].[Name] Name 4 10281 0 0 1 true Sales Cube - Customers Hierarchy - Name Level ]]> ${request.type} ${catalog.name} ${cube.name} <${unique.name.element}>${unique.name} ${data.source.info} ${catalog} ${format} ${content} ]]> FoodMart FoodMart Sales [Customers] [Customers] State Province [Customers].[State Province] State Province 0 13 0 0 1 true Sales Cube - Customers Hierarchy - State Province Level FoodMart FoodMart Sales [Customers] [Customers] City [Customers].[City] City 1 109 0 0 0 true Sales Cube - Customers Hierarchy - City Level ]]> ${request.type} ${catalog.name} ${cube.name} <${unique.name.element}>${unique.name} ${data.source.info} ${catalog} ${format} ${content} ]]> FoodMart FoodMart Sales Customer Count [Measures].[Customer Count] Customer Count 0 3 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Customer Count Member FoodMart FoodMart Sales Profit [Measures].[Profit] Profit 127 130 true Sales Cube - Profit Member FoodMart FoodMart Sales Profit Growth [Measures].[Profit Growth] Gewinn-Wachstum 127 130 true Sales Cube - Profit Growth Member FoodMart FoodMart Sales Profit last Period [Measures].[Profit last Period] Profit last Period 127 130 false Sales Cube - Profit last Period Member FoodMart FoodMart Sales Promotion Sales [Measures].[Promotion Sales] Promotion Sales 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Promotion Sales Member FoodMart FoodMart Sales Sales Count [Measures].[Sales Count] Sales Count 2 3 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Sales Count Member FoodMart FoodMart Sales Store Cost [Measures].[Store Cost] Store Cost 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Store Cost Member FoodMart FoodMart Sales Store Sales [Measures].[Store Sales] Store Sales 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Store Sales Member FoodMart FoodMart Sales Unit Sales [Measures].[Unit Sales] Unit Sales 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Unit Sales Member ]]> ${request.type} ${catalog.name} ${cube.name} ${data.source.info} ${catalog} ${format} ${content} true ]]> FoodMart FoodMart Sales [Gender] [Gender] [Gender].[(All)] 0 0 All Gender [Gender].[All Gender] 2 All Gender 2 0 0 0 FoodMart FoodMart Sales [Gender] [Gender] [Gender].[Gender] 1 1 F [Gender].[F] 1 F 0 0 [Gender].[All Gender] 1 1 FoodMart FoodMart Sales [Gender] [Gender] [Gender].[Gender] 1 2 M [Gender].[M] 1 M 0 0 [Gender].[All Gender] 1 1 ]]> ${request.type} ${catalog.name} ${cube.name} <${unique.name.element}>${unique.name} ${data.source.info} ${catalog} ${format} ${content} ]]> FoodMart FoodMart Sales [Customers] [Customers] [Customers].[City] 3 7930 Portland [Customers].[USA].[OR].[Portland] 1 Portland 90 2 [Customers].[USA].[OR] 1 3 FoodMart FoodMart Sales [Customers] [Customers] [Customers].[State Province] 2 2967 CA [Customers].[USA].[CA] 1 CA 45 1 [Customers].[USA] 1 2 ]]> ${request.type} ${catalog.name} ${cube.name} [Customers].[USA].[CA] [Customers].[USA].[OR].[Portland] ${data.source.info} ${catalog} ${format} ${content} ]]> FoodMart FoodMart Sales [Customers] [Customers] [Customers].[(All)] 0 0 All Customers [Customers].[All Customers] 2 All Customers 3 0 0 0 FoodMart FoodMart Sales [Customers] [Customers] [Customers].[Country] 1 2966 USA [Customers].[USA] 1 USA 3 0 [Customers].[All Customers] 1 1 FoodMart FoodMart Sales [Customers] [Customers] [Customers].[State Province] 2 2967 CA [Customers].[USA].[CA] 1 CA 45 1 [Customers].[USA] 1 2 FoodMart FoodMart Sales [Customers] [Customers] [Customers].[State Province] 2 8298 WA [Customers].[USA].[WA] 1 WA 22 1 [Customers].[USA] 1 2 ]]> ${request.type} ${catalog.name} ${cube.name} 34 [Customers].[USA].[OR] ${data.source.info} ${catalog} ${format} ${content} ]]> FoodMart FoodMart HR [Store] [Store] [Store].[Store Name] Store Type Store Type 1 130 0 HR Cube - Store Hierarchy - Store Name Level - Store Type Property FoodMart FoodMart HR [Store] [Store] [Store].[Store Name] Store Manager Store Manager 1 130 0 HR Cube - Store Hierarchy - Store Name Level - Store Manager Property FoodMart FoodMart HR [Store] [Store] [Store].[Store Name] Store Sqft Store Sqft 1 5 0 HR Cube - Store Hierarchy - Store Name Level - Store Sqft Property FoodMart FoodMart HR [Store] [Store] [Store].[Store Name] Grocery Sqft Grocery Sqft 1 5 0 HR Cube - Store Hierarchy - Store Name Level - Grocery Sqft Property FoodMart FoodMart HR [Store] [Store] [Store].[Store Name] Frozen Sqft Frozen Sqft 1 5 0 HR Cube - Store Hierarchy - Store Name Level - Frozen Sqft Property FoodMart FoodMart HR [Store] [Store] [Store].[Store Name] Meat Sqft Meat Sqft 1 5 0 HR Cube - Store Hierarchy - Store Name Level - Meat Sqft Property FoodMart FoodMart HR [Store] [Store] [Store].[Store Name] Has coffee bar Has coffee bar 1 11 0 HR Cube - Store Hierarchy - Store Name Level - Has coffee bar Property FoodMart FoodMart HR [Store] [Store] [Store].[Store Name] Street address Street address 1 130 0 HR Cube - Store Hierarchy - Store Name Level - Street address Property FoodMart FoodMart HR [Employees] [Employees] [Employees].[Employee Id] Marital Status Marital Status 1 130 0 HR Cube - Employees Hierarchy - Employee Id Level - Marital Status Property FoodMart FoodMart HR [Employees] [Employees] [Employees].[Employee Id] Position Title Position Title 1 130 0 HR Cube - Employees Hierarchy - Employee Id Level - Position Title Property FoodMart FoodMart HR [Employees] [Employees] [Employees].[Employee Id] Gender Gender 1 130 0 HR Cube - Employees Hierarchy - Employee Id Level - Gender Property FoodMart FoodMart HR [Employees] [Employees] [Employees].[Employee Id] Salary Salary 1 130 0 HR Cube - Employees Hierarchy - Employee Id Level - Salary Property FoodMart FoodMart HR [Employees] [Employees] [Employees].[Employee Id] Education Level Education Level 1 130 0 HR Cube - Employees Hierarchy - Employee Id Level - Education Level Property FoodMart FoodMart HR [Employees] [Employees] [Employees].[Employee Id] Management Role Management Role 1 130 0 HR Cube - Employees Hierarchy - Employee Id Level - Management Role Property FoodMart FoodMart Sales [Store] [Store] [Store].[Store Name] Store Type Store Type 1 130 0 Sales Cube - Store Hierarchy - Store Name Level - Store Type Property FoodMart FoodMart Sales [Store] [Store] [Store].[Store Name] Store Manager Store Manager 1 130 0 Sales Cube - Store Hierarchy - Store Name Level - Store Manager Property FoodMart FoodMart Sales [Store] [Store] [Store].[Store Name] Store Sqft Store Sqft 1 5 0 Sales Cube - Store Hierarchy - Store Name Level - Store Sqft Property FoodMart FoodMart Sales [Store] [Store] [Store].[Store Name] Grocery Sqft Grocery Sqft 1 5 0 Sales Cube - Store Hierarchy - Store Name Level - Grocery Sqft Property FoodMart FoodMart Sales [Store] [Store] [Store].[Store Name] Frozen Sqft Frozen Sqft 1 5 0 Sales Cube - Store Hierarchy - Store Name Level - Frozen Sqft Property FoodMart FoodMart Sales [Store] [Store] [Store].[Store Name] Meat Sqft Meat Sqft 1 5 0 Sales Cube - Store Hierarchy - Store Name Level - Meat Sqft Property FoodMart FoodMart Sales [Store] [Store] [Store].[Store Name] Has coffee bar Has coffee bar 1 11 0 Sales Cube - Store Hierarchy - Store Name Level - Has coffee bar Property FoodMart FoodMart Sales [Store] [Store] [Store].[Store Name] Street address Street address 1 130 0 Sales Cube - Store Hierarchy - Store Name Level - Street address Property FoodMart FoodMart Sales [Customers] [Customers] [Customers].[Name] Gender Gender 1 130 0 Sales Cube - Customers Hierarchy - Name Level - Gender Property FoodMart FoodMart Sales [Customers] [Customers] [Customers].[Name] Marital Status Marital Status 1 130 0 Sales Cube - Customers Hierarchy - Name Level - Marital Status Property FoodMart FoodMart Sales [Customers] [Customers] [Customers].[Name] Education Education 1 130 0 Sales Cube - Customers Hierarchy - Name Level - Education Property FoodMart FoodMart Sales [Customers] [Customers] [Customers].[Name] Yearly Income Yearly Income 1 130 0 Sales Cube - Customers Hierarchy - Name Level - Yearly Income Property FoodMart FoodMart Sales Ragged [Store] [Store] [Store].[Store Name] Store Type Store Type 1 130 0 Sales Ragged Cube - Store Hierarchy - Store Name Level - Store Type Property FoodMart FoodMart Sales Ragged [Store] [Store] [Store].[Store Name] Store Manager Store Manager 1 130 0 Sales Ragged Cube - Store Hierarchy - Store Name Level - Store Manager Property FoodMart FoodMart Sales Ragged [Store] [Store] [Store].[Store Name] Store Sqft Store Sqft 1 5 0 Sales Ragged Cube - Store Hierarchy - Store Name Level - Store Sqft Property FoodMart FoodMart Sales Ragged [Store] [Store] [Store].[Store Name] Grocery Sqft Grocery Sqft 1 5 0 Sales Ragged Cube - Store Hierarchy - Store Name Level - Grocery Sqft Property FoodMart FoodMart Sales Ragged [Store] [Store] [Store].[Store Name] Frozen Sqft Frozen Sqft 1 5 0 Sales Ragged Cube - Store Hierarchy - Store Name Level - Frozen Sqft Property FoodMart FoodMart Sales Ragged [Store] [Store] [Store].[Store Name] Meat Sqft Meat Sqft 1 5 0 Sales Ragged Cube - Store Hierarchy - Store Name Level - Meat Sqft Property FoodMart FoodMart Sales Ragged [Store] [Store] [Store].[Store Name] Has coffee bar Has coffee bar 1 11 0 Sales Ragged Cube - Store Hierarchy - Store Name Level - Has coffee bar Property FoodMart FoodMart Sales Ragged [Store] [Store] [Store].[Store Name] Street address Street address 1 130 0 Sales Ragged Cube - Store Hierarchy - Store Name Level - Street address Property FoodMart FoodMart Sales Ragged [Customers] [Customers] [Customers].[Name] Gender Gender 1 130 0 Sales Ragged Cube - Customers Hierarchy - Name Level - Gender Property FoodMart FoodMart Sales Ragged [Customers] [Customers] [Customers].[Name] Marital Status Marital Status 1 130 0 Sales Ragged Cube - Customers Hierarchy - Name Level - Marital Status Property FoodMart FoodMart Sales Ragged [Customers] [Customers] [Customers].[Name] Education Education 1 130 0 Sales Ragged Cube - Customers Hierarchy - Name Level - Education Property FoodMart FoodMart Sales Ragged [Customers] [Customers] [Customers].[Name] Yearly Income Yearly Income 1 130 0 Sales Ragged Cube - Customers Hierarchy - Name Level - Yearly Income Property FoodMart FoodMart Store [Store] [Store] [Store].[Store Name] Store Type Store Type 1 130 0 Store Cube - Store Hierarchy - Store Name Level - Store Type Property FoodMart FoodMart Store [Store] [Store] [Store].[Store Name] Store Manager Store Manager 1 130 0 Store Cube - Store Hierarchy - Store Name Level - Store Manager Property FoodMart FoodMart Store [Store] [Store] [Store].[Store Name] Store Sqft Store Sqft 1 5 0 Store Cube - Store Hierarchy - Store Name Level - Store Sqft Property FoodMart FoodMart Store [Store] [Store] [Store].[Store Name] Grocery Sqft Grocery Sqft 1 5 0 Store Cube - Store Hierarchy - Store Name Level - Grocery Sqft Property FoodMart FoodMart Store [Store] [Store] [Store].[Store Name] Frozen Sqft Frozen Sqft 1 5 0 Store Cube - Store Hierarchy - Store Name Level - Frozen Sqft Property FoodMart FoodMart Store [Store] [Store] [Store].[Store Name] Meat Sqft Meat Sqft 1 5 0 Store Cube - Store Hierarchy - Store Name Level - Meat Sqft Property FoodMart FoodMart Store [Store] [Store] [Store].[Store Name] Has coffee bar Has coffee bar 1 11 0 Store Cube - Store Hierarchy - Store Name Level - Has coffee bar Property FoodMart FoodMart Store [Store] [Store] [Store].[Store Name] Street address Street address 1 130 0 Store Cube - Store Hierarchy - Store Name Level - Street address Property FoodMart FoodMart Warehouse [Store] [Store] [Store].[Store Name] Store Type Store Type 1 130 0 Warehouse Cube - Store Hierarchy - Store Name Level - Store Type Property FoodMart FoodMart Warehouse [Store] [Store] [Store].[Store Name] Store Manager Store Manager 1 130 0 Warehouse Cube - Store Hierarchy - Store Name Level - Store Manager Property FoodMart FoodMart Warehouse [Store] [Store] [Store].[Store Name] Store Sqft Store Sqft 1 5 0 Warehouse Cube - Store Hierarchy - Store Name Level - Store Sqft Property FoodMart FoodMart Warehouse [Store] [Store] [Store].[Store Name] Grocery Sqft Grocery Sqft 1 5 0 Warehouse Cube - Store Hierarchy - Store Name Level - Grocery Sqft Property FoodMart FoodMart Warehouse [Store] [Store] [Store].[Store Name] Frozen Sqft Frozen Sqft 1 5 0 Warehouse Cube - Store Hierarchy - Store Name Level - Frozen Sqft Property FoodMart FoodMart Warehouse [Store] [Store] [Store].[Store Name] Meat Sqft Meat Sqft 1 5 0 Warehouse Cube - Store Hierarchy - Store Name Level - Meat Sqft Property FoodMart FoodMart Warehouse [Store] [Store] [Store].[Store Name] Has coffee bar Has coffee bar 1 11 0 Warehouse Cube - Store Hierarchy - Store Name Level - Has coffee bar Property FoodMart FoodMart Warehouse [Store] [Store] [Store].[Store Name] Street address Street address 1 130 0 Warehouse Cube - Store Hierarchy - Store Name Level - Street address Property FoodMart FoodMart Warehouse and Sales [Customers] [Customers] [Customers].[Name] Gender Gender 1 130 0 Warehouse and Sales Cube - Customers Hierarchy - Name Level - Gender Property FoodMart FoodMart Warehouse and Sales [Customers] [Customers] [Customers].[Name] Marital Status Marital Status 1 130 0 Warehouse and Sales Cube - Customers Hierarchy - Name Level - Marital Status Property FoodMart FoodMart Warehouse and Sales [Customers] [Customers] [Customers].[Name] Education Education 1 130 0 Warehouse and Sales Cube - Customers Hierarchy - Name Level - Education Property FoodMart FoodMart Warehouse and Sales [Customers] [Customers] [Customers].[Name] Yearly Income Yearly Income 1 130 0 Warehouse and Sales Cube - Customers Hierarchy - Name Level - Yearly Income Property FoodMart FoodMart Warehouse and Sales [Store] [Store] [Store].[Store Name] Store Type Store Type 1 130 0 Warehouse and Sales Cube - Store Hierarchy - Store Name Level - Store Type Property FoodMart FoodMart Warehouse and Sales [Store] [Store] [Store].[Store Name] Store Manager Store Manager 1 130 0 Warehouse and Sales Cube - Store Hierarchy - Store Name Level - Store Manager Property FoodMart FoodMart Warehouse and Sales [Store] [Store] [Store].[Store Name] Store Sqft Store Sqft 1 5 0 Warehouse and Sales Cube - Store Hierarchy - Store Name Level - Store Sqft Property FoodMart FoodMart Warehouse and Sales [Store] [Store] [Store].[Store Name] Grocery Sqft Grocery Sqft 1 5 0 Warehouse and Sales Cube - Store Hierarchy - Store Name Level - Grocery Sqft Property FoodMart FoodMart Warehouse and Sales [Store] [Store] [Store].[Store Name] Frozen Sqft Frozen Sqft 1 5 0 Warehouse and Sales Cube - Store Hierarchy - Store Name Level - Frozen Sqft Property FoodMart FoodMart Warehouse and Sales [Store] [Store] [Store].[Store Name] Meat Sqft Meat Sqft 1 5 0 Warehouse and Sales Cube - Store Hierarchy - Store Name Level - Meat Sqft Property FoodMart FoodMart Warehouse and Sales [Store] [Store] [Store].[Store Name] Has coffee bar Has coffee bar 1 11 0 Warehouse and Sales Cube - Store Hierarchy - Store Name Level - Has coffee bar Property FoodMart FoodMart Warehouse and Sales [Store] [Store] [Store].[Store Name] Street address Street address 1 130 0 Warehouse and Sales Cube - Store Hierarchy - Store Name Level - Street address Property ]]> ${request.type} ${data.source.info} ${content} ]]> FoodMart Mondrian FoodMart data source http://localhost:8080/mondrian/xmla FoodMart Mondrian MDP Unauthenticated ]]> ${request.type} ${content} ]]> Access The read/write behavior of a property string Read 1 Access The read/write behavior of a property string Write 2 Access The read/write behavior of a property string ReadWrite 3 AuthenticationMode Specification of what type of security mode the data source uses. string Unauthenticated no user ID or password needs to be sent. 0 AuthenticationMode Specification of what type of security mode the data source uses. string Authenticated User ID and Password must be included in the information required for the connection. 1 AuthenticationMode Specification of what type of security mode the data source uses. string Integrated the data source uses the underlying security to determine authorization, such as Integrated Security provided by Microsoft Internet Information Services (IIS). 2 ProviderType The types of data supported by the provider. string TDP tabular data provider. 0 ProviderType The types of data supported by the provider. string MDP multidimensional data provider. 1 ProviderType The types of data supported by the provider. string DMP data mining provider. A DMP provider implements the OLE DB for Data Mining specification. 2 TREE_OP Bitmap which controls which relatives of a member are returned string MDTREEOP_CHILDREN Tree operation which returns only the immediate children. 1 TREE_OP Bitmap which controls which relatives of a member are returned string MDTREEOP_SIBLINGS Tree operation which returns members on the same level. 2 TREE_OP Bitmap which controls which relatives of a member are returned string MDTREEOP_PARENT Tree operation which returns only the immediate parent. 4 TREE_OP Bitmap which controls which relatives of a member are returned string MDTREEOP_SELF Tree operation which returns itself in the list of returned rows. 8 TREE_OP Bitmap which controls which relatives of a member are returned string MDTREEOP_DESCENDANTS Tree operation which returns all of the descendants. 16 TREE_OP Bitmap which controls which relatives of a member are returned string MDTREEOP_ANCESTORS Tree operation which returns all of the ancestors. 32 ]]> ${request.type} ${data.source.info} ${content} ]]> $AdjustedProbability $Distance $Probability $ProbabilityStDev $ProbabilityStdDeV $ProbabilityVariance $StDev $StdDeV $Support $Variance AddCalculatedMembers Action After Aggregate All Alter Ancestor And Append As ASC Axis Automatic Back_Color BASC BDESC Before Before_And_After Before_And_Self Before_Self_After BottomCount BottomPercent BottomSum Break Boolean Cache Calculated Call Case Catalog_Name Cell Cell_Ordinal Cells Chapters Children Children_Cardinality ClosingPeriod Cluster ClusterDistance ClusterProbability Clusters CoalesceEmpty Column_Values Columns Content Contingent Continuous Correlation Cousin Covariance CovarianceN Create CreatePropertySet CrossJoin Cube Cube_Name CurrentMember CurrentCube Custom Cyclical DefaultMember Default_Member DESC Descendents Description Dimension Dimension_Unique_Name Dimensions Discrete Discretized DrillDownLevel DrillDownLevelBottom DrillDownLevelTop DrillDownMember DrillDownMemberBottom DrillDownMemberTop DrillTrough DrillUpLevel DrillUpMember Drop Else Empty End Equal_Areas Exclude_Null ExcludeEmpty Exclusive Expression Filter FirstChild FirstRowset FirstSibling Flattened Font_Flags Font_Name Font_size Fore_Color Format_String Formatted_Value Formula From Generate Global Head Hierarchize Hierarchy Hierary_Unique_name IIF IsEmpty Include_Null Include_Statistics Inclusive Input_Only IsDescendant Item Lag LastChild LastPeriods LastSibling Lead Level Level_Unique_Name Levels LinRegIntercept LinRegR2 LinRegPoint LinRegSlope LinRegVariance Long MaxRows Median Member Member_Caption Member_Guid Member_Name Member_Ordinal Member_Type Member_Unique_Name Members Microsoft_Clustering Microsoft_Decision_Trees Mining Model Model_Existence_Only Models Move MTD Name Nest NextMember Non Normal Not Ntext Nvarchar OLAP On OpeningPeriod OpenQuery Or Ordered Ordinal Pages Pages ParallelPeriod Parent Parent_Level Parent_Unique_Name PeriodsToDate PMML Predict Predict_Only PredictAdjustedProbability PredictHistogram Prediction PredictionScore PredictProbability PredictProbabilityStDev PredictProbabilityVariance PredictStDev PredictSupport PredictVariance PrevMember Probability Probability_StDev Probability_StdDev Probability_Variance Properties Property QTD RangeMax RangeMid RangeMin Rank Recursive Refresh Related Rename Rollup Rows Schema_Name Sections Select Self Self_And_After Sequence_Time Server Session Set SetToArray SetToStr Shape Skip Solve_Order Sort StdDev Stdev StripCalculatedMembers StrToSet StrToTuple SubSet Support Tail Text Thresholds ToggleDrillState TopCount TopPercent TopSum TupleToStr Under Uniform UniqueName Use Value Value Var Variance VarP VarianceP VisualTotals When Where With WTD Xor ]]> ${request.type} ${data.source.info} ${content} ]]> DBLITERAL_CATALOG_NAME . 0123456789 24 DBLITERAL_CATALOG_SEPARATOR . 0 DBLITERAL_COLUMN_ALIAS '"[] 0123456789 -1 DBLITERAL_COLUMN_NAME . 0123456789 -1 DBLITERAL_CORRELATION_NAME '"[] 0123456789 -1 DBLITERAL_CUBE_NAME . 0123456789 -1 DBLITERAL_DIMENSION_NAME . 0123456789 -1 DBLITERAL_HIERARCHY_NAME . 0123456789 -1 DBLITERAL_LEVEL_NAME . 0123456789 -1 DBLITERAL_MEMBER_NAME . 0123456789 -1 DBLITERAL_PROCEDURE_NAME . 0123456789 -1 DBLITERAL_PROPERTY_NAME . 0123456789 -1 DBLITERAL_QUOTE [ -1 DBLITERAL_QUOTE_SUFFIX ] -1 DBLITERAL_TABLE_NAME . 0123456789 -1 DBLITERAL_TEXT_COMMAND -1 DBLITERAL_USER_NAME 0 ]]> ${request.type} ${data.source.info} ${content} ]]> AxisFormat Determines the format used within an MDDataSet result set to describe the axes of the multidimensional dataset. This property can have the values listed in the following table: TupleFormat (default), ClusterFormat, CustomFormat. Enumeration Write false BeginRange Contains a zero-based integer value corresponding to a CellOrdinal attribute value. (The CellOrdinal attribute is part of the Cell element in the CellData section of MDDataSet.) Used together with the EndRange property, the client application can use this property to restrict an OLAP dataset returned by a command to a specific range of cells. If -1 is specified, all cells up to the cell specified in the EndRange property are returned. The default value for this property is -1. Integer Write false -1 Catalog When establishing a session with an Analysis Services instance to send an XMLA command, this property is equivalent to the OLE DB property, DBPROP_INIT_CATALOG. When you set this property during a session to change the current database for the session, this property is equivalent to the OLE DB property, DBPROP_CURRENTCATALOG. The default value for this property is an empty string. string ReadWrite false Content An enumerator that specifies what type of data is returned in the result set. None: Allows the structure of the command to be verified, but not executed. Analogous to using Prepare to check syntax, and so on. Schema: Contains the XML schema (which indicates column information, and so on) that relates to the requested query. Data: Contains only the data that was requested. SchemaData: Returns both the schema information as well as the data. EnumString Write false SchemaData Cube The cube context for the Command parameter. If the command contains a cube name (such as an MDX FROM clause) the setting of this property is ignored. string ReadWrite false DataSourceInfo A string containing provider specific information, required to access the data source. string ReadWrite false Deep In an MDSCHEMA_CUBES request, whether to include sub-elements (dimensions, hierarchies, levels, measures, named sets) of each cube. Boolean ReadWrite false EmitInvisibleMembers Whether to include members whose VISIBLE property is false, or measures whose MEASURE_IS_VISIBLE property is false. Boolean ReadWrite false EndRange An integer value corresponding to a CellOrdinal used to restrict an MDDataSet returned by a command to a specific range of cells. Used in conjunction with the BeginRange property. If unspecified, all cells are returned in the rowset. The value -1 means unspecified. Integer Write false -1 Format Enumerator that determines the format of the returned result set. Values include: Tabular: a flat or hierarchical rowset. Similar to the XML RAW format in SQL. The Format property should be set to Tabular for OLE DB for Data Mining commands. Multidimensional: Indicates that the result set will use the MDDataSet format (Execute method only). Native: The client does not request a specific format, so the provider may return the format appropriate to the query. (The actual result type is identified by namespace of the result.) EnumString Write false Native LocaleIdentifier Use this to read or set the numeric locale identifier for this request. The default is provider-specific. For the complete hexadecimal list of language identifiers, search on "Language Identifiers" in the MSDN Library at http://www.msdn.microsoft.com. As an extension to the XMLA standard, Mondrian also allows locale codes as specified by ISO-639 and ISO-3166 and as used by Java; for example 'en-US'. UnsignedInteger ReadWrite false None MDXSupport Enumeration that describes the degree of MDX support. At initial release Core is the only value in the enumeration. In future releases, other values will be defined for this enumeration. EnumString Read false Core Password This property is deprecated in XMLA 1.1. To support legacy applications, the provider accepts but ignores the Password property setting when it is used with the Discover and Execute method string Read false ProviderName The XML for Analysis Provider name. string Read false Mondrian XML for Analysis Provider ProviderVersion The version of the Mondrian XMLA Provider string Read false ${mondrianVersion} ResponseMimeType Accepted mime type for RPC response; accepted are 'text/xml' (default), 'application/xml' (equivalent to 'text/xml'), or 'application/json'. If not specified, value in the 'Accept' header of the HTTP request is used. string ReadWrite false None StateSupport Property that specifies the degree of support in the provider for state. For information about state in XML for Analysis, see "Support for Statefulness in XML for Analysis." Minimum enumeration values are as follows: None - No support for sessions or stateful operations. Sessions - Provider supports sessions. EnumString Read false None Timeout A numeric time-out specifying in seconds the amount of time to wait for a request to be successful. UnsignedInteger ReadWrite false Undefined UserName Returns the UserName the server associates with the command. This property is deprecated as writeable in XMLA 1.1. To support legacy applications, servers accept but ignore the password setting when it is used with the Execute method. string Read false VisualMode This property is equivalent to the OLE DB property, MDPROP_VISUALMODE. The default value for this property is zero (0), equivalent to DBPROPVAL_VISUAL_MODE_DEFAULT. Enumeration Write false 1 TableFields List of fields to return for drill-through. The default value of this property is the empty string,in which case, all fields are returned. string Read false AdvancedFlag Boolean Read false false ]]> ${request.type} ${content} ]]> DBSCHEMA_CATALOGS CATALOG_NAME xsd:string Identifies the physical attributes associated with catalogs accessible from the provider. DBSCHEMA_COLUMNS TABLE_CATALOG xsd:string TABLE_SCHEMA xsd:string TABLE_NAME xsd:string COLUMN_NAME xsd:string DBSCHEMA_PROVIDER_TYPES DATA_TYPE xsd:unsignedShort BEST_MATCH xsd:boolean DBSCHEMA_SCHEMATA CATALOG_NAME xsd:string SCHEMA_NAME xsd:string SCHEMA_OWNER xsd:string DBSCHEMA_TABLES TABLE_CATALOG xsd:string TABLE_SCHEMA xsd:string TABLE_NAME xsd:string TABLE_TYPE xsd:string DBSCHEMA_TABLES_INFO TABLE_CATALOG xsd:string TABLE_SCHEMA xsd:string TABLE_NAME xsd:string TABLE_TYPE xsd:string DISCOVER_DATASOURCES DataSourceName xsd:string URL xsd:string ProviderName xsd:string ProviderType xsd:string AuthenticationMode xsd:string Returns a list of XML for Analysis data sources available on the server or Web Service. DISCOVER_ENUMERATORS EnumName xsd:string Returns a list of names, data types, and enumeration values for enumerators supported by the provider of a specific data source. DISCOVER_KEYWORDS Keyword xsd:string Returns an XML list of keywords reserved by the provider. DISCOVER_LITERALS LiteralName xsd:string Returns information about literals supported by the provider. DISCOVER_PROPERTIES PropertyName xsd:string Returns a list of information and values about the requested properties that are supported by the specified data source provider. DISCOVER_SCHEMA_ROWSETS SchemaName xsd:string Returns the names, values, and other information of all supported RequestType enumeration values. MDSCHEMA_ACTIONS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string ACTION_NAME xsd:string COORDINATE xsd:string COORDINATE_TYPE xsd:int MDSCHEMA_CUBES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string CUBE_TYPE xsd:string MDSCHEMA_DIMENSIONS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string MDSCHEMA_FUNCTIONS FUNCTION_NAME xsd:string ORIGIN xsd:int INTERFACE_NAME xsd:string LIBRARY_NAME xsd:string MDSCHEMA_HIERARCHIES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string MDSCHEMA_LEVELS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string LEVEL_NAME xsd:string LEVEL_UNIQUE_NAME xsd:string MDSCHEMA_MEASURES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string MEASURE_NAME xsd:string MEASURE_UNIQUE_NAME xsd:string MDSCHEMA_MEMBERS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string LEVEL_UNIQUE_NAME xsd:string LEVEL_NUMBER xsd:unsignedInt MEMBER_NAME xsd:string MEMBER_UNIQUE_NAME xsd:string MEMBER_TYPE xsd:int MEMBER_CAPTION xsd:string TREE_OP xsd:int MDSCHEMA_PROPERTIES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string LEVEL_UNIQUE_NAME xsd:string MEMBER_UNIQUE_NAME xsd:string PROPERTY_NAME xsd:string PROPERTY_TYPE xsd:short PROPERTY_CONTENT_TYPE xsd:short MDSCHEMA_SETS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string SET_NAME xsd:string SCOPE xsd:int ]]> ${request.type} ${data.source.info} ${content} ]]> FoodMart No description available California manager,No HR Cube ]]> ${request.type} ${data.source.info} ${content} ]]> FoodMart FoodMart ]]> ${request.type} ${data.source.info} ${content} ]]> FoodMart FoodMart Sales [Marital Status] [Marital Status] (All) [Marital Status].[(All)] (All) 0 1 1 0 3 true Sales Cube - Marital Status Hierarchy - (All) Level FoodMart FoodMart Sales [Marital Status] [Marital Status] Marital Status [Marital Status].[Marital Status] Marital Status 1 111 0 0 1 true Sales Cube - Marital Status Hierarchy - Marital Status Level ]]> ${request.type} ${catalog.name} ${cube.name} <${unique.name.element}>${unique.name} ${data.source.info} ${catalog} ${format} ${content} ]]> FoodMart FoodMart Sales [Marital Status] Marital Status [Marital Status] Marital Status 3 112 [Marital Status].[All Marital Status] [Marital Status].[All Marital Status] Sales Cube - Marital Status Hierarchy 0 false false 0 true true 12 true false ]]> ${request.type} ${catalog.name} ${cube.name} <${unique.name.element}>${unique.name} ${data.source.info} ${catalog} ${format} ${content} ]]> 86 86 86 86 86 86 86 86 86 86 86 86 86 86 86 86 86 86 86 86 86 86 86 86 86 86 86 86 USA CA San Francisco Store 14 22478 Small Grocery 1997 Q1 3 11 6 Drink Beverages Drinks Flavored Drinks Fabulous Fabulous Strawberry Drink No Media No Promotion CA Berkeley Achari Harp 4617 High School Degree M M $110K - $130K 3 USA CA San Francisco Store 14 22478 Small Grocery 1997 Q1 3 11 6 Food Baked Goods Bread Sliced Bread Sphinx Sphinx Wheat Bread No Media No Promotion CA Berkeley Achari Harp 4617 High School Degree M M $110K - $130K 3 USA CA San Francisco Store 14 22478 Small Grocery 1997 Q1 3 11 6 Food Deli Meat Bologna Red Spade Red Spade Beef Bologna No Media No Promotion CA Berkeley Achari Harp 4617 High School Degree M M $110K - $130K 3 ]]> DRILLTHROUGH MAXROWS 3 SELECT {[Customers].[USA].[CA].[Berkeley]} ON 0, {[Time].[1997]} ON 1 FROM Sales ${catalog} ${data.source.info} Tabular TupleFormat ]]> 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 USA CA San Francisco Store 14 22478 Small Grocery 1997 Q1 2 9 20 Drink Beverages Pure Juice Beverages Juice Skinner Skinner Apple Juice No Media No Promotion CA Concord Nina Medina 2948 High School Degree M S $30K - $50K 1259 USA CA San Francisco Store 14 22478 Small Grocery 1997 Q3 9 39 19 Drink Beverages Pure Juice Beverages Juice Excellent Excellent Orange Juice No Media No Promotion CA Concord Linda Macias 3956 Bachelors Degree F S $50K - $70K 319 ]]> drillthrough select non empty{[Customers].[USA].[CA].[Concord]} on 0, non empty {[Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice]} on 1 from [Sales] where([Measures].[Sales Count]) ${catalog} ${data.source.info} Tabular TupleFormat ]]> 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 USA CA San Francisco Store 14 22478 Small Grocery 1997 Q3 9 39 19 Drink Beverages Pure Juice Beverages Juice Excellent Excellent Orange Juice No Media No Promotion CA Concord Linda Macias 3956 Bachelors Degree F S $50K - $70K 319 ]]> DRILLTHROUGH MAXROWS 2 select from [Sales] where ([Measures].[Sales Count], [Customers].[USA].[CA].[Concord], [Time].[1997].[Q3], [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice], [Gender]) ${catalog} ${data.source.info} Tabular TupleFormat ]]> Sales [Customers].[Canada] Canada [Customers].[Country] 1 1 [Customers].[Mexico] Mexico [Customers].[Country] 1 131081 [Customers].[USA] USA [Customers].[Country] 1 131075 [Gender].[F] F [Gender].[Gender] 1 0 F [Gender].[M] M [Gender].[Gender] 1 131072 M [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 3 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 [Measures].[Store Sales] Store Sales [Measures].[MeasuresLevel] 0 0 #,###.00 65857.14 65,857.14 #,###.00 66809.13 66,809.13 #,###.00 ]]> SELECT {[Customers].Children} ON 0, {[Gender].Children} DIMENSION PROPERTIES MEMBER_KEY ON 1 FROM Sales WHERE ([Time].[1997].[Q2], [Marital Status], [Measures].[Store Sales]) ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Customers].[USA].[CA].[Altadena].[Alice Cantrell] Alice Cantrell [Customers].[Name] 4 0 4422 55 55 Standard ]]> SELECT {customers.[all customers].[USA].[CA].[Altadena].[Alice Cantrell]} DIMENSION PROPERTIES MEMBER_KEY ON 0 FROM Sales ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Customers].[All Customers] All Customers [Customers].[(All)] 0 3 0 266773 266,773 Standard ]]> SELECT {customers.[all customers]} DIMENSION PROPERTIES MEMBER_KEY ON 0 FROM Sales ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales 4422 [Customers].[USA].[CA].[Altadena].[Alice Cantrell] Alice Cantrell [Customers].[Name] 4 0 55 55 Standard ]]> SELECT {customers.[all customers].[USA].[CA].[Altadena].[Alice Cantrell]} DIMENSION PROPERTIES MEMBER_KEY ON 0 FROM Sales ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Customers].[Canada] Canada [Customers].[Country] 1 1 [Customers].[Mexico] Mexico [Customers].[Country] 1 131081 [Customers].[USA] USA [Customers].[Country] 1 131075 0 [Gender].[All Gender] [Gender].[F] F [Gender].[Gender] 1 0 0 [Gender].[All Gender] [Gender].[M] M [Gender].[Gender] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 3 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 [Measures].[Store Sales] Store Sales [Measures].[MeasuresLevel] 0 0 #,###.00 65857.14 65,857.14 #,###.00 66809.13 66,809.13 #,###.00 ]]> SELECT {[Customers].Children} ON 0, {[Gender].Children} DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON 1 FROM Sales WHERE ([Time].[1997].[Q2], [Marital Status], [Measures].[Store Sales]) ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Customers].[Canada] Canada [Customers].[Country] 1 1 [Customers].[Mexico] Mexico [Customers].[Country] 1 131081 [Customers].[USA] USA [Customers].[Country] 1 131075 [Gender].[F] F [Gender].[Gender] 1 0 [Gender].[M] M [Gender].[Gender] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 3 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 [Measures].[Store Sales] Store Sales [Measures].[MeasuresLevel] 0 0 #,###.00 65857.14 65,857.14 #,###.00 66809.13 66,809.13 #,###.00 ]]> SELECT {[Customers].Children} ON 0, {[Gender].Children} ON 1 FROM Sales WHERE ([Time].[1997].[Q2], [Marital Status], [Measures].[Store Sales]) ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Customers].[Canada] Canada [Customers].[Country] 1 1 [Customers].[Mexico] Mexico [Customers].[Country] 1 131081 [Customers].[USA] USA [Customers].[Country] 1 131075 [Gender].[F] F [Gender].[Gender] 1 0 [Gender].[M] M [Gender].[Gender] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 3 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 [Measures].[Store Sales] Store Sales [Measures].[MeasuresLevel] 0 0 #,###.00 65,857.14 #,###.00 66,809.13 #,###.00 ]]> SELECT {[Customers].Children} ON 0, {[Gender].Children} ON 1 FROM Sales WHERE ([Time].[1997].[Q2], [Marital Status], [Measures].[Store Sales]) Cell Properties Format_String, Formatted_Value ${catalog} ${data.source.info} ${format} TupleFormat ]]> Sales [Customers].[Canada] Canada [Customers].[Country] 1 1 [Customers].[Mexico] Mexico [Customers].[Country] 1 131081 [Customers].[USA] USA [Customers].[Country] 1 131075 [Gender].[F] F [Gender].[Gender] 1 0 [Gender].[M] M [Gender].[Gender] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 3 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 [Measures].[Store Sales] Store Sales [Measures].[MeasuresLevel] 0 0 #,###.00 65857.14 65,857.14 #,###.00 66809.13 66,809.13 #,###.00 ]]> SELECT {[Customers].Children} ON 0, {[Gender].Children} ON 1 FROM Sales WHERE ([Time].[1997].[Q2], [Marital Status], [Measures].[Store Sales]) ${catalog} ${data.source.info} ${format} TupleFormat ]]> SELECT {[Customers].Children} ON 0, {[Gender].Children} ON 1 FROM Sales WHERE ([Time].[1997].[Q2], [Marital Status], [Measures].[Store Sales]) ${catalog} ${data.source.info} ${format} TupleFormat application/json ]]> Sales [Customers].[Canada] Canada [Customers].[Country] 1 1 [Customers].[Mexico] Mexico [Customers].[Country] 1 131081 [Customers].[USA] USA [Customers].[Country] 1 131075 [Gender].[F] F [Gender].[Gender] 1 0 [Gender].[M] M [Gender].[Gender] 1 131072 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 3 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 [Measures].[Store Sales] Store Sales [Measures].[MeasuresLevel] 0 0 #,###.00 65857.14 65,857.14 #,###.00 66809.13 66,809.13 #,###.00 ]]> SELECT {[Customers].Children} ON 0, {[Gender].Children} ON 1 FROM Sales WHERE ([Time].[1997].[Q2], [Marital Status], [Measures].[Store Sales]) ${catalog} ${data.source.info} ${format} TupleFormat ${content} ]]> Sales [Customers].[Canada] Canada [Customers].[Country] 1 1 [Customers].[Mexico] Mexico [Customers].[Country] 1 131081 [Customers].[USA] USA [Customers].[Country] 1 131075 [Gender].[F] F [Gender].[Gender] 1 0 [Gender].[M] M [Gender].[Gender] 1 131072 [Measures].[Store Sales] Store Sales [Measures].[MeasuresLevel] 0 0 [Store].[All Stores] All Stores [Store].[(All)] 0 3 [Store Size in SQFT].[All Store Size in SQFTs] All Store Size in SQFTs [Store Size in SQFT].[(All)] 0 21 [Store Type].[All Store Types] All Store Types [Store Type].[(All)] 0 6 [Time].[1997].[Q2] Q2 [Time].[Quarter] 1 3 [Time].[Weekly].[All Weeklys] All Time.Weeklys [Time].[Weekly].[(All)] 0 2 [Product].[All Products] All Products [Product].[(All)] 0 3 [Promotion Media].[All Media] All Media [Promotion Media].[(All)] 0 14 [Promotions].[All Promotions] All Promotions [Promotions].[(All)] 0 51 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 5 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 [Yearly Income].[All Yearly Incomes] All Yearly Incomes [Yearly Income].[(All)] 0 8 #,###.00 65857.14 65,857.14 #,###.00 66809.13 66,809.13 #,###.00 ]]> SELECT {[Customers].Children} ON 0, {[Gender].Children} ON 1 FROM Sales WHERE ([Time].[1997].[Q2], [Marital Status], [Measures].[Store Sales]) ${catalog} ${data.source.info} ${format} TupleFormat ${content} ]]> Sales [Customers].[Canada] Canada [Customers].[Country] 1 1 [Customers].[Mexico] Mexico [Customers].[Country] 1 131081 [Customers].[USA] USA [Customers].[Country] 1 131075 [Gender].[F] F [Gender].[Gender] 1 0 [Gender].[M] M [Gender].[Gender] 1 131072 Standard 131558 131,558 Standard 135215 135,215 Standard ]]> SELECT {[Customers].Children} ON 0, {[Gender].Children} ON 1 FROM Sales ${catalog} ${data.source.info} ${format} TupleFormat ${content} ]]> Sales [Customers].[Canada] Canada [Customers].[Country] 1 1 [Customers].[Mexico] Mexico [Customers].[Country] 1 131081 [Customers].[USA] USA [Customers].[Country] 1 131075 [Gender].[F] F [Gender].[Gender] 1 0 [Gender].[M] M [Gender].[Gender] 1 131072 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Store].[All Stores] All Stores [Store].[(All)] 0 3 [Store Size in SQFT].[All Store Size in SQFTs] All Store Size in SQFTs [Store Size in SQFT].[(All)] 0 21 [Store Type].[All Store Types] All Store Types [Store Type].[(All)] 0 6 [Time].[1997] 1997 [Time].[Year] 0 4 [Time].[Weekly].[All Weeklys] All Time.Weeklys [Time].[Weekly].[(All)] 0 2 [Product].[All Products] All Products [Product].[(All)] 0 3 [Promotion Media].[All Media] All Media [Promotion Media].[(All)] 0 14 [Promotions].[All Promotions] All Promotions [Promotions].[(All)] 0 51 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 5 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 [Yearly Income].[All Yearly Incomes] All Yearly Incomes [Yearly Income].[(All)] 0 8 Standard 131558 131,558 Standard 135215 135,215 Standard ]]> SELECT {[Customers].Children} ON 0, {[Gender].Children} ON 1 FROM Sales ${catalog} ${data.source.info} ${format} TupleFormat ${content} ]]> Sales [Customers].[Canada] Canada [Customers].[Country] 1 1 [Customers].[Mexico] Mexico [Customers].[Country] 1 131081 [Customers].[USA] USA [Customers].[Country] 1 131075 [Gender].[F] F [Gender].[Gender] 1 0 [Gender].[M] M [Gender].[Gender] 1 131072 [Measures].[Unit Sales] Unit Sales [Measures].[MeasuresLevel] 0 0 [Store].[All Stores] All Stores [Store].[(All)] 0 3 [Store Size in SQFT].[All Store Size in SQFTs] All Store Size in SQFTs [Store Size in SQFT].[(All)] 0 21 [Store Type].[All Store Types] All Store Types [Store Type].[(All)] 0 6 [Time].[1997] 1997 [Time].[Year] 0 4 [Time].[Weekly].[All Weeklys] All Time.Weeklys [Time].[Weekly].[(All)] 0 2 [Product].[All Products] All Products [Product].[(All)] 0 3 [Promotion Media].[All Media] All Media [Promotion Media].[(All)] 0 14 [Promotions].[All Promotions] All Promotions [Promotions].[(All)] 0 51 [Education Level].[All Education Levels] All Education Levels [Education Level].[(All)] 0 5 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 111 [Yearly Income].[All Yearly Incomes] All Yearly Incomes [Yearly Income].[(All)] 0 8 Standard ]]> SELECT {[Customers].Children} ON 0, {[Gender].Children} ON 1 FROM Sales WHERE [Marital Status].Parent * [Time].[1997].[Q1] ${catalog} ${data.source.info} ${format} TupleFormat ${content} ]]> Sales [Customers].[Canada] Canada [Customers].[Country] 1 1 [Customers].[Mexico] Mexico [Customers].[Country] 1 131081 [Customers].[USA] USA [Customers].[Country] 1 131075 [Gender].[F] F [Gender].[Gender] 1 0 [Gender].[M] M [Gender].[Gender] 1 131072 Standard ]]> SELECT {[Customers].Children} ON 0, {[Gender].Children} ON 1 FROM Sales WHERE [Marital Status].Parent * [Time].[1997].[Q1] ${catalog} ${data.source.info} ${format} TupleFormat ${content} ]]> Sales [Product].[Drink] Drink [Product].[Product Family] 1 3 [Customers].[Canada] Canada [Customers].[Country] 1 1 [Product].[Drink] Drink [Product].[Product Family] 1 131075 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food] Food [Product].[Product Family] 1 131087 [Customers].[Canada] Canada [Customers].[Country] 1 131073 [Product].[Food] Food [Product].[Product Family] 1 131087 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable] Non-Consumable [Product].[Product Family] 1 131077 [Customers].[Canada] Canada [Customers].[Country] 1 131073 [Product].[Non-Consumable] Non-Consumable [Product].[Product Family] 1 131077 [Customers].[USA] USA [Customers].[Country] 1 131075 Standard 24597 24,597 Standard 191940 191,940 Standard 50236 50,236 Standard ]]> Sales [Product].[Drink] Drink [Product].[Product Family] 1 3 [Customers].[Canada] Canada [Customers].[Country] 1 1 [Product].[Drink] Drink [Product].[Product Family] 1 131075 [Customers].[Mexico] Mexico [Customers].[Country] 1 131081 [Product].[Drink] Drink [Product].[Product Family] 1 131075 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Food] Food [Product].[Product Family] 1 131087 [Customers].[Canada] Canada [Customers].[Country] 1 131073 [Product].[Food] Food [Product].[Product Family] 1 131087 [Customers].[Mexico] Mexico [Customers].[Country] 1 131081 [Product].[Food] Food [Product].[Product Family] 1 131087 [Customers].[USA] USA [Customers].[Country] 1 131075 [Product].[Non-Consumable] Non-Consumable [Product].[Product Family] 1 131077 [Customers].[Canada] Canada [Customers].[Country] 1 131073 [Product].[Non-Consumable] Non-Consumable [Product].[Product Family] 1 131077 [Customers].[Mexico] Mexico [Customers].[Country] 1 131081 [Product].[Non-Consumable] Non-Consumable [Product].[Product Family] 1 131077 [Customers].[USA] USA [Customers].[Country] 1 131075 Standard 24597 24,597 Standard 191940 191,940 Standard 50236 50,236 Standard ]]> * Multiplies two numbers. Numeric Expression, Numeric Expression 5 1 * * Returns the cross product of two sets. Set, Set 12 1 * * Returns the cross product of two sets. Member, Set 12 1 * * Returns the cross product of two sets. Set, Member 12 1 * * Returns the cross product of two sets. Member, Member 12 1 * + Adds two numbers. Numeric Expression, Numeric Expression 5 1 + - Subtracts two numbers. Numeric Expression, Numeric Expression 5 1 - - Returns the negative of a number. Numeric Expression 5 1 - / Divides two numbers. Numeric Expression, Numeric Expression 5 1 / : Infix colon operator returns the set of members between a given pair of members. Member, Member 12 1 : < Returns whether an expression is less than another. Numeric Expression, Numeric Expression 11 1 < < Returns whether an expression is less than another. String, String 11 1 < <= Returns whether an expression is less than or equal to another. Numeric Expression, Numeric Expression 11 1 <= <= Returns whether an expression is less than or equal to another. String, String 11 1 <= <> Returns whether two expressions are not equal. Numeric Expression, Numeric Expression 11 1 <> <> Returns whether two expressions are not equal. String, String 11 1 <> = Returns whether two expressions are equal. Numeric Expression, Numeric Expression 11 1 = = Returns whether two expressions are equal. String, String 11 1 = > Returns whether an expression is greater than another. Numeric Expression, Numeric Expression 11 1 > > Returns whether an expression is greater than another. String, String 11 1 > >= Returns whether an expression is greater than or equal to another. Numeric Expression, Numeric Expression 11 1 >= >= Returns whether an expression is greater than or equal to another. String, String 11 1 >= _CaseMatch Evaluates various expressions, and returns the corresponding expression for the first which matches a particular value. (none) 1 1 _CaseMatch _CaseTest Evaluates various conditions, and returns the corresponding expression for the first which evaluates to true. (none) 1 1 _CaseTest abs Numeric Expression 5 1 abs acos Numeric Expression 5 1 acos acosh Numeric Expression 5 1 acosh AddCalculatedMembers Adds calculated members to a set. Set 12 1 AddCalculatedMembers Aggregate Returns a calculated value using the appropriate aggregate function, based on the context of the query. Set 5 1 Aggregate Aggregate Returns a calculated value using the appropriate aggregate function, based on the context of the query. Set, Numeric Expression 5 1 Aggregate AllMembers Returns a set that contains all members, including calculated members, of the specified hierarchy. Hierarchy 12 1 AllMembers AllMembers Returns a set that contains all members, including calculated members, of the specified level. Level 12 1 AllMembers Ancestor Returns the ancestor of a member at a specified level. Member, Level 12 1 Ancestor Ancestor Returns the ancestor of a member at a specified level. Member, Numeric Expression 12 1 Ancestor AND Returns the conjunction of two conditions. Logical Expression, Logical Expression 11 1 AND AS (none) 1 1 AS asc String 2 1 asc ascB String 2 1 ascB Ascendants Returns the set of the ascendants of a specified member. Member 12 1 Ascendants ascW String 2 1 ascW asin Numeric Expression 5 1 asin asinh Numeric Expression 5 1 asinh atan2 Numeric Expression, Numeric Expression 5 1 atan2 atanh Numeric Expression 5 1 atanh atn Numeric Expression 5 1 atn Avg Returns the average value of a numeric expression evaluated over a set. Set 5 1 Avg Avg Returns the average value of a numeric expression evaluated over a set. Set, Numeric Expression 5 1 Avg BottomCount Returns a specified number of items from the bottom of a set, optionally ordering the set first. Set, Numeric Expression, Numeric Expression 12 1 BottomCount BottomCount Returns a specified number of items from the bottom of a set, optionally ordering the set first. Set, Numeric Expression 12 1 BottomCount BottomPercent Sorts a set and returns the bottom N elements whose cumulative total is at least a specified percentage. Set, Numeric Expression, Numeric Expression 12 1 BottomPercent BottomSum Sorts a set and returns the bottom N elements whose cumulative total is at least a specified value. Set, Numeric Expression, Numeric Expression 12 1 BottomSum Cache Evaluates and returns its sole argument, applying statement-level caching (none) 1 1 Cache CalculatedChild Returns an existing calculated child member with name <String> from the specified <Member>. Member, String 12 1 CalculatedChild Caption Returns the caption of a dimension. Dimension 8 1 Caption Caption Returns the caption of a hierarchy. Hierarchy 8 1 Caption Caption Returns the caption of a level. Level 8 1 Caption Caption Returns the caption of a member. Member 8 1 Caption Cast Converts values to another type. (none) 1 1 Cast cBool Value 11 1 cBool cByte Value 2 1 cByte cDate Value 7 1 cDate cDbl Value 5 1 cDbl Children Returns the children of a member. Member 12 1 Children chr Integer 8 1 chr chrB Integer 8 1 chrB chrW Integer 8 1 chrW cInt Value 2 1 cInt ClosingPeriod Returns the last descendant of a member at a level. 12 1 ClosingPeriod ClosingPeriod Returns the last descendant of a member at a level. Level 12 1 ClosingPeriod ClosingPeriod Returns the last descendant of a member at a level. Level, Member 12 1 ClosingPeriod ClosingPeriod Returns the last descendant of a member at a level. Member 12 1 ClosingPeriod CoalesceEmpty Coalesces an empty cell value to a different value. All of the expressions must be of the same type (number or string). (none) 1 1 CoalesceEmpty Correlation Returns the correlation of two series evaluated over a set. Set, Numeric Expression 5 1 Correlation Correlation Returns the correlation of two series evaluated over a set. Set, Numeric Expression, Numeric Expression 5 1 Correlation cos Numeric Expression 5 1 cos cosh Numeric Expression 5 1 cosh Count Returns the number of tuples in a set, empty cells included unless the optional EXCLUDEEMPTY flag is used. Set 5 1 Count Count Returns the number of tuples in a set, empty cells included unless the optional EXCLUDEEMPTY flag is used. Set, Symbol 5 1 Count Count Returns the number of tuples in a set including empty cells. Set 5 1 Count Cousin Returns the member with the same relative position under <ancestor member> as the member specified. Member, Member 12 1 Cousin Covariance Returns the covariance of two series evaluated over a set (biased). Set, Numeric Expression 5 1 Covariance Covariance Returns the covariance of two series evaluated over a set (biased). Set, Numeric Expression, Numeric Expression 5 1 Covariance CovarianceN Returns the covariance of two series evaluated over a set (unbiased). Set, Numeric Expression 5 1 CovarianceN CovarianceN Returns the covariance of two series evaluated over a set (unbiased). Set, Numeric Expression, Numeric Expression 5 1 CovarianceN Crossjoin Returns the cross product of two sets. Set, Set 12 1 Crossjoin Current Returns the current member or tuple of a named set. Set 12 1 Current CurrentDateMember Hierarchy, String, Symbol 12 1 CurrentDateMember CurrentDateMember Hierarchy, String 12 1 CurrentDateMember CurrentDateString String 8 1 CurrentDateString CurrentMember Returns the current member along a hierarchy during an iteration. Hierarchy 12 1 CurrentMember CurrentOrdinal Returns the ordinal of the current iteration through a named set. Set 2 1 CurrentOrdinal DataMember Returns the system-generated data member that is associated with a nonleaf member of a dimension. Member 12 1 DataMember date 7 1 date dateAdd String, Numeric Expression, DateTime 7 1 dateAdd dateDiff String, DateTime, DateTime, Integer, Integer 5 1 dateDiff dateDiff String, DateTime, DateTime, Integer 5 1 dateDiff dateDiff String, DateTime, DateTime 5 1 dateDiff datePart String, DateTime, Integer, Integer 2 1 datePart datePart String, DateTime, Integer 2 1 datePart datePart String, DateTime 2 1 datePart dateSerial Integer, Integer, Integer 7 1 dateSerial dateValue DateTime 7 1 dateValue day DateTime 2 1 day dDB Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 dDB dDB Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 dDB DefaultMember Returns the default member of a hierarchy. Hierarchy 12 1 DefaultMember degrees Numeric Expression 5 1 degrees Descendants Returns the set of descendants of a member at a specified level, optionally including or excluding descendants in other levels. Member 12 1 Descendants Descendants Returns the set of descendants of a member at a specified level, optionally including or excluding descendants in other levels. Member, Level 12 1 Descendants Descendants Returns the set of descendants of a member at a specified level, optionally including or excluding descendants in other levels. Member, Level, Symbol 12 1 Descendants Descendants Returns the set of descendants of a member at a specified level, optionally including or excluding descendants in other levels. Member, Numeric Expression 12 1 Descendants Descendants Returns the set of descendants of a member at a specified level, optionally including or excluding descendants in other levels. Member, Numeric Expression, Symbol 12 1 Descendants Descendants Returns the set of descendants of a member at a specified level, optionally including or excluding descendants in other levels. Member, Empty, Symbol 12 1 Descendants Descendants Returns the set of descendants of a set of members at a specified level, optionally including or excluding descendants in other levels. Set 12 1 Descendants Descendants Returns the set of descendants of a set of members at a specified level, optionally including or excluding descendants in other levels. Set, Level 12 1 Descendants Descendants Returns the set of descendants of a set of members at a specified level, optionally including or excluding descendants in other levels. Set, Level, Symbol 12 1 Descendants Descendants Returns the set of descendants of a set of members at a specified level, optionally including or excluding descendants in other levels. Set, Numeric Expression 12 1 Descendants Descendants Returns the set of descendants of a set of members at a specified level, optionally including or excluding descendants in other levels. Set, Numeric Expression, Symbol 12 1 Descendants Descendants Returns the set of descendants of a set of members at a specified level, optionally including or excluding descendants in other levels. Set, Empty, Symbol 12 1 Descendants Dimension Returns the dimension that contains a specified hierarchy. Dimension 12 1 Dimension Dimension Returns the dimension that contains a specified hierarchy. Hierarchy 12 1 Dimension Dimension Returns the dimension that contains a specified level. Level 12 1 Dimension Dimension Returns the dimension that contains a specified member. Member 12 1 Dimension Dimensions Returns the hierarchy whose zero-based position within the cube is specified by a numeric expression. Numeric Expression 12 1 Dimensions Dimensions Returns the hierarchy whose name is specified by a string. String 12 1 Dimensions Distinct Eliminates duplicate tuples from a set. Set 12 1 Distinct DrilldownLevel Drills down the members of a set, at a specified level, to one level below. Alternatively, drills down on a specified dimension in the set. Set 12 1 DrilldownLevel DrilldownLevel Drills down the members of a set, at a specified level, to one level below. Alternatively, drills down on a specified dimension in the set. Set, Level 12 1 DrilldownLevel DrilldownLevel Drills down the members of a set, at a specified level, to one level below. Alternatively, drills down on a specified dimension in the set. Set, Empty, Numeric Expression 12 1 DrilldownLevel DrilldownLevelBottom Drills down the bottommost members of a set, at a specified level, to one level below. Set, Numeric Expression 12 1 DrilldownLevelBottom DrilldownLevelBottom Drills down the bottommost members of a set, at a specified level, to one level below. Set, Numeric Expression, Level 12 1 DrilldownLevelBottom DrilldownLevelBottom Drills down the bottommost members of a set, at a specified level, to one level below. Set, Numeric Expression, Level, Numeric Expression 12 1 DrilldownLevelBottom DrilldownLevelBottom Drills down the bottommost members of a set, at a specified level, to one level below. Set, Numeric Expression, Empty, Numeric Expression 12 1 DrilldownLevelBottom DrilldownLevelTop Drills down the topmost members of a set, at a specified level, to one level below. Set, Numeric Expression 12 1 DrilldownLevelTop DrilldownLevelTop Drills down the topmost members of a set, at a specified level, to one level below. Set, Numeric Expression, Level 12 1 DrilldownLevelTop DrilldownLevelTop Drills down the topmost members of a set, at a specified level, to one level below. Set, Numeric Expression, Level, Numeric Expression 12 1 DrilldownLevelTop DrilldownLevelTop Drills down the topmost members of a set, at a specified level, to one level below. Set, Numeric Expression, Empty, Numeric Expression 12 1 DrilldownLevelTop DrilldownMember Drills down the members in a set that are present in a second specified set. Set, Set 12 1 DrilldownMember DrilldownMember Drills down the members in a set that are present in a second specified set. Set, Set, Symbol 12 1 DrilldownMember Except Finds the difference between two sets, optionally retaining duplicates. Set, Set 12 1 Except Except Finds the difference between two sets, optionally retaining duplicates. Set, Set, Symbol 12 1 Except Exists Returns the the set of tuples of the first set that exist with one or more tuples of the second set. Set, Set 12 1 Exists exp Numeric Expression 5 1 exp Extract Returns a set of tuples from extracted hierarchy elements. The opposite of Crossjoin. (none) 1 1 Extract Filter Returns the set resulting from filtering a set based on a search condition. Set, Logical Expression 12 1 Filter FirstChild Returns the first child of a member. Member 12 1 FirstChild FirstQ Returns the 1st quartile value of a numeric expression evaluated over a set. Set 5 1 FirstQ FirstQ Returns the 1st quartile value of a numeric expression evaluated over a set. Set, Numeric Expression 5 1 FirstQ FirstSibling Returns the first child of the parent of a member. Member 12 1 FirstSibling fix Value 2 1 fix Format Formats a number or date to a string. Member, String 8 1 Format Format Formats a number or date to a string. Numeric Expression, String 8 1 Format Format Formats a number or date to a string. DateTime, String 8 1 Format formatCurrency Value, Integer, Integer, Integer, Integer 8 1 formatCurrency formatCurrency Value, Integer, Integer, Integer 8 1 formatCurrency formatCurrency Value, Integer, Integer 8 1 formatCurrency formatCurrency Value, Integer 8 1 formatCurrency formatCurrency Value 8 1 formatCurrency formatDateTime DateTime, Integer 8 1 formatDateTime formatDateTime DateTime 8 1 formatDateTime formatNumber Value, Integer, Integer, Integer, Integer 8 1 formatNumber formatNumber Value, Integer, Integer, Integer 8 1 formatNumber formatNumber Value, Integer, Integer 8 1 formatNumber formatNumber Value, Integer 8 1 formatNumber formatNumber Value 8 1 formatNumber formatPercent Value, Integer, Integer, Integer, Integer 8 1 formatPercent formatPercent Value, Integer, Integer, Integer 8 1 formatPercent formatPercent Value, Integer, Integer 8 1 formatPercent formatPercent Value, Integer 8 1 formatPercent formatPercent Value 8 1 formatPercent fV Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Logical Expression 5 1 fV fV Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 fV fV Numeric Expression, Numeric Expression, Numeric Expression 5 1 fV Generate Applies a set to each member of another set and joins the resulting sets by union. Set, Set 12 1 Generate Generate Applies a set to each member of another set and joins the resulting sets by union. Set, Set, Symbol 12 1 Generate Generate Applies a set to a string expression and joins resulting sets by string concatenation. Set, String 8 1 Generate Generate Applies a set to a string expression and joins resulting sets by string concatenation. Set, String, String 8 1 Generate Head Returns the first specified number of elements in a set. Set 12 1 Head Head Returns the first specified number of elements in a set. Set, Numeric Expression 12 1 Head hex Value 8 1 hex Hierarchize Orders the members of a set in a hierarchy. Set 12 1 Hierarchize Hierarchize Orders the members of a set in a hierarchy. Set, Symbol 12 1 Hierarchize Hierarchy Returns a level's hierarchy. Level 12 1 Hierarchy Hierarchy Returns a member's hierarchy. Member 12 1 Hierarchy hour DateTime 2 1 hour IIf Returns one of two tuples determined by a logical test. Logical Expression, Tuple, Tuple 12 1 IIf IIf Returns one of two dimension values determined by a logical test. Logical Expression, Dimension, Dimension 12 1 IIf IIf Returns one of two hierarchy values determined by a logical test. Logical Expression, Hierarchy, Hierarchy 12 1 IIf IIf Returns one of two level values determined by a logical test. Logical Expression, Level, Level 12 1 IIf IIf Returns boolean determined by a logical test. Logical Expression, Logical Expression, Logical Expression 11 1 IIf IIf Returns one of two member values determined by a logical test. Logical Expression, Member, Member 12 1 IIf IIf Returns one of two numeric values determined by a logical test. Logical Expression, Numeric Expression, Numeric Expression 5 1 IIf IIf Returns one of two set values determined by a logical test. Logical Expression, Set, Set 12 1 IIf IIf Returns one of two string values determined by a logical test. Logical Expression, String, String 8 1 IIf IN Member, Set 11 1 IN inStr Integer, String, String, Integer 2 1 inStr inStr Integer, String, String 2 1 inStr inStr String, String 2 1 inStr inStrRev String, String, Integer, Integer 2 1 inStrRev inStrRev String, String, Integer 2 1 inStrRev inStrRev String, String 2 1 inStrRev int Value 2 1 int Intersect Returns the intersection of two input sets, optionally retaining duplicates. Set, Set, Symbol 12 1 Intersect Intersect Returns the intersection of two input sets, optionally retaining duplicates. Set, Set 12 1 Intersect InverseNormal Numeric Expression 5 1 InverseNormal iPmt Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Logical Expression 5 1 iPmt iPmt Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 iPmt iPmt Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 iPmt IRR Array, Numeric Expression 5 1 IRR IRR Array 5 1 IRR IS Returns whether two objects are the same Member, Member 11 1 IS IS Returns whether two objects are the same Level, Level 11 1 IS IS Returns whether two objects are the same Hierarchy, Hierarchy 11 1 IS IS Returns whether two objects are the same Dimension, Dimension 11 1 IS IS Returns whether two objects are the same Tuple, Tuple 11 1 IS IS EMPTY Determines if an expression evaluates to the empty cell value. Member 11 1 IS EMPTY IS EMPTY Determines if an expression evaluates to the empty cell value. Tuple 11 1 IS EMPTY IS NULL Returns whether an object is null Member 11 1 IS NULL IS NULL Returns whether an object is null Level 11 1 IS NULL IS NULL Returns whether an object is null Hierarchy 11 1 IS NULL IS NULL Returns whether an object is null Dimension 11 1 IS NULL isDate Value 11 1 isDate IsEmpty Determines if an expression evaluates to the empty cell value. String 11 1 IsEmpty IsEmpty Determines if an expression evaluates to the empty cell value. Numeric Expression 11 1 IsEmpty Item Returns a member from the tuple specified in <Tuple>. The member to be returned is specified by the zero-based position of the member in the set in <Index>. Tuple, Numeric Expression 12 1 Item Item Returns a tuple from the set specified in <Set>. The tuple to be returned is specified by the zero-based position of the tuple in the set in <Index>. Set, Numeric Expression 12 1 Item Item Returns a tuple from the set specified in <Set>. The tuple to be returned is specified by the member name (or names) in <String>. (none) 1 1 Item Lag Returns a member further along the specified member's dimension. Member, Numeric Expression 12 1 Lag LastChild Returns the last child of a member. Member 12 1 LastChild LastNonEmpty Set, Member 12 1 LastNonEmpty LastPeriods Returns a set of members prior to and including a specified member. Numeric Expression 12 1 LastPeriods LastPeriods Returns a set of members prior to and including a specified member. Numeric Expression, Member 12 1 LastPeriods LastSibling Returns the last child of the parent of a member. Member 12 1 LastSibling lCase String 8 1 lCase Lead Returns a member further along the specified member's dimension. Member, Numeric Expression 12 1 Lead left String, Integer 8 1 left Len Returns the number of characters in a string String 5 1 Len Level Returns a member's level. Member 12 1 Level Levels Returns the level whose position in a hierarchy is specified by a numeric expression. Hierarchy, Numeric Expression 12 1 Levels Levels Returns the level whose name is specified by a string expression. Hierarchy, String 12 1 Levels Levels Returns the level whose name is specified by a string expression. String 12 1 Levels LinRegIntercept Calculates the linear regression of a set and returns the value of b in the regression line y = ax + b. Set, Numeric Expression 5 1 LinRegIntercept LinRegIntercept Calculates the linear regression of a set and returns the value of b in the regression line y = ax + b. Set, Numeric Expression, Numeric Expression 5 1 LinRegIntercept LinRegPoint Calculates the linear regression of a set and returns the value of y in the regression line y = ax + b. Numeric Expression, Set, Numeric Expression 5 1 LinRegPoint LinRegPoint Calculates the linear regression of a set and returns the value of y in the regression line y = ax + b. Numeric Expression, Set, Numeric Expression, Numeric Expression 5 1 LinRegPoint LinRegR2 Calculates the linear regression of a set and returns R2 (the coefficient of determination). Set, Numeric Expression 5 1 LinRegR2 LinRegR2 Calculates the linear regression of a set and returns R2 (the coefficient of determination). Set, Numeric Expression, Numeric Expression 5 1 LinRegR2 LinRegSlope Calculates the linear regression of a set and returns the value of a in the regression line y = ax + b. Set, Numeric Expression 5 1 LinRegSlope LinRegSlope Calculates the linear regression of a set and returns the value of a in the regression line y = ax + b. Set, Numeric Expression, Numeric Expression 5 1 LinRegSlope LinRegVariance Calculates the linear regression of a set and returns the variance associated with the regression line y = ax + b. Set, Numeric Expression 5 1 LinRegVariance LinRegVariance Calculates the linear regression of a set and returns the variance associated with the regression line y = ax + b. Set, Numeric Expression, Numeric Expression 5 1 LinRegVariance log Numeric Expression 5 1 log log10 Numeric Expression 5 1 log10 lTrim String 8 1 lTrim MATCHES String, String 11 1 MATCHES Max Returns the maximum value of a numeric expression evaluated over a set. Set 5 1 Max Max Returns the maximum value of a numeric expression evaluated over a set. Set, Numeric Expression 5 1 Max Median Returns the median value of a numeric expression evaluated over a set. Set 5 1 Median Median Returns the median value of a numeric expression evaluated over a set. Set, Numeric Expression 5 1 Median Members Returns the set of members in a hierarchy. Hierarchy 12 1 Members Members Returns the set of members in a level. Level 12 1 Members Members Returns the member whose name is specified by a string expression. String 12 1 Members mid String, Integer, Integer 8 1 mid mid String, Integer 8 1 mid Min Returns the minimum value of a numeric expression evaluated over a set. Set 5 1 Min Min Returns the minimum value of a numeric expression evaluated over a set. Set, Numeric Expression 5 1 Min minute DateTime 2 1 minute MIRR Array, Numeric Expression, Numeric Expression 5 1 MIRR mod Value, Value 5 1 mod month DateTime 2 1 month monthName Integer, Logical Expression 8 1 monthName Mtd A shortcut function for the PeriodsToDate function that specifies the level to be Month. 12 1 Mtd Mtd A shortcut function for the PeriodsToDate function that specifies the level to be Month. Member 12 1 Mtd Name Returns the name of a dimension. Dimension 8 1 Name Name Returns the name of a hierarchy. Hierarchy 8 1 Name Name Returns the name of a level. Level 8 1 Name Name Returns the name of a member. Member 8 1 Name NativizeSet Tries to natively evaluate <Set>. Set 12 1 NativizeSet NextMember Returns the next member in the level that contains a specified member. Member 12 1 NextMember NonEmptyCrossJoin Returns the cross product of two sets, excluding empty tuples and tuples without associated fact table data. Set, Set 12 1 NonEmptyCrossJoin NOT Returns the negation of a condition. Logical Expression 11 1 NOT now 7 1 now nPer Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Logical Expression 5 1 nPer nPV Numeric Expression, Array 5 1 nPV NullValue 5 1 NullValue oct Value 8 1 oct OpeningPeriod Returns the first descendant of a member at a level. 12 1 OpeningPeriod OpeningPeriod Returns the first descendant of a member at a level. Level 12 1 OpeningPeriod OpeningPeriod Returns the first descendant of a member at a level. Level, Member 12 1 OpeningPeriod OR Returns the disjunction of two conditions. Logical Expression, Logical Expression 11 1 OR Order Arranges members of a set, optionally preserving or breaking the hierarchy. (none) 1 1 Order OrderKey Returns the member order key. Member 12 1 OrderKey Ordinal Returns the zero-based ordinal value associated with a level. Level 5 1 Ordinal ParallelPeriod Returns a member from a prior period in the same relative position as a specified member. 12 1 ParallelPeriod ParallelPeriod Returns a member from a prior period in the same relative position as a specified member. Level 12 1 ParallelPeriod ParallelPeriod Returns a member from a prior period in the same relative position as a specified member. Level, Numeric Expression 12 1 ParallelPeriod ParallelPeriod Returns a member from a prior period in the same relative position as a specified member. Level, Numeric Expression, Member 12 1 ParallelPeriod Parameter Returns default value of parameter. String, Symbol, String, String 8 1 Parameter Parameter Returns default value of parameter. String, Symbol, String 8 1 Parameter Parameter Returns default value of parameter. String, Symbol, Numeric Expression, String 5 1 Parameter Parameter Returns default value of parameter. String, Symbol, Numeric Expression 5 1 Parameter Parameter Returns default value of parameter. String, Hierarchy, Member, String 12 1 Parameter Parameter Returns default value of parameter. String, Hierarchy, Member 12 1 Parameter Parameter Returns default value of parameter. String, Hierarchy, Set, String 12 1 Parameter Parameter Returns default value of parameter. String, Hierarchy, Set 12 1 Parameter ParamRef Returns the current value of this parameter. If it is null, returns the default value. String 12 1 ParamRef Parent Returns the parent of a member. Member 12 1 Parent Percentile Returns the value of the tuple that is at a given percentile of a set. Set, Numeric Expression, Numeric Expression 5 1 Percentile PeriodsToDate Returns a set of periods (members) from a specified level starting with the first period and ending with a specified member. 12 1 PeriodsToDate PeriodsToDate Returns a set of periods (members) from a specified level starting with the first period and ending with a specified member. Level 12 1 PeriodsToDate PeriodsToDate Returns a set of periods (members) from a specified level starting with the first period and ending with a specified member. Level, Member 12 1 PeriodsToDate pi 5 1 pi pmt Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Logical Expression 5 1 pmt power Numeric Expression, Numeric Expression 5 1 power pPmt Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Logical Expression 5 1 pPmt pPmt Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 pPmt pPmt Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 pPmt PrevMember Returns the previous member in the level that contains a specified member. Member 12 1 PrevMember Properties Returns the value of a member property. (none) 1 1 Properties pV Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Logical Expression 5 1 pV Qtd A shortcut function for the PeriodsToDate function that specifies the level to be Quarter. 12 1 Qtd Qtd A shortcut function for the PeriodsToDate function that specifies the level to be Quarter. Member 12 1 Qtd radians Numeric Expression 5 1 radians Rank Returns the one-based rank of a tuple in a set. Tuple, Set 2 1 Rank Rank Returns the one-based rank of a tuple in a set. Tuple, Set, Numeric Expression 2 1 Rank Rank Returns the one-based rank of a tuple in a set. Member, Set 2 1 Rank Rank Returns the one-based rank of a tuple in a set. Member, Set, Numeric Expression 2 1 Rank rate Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Logical Expression, Numeric Expression 5 1 rate rate Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression, Logical Expression 5 1 rate rate Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 rate rate Numeric Expression, Numeric Expression, Numeric Expression 5 1 rate replace String, String, String, Integer, Integer, Integer 8 1 replace replace String, String, String, Integer, Integer 8 1 replace replace String, String, String, Integer 8 1 replace replace String, String, String 8 1 replace right String, Integer 8 1 right round Numeric Expression, Integer 5 1 round round Numeric Expression 5 1 round rTrim String 8 1 rTrim second DateTime 2 1 second SetToStr Constructs a string from a set. Set 8 1 SetToStr sgn Numeric Expression 2 1 sgn Siblings Returns the siblings of a specified member, including the member itself. Member 12 1 Siblings sin Numeric Expression 5 1 sin sinh Numeric Expression 5 1 sinh sLN Numeric Expression, Numeric Expression, Numeric Expression 5 1 sLN space Integer 8 1 space sqr Numeric Expression 5 1 sqr sqrtPi Numeric Expression 5 1 sqrtPi Stddev Alias for Stdev. Set 5 1 Stddev Stddev Alias for Stdev. Set, Numeric Expression 5 1 Stddev StddevP Alias for StdevP. Set 5 1 StddevP StddevP Alias for StdevP. Set, Numeric Expression 5 1 StddevP Stdev Returns the standard deviation of a numeric expression evaluated over a set (unbiased). Set 5 1 Stdev Stdev Returns the standard deviation of a numeric expression evaluated over a set (unbiased). Set, Numeric Expression 5 1 Stdev StdevP Returns the standard deviation of a numeric expression evaluated over a set (biased). Set 5 1 StdevP StdevP Returns the standard deviation of a numeric expression evaluated over a set (biased). Set, Numeric Expression 5 1 StdevP str Value 8 1 str strComp String, String, Integer 2 1 strComp strComp String, String 2 1 strComp string Integer, String 8 1 string StripCalculatedMembers Removes calculated members from a set. Set 12 1 StripCalculatedMembers strReverse String 8 1 strReverse StrToMember Returns a member from a unique name String in MDX format. String 12 1 StrToMember StrToSet Constructs a set from a string expression. String 12 1 StrToSet StrToTuple Constructs a tuple from a string. String 12 1 StrToTuple Subset Returns a subset of elements from a set. Set, Numeric Expression 12 1 Subset Subset Returns a subset of elements from a set. Set, Numeric Expression, Numeric Expression 12 1 Subset Sum Returns the sum of a numeric expression evaluated over a set. Set 5 1 Sum Sum Returns the sum of a numeric expression evaluated over a set. Set, Numeric Expression 5 1 Sum sYD Numeric Expression, Numeric Expression, Numeric Expression, Numeric Expression 5 1 sYD Tail Returns a subset from the end of a set. Set 12 1 Tail Tail Returns a subset from the end of a set. Set, Numeric Expression 12 1 Tail tan Numeric Expression 5 1 tan tanh Numeric Expression 5 1 tanh ThirdQ Returns the 3rd quartile value of a numeric expression evaluated over a set. Set 5 1 ThirdQ ThirdQ Returns the 3rd quartile value of a numeric expression evaluated over a set. Set, Numeric Expression 5 1 ThirdQ time 7 1 time timer 5 1 timer timeSerial Integer, Integer, Integer 7 1 timeSerial timeValue DateTime 7 1 timeValue ToggleDrillState Toggles the drill state of members. This function is a combination of DrillupMember and DrilldownMember. Set, Set 12 1 ToggleDrillState ToggleDrillState Toggles the drill state of members. This function is a combination of DrillupMember and DrilldownMember. Set, Set, Symbol 12 1 ToggleDrillState TopCount Returns a specified number of items from the top of a set, optionally ordering the set first. Set, Numeric Expression, Numeric Expression 12 1 TopCount TopCount Returns a specified number of items from the top of a set, optionally ordering the set first. Set, Numeric Expression 12 1 TopCount TopPercent Sorts a set and returns the top N elements whose cumulative total is at least a specified percentage. Set, Numeric Expression, Numeric Expression 12 1 TopPercent TopSum Sorts a set and returns the top N elements whose cumulative total is at least a specified value. Set, Numeric Expression, Numeric Expression 12 1 TopSum trim String 8 1 trim TupleToStr Constructs a string from a tuple. Tuple 8 1 TupleToStr typeName Value 8 1 typeName UCase Returns a string that has been converted to uppercase String 8 1 UCase Union Returns the union of two sets, optionally retaining duplicates. Set, Set 12 1 Union Union Returns the union of two sets, optionally retaining duplicates. Set, Set, Symbol 12 1 Union UniqueName Returns the unique name of a dimension. Dimension 8 1 UniqueName UniqueName Returns the unique name of a hierarchy. Hierarchy 8 1 UniqueName UniqueName Returns the unique name of a level. Level 8 1 UniqueName UniqueName Returns the unique name of a member. Member 8 1 UniqueName Unorder Removes any enforced ordering from a specified set. Set 12 1 Unorder Val Numeric Expression 5 1 Val val String 5 1 val ValidMeasure Returns a valid measure in a virtual cube by forcing inapplicable dimensions to their top level. Tuple 5 1 ValidMeasure Value Returns the value of a measure. Member 5 1 Value Var Returns the variance of a numeric expression evaluated over a set (unbiased). Set 5 1 Var Var Returns the variance of a numeric expression evaluated over a set (unbiased). Set, Numeric Expression 5 1 Var Variance Alias for Var. Set 5 1 Variance Variance Alias for Var. Set, Numeric Expression 5 1 Variance VarianceP Alias for VarP. Set 5 1 VarianceP VarianceP Alias for VarP. Set, Numeric Expression 5 1 VarianceP VarP Returns the variance of a numeric expression evaluated over a set (biased). Set 5 1 VarP VarP Returns the variance of a numeric expression evaluated over a set (biased). Set, Numeric Expression 5 1 VarP VisualTotals Dynamically totals child members specified in a set using a pattern for the total label in the result set. Set 12 1 VisualTotals VisualTotals Dynamically totals child members specified in a set using a pattern for the total label in the result set. Set, String 12 1 VisualTotals weekday DateTime, Integer 2 1 weekday weekday DateTime 2 1 weekday weekdayName Integer, Logical Expression, Integer 8 1 weekdayName Wtd A shortcut function for the PeriodsToDate function that specifies the level to be Week. 12 1 Wtd Wtd A shortcut function for the PeriodsToDate function that specifies the level to be Week. Member 12 1 Wtd XOR Returns whether two conditions are mutually exclusive. Logical Expression, Logical Expression 11 1 XOR year DateTime 2 1 year Ytd A shortcut function for the PeriodsToDate function that specifies the level to be Year. 12 1 Ytd Ytd A shortcut function for the PeriodsToDate function that specifies the level to be Year. Member 12 1 Ytd {} Brace operator constructs a set. (none) 1 1 {} || Concatenates two strings. String, String 8 1 || ]]> ${request.type} ${data.source.info} ${content} ]]> ${request.type} ${data.source.info} ${catalog} ${format} application/json ${content} ]]> HR [Time].[1997] 1997 [Time].[Year] 0 4 [Time].[1998] 1998 [Time].[Year] 0 4 [Measures].[Count] Count [Measures].[MeasuresLevel] 0 0 7392 7,392 #,# 13860 13,860 #,# ]]> select NON EMPTY Hierarchize({[Time].[Year].Members}) DIMENSION PROPERTIES PARENT_UNIQUE_NAME ON COLUMNS from [HR] where [Measures].[Count] ${catalog} ${data.source.info} ${format} TupleFormat ]]> mondrian-3.4.1/testsrc/main/mondrian/xmla/data/0000755000175000017500000000000011735330606021322 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/xmla/data/MDSCHEMA_MEASURES_SchemaData_respose.xml0000644000175000017500000003411011735330606030342 0ustar drazzibdrazzib FoodMart 2000 Sales Profit [Measures].[Profit] Profit 127 12 65535 -1 [Measures].[Store Sales]-[Measures].[Store Cost] true Measures:Profit FoodMart 2000 Sales Sales Average [Measures].[Sales Average] Sales Average 127 12 65535 -1 [Measures].[Store Sales]/[Measures].[Sales Count] true Measures:Sales Average xmlns="urn:schemas-microsoft-com:xml-analysis:rowset" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:EX="urn:schemas-microsoft-com:xml-analysis:exception"> FoodMart Sales Promotion Sales [Measures].[Promotion Sales] Promotion Sales 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time.Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Measures:Promotion Sales FoodMart 2000 Sales Sales Count [Measures].[Sales Count] Sales Count 2 20 19 255 true [Store].[Store Name],[Time].[Month],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Yearly Income].[Yearly Income] Measures:Sales Count FoodMart 2000 Sales Store Cost [Measures].[Store Cost] Store Cost 1 5 16 255 true [Store].[Store Name],[Time].[Month],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Yearly Income].[Yearly Income] Measures:Store Cost FoodMart 2000 Sales Store Sales [Measures].[Store Sales] Store Sales 1 5 16 255 true [Store].[Store Name],[Time].[Month],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Yearly Income].[Yearly Income] Measures:Store Sales FoodMart 2000 Sales Store Sales Net [Measures].[Store Sales Net] Store Sales Net 1 5 16 255 true [Store].[Store Name],[Time].[Month],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Yearly Income].[Yearly Income] Measures:Store Sales Net FoodMart 2000 Sales Unit Sales [Measures].[Unit Sales] Unit Sales 1 5 16 255 true [Store].[Store Name],[Time].[Month],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Yearly Income].[Yearly Income] Measures:Unit Sales mondrian-3.4.1/testsrc/main/mondrian/xmla/data/DISCOVER_DATASOURCES_None_response.xml0000644000175000017500000000056111735330606030016 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/xmla/data/DISCOVER_DATASOURCES_SchemaData_request.xml0000644000175000017500000000136311735330606030744 0ustar drazzibdrazzib DISCOVER_DATASOURCES SchemaData mondrian-3.4.1/testsrc/main/mondrian/xmla/data/MDSCHEMA_LEVELS_SchemaData_request_12.xml0000644000175000017500000000362311735330606030427 0ustar drazzibdrazzib MDSCHEMA_LEVELS FoodMart 2000 Sales [Yearly Income] Foodmart 2000 FoodMart 2000 0 0 Tabular SchemaData mondrian-3.4.1/testsrc/main/mondrian/xmla/data/DISCOVER_DATASOURCES_SchemaData_response.xml0000644000175000017500000001040211735330606031104 0ustar drazzibdrazzib Foodmart 2000 Foodmart 2000 on local machine http://localhost/xmla/msxisapi.dll Foodmart 2000 Microsoft XML for Analysis TDP MDP DMP Unauthenticated mondrian-3.4.1/testsrc/main/mondrian/xmla/data/DISCOVER_DATASOURCES_Default_request.xml0000644000175000017500000000130511735330606030332 0ustar drazzibdrazzib DISCOVER_DATASOURCES mondrian-3.4.1/testsrc/main/mondrian/xmla/data/MDSCHEMA_CUBES_SchemaData_response.xml0000644000175000017500000002247611735330606030151 0ustar drazzibdrazzib FoodMart 2000 Budget CUBE 2005-01-25T17:35:32 2005-01-25T17:35:32 FoodMart 2000 - Budget Planing false true false false FoodMart 2000 HR CUBE 2005-01-25T17:35:32 2005-01-25T17:35:32 FoodMart 2000 - HR Salary Cube false true false true FoodMart 2000 Sales CUBE 2005-01-25T17:35:32 2005-01-25T17:35:32 FoodMart 2000 - Sales Reporting false true false true FoodMart 2000 Trained Cube VIRTUAL CUBE 2005-01-25T17:35:32 2005-01-25T17:35:32 false true false false FoodMart 2000 Warehouse CUBE 2005-01-25T17:35:33 2005-01-25T17:35:33 FoodMart 2000 - Warehouse Cube false true false true FoodMart 2000 Warehouse and Sales VIRTUAL CUBE 2005-01-25T17:35:33 2005-01-25T17:35:33 false true false true mondrian-3.4.1/testsrc/main/mondrian/xmla/data/MDSCHEMA_LEVELS_SchemaData_response_12.xml0000644000175000017500000002132011735330606030567 0ustar drazzibdrazzib FoodMart 2000 Sales [Yearly Income] [Yearly Income] (All) [Yearly Income].[(All)] (All) 0 1 1 0 3 true Name 1 Yearly Income:(All)!NAME Yearly Income:(All)!KEY Yearly Income:(All)!UNIQUE_NAME FoodMart 2000 Sales [Yearly Income] [Yearly Income] Yearly Income [Yearly Income].[Yearly Income] Yearly Income 1 8 0 0 1 true Name 130 Yearly Income:Yearly Income!NAME Yearly Income:Yearly Income!KEY Yearly Income:Yearly Income!UNIQUE_NAME mondrian-3.4.1/testsrc/main/mondrian/xmla/data/MDSCHEMA_CUBES_SchemaData_request.xml0000644000175000017500000000275111735330606027775 0ustar drazzibdrazzib MDSCHEMA_CUBES Foodmart 2000 FoodMart 2000 0 0 Tabular SchemaData mondrian-3.4.1/testsrc/main/mondrian/xmla/data/DISCOVER_DATASOURCES_Data_response.xml0000644000175000017500000000216511735330606027772 0ustar drazzibdrazzib Foodmart 2000 Foodmart 2000 on local machine http://localhost/xmla/msxisapi.dll Foodmart 2000 Microsoft XML for Analysis TDP MDP DMP Unauthenticated mondrian-3.4.1/testsrc/main/mondrian/xmla/data/DISCOVER_DATASOURCES_None_request.xml0000644000175000017500000000135511735330606027652 0ustar drazzibdrazzib DISCOVER_DATASOURCES None mondrian-3.4.1/testsrc/main/mondrian/xmla/data/DISCOVER_DATASOURCES_Data_request.xml0000644000175000017500000000135511735330606027624 0ustar drazzibdrazzib DISCOVER_DATASOURCES Data mondrian-3.4.1/testsrc/main/mondrian/xmla/data/EXECUTE_Data_response.xml0000644000175000017500000030050011735330606026013 0ustar drazzibdrazzib [Customers].[USA].[CA].[Altadena] Altadena [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Arcadia] Arcadia [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Bellflower] Bellflower [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Berkeley] Berkeley [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Beverly Hills] Beverly Hills [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Burbank] Burbank [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Burlingame] Burlingame [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Chula Vista] Chula Vista [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Colma] Colma [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Concord] Concord [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Coronado] Coronado [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Daly City] Daly City [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Downey] Downey [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[El Cajon] El Cajon [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Fremont] Fremont [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Glendale] Glendale [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Grossmont] Grossmont [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Imperial Beach] Imperial Beach [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[La Jolla] La Jolla [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[La Mesa] La Mesa [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Lakewood] Lakewood [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Lemon Grove] Lemon Grove [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Lincoln Acres] Lincoln Acres [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Long Beach] Long Beach [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Los Angeles] Los Angeles [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Mill Valley] Mill Valley [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[National City] National City [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Newport Beach] Newport Beach [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Novato] Novato [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Oakland] Oakland [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Palo Alto] Palo Alto [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Pomona] Pomona [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Redwood City] Redwood City [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Richmond] Richmond [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[San Carlos] San Carlos [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[San Diego] San Diego [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[San Francisco] San Francisco [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[San Gabriel] San Gabriel [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[San Jose] San Jose [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Santa Cruz] Santa Cruz [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Santa Monica] Santa Monica [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Spring Valley] Spring Valley [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Torrance] Torrance [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[West Covina] West Covina [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Woodland Hills] Woodland Hills [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Albany] Albany [Customers].[City] 3 1000 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Beaverton] Beaverton [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Corvallis] Corvallis [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Lake Oswego] Lake Oswego [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Lebanon] Lebanon [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Milwaukie] Milwaukie [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Oregon City] Oregon City [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Portland] Portland [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Salem] Salem [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[W. Linn] W. Linn [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Woodburn] Woodburn [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Anacortes] Anacortes [Customers].[City] 3 1000 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Ballard] Ballard [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Bellingham] Bellingham [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Bremerton] Bremerton [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Burien] Burien [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Edmonds] Edmonds [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Everett] Everett [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Issaquah] Issaquah [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Kirkland] Kirkland [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Lynnwood] Lynnwood [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Marysville] Marysville [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Olympia] Olympia [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Port Orchard] Port Orchard [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Puyallup] Puyallup [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Redmond] Redmond [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Renton] Renton [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Seattle] Seattle [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Sedro Woolley] Sedro Woolley [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Spokane] Spokane [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Tacoma] Tacoma [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Walla Walla] Walla Walla [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Yakima] Yakima [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Store].[USA].[CA].[Beverly Hills] Beverly Hills [Store].[Store City] 3 131073 [Store].[USA].[CA].[Los Angeles] Los Angeles [Store].[Store City] 3 131073 [Store].[USA].[CA].[San Diego] San Diego [Store].[Store City] 3 131073 [Store].[USA].[CA].[San Francisco] San Francisco [Store].[Store City] 3 131073 [Store].[USA].[OR].[Portland] Portland [Store].[Store City] 3 1 [Store].[USA].[OR].[Salem] Salem [Store].[Store City] 3 131073 [Store].[USA].[WA].[Bellingham] Bellingham [Store].[Store City] 3 1 [Store].[USA].[WA].[Bremerton] Bremerton [Store].[Store City] 3 131073 [Store].[USA].[WA].[Seattle] Seattle [Store].[Store City] 3 131073 [Store].[USA].[WA].[Spokane] Spokane [Store].[Store City] 3 131073 [Store].[USA].[WA].[Tacoma] Tacoma [Store].[Store City] 3 131073 [Store].[USA].[WA].[Walla Walla] Walla Walla [Store].[Store City] 3 131073 [Store].[USA].[WA].[Yakima] Yakima [Store].[Store City] 3 131073 [Time].[1997] 1997 [Time].[Year] 0 4 [Product].[All Products] All Products [Product].[(All)] 0 3 [Promotion Media].[All Media] All Media [Promotion Media].[(All)] 0 14 [Promotions].[All Promotions] All Promotions [Promotions].[(All)] 0 51 [Education Level].[All Education Level] All Education Level [Education Level].[(All)] 0 5 [Gender].[All Gender] All Gender [Gender].[(All)] 0 2 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 2 [Store Size in SQFT].[All Store Size in SQFT] All Store Size in SQFT [Store Size in SQFT].[(All)] 0 21 [Store Type].[All Store Type] All Store Type [Store Type].[(All)] 0 6 [Yearly Income].[All Yearly Income] All Yearly Income [Yearly Income].[(All)] 0 8 1001.1069 1,001.11 898.5578 898.56 1205.9452 1,205.95 994.7675 994.77 1151.6987 1,151.70 1367.4653 1,367.47 1372.8403 1,372.84 965.4238 965.42 1191.4299 1,191.43 775.8819 775.88 1027.6061 1,027.61 975.6317 975.63 1102.5056 1,102.51 963.7611 963.76 997.2073 997.21 1186.8846 1,186.88 1087.7267 1,087.73 1238.6062 1,238.61 1147.1708 1,147.17 1432.5258 1,432.53 1484.6944 1,484.69 1471.3451 1,471.35 1554.2816 1,554.28 1456.2808 1,456.28 1068.2778 1,068.28 1386.9222 1,386.92 973.8091 973.81 1636.7446 1,636.74 1289.9055 1,289.91 1158.0826 1,158.08 1310.6892 1,310.69 1131.9651 1,131.97 933.8937 933.89 1096.3415 1,096.34 2492.9104 2,492.91 2031.1717 2,031.17 2173.7062 2,173.71 1785.3183 1,785.32 1340.2879 1,340.29 1640.6061 1,640.61 1565.2103 1,565.21 2243.3616 2,243.36 1867.9515 1,867.95 1680.898 1,680.90 1349.1013 1,349.10 1543.0095 1,543.01 129.2897 129.29 160.4193 160.42 114.1765 114.18 86.4811 86.48 112.4619 112.46 141.1693 141.17 43.5545 43.55 135.293 135.29 60.4207 60.42 108.8179 108.82 123.518 123.52 81.001 81.00 116.2018 116.20 80.2158 80.22 138.1977 138.20 147.6977 147.70 3798.0392 3,798.04 4134.7989 4,134.80 4347.82460000001 4,347.82 3139.5415 3,139.54 2985.40549999999 2,985.41 3543.3343 3,543.33 5593.27710000001 5,593.28 8006.7321 8,006.73 8127.6501 8,127.65 6430.5984 6,430.60 6665.29889999999 6,665.30 641.9349 641.93 646.714 646.71 607.9685 607.97 10505.9363 10,505.94 10616.0268 10,616.03 2169.5106 2,169.51 2342.9026 2,342.90 1685.905 1,685.91 2267.4592 2,267.46 1734.8628 1,734.86 1903.4541 1,903.45 1903.74 1,903.74 1800.8132 1,800.81 1705.1584 1,705.16 1843.6079 1,843.61 1599.3887 1,599.39 19795.491 19,795.49 10581.9671 10,581.97 10139.8147 10,139.81 9237.49949999999 9,237.50 1880.3396 1,880.34 9713.81299999999 9,713.81 mondrian-3.4.1/testsrc/main/mondrian/xmla/data/DBSCHEMA_CATALOGS_Default_response.xml0000644000175000017500000000651411735330606030057 0ustar drazzibdrazzib FoodMart 2000 All Users 2005-01-25T17:35:32 mondrian-3.4.1/testsrc/main/mondrian/xmla/data/EXECUTE_SchemaData_request.xml0000644000175000017500000000334611735330606026776 0ustar drazzibdrazzib SELECT NON EMPTY { { {[Customers].[City].Members} } * { {[Measures].[Store Cost]} } } ON COLUMNS, NON EMPTY { {[Store].[Store City].Members} } ON ROWS FROM [Sales] Foodmart 2000 FoodMart 2000 Native SchemaData TupleFormat mondrian-3.4.1/testsrc/main/mondrian/xmla/data/MDSCHEMA_DIMENSIONS_SchemaData_response.xml0000644000175000017500000004250411735330606030752 0ustar drazzibdrazzib FoodMart 2000 Sales Customers [Customers] Customers 6 3 10407 [Customers] false false 0 true FoodMart 2000 Sales Education Level [Education Level] Education Level 7 3 6 [Education Level] false false 0 true FoodMart 2000 Sales Gender [Gender] Gender 8 3 3 [Gender] false false 0 true FoodMart 2000 Sales Marital Status [Marital Status] Marital Status 9 3 112 [Marital Status] false false 0 true FoodMart 2000 Sales Measures [Measures] Measures 0 2 5 [Measures] false false 0 true FoodMart 2000 Sales Product [Product] Product 3 3 2256 [Product] false false 0 true FoodMart 2000 Sales Promotion Media [Promotion Media] Promotion Media 4 3 15 [Promotion Media] false false 0 true FoodMart 2000 Sales Promotions [Promotions] Promotions 5 3 52 [Promotions] false false 0 true FoodMart 2000 Sales Store [Store] Store 1 3 63 [Store] false false 0 true FoodMart 2000 Sales Store Size in SQFT [Store Size in SQFT] Store Size in SQFT 10 3 22 [Store Size in SQFT] false false 0 [Store Size in SQFT] true FoodMart 2000 Sales Store Type [Store Type] Store Type 11 3 7 [Store Type] false false 0 [Store Type] true FoodMart 2000 Sales Time [Time] Time 2 1 34 [Time] false false 0 true FoodMart 2000 Sales Yearly Income [Yearly Income] Yearly Income 12 3 9 [Yearly Income] false false 0 true mondrian-3.4.1/testsrc/main/mondrian/xmla/data/DISCOVER_DATASOURCES_Schema_request.xml0000644000175000017500000000135711735330606030155 0ustar drazzibdrazzib DISCOVER_DATASOURCES Schema mondrian-3.4.1/testsrc/main/mondrian/xmla/data/EXECUTE_Data_request.xml0000644000175000017500000000334011735330606025647 0ustar drazzibdrazzib SELECT NON EMPTY { { {[Customers].[City].Members} } * { {[Measures].[Store Cost]} } } ON COLUMNS, NON EMPTY { {[Store].[Store City].Members} } ON ROWS FROM [Sales] Foodmart 2000 FoodMart 2000 Native Data TupleFormat mondrian-3.4.1/testsrc/main/mondrian/xmla/data/DBSCHEMA_CATALOGS_Default_request.xml0000644000175000017500000000177111735330606027711 0ustar drazzibdrazzib DBSCHEMA_CATALOGS Foodmart 2000 0 0 mondrian-3.4.1/testsrc/main/mondrian/xmla/data/DISCOVER_DATASOURCES_Schema_response.xml0000644000175000017500000000700211735330606030314 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/xmla/data/MDSCHEMA_DIMENSIONS_SchemaData_request.xml0000644000175000017500000000342411735330606030602 0ustar drazzibdrazzib MDSCHEMA_DIMENSIONS FoodMart 2000 Sales Foodmart 2000 FoodMart 2000 0 0 Tabular SchemaData mondrian-3.4.1/testsrc/main/mondrian/xmla/data/MDSCHEMA_MEASURES_SchemaData_request.xml0000644000175000017500000000321011735330606030347 0ustar drazzibdrazzib MDSCHEMA_MEASURES FoodMart 2000 Sales Foodmart 2000 FoodMart 2000 0 0 Tabular SchemaData mondrian-3.4.1/testsrc/main/mondrian/xmla/data/EXECUTE_SchemaData_response.xml0000644000175000017500000020070611735330606027143 0ustar drazzibdrazzib [Customers].[USA].[CA].[Altadena] Altadena [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Arcadia] Arcadia [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Bellflower] Bellflower [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Berkeley] Berkeley [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Beverly Hills] Beverly Hills [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Burbank] Burbank [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Burlingame] Burlingame [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Chula Vista] Chula Vista [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Colma] Colma [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Concord] Concord [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Coronado] Coronado [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Daly City] Daly City [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Downey] Downey [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[El Cajon] El Cajon [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Fremont] Fremont [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Glendale] Glendale [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Grossmont] Grossmont [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Imperial Beach] Imperial Beach [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[La Jolla] La Jolla [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[La Mesa] La Mesa [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Lakewood] Lakewood [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Lemon Grove] Lemon Grove [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Lincoln Acres] Lincoln Acres [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Long Beach] Long Beach [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Los Angeles] Los Angeles [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Mill Valley] Mill Valley [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[National City] National City [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Newport Beach] Newport Beach [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Novato] Novato [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Oakland] Oakland [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Palo Alto] Palo Alto [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Pomona] Pomona [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Redwood City] Redwood City [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Richmond] Richmond [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[San Carlos] San Carlos [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[San Diego] San Diego [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[San Francisco] San Francisco [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[San Gabriel] San Gabriel [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[San Jose] San Jose [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Santa Cruz] Santa Cruz [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Santa Monica] Santa Monica [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Spring Valley] Spring Valley [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Torrance] Torrance [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[West Covina] West Covina [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[CA].[Woodland Hills] Woodland Hills [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Albany] Albany [Customers].[City] 3 1000 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Beaverton] Beaverton [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Corvallis] Corvallis [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Lake Oswego] Lake Oswego [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Lebanon] Lebanon [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Milwaukie] Milwaukie [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Oregon City] Oregon City [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Portland] Portland [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Salem] Salem [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[W. Linn] W. Linn [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[OR].[Woodburn] Woodburn [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Anacortes] Anacortes [Customers].[City] 3 1000 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Ballard] Ballard [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Bellingham] Bellingham [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Bremerton] Bremerton [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Burien] Burien [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Edmonds] Edmonds [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Everett] Everett [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Issaquah] Issaquah [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Kirkland] Kirkland [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Lynnwood] Lynnwood [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Marysville] Marysville [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Olympia] Olympia [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Port Orchard] Port Orchard [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Puyallup] Puyallup [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Redmond] Redmond [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Renton] Renton [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Seattle] Seattle [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Sedro Woolley] Sedro Woolley [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Spokane] Spokane [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Tacoma] Tacoma [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Walla Walla] Walla Walla [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Customers].[USA].[WA].[Yakima] Yakima [Customers].[City] 3 132072 [Measures].[Store Cost] Store Cost [Measures].[MeasuresLevel] 0 131072 [Store].[USA].[CA].[Beverly Hills] Beverly Hills [Store].[Store City] 3 131073 [Store].[USA].[CA].[Los Angeles] Los Angeles [Store].[Store City] 3 131073 [Store].[USA].[CA].[San Diego] San Diego [Store].[Store City] 3 131073 [Store].[USA].[CA].[San Francisco] San Francisco [Store].[Store City] 3 131073 [Store].[USA].[OR].[Portland] Portland [Store].[Store City] 3 1 [Store].[USA].[OR].[Salem] Salem [Store].[Store City] 3 131073 [Store].[USA].[WA].[Bellingham] Bellingham [Store].[Store City] 3 1 [Store].[USA].[WA].[Bremerton] Bremerton [Store].[Store City] 3 131073 [Store].[USA].[WA].[Seattle] Seattle [Store].[Store City] 3 131073 [Store].[USA].[WA].[Spokane] Spokane [Store].[Store City] 3 131073 [Store].[USA].[WA].[Tacoma] Tacoma [Store].[Store City] 3 131073 [Store].[USA].[WA].[Walla Walla] Walla Walla [Store].[Store City] 3 131073 [Store].[USA].[WA].[Yakima] Yakima [Store].[Store City] 3 131073 [Time].[1997] 1997 [Time].[Year] 0 4 [Product].[All Products] All Products [Product].[(All)] 0 3 [Promotion Media].[All Media] All Media [Promotion Media].[(All)] 0 14 [Promotions].[All Promotions] All Promotions [Promotions].[(All)] 0 51 [Education Level].[All Education Level] All Education Level [Education Level].[(All)] 0 5 [Gender].[All Gender] All Gender [Gender].[(All)] 0 2 [Marital Status].[All Marital Status] All Marital Status [Marital Status].[(All)] 0 2 [Store Size in SQFT].[All Store Size in SQFT] All Store Size in SQFT [Store Size in SQFT].[(All)] 0 21 [Store Type].[All Store Type] All Store Type [Store Type].[(All)] 0 6 [Yearly Income].[All Yearly Income] All Yearly Income [Yearly Income].[(All)] 0 8 1001.1069 1,001.11 898.5578 898.56 1205.9452 1,205.95 994.7675 994.77 1151.6987 1,151.70 1367.4653 1,367.47 1372.8403 1,372.84 965.4238 965.42 1191.4299 1,191.43 775.8819 775.88 1027.6061 1,027.61 975.6317 975.63 1102.5056 1,102.51 963.7611 963.76 997.2073 997.21 1186.8846 1,186.88 1087.7267 1,087.73 1238.6062 1,238.61 1147.1708 1,147.17 1432.5258 1,432.53 1484.6944 1,484.69 1471.3451 1,471.35 1554.2816 1,554.28 1456.2808 1,456.28 1068.2778 1,068.28 1386.9222 1,386.92 973.8091 973.81 1636.7446 1,636.74 1289.9055 1,289.91 1158.0826 1,158.08 1310.6892 1,310.69 1131.9651 1,131.97 933.8937 933.89 1096.3415 1,096.34 2492.9104 2,492.91 2031.1717 2,031.17 2173.7062 2,173.71 1785.3183 1,785.32 1340.2879 1,340.29 1640.6061 1,640.61 1565.2103 1,565.21 2243.3616 2,243.36 1867.9515 1,867.95 1680.898 1,680.90 1349.1013 1,349.10 1543.0095 1,543.01 129.2897 129.29 160.4193 160.42 114.1765 114.18 86.4811 86.48 112.4619 112.46 141.1693 141.17 43.5545 43.55 135.293 135.29 60.4207 60.42 108.8179 108.82 123.518 123.52 81.001 81.00 116.2018 116.20 80.2158 80.22 138.1977 138.20 147.6977 147.70 3798.0392 3,798.04 4134.7989 4,134.80 4347.82460000001 4,347.82 3139.5415 3,139.54 2985.40549999999 2,985.41 3543.3343 3,543.33 5593.27710000001 5,593.28 8006.7321 8,006.73 8127.6501 8,127.65 6430.5984 6,430.60 6665.29889999999 6,665.30 641.9349 641.93 646.714 646.71 607.9685 607.97 10505.9363 10,505.94 10616.0268 10,616.03 2169.5106 2,169.51 2342.9026 2,342.90 1685.905 1,685.91 2267.4592 2,267.46 1734.8628 1,734.86 1903.4541 1,903.45 1903.74 1,903.74 1800.8132 1,800.81 1705.1584 1,705.16 1843.6079 1,843.61 1599.3887 1,599.39 19795.491 19,795.49 10581.9671 10,581.97 10139.8147 10,139.81 9237.49949999999 9,237.50 1880.3396 1,880.34 9713.81299999999 9,713.81 mondrian-3.4.1/testsrc/main/mondrian/xmla/data/DISCOVER_DATASOURCES_Default_response.xml0000644000175000017500000001040211735330606030476 0ustar drazzibdrazzib Foodmart 2000 Foodmart 2000 on local machine http://localhost/xmla/msxisapi.dll Foodmart 2000 Microsoft XML for Analysis TDP MDP DMP Unauthenticated mondrian-3.4.1/testsrc/main/mondrian/xmla/XmlaExcelXPTest.java0000644000175000017500000000702011735330606024245 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho and others // All Rights Reserved. */ package mondrian.xmla; import mondrian.test.DiffRepository; /** * Test suite for compatibility of Mondrian XMLA with Excel XP. * Simba (the maker of the O2X bridge) supplied captured request/response * soap messages between Excel XP and SQL Server. These form the * basis of the output files in the excel_XP directory. * * @author Richard M. Emberson */ public class XmlaExcelXPTest extends XmlaBaseTestCase { protected String getSessionId(Action action) { return getSessionId("XmlaExcel2000Test", action); } static class Callback extends XmlaRequestCallbackImpl { Callback() { super("XmlaExcel2000Test"); } } public XmlaExcelXPTest() { } public XmlaExcelXPTest(String name) { super(name); } protected Class getServletCallbackClass() { return Callback.class; } protected DiffRepository getDiffRepos() { return DiffRepository.lookup(XmlaExcelXPTest.class); } public void test01() { helperTest(false); } // BeginSession public void test02() { helperTest(false); } public void test03() { helperTest(true); } public void test04() { helperTest(true); } public void test05() { helperTest(true); } public void test06() { helperTest(true); } // BeginSession public void test07() { helperTest(false); } public void test08() { helperTest(true); } public void test09() { helperTest(true); } public void test10() { helperTest(true); } public void test11() { helperTest(true); } public void test12() { helperTest(true); } public void test13() { helperTest(true); } public void test14() { helperTest(true); } public void test15() { helperTest(true); } public void test16() { helperTest(true); } public void test17() { helperTest(true); } // The slicerAxis is empty in Mondrian by not empty in SQLServer. // The xml schema returned by SQL Server is not the version 1.0 // schema returned by Mondrian. // Values are correct. public void _test18() { helperTest(true); } public void test19() { helperTest(true); } public void test20() { helperTest(true); } // Same issue as test18: slicerAxis public void _test21() { helperTest(true); } // Same issue as test18: slicerAxis public void _test22() { helperTest(true); } public void test23() { helperTest(true); } public void test24() { helperTest(true); } public void testExpect01() { helperTestExpect(false); } public void testExpect02() { helperTestExpect(false); } public void testExpect03() { helperTestExpect(true); } public void testExpect04() { helperTestExpect(true); } public void testExpect05() { helperTestExpect(true); } public void testExpect06() { helperTestExpect(true); } } // End XmlaExcelXPTest.java mondrian-3.4.1/testsrc/main/mondrian/xmla/XmlaExcel2000Test.ref.xml0000644000175000017500000061106611735330606024744 0ustar drazzibdrazzib
DISCOVER_DATASOURCES 1033 SchemaData Tabular ]]> FoodMart Mondrian FoodMart data source http://localhost:8080/mondrian/xmla FoodMart Mondrian MDP Unauthenticated ]]>
DISCOVER_SCHEMA_ROWSETS 1033 FoodMart FoodMart Tabular ]]>
DBSCHEMA_CATALOGS CATALOG_NAME xsd:string Identifies the physical attributes associated with catalogs accessible from the provider. DBSCHEMA_COLUMNS TABLE_CATALOG xsd:string TABLE_SCHEMA xsd:string TABLE_NAME xsd:string COLUMN_NAME xsd:string DBSCHEMA_PROVIDER_TYPES DATA_TYPE xsd:unsignedShort BEST_MATCH xsd:boolean DBSCHEMA_SCHEMATA CATALOG_NAME xsd:string SCHEMA_NAME xsd:string SCHEMA_OWNER xsd:string DBSCHEMA_TABLES TABLE_CATALOG xsd:string TABLE_SCHEMA xsd:string TABLE_NAME xsd:string TABLE_TYPE xsd:string DBSCHEMA_TABLES_INFO TABLE_CATALOG xsd:string TABLE_SCHEMA xsd:string TABLE_NAME xsd:string TABLE_TYPE xsd:string DISCOVER_DATASOURCES DataSourceName xsd:string URL xsd:string ProviderName xsd:string ProviderType xsd:string AuthenticationMode xsd:string Returns a list of XML for Analysis data sources available on the server or Web Service. DISCOVER_ENUMERATORS EnumName xsd:string Returns a list of names, data types, and enumeration values for enumerators supported by the provider of a specific data source. DISCOVER_KEYWORDS Keyword xsd:string Returns an XML list of keywords reserved by the provider. DISCOVER_LITERALS LiteralName xsd:string Returns information about literals supported by the provider. DISCOVER_PROPERTIES PropertyName xsd:string Returns a list of information and values about the requested properties that are supported by the specified data source provider. DISCOVER_SCHEMA_ROWSETS SchemaName xsd:string Returns the names, values, and other information of all supported RequestType enumeration values. MDSCHEMA_ACTIONS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string ACTION_NAME xsd:string COORDINATE xsd:string COORDINATE_TYPE xsd:int MDSCHEMA_CUBES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string CUBE_TYPE xsd:string MDSCHEMA_DIMENSIONS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string MDSCHEMA_FUNCTIONS FUNCTION_NAME xsd:string ORIGIN xsd:int INTERFACE_NAME xsd:string LIBRARY_NAME xsd:string MDSCHEMA_HIERARCHIES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string MDSCHEMA_LEVELS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string LEVEL_NAME xsd:string LEVEL_UNIQUE_NAME xsd:string MDSCHEMA_MEASURES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string MEASURE_NAME xsd:string MEASURE_UNIQUE_NAME xsd:string MDSCHEMA_MEMBERS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string LEVEL_UNIQUE_NAME xsd:string LEVEL_NUMBER xsd:unsignedInt MEMBER_NAME xsd:string MEMBER_UNIQUE_NAME xsd:string MEMBER_TYPE xsd:int MEMBER_CAPTION xsd:string TREE_OP xsd:int MDSCHEMA_PROPERTIES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string LEVEL_UNIQUE_NAME xsd:string MEMBER_UNIQUE_NAME xsd:string PROPERTY_NAME xsd:string PROPERTY_TYPE xsd:short PROPERTY_CONTENT_TYPE xsd:short MDSCHEMA_SETS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string SET_NAME xsd:string SCOPE xsd:int ]]>
DBSCHEMA_CATALOGS 1033 FoodMart SchemaData Tabular ]]>
FoodMart No description available California manager,No HR Cube ]]>
DISCOVER_PROPERTIES ProviderVersion 1033 FoodMart SchemaData Tabular ]]>
ProviderVersion The version of the Mondrian XMLA Provider string Read false ${mondrianVersion} ]]>
DISCOVER_PROPERTIES Catalog 1033 FoodMart SchemaData Tabular ]]>
Catalog When establishing a session with an Analysis Services instance to send an XMLA command, this property is equivalent to the OLE DB property, DBPROP_INIT_CATALOG. When you set this property during a session to change the current database for the session, this property is equivalent to the OLE DB property, DBPROP_CURRENTCATALOG. The default value for this property is an empty string. string ReadWrite false ]]>
1033 FoodMart ]]>
]]>
1033 FoodMart FoodMart ]]>
]]>
MDSCHEMA_CUBES This restriction should never match. 1033 FoodMart FoodMart SchemaData Tabular ]]>
]]>
MDSCHEMA_CUBES Sales 1033 FoodMart FoodMart SchemaData Tabular ]]>
FoodMart FoodMart Sales CUBE xxxx-xx-xxTxx:xx:xx true false false false Sales FoodMart Schema - Sales Cube ]]>
MDSCHEMA_HIERARCHIES This restriction should never match. 1033 FoodMart FoodMart SchemaData Tabular ]]>
]]>
MDSCHEMA_HIERARCHIES Sales 1033 FoodMart FoodMart SchemaData Tabular ]]>
FoodMart FoodMart Sales [Customers] Customers [Customers] Customers 3 10407 [Customers].[All Customers] [Customers].[All Customers] Sales Cube - Customers Hierarchy 0 false false 0 true true 9 true false FoodMart FoodMart Sales [Education Level] Education Level [Education Level] Education Level 3 6 [Education Level].[All Education Levels] [Education Level].[All Education Levels] Sales Cube - Education Level Hierarchy 0 false false 0 true true 10 true false FoodMart FoodMart Sales [Gender] Gender [Gender] Gender 3 3 [Gender].[All Gender] [Gender].[All Gender] Sales Cube - Gender Hierarchy 0 false false 0 true true 11 true false FoodMart FoodMart Sales [Marital Status] Marital Status [Marital Status] Marital Status 3 112 [Marital Status].[All Marital Status] [Marital Status].[All Marital Status] Sales Cube - Marital Status Hierarchy 0 false false 0 true true 12 true false FoodMart FoodMart Sales [Measures] Measures [Measures] Measures 2 9 [Measures].[Unit Sales] Sales Cube - Measures Hierarchy 0 false false 0 true true 0 true false FoodMart FoodMart Sales [Product] Product [Product] Product 3 2256 [Product].[All Products] [Product].[All Products] Sales Cube - Product Hierarchy 0 false false 0 true true 6 true false FoodMart FoodMart Sales [Promotion Media] Promotion Media [Promotion Media] Promotion Media 3 15 [Promotion Media].[All Media] [Promotion Media].[All Media] Sales Cube - Promotion Media Hierarchy 0 false false 0 true true 7 true false FoodMart FoodMart Sales [Promotions] Promotions [Promotions] Promotions 3 52 [Promotions].[All Promotions] [Promotions].[All Promotions] Sales Cube - Promotions Hierarchy 0 false false 0 true true 8 true false FoodMart FoodMart Sales [Store Size in SQFT] Store Size in SQFT [Store Size in SQFT] Store Size in SQFT 3 22 [Store Size in SQFT].[All Store Size in SQFTs] [Store Size in SQFT].[All Store Size in SQFTs] Sales Cube - Store Size in SQFT Hierarchy 0 false false 0 true true 2 true false FoodMart FoodMart Sales [Store Type] Store Type [Store Type] Store Type 3 7 [Store Type].[All Store Types] [Store Type].[All Store Types] Sales Cube - Store Type Hierarchy 0 false false 0 true true 3 true false FoodMart FoodMart Sales [Store] Store [Store] Store 3 63 [Store].[All Stores] [Store].[All Stores] Sales Cube - Store Hierarchy 0 false false 0 true true 1 true false FoodMart FoodMart Sales [Time] Time [Time] Time 1 34 [Time].[1997] Sales Cube - Time Hierarchy 0 false false 0 true true 4 true false FoodMart FoodMart Sales [Time] Weekly [Time].[Weekly] Weekly 1 837 [Time].[Weekly].[All Weeklys] [Time].[Weekly].[All Weeklys] Sales Cube - Time.Weekly Hierarchy 0 false false 0 true true 5 true false FoodMart FoodMart Sales [Yearly Income] Yearly Income [Yearly Income] Yearly Income 3 9 [Yearly Income].[All Yearly Incomes] [Yearly Income].[All Yearly Incomes] Sales Cube - Yearly Income Hierarchy 0 false false 0 true true 13 true false ]]>
MDSCHEMA_MEASURES This restriction should never match. 1033 FoodMart FoodMart SchemaData Tabular ]]>
]]>
MDSCHEMA_MEASURES Sales 1033 FoodMart FoodMart SchemaData Tabular true ]]>
FoodMart FoodMart Sales Customer Count [Measures].[Customer Count] Customer Count 0 3 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Customer Count Member FoodMart FoodMart Sales Profit [Measures].[Profit] Profit 127 130 true Sales Cube - Profit Member FoodMart FoodMart Sales Profit Growth [Measures].[Profit Growth] Gewinn-Wachstum 127 130 true Sales Cube - Profit Growth Member FoodMart FoodMart Sales Profit last Period [Measures].[Profit last Period] Profit last Period 127 130 false Sales Cube - Profit last Period Member FoodMart FoodMart Sales Promotion Sales [Measures].[Promotion Sales] Promotion Sales 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Promotion Sales Member FoodMart FoodMart Sales Sales Count [Measures].[Sales Count] Sales Count 2 3 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Sales Count Member FoodMart FoodMart Sales Store Cost [Measures].[Store Cost] Store Cost 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Store Cost Member FoodMart FoodMart Sales Store Sales [Measures].[Store Sales] Store Sales 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Store Sales Member FoodMart FoodMart Sales Unit Sales [Measures].[Unit Sales] Unit Sales 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Unit Sales Member ]]>
MDSCHEMA_MEASURES Sales 1033 FoodMart FoodMart SchemaData Tabular ]]>
FoodMart FoodMart Sales Customer Count [Measures].[Customer Count] Customer Count 0 3 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Customer Count Member FoodMart FoodMart Sales Profit [Measures].[Profit] Profit 127 130 true Sales Cube - Profit Member FoodMart FoodMart Sales Profit Growth [Measures].[Profit Growth] Gewinn-Wachstum 127 130 true Sales Cube - Profit Growth Member FoodMart FoodMart Sales Promotion Sales [Measures].[Promotion Sales] Promotion Sales 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Promotion Sales Member FoodMart FoodMart Sales Sales Count [Measures].[Sales Count] Sales Count 2 3 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Sales Count Member FoodMart FoodMart Sales Store Cost [Measures].[Store Cost] Store Cost 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Store Cost Member FoodMart FoodMart Sales Store Sales [Measures].[Store Sales] Store Sales 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Store Sales Member FoodMart FoodMart Sales Unit Sales [Measures].[Unit Sales] Unit Sales 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Unit Sales Member ]]>
MDSCHEMA_LEVELS This restriction should never match. 1033 FoodMart FoodMart SchemaData Tabular ]]>
]]>
MDSCHEMA_LEVELS Sales [Time] 1033 FoodMart FoodMart SchemaData Tabular ]]>
FoodMart FoodMart Sales [Time] [Time] Year [Time].[Year] Year 0 2 20 0 1 true Sales Cube - Time Hierarchy - Year Level FoodMart FoodMart Sales [Time] [Time] Quarter [Time].[Quarter] Quarter 1 8 68 0 0 true Sales Cube - Time Hierarchy - Quarter Level FoodMart FoodMart Sales [Time] [Time] Month [Time].[Month] Month 2 24 132 0 0 true Sales Cube - Time Hierarchy - Month Level ]]>
MDSCHEMA_LEVELS Sales [Customers] 1033 FoodMart FoodMart SchemaData Tabular ]]>
FoodMart FoodMart Sales [Customers] [Customers] (All) [Customers].[(All)] (All) 0 1 1 0 3 true Sales Cube - Customers Hierarchy - (All) Level FoodMart FoodMart Sales [Customers] [Customers] Country [Customers].[Country] Country 1 3 0 0 1 true Sales Cube - Customers Hierarchy - Country Level FoodMart FoodMart Sales [Customers] [Customers] State Province [Customers].[State Province] State Province 2 13 0 0 1 true Sales Cube - Customers Hierarchy - State Province Level FoodMart FoodMart Sales [Customers] [Customers] City [Customers].[City] City 3 109 0 0 0 true Sales Cube - Customers Hierarchy - City Level FoodMart FoodMart Sales [Customers] [Customers] Name [Customers].[Name] Name 4 10281 0 0 1 true Sales Cube - Customers Hierarchy - Name Level ]]>
SELECT HIERARCHIZE({[Measures].[Unit Sales]}) DIMENSION PROPERTIES MEMBER_CAPTION, MEMBER_UNIQUE_NAME ON COLUMNS , HIERARCHIZE({[Time].[Year].members}) DIMENSION PROPERTIES MEMBER_CAPTION, MEMBER_UNIQUE_NAME ON ROWS , HIERARCHIZE({DrillDownLevel({[Customers].[All Customers]})}) DIMENSION PROPERTIES MEMBER_CAPTION, MEMBER_UNIQUE_NAME ON PAGES FROM [Sales] 1033 FoodMart FoodMart Tabular SchemaData ]]>
<_x005b_Time_x005d_._x005b_Year_x005d_._x005b_MEMBER_CAPTION_x005d_>1997 <_x005b_Time_x005d_._x005b_Year_x005d_._x005b_MEMBER_UNIQUE_NAME_x005d_>[Time].[1997] <_x005b_Measures_x005d_._x005b_Unit_x0020_Sales_x005d_ xsi:type="xsd:double">266773 <_x005b_Time_x005d_._x005b_Year_x005d_._x005b_MEMBER_CAPTION_x005d_>1998 <_x005b_Time_x005d_._x005b_Year_x005d_._x005b_MEMBER_UNIQUE_NAME_x005d_>[Time].[1998] <_x005b_Customers_x005d_._x005b_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>Canada <_x005b_Customers_x005d_._x005b_Country_x005d_._x005b_MEMBER_UNIQUE_NAME_x005d_>[Customers].[Canada] <_x005b_Time_x005d_._x005b_Year_x005d_._x005b_MEMBER_CAPTION_x005d_>1997 <_x005b_Time_x005d_._x005b_Year_x005d_._x005b_MEMBER_UNIQUE_NAME_x005d_>[Time].[1997] <_x005b_Measures_x005d_._x005b_Unit_x0020_Sales_x005d_ xsi:type="xsd:double">266773 <_x005b_Customers_x005d_._x005b_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>Canada <_x005b_Customers_x005d_._x005b_Country_x005d_._x005b_MEMBER_UNIQUE_NAME_x005d_>[Customers].[Canada] <_x005b_Time_x005d_._x005b_Year_x005d_._x005b_MEMBER_CAPTION_x005d_>1998 <_x005b_Time_x005d_._x005b_Year_x005d_._x005b_MEMBER_UNIQUE_NAME_x005d_>[Time].[1998] <_x005b_Customers_x005d_._x005b_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>Mexico <_x005b_Customers_x005d_._x005b_Country_x005d_._x005b_MEMBER_UNIQUE_NAME_x005d_>[Customers].[Mexico] <_x005b_Time_x005d_._x005b_Year_x005d_._x005b_MEMBER_CAPTION_x005d_>1997 <_x005b_Time_x005d_._x005b_Year_x005d_._x005b_MEMBER_UNIQUE_NAME_x005d_>[Time].[1997] <_x005b_Measures_x005d_._x005b_Unit_x0020_Sales_x005d_ xsi:type="xsd:double">266773 <_x005b_Customers_x005d_._x005b_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>Mexico <_x005b_Customers_x005d_._x005b_Country_x005d_._x005b_MEMBER_UNIQUE_NAME_x005d_>[Customers].[Mexico] <_x005b_Time_x005d_._x005b_Year_x005d_._x005b_MEMBER_CAPTION_x005d_>1998 <_x005b_Time_x005d_._x005b_Year_x005d_._x005b_MEMBER_UNIQUE_NAME_x005d_>[Time].[1998] <_x005b_Customers_x005d_._x005b_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Customers_x005d_._x005b_Country_x005d_._x005b_MEMBER_UNIQUE_NAME_x005d_>[Customers].[USA] <_x005b_Time_x005d_._x005b_Year_x005d_._x005b_MEMBER_CAPTION_x005d_>1997 <_x005b_Time_x005d_._x005b_Year_x005d_._x005b_MEMBER_UNIQUE_NAME_x005d_>[Time].[1997] <_x005b_Measures_x005d_._x005b_Unit_x0020_Sales_x005d_ xsi:type="xsd:double">266773 <_x005b_Customers_x005d_._x005b_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Customers_x005d_._x005b_Country_x005d_._x005b_MEMBER_UNIQUE_NAME_x005d_>[Customers].[USA] <_x005b_Time_x005d_._x005b_Year_x005d_._x005b_MEMBER_CAPTION_x005d_>1998 <_x005b_Time_x005d_._x005b_Year_x005d_._x005b_MEMBER_UNIQUE_NAME_x005d_>[Time].[1998] ]]>
1033 FoodMart FoodMart ]]>
]]>
DISCOVER_DATASOURCES 1033 SchemaData Tabular ]]> FoodMart Mondrian FoodMart data source http://localhost:8080/mondrian/xmla FoodMart Mondrian MDP Unauthenticated ]]>
DISCOVER_SCHEMA_ROWSETS 1033 FoodMart FoodMart Tabular ]]>
DBSCHEMA_CATALOGS CATALOG_NAME xsd:string Identifies the physical attributes associated with catalogs accessible from the provider. DBSCHEMA_COLUMNS TABLE_CATALOG xsd:string TABLE_SCHEMA xsd:string TABLE_NAME xsd:string COLUMN_NAME xsd:string DBSCHEMA_PROVIDER_TYPES DATA_TYPE xsd:unsignedShort BEST_MATCH xsd:boolean DBSCHEMA_SCHEMATA CATALOG_NAME xsd:string SCHEMA_NAME xsd:string SCHEMA_OWNER xsd:string DBSCHEMA_TABLES TABLE_CATALOG xsd:string TABLE_SCHEMA xsd:string TABLE_NAME xsd:string TABLE_TYPE xsd:string DBSCHEMA_TABLES_INFO TABLE_CATALOG xsd:string TABLE_SCHEMA xsd:string TABLE_NAME xsd:string TABLE_TYPE xsd:string DISCOVER_DATASOURCES DataSourceName xsd:string URL xsd:string ProviderName xsd:string ProviderType xsd:string AuthenticationMode xsd:string Returns a list of XML for Analysis data sources available on the server or Web Service. DISCOVER_ENUMERATORS EnumName xsd:string Returns a list of names, data types, and enumeration values for enumerators supported by the provider of a specific data source. DISCOVER_KEYWORDS Keyword xsd:string Returns an XML list of keywords reserved by the provider. DISCOVER_LITERALS LiteralName xsd:string Returns information about literals supported by the provider. DISCOVER_PROPERTIES PropertyName xsd:string Returns a list of information and values about the requested properties that are supported by the specified data source provider. DISCOVER_SCHEMA_ROWSETS SchemaName xsd:string Returns the names, values, and other information of all supported RequestType enumeration values. MDSCHEMA_ACTIONS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string ACTION_NAME xsd:string COORDINATE xsd:string COORDINATE_TYPE xsd:int MDSCHEMA_CUBES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string CUBE_TYPE xsd:string MDSCHEMA_DIMENSIONS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string MDSCHEMA_FUNCTIONS FUNCTION_NAME xsd:string ORIGIN xsd:int INTERFACE_NAME xsd:string LIBRARY_NAME xsd:string MDSCHEMA_HIERARCHIES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string MDSCHEMA_LEVELS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string LEVEL_NAME xsd:string LEVEL_UNIQUE_NAME xsd:string MDSCHEMA_MEASURES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string MEASURE_NAME xsd:string MEASURE_UNIQUE_NAME xsd:string MDSCHEMA_MEMBERS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string LEVEL_UNIQUE_NAME xsd:string LEVEL_NUMBER xsd:unsignedInt MEMBER_NAME xsd:string MEMBER_UNIQUE_NAME xsd:string MEMBER_TYPE xsd:int MEMBER_CAPTION xsd:string TREE_OP xsd:int MDSCHEMA_PROPERTIES CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string DIMENSION_UNIQUE_NAME xsd:string HIERARCHY_UNIQUE_NAME xsd:string LEVEL_UNIQUE_NAME xsd:string MEMBER_UNIQUE_NAME xsd:string PROPERTY_NAME xsd:string PROPERTY_TYPE xsd:short PROPERTY_CONTENT_TYPE xsd:short MDSCHEMA_SETS CATALOG_NAME xsd:string SCHEMA_NAME xsd:string CUBE_NAME xsd:string SET_NAME xsd:string SCOPE xsd:int ]]>
DBSCHEMA_CATALOGS 1033 FoodMart SchemaData Tabular ]]>
FoodMart No description available California manager,No HR Cube ]]>
DISCOVER_PROPERTIES ProviderVersion 1033 FoodMart SchemaData Tabular ]]>
ProviderVersion The version of the Mondrian XMLA Provider string Read false ${mondrianVersion} ]]>
DISCOVER_PROPERTIES Catalog 1033 FoodMart SchemaData Tabular ]]>
Catalog When establishing a session with an Analysis Services instance to send an XMLA command, this property is equivalent to the OLE DB property, DBPROP_INIT_CATALOG. When you set this property during a session to change the current database for the session, this property is equivalent to the OLE DB property, DBPROP_CURRENTCATALOG. The default value for this property is an empty string. string ReadWrite false ]]>
1033 FoodMart ]]>
]]>
mondrian-3.4.1/testsrc/main/mondrian/xmla/XmlaTabularTest.ref.xml0000644000175000017500000015117711735330606024776 0ustar drazzibdrazzib select [Measures].[Sales Count] on 0, non empty [Store].[Store State].members on 1 from [Sales] ${data.source.info} ${catalog} Tabular TupleFormat ]]> <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>CA <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">24442 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>OR <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">21611 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>WA <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">40784 ]]> select {[Measures].[Sales Count], [Measures].[Store Sales]} on 0, non empty [Store].[Store State].members on 1 from [Sales] ${data.source.info} ${catalog} Tabular TupleFormat ]]> <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>CA <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">24442 <_x005b_Measures_x005d_._x005b_Store_x0020_Sales_x005d_ xsi:type="xsd:double">159167.84 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>OR <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">21611 <_x005b_Measures_x005d_._x005b_Store_x0020_Sales_x005d_ xsi:type="xsd:double">142277.07 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>WA <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">40784 <_x005b_Measures_x005d_._x005b_Store_x0020_Sales_x005d_ xsi:type="xsd:double">263793.22 ]]> select [Measures].[Sales Count] on 0, non empty [Store].[Store State].members * [Product].[Product Family].members on 1 from [Sales] ${data.source.info} ${catalog} Tabular TupleFormat ]]> <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>CA <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Drink <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">2291 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>CA <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Food <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">17556 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>CA <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Non-Consumable <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">4595 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>OR <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Drink <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">1953 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>OR <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Food <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">15516 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>OR <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Non-Consumable <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">4142 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>WA <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Drink <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">3734 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>WA <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Food <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">29373 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>WA <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Non-Consumable <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">7677 ]]> select {[Measures].[Sales Count], [Measures].[Store Sales]} on 0, non empty [Store].[Store State].members * [Product].[Product Family].members on 1 from [Sales] ${data.source.info} ${catalog} Tabular TupleFormat ]]> <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>CA <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Drink <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">2291 <_x005b_Measures_x005d_._x005b_Store_x0020_Sales_x005d_ xsi:type="xsd:double">14203.24 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>CA <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Food <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">17556 <_x005b_Measures_x005d_._x005b_Store_x0020_Sales_x005d_ xsi:type="xsd:double">115193.17 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>CA <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Non-Consumable <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">4595 <_x005b_Measures_x005d_._x005b_Store_x0020_Sales_x005d_ xsi:type="xsd:double">29771.43 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>OR <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Drink <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">1953 <_x005b_Measures_x005d_._x005b_Store_x0020_Sales_x005d_ xsi:type="xsd:double">12137.29 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>OR <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Food <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">15516 <_x005b_Measures_x005d_._x005b_Store_x0020_Sales_x005d_ xsi:type="xsd:double">102564.67 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>OR <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Non-Consumable <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">4142 <_x005b_Measures_x005d_._x005b_Store_x0020_Sales_x005d_ xsi:type="xsd:double">27575.11 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>WA <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Drink <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">3734 <_x005b_Measures_x005d_._x005b_Store_x0020_Sales_x005d_ xsi:type="xsd:double">22495.68 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>WA <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Food <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">29373 <_x005b_Measures_x005d_._x005b_Store_x0020_Sales_x005d_ xsi:type="xsd:double">191277.75 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>WA <_x005b_Product_x005d_._x005b_Product_x0020_Family_x005d_._x005b_MEMBER_CAPTION_x005d_>Non-Consumable <_x005b_Measures_x005d_._x005b_Sales_x0020_Count_x005d_ xsi:type="xsd:int">7677 <_x005b_Measures_x005d_._x005b_Store_x0020_Sales_x005d_ xsi:type="xsd:double">50019.79 ]]> select from [Sales] where([Measures].[Sales Count]) ${data.source.info} ${catalog} Tabular TupleFormat ]]> ]]> select [Measures].[Sales Count] on 0, non empty filter([Store].[Store State].members, [Measures].[Sales Count]>50000) on 1 from [Sales] ${data.source.info} ${catalog} Tabular TupleFormat ]]> ]]> select [Product].Children on 0, non empty [Store].[Store State].members on 1, [Gender].members on 2 from [Sales] ${data.source.info} ${catalog} Tabular TupleFormat ]]> <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>CA <_x005b_Product_x005d_._x005b_Drink_x005d_ xsi:type="xsd:double">7102 <_x005b_Product_x005d_._x005b_Food_x005d_ xsi:type="xsd:double">53656 <_x005b_Product_x005d_._x005b_Non-Consumable_x005d_ xsi:type="xsd:double">13990 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>OR <_x005b_Product_x005d_._x005b_Drink_x005d_ xsi:type="xsd:double">6106 <_x005b_Product_x005d_._x005b_Food_x005d_ xsi:type="xsd:double">48537 <_x005b_Product_x005d_._x005b_Non-Consumable_x005d_ xsi:type="xsd:double">13016 <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>WA <_x005b_Product_x005d_._x005b_Drink_x005d_ xsi:type="xsd:double">11389 <_x005b_Product_x005d_._x005b_Food_x005d_ xsi:type="xsd:double">89747 <_x005b_Product_x005d_._x005b_Non-Consumable_x005d_ xsi:type="xsd:double">23230 <_x005b_Gender_x005d_._x005b_Gender_x005d_._x005b_MEMBER_CAPTION_x005d_>F <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>CA <_x005b_Product_x005d_._x005b_Drink_x005d_ xsi:type="xsd:double">7102 <_x005b_Product_x005d_._x005b_Food_x005d_ xsi:type="xsd:double">53656 <_x005b_Product_x005d_._x005b_Non-Consumable_x005d_ xsi:type="xsd:double">13990 <_x005b_Gender_x005d_._x005b_Gender_x005d_._x005b_MEMBER_CAPTION_x005d_>F <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>OR <_x005b_Product_x005d_._x005b_Drink_x005d_ xsi:type="xsd:double">6106 <_x005b_Product_x005d_._x005b_Food_x005d_ xsi:type="xsd:double">48537 <_x005b_Product_x005d_._x005b_Non-Consumable_x005d_ xsi:type="xsd:double">13016 <_x005b_Gender_x005d_._x005b_Gender_x005d_._x005b_MEMBER_CAPTION_x005d_>F <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>WA <_x005b_Product_x005d_._x005b_Drink_x005d_ xsi:type="xsd:double">11389 <_x005b_Product_x005d_._x005b_Food_x005d_ xsi:type="xsd:double">89747 <_x005b_Product_x005d_._x005b_Non-Consumable_x005d_ xsi:type="xsd:double">23230 <_x005b_Gender_x005d_._x005b_Gender_x005d_._x005b_MEMBER_CAPTION_x005d_>M <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>CA <_x005b_Product_x005d_._x005b_Drink_x005d_ xsi:type="xsd:double">7102 <_x005b_Product_x005d_._x005b_Food_x005d_ xsi:type="xsd:double">53656 <_x005b_Product_x005d_._x005b_Non-Consumable_x005d_ xsi:type="xsd:double">13990 <_x005b_Gender_x005d_._x005b_Gender_x005d_._x005b_MEMBER_CAPTION_x005d_>M <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>OR <_x005b_Product_x005d_._x005b_Drink_x005d_ xsi:type="xsd:double">6106 <_x005b_Product_x005d_._x005b_Food_x005d_ xsi:type="xsd:double">48537 <_x005b_Product_x005d_._x005b_Non-Consumable_x005d_ xsi:type="xsd:double">13016 <_x005b_Gender_x005d_._x005b_Gender_x005d_._x005b_MEMBER_CAPTION_x005d_>M <_x005b_Store_x005d_._x005b_Store_x0020_Country_x005d_._x005b_MEMBER_CAPTION_x005d_>USA <_x005b_Store_x005d_._x005b_Store_x0020_State_x005d_._x005b_MEMBER_CAPTION_x005d_>WA <_x005b_Product_x005d_._x005b_Drink_x005d_ xsi:type="xsd:double">11389 <_x005b_Product_x005d_._x005b_Food_x005d_ xsi:type="xsd:double">89747 <_x005b_Product_x005d_._x005b_Non-Consumable_x005d_ xsi:type="xsd:double">23230 ]]> mondrian-3.4.1/testsrc/main/mondrian/xmla/test/0000755000175000017500000000000011735330606021370 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/xmla/test/XmlaTest.java0000644000175000017500000002156111735330606024001 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.xmla.test; import mondrian.olap.*; import mondrian.server.StringRepositoryContentFinder; import mondrian.test.DiffRepository; import mondrian.test.TestContext; import mondrian.xmla.*; import mondrian.xmla.impl.DefaultXmlaRequest; import mondrian.xmla.impl.DefaultXmlaResponse; import junit.framework.*; import org.apache.log4j.Logger; import org.custommonkey.xmlunit.XMLAssert; import org.custommonkey.xmlunit.XMLUnit; import org.w3c.dom.*; import java.io.*; import java.util.Iterator; import java.util.List; import java.util.regex.Pattern; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; /** * Unit test for refined Mondrian's XML for Analysis API (package * {@link mondrian.xmla}). * * @author Gang Chen */ public class XmlaTest extends TestCase { private static final Logger LOGGER = Logger.getLogger(XmlaTest.class); static { XMLUnit.setControlParser( "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"); XMLUnit.setTestParser( "org.apache.xerces.jaxp.DocumentBuilderFactoryImpl"); XMLUnit.setIgnoreWhitespace(true); } private static final XmlaTestContext context = new XmlaTestContext(); private XmlaHandler handler; private MondrianServer server; public XmlaTest(String name) { super(name); } // implement TestCase protected void setUp() throws Exception { super.setUp(); DiffRepository diffRepos = getDiffRepos(); diffRepos.setCurrentTestCaseName(getName()); server = MondrianServer.createWithRepository( new StringRepositoryContentFinder( context.getDataSourcesString()), XmlaTestContext.CATALOG_LOCATOR); handler = new XmlaHandler( (XmlaHandler.ConnectionFactory) server, "xmla"); } // implement TestCase protected void tearDown() throws Exception { DiffRepository diffRepos = getDiffRepos(); diffRepos.setCurrentTestCaseName(null); server.shutdown(); super.tearDown(); } private static DiffRepository getDiffRepos() { return DiffRepository.lookup(XmlaTest.class); } protected void runTest() throws Exception { if (!MondrianProperties.instance().SsasCompatibleNaming.get() && getName().equals("mdschemaLevelsCubeDimRestrictions")) { // Changes in unique names of hierarchies and levels mean that the // output is a different order in the old behavior, and cannot be // fixed by a few sed-like comparisons. return; } DiffRepository diffRepos = getDiffRepos(); String request = diffRepos.expand(null, "${request}"); String expectedResponse = diffRepos.expand(null, "${response}"); Element requestElem = XmlaUtil.text2Element( XmlaTestContext.xmlFromTemplate( request, XmlaTestContext.ENV)); Element responseElem = ignoreLastUpdateDate(executeRequest(requestElem)); TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); StringWriter bufWriter = new StringWriter(); transformer.transform( new DOMSource(responseElem), new StreamResult(bufWriter)); bufWriter.write(Util.nl); String actualResponse = TestContext.instance().upgradeActual( bufWriter.getBuffer().toString()); try { // Start with a purely logical XML diff to avoid test noise // from non-determinism in XML generation. XMLAssert.assertXMLEqual(expectedResponse, actualResponse); } catch (AssertionFailedError e) { // In case of failure, re-diff using DiffRepository's comparison // method. It may have noise due to physical vs logical structure, // but it will maintain the expected/actual, and some IDEs can even // display visual diffs. diffRepos.assertEquals("response", "${response}", actualResponse); } } private Element ignoreLastUpdateDate(Element element) { NodeList elements = element.getElementsByTagName("LAST_SCHEMA_UPDATE"); for (int i = elements.getLength(); i > 0; i--) { removeNode(elements.item(i - 1)); } return element; } private void removeNode(Node node) { Node parentNode = node.getParentNode(); parentNode.removeChild(node); } private Element executeRequest(Element requestElem) { ByteArrayOutputStream resBuf = new ByteArrayOutputStream(); XmlaRequest request = new DefaultXmlaRequest(requestElem, null, null, null, null); XmlaResponse response = new DefaultXmlaResponse( resBuf, "UTF-8", Enumeration.ResponseMimeType.SOAP); handler.process(request, response); return XmlaUtil.stream2Element( new ByteArrayInputStream(resBuf.toByteArray())); } public static TestSuite suite() { TestSuite suite = new TestSuite(); DiffRepository diffRepos = getDiffRepos(); MondrianProperties properties = MondrianProperties.instance(); String filePattern = properties.QueryFilePattern.get(); final Pattern pattern = filePattern == null ? null : Pattern.compile(filePattern); List testCaseNames = diffRepos.getTestCaseNames(); if (pattern != null) { Iterator iter = testCaseNames.iterator(); while (iter.hasNext()) { String name = iter.next(); if (!pattern.matcher(name).matches()) { iter.remove(); } } } LOGGER.debug("Found " + testCaseNames.size() + " XML/A test cases"); for (String name : testCaseNames) { suite.addTest(new XmlaTest(name)); } suite.addTestSuite(OtherTest.class); return suite; } /** * Non diff-based unit tests for XML/A support. */ public static class OtherTest extends TestCase { public void testEncodeElementName() { final XmlaUtil.ElementNameEncoder encoder = XmlaUtil.ElementNameEncoder.INSTANCE; assertEquals("Foo", encoder.encode("Foo")); assertEquals("Foo_x0020_Bar", encoder.encode("Foo Bar")); if (false) // FIXME: assertEquals( "Foo_x00xx_Bar", encoder.encode("Foo_Bar")); // Caching: decode same string, get same string back final String s1 = encoder.encode("Abc def"); final String s2 = encoder.encode("Abc def"); assertSame(s1, s2); } /** * Unit test for {@link XmlaUtil#chooseResponseMimeType(String)}. */ public void testAccept() { // simple assertEquals( Enumeration.ResponseMimeType.SOAP, XmlaUtil.chooseResponseMimeType("application/xml")); // deal with ",q=" quality codes by ignoring them assertEquals( Enumeration.ResponseMimeType.SOAP, XmlaUtil.chooseResponseMimeType( "text/html,application/xhtml+xml," + "application/xml;q=0.9,*/*;q=0.8")); // return null if nothing matches assertNull( XmlaUtil.chooseResponseMimeType( "text/html,application/xhtml+xml")); // quality codes all over the place; return JSON because we see // it before application/xml assertEquals( Enumeration.ResponseMimeType.JSON, XmlaUtil.chooseResponseMimeType( "text/html;q=0.9," + "application/xhtml+xml;q=0.9," + "application/json;q=0.9," + "application/xml;q=0.9," + "*/*;q=0.8")); // allow application/soap+xml as synonym for application/xml assertEquals( Enumeration.ResponseMimeType.SOAP, XmlaUtil.chooseResponseMimeType( "text/html,application/soap+xml")); // allow text/xml as synonym for application/xml assertEquals( Enumeration.ResponseMimeType.SOAP, XmlaUtil.chooseResponseMimeType( "text/html,application/soap+xml")); } } } // End XmlaTest.java mondrian-3.4.1/testsrc/main/mondrian/xmla/test/XmlaTestServletRequestWrapper.java0000644000175000017500000001025411735330606030255 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho and others // All Rights Reserved. */ package mondrian.xmla.test; import mondrian.olap.Util; import mondrian.xmla.XmlaServlet; import java.io.*; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; /** * Dummy request for testing XmlaServlet. Provides a 'text/xml' content stream * from a post from xmlaTest.jsp. Assumes that the SOAPRequest parameter * contains XML/A SOAP request body. * * @author Sherman Wood */ public class XmlaTestServletRequestWrapper extends HttpServletRequestWrapper { private HttpServletRequest originalRequest; private ServletInputStream servletInStream; public XmlaTestServletRequestWrapper(HttpServletRequest req) { super(req); originalRequest = req; init(); } /** * Extract the data from the HTTP request and create an XML/A request */ private void init() { String soapRequest = originalRequest.getParameter("SOAPRequest"); if (soapRequest == null || soapRequest.length() == 0) { // Parameter not set. Look for the request in the body of the http // request. try { final ServletInputStream inputStream = originalRequest.getInputStream(); soapRequest = Util.readFully( new InputStreamReader(inputStream), 2048); } catch (IOException e) { throw Util.newInternal(e, "error reading body of soap request"); } if (soapRequest.length() == 0) { throw new RuntimeException("SOAPRequest not set"); } } /* * Strip the XML premable if it is there */ if (soapRequest.indexOf("") + 2); } /* * Make a SOAP message */ String request = "\r\n" + "\r\n" + "\r\n" + "\r\n" + soapRequest + "\r\n\r\n"; servletInStream = new XmlaTestServletInputStream(request); } public String getContentType() { return "text/xml"; } public ServletInputStream getInputStream() { return servletInStream; } private static class XmlaTestServletInputStream extends ServletInputStream { private ByteArrayInputStream bais; XmlaTestServletInputStream(String source) { bais = new ByteArrayInputStream(source.getBytes()); } public int readLine(byte[] arg0, int arg1, int arg2) throws IOException { return bais.read(arg0, arg1, arg2); } public int available() throws IOException { return bais.available(); } public void close() throws IOException { bais.close(); } public synchronized void mark(int readlimit) { bais.mark(readlimit); } public boolean markSupported() { return bais.markSupported(); } public int read() throws IOException { return bais.read(); } public int read(byte[] b, int off, int len) throws IOException { return bais.read(b, off, len); } public int read(byte[] b) throws IOException { return bais.read(b); } public synchronized void reset() throws IOException { bais.reset(); } public long skip(long n) throws IOException { return bais.skip(n); } } } // End XmlaTestServletRequestWrapper.java mondrian-3.4.1/testsrc/main/mondrian/xmla/test/XmlaTestContext.java0000644000175000017500000001322311735330606025342 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2010 Pentaho and others // All Rights Reserved. */ package mondrian.xmla.test; import mondrian.olap.Util; import mondrian.rolap.RolapConnectionProperties; import mondrian.spi.CatalogLocator; import mondrian.spi.impl.CatalogLocatorImpl; import mondrian.test.DiffRepository; import mondrian.test.TestContext; import mondrian.xmla.DataSourcesConfig; import org.apache.log4j.Logger; import org.eigenbase.xom.*; import org.olap4j.impl.Olap4jUtil; import java.io.StringReader; import java.net.URL; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Common utilities for XML/A testing, used in test suite and * example XML/A web pages. Refactored from XmlaTest. * * @author Sherman Wood */ public class XmlaTestContext { private static final Logger LOGGER = Logger.getLogger(XmlaTestContext.class); public static final String CATALOG_NAME = "FoodMart"; public static final String DATASOURCE_NAME = "FoodMart"; public static final String DATASOURCE_DESCRIPTION = "Mondrian FoodMart Test data source"; public static final String DATASOURCE_INFO = "Provider=Mondrian;DataSource=FoodMart;"; public static final Map ENV = Olap4jUtil.mapOf( "catalog", CATALOG_NAME, "datasource", DATASOURCE_NAME); private static DataSourcesConfig.DataSources DATASOURCES; public static final CatalogLocator CATALOG_LOCATOR = new CatalogLocatorImpl(); private String connectString; public XmlaTestContext() { super(); } public String getConnectString() { if (connectString != null) { return connectString; } connectString = TestContext.instance().getConnectString(); // Deal with MySQL and other connect strings with & in them connectString = connectString.replaceAll("&", "&"); return connectString; } public DataSourcesConfig.DataSources dataSources() { if (DATASOURCES != null) { return DATASOURCES; } StringReader dsConfigReader = new StringReader( getDataSourcesString()); try { final Parser xmlParser = XOMUtil.createDefaultParser(); final DOMWrapper def = xmlParser.parse(dsConfigReader); DATASOURCES = new DataSourcesConfig.DataSources(def); } catch (Exception e) { throw new RuntimeException(e); } return DATASOURCES; } public String getDataSourcesString() { Util.PropertyList connectProperties = Util.parseConnectString(getConnectString()); String catalogUrl = connectProperties.get( RolapConnectionProperties.Catalog.name()); return "" + "" + " " + " " + DATASOURCE_NAME + "" + " " + DATASOURCE_DESCRIPTION + "" + " http://localhost:8080/mondrian/xmla" + " " + getConnectString() + "" + " Mondrian" + " MDP" + " Unauthenticated" + " " + " " + catalogUrl + "" + " " + " " + ""; } public static String xmlFromTemplate( String xmlText, Map env) { StringBuffer buf = new StringBuffer(); Pattern pattern = Pattern.compile("\\$\\{([^}]+)\\}"); Matcher matcher = pattern.matcher(xmlText); while (matcher.find()) { String varName = matcher.group(1); String varValue = env.get(varName); if (varValue != null) { matcher.appendReplacement(buf, varValue); } else { matcher.appendReplacement(buf, "\\${$1}"); } } matcher.appendTail(buf); return buf.toString(); } /** * Returns a list of sample XML requests. * *

Each item is a pair of strings: {test name, request}. * *

NOTE: This method is called from xmlaTest.jsp. Do not * remove it if you can't find calls from Java. * * @return List of sample XML requests */ public String[][] defaultRequests() { // Assume that the ref file is in the same tree (WEB-INF/classes) as // DiffRepository.class. URL refUrl = DiffRepository.class.getClassLoader().getResource( "mondrian/xmla/test/XmlaTest.ref.xml"); DiffRepository diffRepos = new DiffRepository(refUrl); List stringList = new ArrayList(); for (String testName : diffRepos.getTestCaseNames()) { String templateRequest = diffRepos.get(testName, "request"); String request = xmlFromTemplate(templateRequest, ENV); stringList.add(new String[] {testName, request}); } return stringList.toArray(new String[stringList.size()][]); } } // End XmlaTestContext.java mondrian-3.4.1/testsrc/main/mondrian/xmla/test/XmlaTest.ref.xml0000644000175000017500000040340111735330606024430 0ustar drazzibdrazzib DBSCHEMA_CATALOGS ${datasource} ${catalog} Tabular ]]> FoodMart No description available California manager,No HR Cube ]]> DBSCHEMA_SCHEMATA ${datasource} ${catalog} Tabular ]]> FoodMart FoodMart ]]> DISCOVER_DATASOURCES Tabular ]]> FoodMart Mondrian FoodMart Test data source http://localhost:8080/mondrian/xmla FoodMart Mondrian MDP Unauthenticated ]]> SELECT {[Measures].[Org Salary]} ON columns, Hierarchize(Union({[Employees].[All Employees]}, [Employees].[All Employees].Children)) ON rows FROM HR WHERE ([Time].[1997]) ${catalog} ${datasource} Tabular TupleFormat ]]> <_x005b_Measures_x005d_._x005b_Org_x0020_Salary_x005d_ xsi:type="xsd:double">39431.6712 <_x005b_Employees_x005d_._x005b_Employee_x0020_Id_x005d_._x005b_MEMBER_CAPTION_x005d_>Sheri Nowmer <_x005b_Measures_x005d_._x005b_Org_x0020_Salary_x005d_ xsi:type="xsd:double">39431.6712 ]]> SELECT {[Measures].[Org Salary]} ON columns, Hierarchize(Union({[Employees].[All Employees]}, [Employees].[All Employees].Children)) ON rows FROM HR WHERE ([Time].[1997]) ${catalog} ${datasource} Multidimensional TupleFormat ]]> HR [Measures].[Org Salary] Org Salary [Measures].[MeasuresLevel] 0 0 [Employees].[All Employees] All Employees [Employees].[(All)] 0 65537 [Employees].[Sheri Nowmer] Sheri Nowmer [Employees].[Employee Id] 1 7 [Time].[1997] 1997 [Time].[Year] 0 4 39431.6712 $39,431.67 Currency 39431.6712 $39,431.67 Currency ]]> MDSCHEMA_CUBES ${catalog} Sales ${datasource} ${catalog} Tabular ]]> FoodMart FoodMart Sales CUBE true false false false Sales FoodMart Schema - Sales Cube ]]> MDSCHEMA_CUBES ${catalog} ${datasource} ${catalog} Tabular ]]> FoodMart FoodMart HR CUBE true false false false HR FoodMart Schema - HR Cube FoodMart FoodMart Sales CUBE true false false false Sales FoodMart Schema - Sales Cube FoodMart FoodMart Sales 2 CUBE true false false false Sales 2 FoodMart Schema - Sales 2 Cube FoodMart FoodMart Sales Ragged CUBE true false false false Sales Ragged FoodMart Schema - Sales Ragged Cube FoodMart FoodMart Store CUBE true false false false Store FoodMart Schema - Store Cube FoodMart FoodMart Warehouse CUBE true false false false Warehouse FoodMart Schema - Warehouse Cube FoodMart FoodMart Warehouse and Sales VIRTUAL CUBE true false false false Warehouse and Sales FoodMart Schema - Warehouse and Sales Cube ]]> MDSCHEMA_DIMENSIONS ${catalog} Sales Time ${datasource} ${catalog} Tabular ]]> FoodMart FoodMart Sales Time [Time] Time 4 1 25 [Time] Sales Cube - Time Dimension false false 0 true ]]> MDSCHEMA_DIMENSIONS ${catalog} Sales ${datasource} ${catalog} Tabular ]]> FoodMart FoodMart Sales Customers [Customers] Customers 8 3 10282 [Customers] Sales Cube - Customers Dimension false false 0 true FoodMart FoodMart Sales Education Level [Education Level] Education Level 9 3 6 [Education Level] Sales Cube - Education Level Dimension false false 0 true FoodMart FoodMart Sales Gender [Gender] Gender 10 3 3 [Gender] Sales Cube - Gender Dimension false false 0 true FoodMart FoodMart Sales Marital Status [Marital Status] Marital Status 11 3 112 [Marital Status] Sales Cube - Marital Status Dimension false false 0 true FoodMart FoodMart Sales Measures [Measures] Measures 0 2 10 [Measures] Sales Cube - Measures Dimension false false 0 true FoodMart FoodMart Sales Product [Product] Product 5 3 1561 [Product] Sales Cube - Product Dimension false false 0 true FoodMart FoodMart Sales Promotion Media [Promotion Media] Promotion Media 6 3 15 [Promotion Media] Sales Cube - Promotion Media Dimension false false 0 true FoodMart FoodMart Sales Promotions [Promotions] Promotions 7 3 52 [Promotions] Sales Cube - Promotions Dimension false false 0 true FoodMart FoodMart Sales Store [Store] Store 1 3 26 [Store] Sales Cube - Store Dimension false false 0 true FoodMart FoodMart Sales Store Size in SQFT [Store Size in SQFT] Store Size in SQFT 2 3 22 [Store Size in SQFT] Sales Cube - Store Size in SQFT Dimension false false 0 true FoodMart FoodMart Sales Store Type [Store Type] Store Type 3 3 7 [Store Type] Sales Cube - Store Type Dimension false false 0 true FoodMart FoodMart Sales Time [Time] Time 4 1 25 [Time] Sales Cube - Time Dimension false false 0 true FoodMart FoodMart Sales Yearly Income [Yearly Income] Yearly Income 12 3 9 [Yearly Income] Sales Cube - Yearly Income Dimension false false 0 true ]]> MDSCHEMA_HIERARCHIES ${catalog} Sales [Time] ${datasource} ${catalog} Tabular ]]> FoodMart FoodMart Sales [Time] Time [Time] Time 1 34 [Time].[1997] Sales Cube - Time Hierarchy 0 false false 0 true true 4 true false FoodMart FoodMart Sales [Time] Weekly [Time].[Weekly] Weekly 1 837 [Time].[Weekly].[All Weeklys] [Time].[Weekly].[All Weeklys] Sales Cube - Time.Weekly Hierarchy 0 false false 0 true true 5 true false ]]> MDSCHEMA_HIERARCHIES ${catalog} Sales ${datasource} ${catalog} Tabular ]]> FoodMart FoodMart Sales [Customers] Customers [Customers] Customers 3 10407 [Customers].[All Customers] [Customers].[All Customers] Sales Cube - Customers Hierarchy 0 false false 0 true true 9 true false FoodMart FoodMart Sales [Education Level] Education Level [Education Level] Education Level 3 6 [Education Level].[All Education Levels] [Education Level].[All Education Levels] Sales Cube - Education Level Hierarchy 0 false false 0 true true 10 true false FoodMart FoodMart Sales [Gender] Gender [Gender] Gender 3 3 [Gender].[All Gender] [Gender].[All Gender] Sales Cube - Gender Hierarchy 0 false false 0 true true 11 true false FoodMart FoodMart Sales [Marital Status] Marital Status [Marital Status] Marital Status 3 112 [Marital Status].[All Marital Status] [Marital Status].[All Marital Status] Sales Cube - Marital Status Hierarchy 0 false false 0 true true 12 true false FoodMart FoodMart Sales [Measures] Measures [Measures] Measures 2 9 [Measures].[Unit Sales] Sales Cube - Measures Hierarchy 0 false false 0 true true 0 true false FoodMart FoodMart Sales [Product] Product [Product] Product 3 2256 [Product].[All Products] [Product].[All Products] Sales Cube - Product Hierarchy 0 false false 0 true true 6 true false FoodMart FoodMart Sales [Promotion Media] Promotion Media [Promotion Media] Promotion Media 3 15 [Promotion Media].[All Media] [Promotion Media].[All Media] Sales Cube - Promotion Media Hierarchy 0 false false 0 true true 7 true false FoodMart FoodMart Sales [Promotions] Promotions [Promotions] Promotions 3 52 [Promotions].[All Promotions] [Promotions].[All Promotions] Sales Cube - Promotions Hierarchy 0 false false 0 true true 8 true false FoodMart FoodMart Sales [Store Size in SQFT] Store Size in SQFT [Store Size in SQFT] Store Size in SQFT 3 22 [Store Size in SQFT].[All Store Size in SQFTs] [Store Size in SQFT].[All Store Size in SQFTs] Sales Cube - Store Size in SQFT Hierarchy 0 false false 0 true true 2 true false FoodMart FoodMart Sales [Store Type] Store Type [Store Type] Store Type 3 7 [Store Type].[All Store Types] [Store Type].[All Store Types] Sales Cube - Store Type Hierarchy 0 false false 0 true true 3 true false FoodMart FoodMart Sales [Store] Store [Store] Store 3 63 [Store].[All Stores] [Store].[All Stores] Sales Cube - Store Hierarchy 0 false false 0 true true 1 true false FoodMart FoodMart Sales [Time] Time [Time] Time 1 34 [Time].[1997] Sales Cube - Time Hierarchy 0 false false 0 true true 4 true false FoodMart FoodMart Sales [Time] Weekly [Time].[Weekly] Weekly 1 837 [Time].[Weekly].[All Weeklys] [Time].[Weekly].[All Weeklys] Sales Cube - Time.Weekly Hierarchy 0 false false 0 true true 5 true false FoodMart FoodMart Sales [Yearly Income] Yearly Income [Yearly Income] Yearly Income 3 9 [Yearly Income].[All Yearly Incomes] [Yearly Income].[All Yearly Incomes] Sales Cube - Yearly Income Hierarchy 0 false false 0 true true 13 true false ]]> MDSCHEMA_LEVELS Sales [Time] ${datasource} ${catalog} Tabular ]]> FoodMart FoodMart Sales [Time] [Time] Year [Time].[Year] Year 0 2 20 0 1 true Sales Cube - Time Hierarchy - Year Level FoodMart FoodMart Sales [Time] [Time] Quarter [Time].[Quarter] Quarter 1 8 68 0 0 true Sales Cube - Time Hierarchy - Quarter Level FoodMart FoodMart Sales [Time] [Time] Month [Time].[Month] Month 2 24 132 0 0 true Sales Cube - Time Hierarchy - Month Level FoodMart FoodMart Sales [Time] [Time].[Weekly] (All) [Time].[Weekly].[(All)] (All) 0 1 1 0 3 true Sales Cube - Time.Weekly Hierarchy - (All) Level FoodMart FoodMart Sales [Time] [Time].[Weekly] Year [Time].[Weekly].[Year] Year 1 2 20 0 1 true Sales Cube - Time.Weekly Hierarchy - Year Level FoodMart FoodMart Sales [Time] [Time].[Weekly] Week [Time].[Weekly].[Week] Week 2 104 260 0 0 true Sales Cube - Time.Weekly Hierarchy - Week Level FoodMart FoodMart Sales [Time] [Time].[Weekly] Day [Time].[Weekly].[Day] Day 3 730 516 0 0 true Sales Cube - Time.Weekly Hierarchy - Day Level ]]> MDSCHEMA_MEASURES ${catalog} Sales ${datasource} ${catalog} Tabular ]]> FoodMart FoodMart Sales Customer Count [Measures].[Customer Count] Customer Count 0 3 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Customer Count Member FoodMart FoodMart Sales Profit [Measures].[Profit] Profit 127 130 true Sales Cube - Profit Member FoodMart FoodMart Sales Profit Growth [Measures].[Profit Growth] Gewinn-Wachstum 127 130 true Sales Cube - Profit Growth Member FoodMart FoodMart Sales Promotion Sales [Measures].[Promotion Sales] Promotion Sales 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Promotion Sales Member FoodMart FoodMart Sales Sales Count [Measures].[Sales Count] Sales Count 2 3 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Sales Count Member FoodMart FoodMart Sales Store Cost [Measures].[Store Cost] Store Cost 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Store Cost Member FoodMart FoodMart Sales Store Sales [Measures].[Store Sales] Store Sales 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Store Sales Member FoodMart FoodMart Sales Unit Sales [Measures].[Unit Sales] Unit Sales 1 5 true [Store].[Store Name],[Store Size in SQFT].[Store Sqft],[Store Type].[Store Type],[Time].[Month],[Time].[Weekly].[Day],[Product].[Product Name],[Promotion Media].[Media Type],[Promotions].[Promotion Name],[Customers].[Name],[Education Level].[Education Level],[Gender].[Gender],[Marital Status].[Marital Status],[Yearly Income].[Yearly Income] Sales Cube - Unit Sales Member ]]> MDSCHEMA_MEMBERS ${catalog} Sales [Time].[Year] ${datasource} ${catalog} Tabular ]]> FoodMart FoodMart Sales [Time] [Time] [Time].[Year] 0 0 1997 [Time].[1997] 1 1997 4 0 0 0 FoodMart FoodMart Sales [Time] [Time] [Time].[Year] 0 17 1998 [Time].[1998] 1 1998 4 0 0 0 ]]> MDSCHEMA_MEMBERS Sales [Measures].[MeasuresLevel] ${datasource} ${catalog} Tabular ]]> FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 0 Unit Sales [Measures].[Unit Sales] 3 Unit Sales 0 0 0 0 FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 1 Store Cost [Measures].[Store Cost] 3 Store Cost 0 0 0 0 FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 2 Store Sales [Measures].[Store Sales] 3 Store Sales 0 0 0 0 FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 3 Sales Count [Measures].[Sales Count] 3 Sales Count 0 0 0 0 FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 4 Customer Count [Measures].[Customer Count] 3 Customer Count 0 0 0 0 FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 5 Promotion Sales [Measures].[Promotion Sales] 3 Promotion Sales 0 0 0 0 FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 6 Profit [Measures].[Profit] 4 Profit 0 0 0 0 FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 8 Profit Growth [Measures].[Profit Growth] 4 Gewinn-Wachstum 0 0 0 0 ]]> MDSCHEMA_MEMBERS Sales [Measures].[Profit Growth] ${datasource} ${catalog} Tabular ]]> FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 8 Profit Growth [Measures].[Profit Growth] 4 Gewinn-Wachstum 0 0 0 0 ]]> MDSCHEMA_MEMBERS Sales [Measures] ${datasource} ${catalog} Tabular ]]> FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 0 Unit Sales [Measures].[Unit Sales] 3 Unit Sales 0 0 0 0 FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 1 Store Cost [Measures].[Store Cost] 3 Store Cost 0 0 0 0 FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 2 Store Sales [Measures].[Store Sales] 3 Store Sales 0 0 0 0 FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 3 Sales Count [Measures].[Sales Count] 3 Sales Count 0 0 0 0 FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 4 Customer Count [Measures].[Customer Count] 3 Customer Count 0 0 0 0 FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 5 Promotion Sales [Measures].[Promotion Sales] 3 Promotion Sales 0 0 0 0 FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 6 Profit [Measures].[Profit] 4 Profit 0 0 0 0 FoodMart FoodMart Sales [Measures] [Measures] [Measures].[MeasuresLevel] 0 8 Profit Growth [Measures].[Profit Growth] 4 Gewinn-Wachstum 0 0 0 0 ]]> mondrian-3.4.1/testsrc/main/mondrian/xmla/XmlaErrorTest.ref.xml0000644000175000017500000004676511735330606024503 0ustar drazzibdrazzib DISCOVER_DATASOURCES ]]> DISCOVER_DATASOURCES ]]> DISCOVER_DATASOURCES ]]> DISCOVER_DATASOURCES DISCOVER_DATASOURCES ]]> DISCOVER_DATASOURCES 1033 MondrianFoodMart ]]>

Have a nice day
1033 MondrianFoodMart ]]>
Have a nice day
1033 MondrianFoodMart 1033 MondrianFoodMart ]]>
1033 MondrianFoodMart ]]>
1033 MondrianFoodMart ]]>
1033 MondrianFoodMart ]]>
1033 MondrianFoodMart ]]>
1033 MondrianFoodMart ]]>
1033 MondrianFoodMart ]]>
Have a nice day
1033 MondrianFoodMart DISCOVER_DATASOURCES Data ]]>
Have a nice day
1033 MondrianFoodMart ]]>
Have a nice day
1033 MondrianFoodMart ]]>
Have a nice day
DISCOVER_DATASOURCES Data ]]>
Have a nice day
DISCOVER_DATASOURCES Data ]]>
Have a nice day
DISCOVER_DATASOURCES Data ]]>
Have a nice day
1033 MondrianFoodMart ]]>
Have a nice day
1033 MondrianFoodMart ]]>
Have a nice day
DISCOVER_DATASOURCES Data ]]>
Have a nice day
DISCOVER_DATASOURCES Data ]]>
Have a nice day
1033 MondrianFoodMart ]]>
Have a nice day
1033 MondrianFoodMart ]]>
Have a nice day
DRILLTHROUGH MAXROWS -1000 SELECT {[Customers].[USA].[CA].[Berkeley]} ON 0, {[Time].[1997]} ON 1, {[Product].[Drink]} ON 2 FROM Sales 1033 MondrianFoodMart ]]>
Have a nice day
DRILLTHROUGH MAXROWS 1000 FIRSTROWSET -200 SELECT {[Customers].[USA].[CA].[Berkeley]} ON 0, {[Time].[1997]} ON 1, {[Product].[Drink]} ON 2 FROM Sales 1033 MondrianFoodMart ]]>
Have a nice day
SELECT {[Customers].[USA].[CA].[Berkeley]} ON 0, {[Time].[1997]} ON 1, {[Product].[Drink]} ON 2 DRILLTHROUGH MAXROWS 1000 FIRSTROWSET 200 FROM Sales 1033 MondrianFoodMart ]]>
mondrian-3.4.1/testsrc/main/mondrian/xmla/impl/0000755000175000017500000000000011735330606021352 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/xmla/impl/DynamicDatasourceXmlaServletTest.java0000644000175000017500000003230011735330606030641 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.xmla.impl; import mondrian.olap.MondrianProperties; import mondrian.server.DynamicContentFinder; import mondrian.xmla.DataSourcesConfig; import junit.framework.TestCase; import org.eigenbase.xom.*; import org.olap4j.impl.Olap4jUtil; import java.io.*; import java.util.*; /** * Unit test for DynamicDatasourceXmlaServlet * * @author Thiyagu, Ajit * @since Mar 30, 2007 */ public class DynamicDatasourceXmlaServletTest extends TestCase { private static final String CATALOG_0_NAME = "FoodMart0"; private static final String CATALOG_1_NAME = "FoodMart1"; private static final String CATALOG_2_NAME = "FoodMart2"; private static final String CATALOG_0_DEFINITION = "" + "Provider=mondrian;Jdbc=jdbc:odbc:MondrianFoodMart0;" + "JdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver" + "" + ""; private static final String CATALOG_0_UPDATED_DEFINITION = "" + "Provider=mondrian;Jdbc=jdbc:odbc:MondrianFoodMart0.0;" + "JdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver" + "" + ""; private static final String CATALOG_1_DEFINITION = "" + "Provider=mondrian;Jdbc=jdbc:odbc:MondrianFoodMart1;" + "JdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver" + "" + ""; private static final String CATALOG_2_DEFINITION = "" + "Provider=mondrian;Jdbc=jdbc:odbc:MondrianFoodMart2;" + "JdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver" + "" + ""; private static final String DATASOURCE_1_NAME = "DATASOURCENAME1"; private static final String DATASOURCE_2_NAME = "DATASOURCENAME2"; public void testFlushObsoleteCatalogsForNewCatalog() throws Exception { DataSourcesConfig.DataSources newDataSources = getDataSources(CATALOG_0_DEFINITION, CATALOG_1_DEFINITION); final MockDynamicContentFinder finder = new MockDynamicContentFinder( "inline:" + getDataSourceContent(CATALOG_0_DEFINITION)); finder.flushObsoleteCatalogs(newDataSources); assertTrue(finder.flushCatalogList().isEmpty()); finder.shutdown(); } public void testFlushObsoleteCatalogsForUpdateCatalog() throws Exception { DataSourcesConfig.DataSources newDataSources = getDataSources(CATALOG_0_UPDATED_DEFINITION); final MockDynamicContentFinder finder = new MockDynamicContentFinder( "inline:" + getDataSourceContent(CATALOG_0_DEFINITION)); finder.flushObsoleteCatalogs(newDataSources); assertTrue(finder.flushCatalogList().contains(CATALOG_0_NAME)); finder.shutdown(); } public void testFlushObsoleteCatalogsForUnchangedCatalog() throws Exception { DataSourcesConfig.DataSources newDataSources = getDataSources(CATALOG_0_DEFINITION, CATALOG_1_DEFINITION); final MockDynamicContentFinder finder = new MockDynamicContentFinder( "inline:" + getDataSourceContent(CATALOG_0_DEFINITION)); finder.flushObsoleteCatalogs(newDataSources); assertFalse(finder.flushCatalogList().contains(CATALOG_0_NAME)); finder.shutdown(); } public void testFlushObsoleteCatalogsForDeletedCatalog() throws Exception { DataSourcesConfig.DataSources newDataSources = getDataSources(CATALOG_1_DEFINITION); final MockDynamicContentFinder finder = new MockDynamicContentFinder( "inline:" + getDataSourceContent(CATALOG_0_DEFINITION)); finder.flushObsoleteCatalogs(newDataSources); assertTrue(finder.flushCatalogList().contains(CATALOG_0_NAME)); finder.shutdown(); } public void testMergeDataSourcesForAlteringCatalogAcrossDataSources() throws Exception { DataSourcesConfig.DataSources newDataSources = getDataSources( Olap4jUtil.mapOf( DATASOURCE_1_NAME, new String[] { CATALOG_0_UPDATED_DEFINITION, CATALOG_1_DEFINITION}, DATASOURCE_2_NAME, new String[] { CATALOG_2_DEFINITION})); final MockDynamicContentFinder finder = new MockDynamicContentFinder( "inline:" + getDataSourceContent(CATALOG_0_DEFINITION)); finder.flushObsoleteCatalogs(newDataSources); assertTrue(finder.flushCatalogList().contains(CATALOG_0_NAME)); finder.shutdown(); } public void testAreCatalogsEqual() throws Exception { DataSourcesConfig.DataSources newDataSources = getDataSources( CATALOG_0_DEFINITION, CATALOG_0_UPDATED_DEFINITION, CATALOG_1_DEFINITION, CATALOG_2_DEFINITION); DataSourcesConfig.DataSource datasource = newDataSources.dataSources[0]; DataSourcesConfig.Catalog catalog0 = datasource.catalogs.catalogs[0]; DataSourcesConfig.Catalog catalog0Updated = datasource.catalogs.catalogs[1]; DataSourcesConfig.Catalog catalog1 = datasource.catalogs.catalogs[2]; DataSourcesConfig.Catalog catalog2 = datasource.catalogs.catalogs[3]; assertFalse( DynamicContentFinder.areCatalogsEqual( datasource, catalog0, datasource, catalog0Updated)); assertTrue( DynamicContentFinder.areCatalogsEqual( datasource, catalog0, datasource, catalog0)); assertFalse( DynamicContentFinder.areCatalogsEqual( datasource, catalog1, datasource, catalog2)); } private static DataSourcesConfig.DataSources getDataSources( String... catalogs) throws XOMException { return getDataSources(Olap4jUtil.mapOf(DATASOURCE_1_NAME, catalogs)); } private static String getDataSourceContent(String... catalogs) { return getDataSourceString( Olap4jUtil.mapOf(DATASOURCE_1_NAME, catalogs)); } private static DataSourcesConfig.DataSources getDataSources( Map dsCatalog) throws XOMException { final String str = getDataSourceString(dsCatalog); final Parser xmlParser = XOMUtil.createDefaultParser(); final DOMWrapper def = xmlParser.parse(str); return new DataSourcesConfig.DataSources(def); } private static String getDataSourceString(Map dsCatalog) { StringBuilder ds = new StringBuilder(); ds.append(""); ds.append(""); for (Map.Entry entry : dsCatalog.entrySet()) { final String dsName = entry.getKey(); ds.append(" "); ds.append(" ") .append(dsName) .append(""); ds.append( " " + "DATASOURCE_DESCRIPTION" + ""); ds.append(" http://localhost:8080/mondrian/xmla"); ds.append(" Provider=mondrian;") .append("Jdbc=jdbc:oracle:thin:foodmart/foodmart@") .append("//marmalade.hydromatic.net:1521/XE;") .append("JdbcUser=foodmart;JdbcPassword=foodmart;") .append("JdbcDrivers=oracle.jdbc.OracleDriver;") .append("Catalog=/WEB-INF/queries/FoodMart.xml") .append(""); ds.append(" Mondrian"); ds.append(" MDP"); ds.append(" ") .append("Unauthenticated") .append(""); ds.append(" "); final String[] catalogs = entry.getValue(); for (String catalog : catalogs) { ds.append(catalog); } ds.append(" "); ds.append(""); } ds.append(""); return ds.toString(); } public void testReloadDataSources() throws Exception { DataSourcesConfig.DataSources ds1 = getDataSources(CATALOG_0_DEFINITION, CATALOG_1_DEFINITION); DataSourcesConfig.DataSources ds2 = getDataSources(CATALOG_1_DEFINITION, CATALOG_2_DEFINITION); File dsFile = File.createTempFile( getClass().getName() + "-datasources", ".xml"); dsFile.deleteOnExit(); OutputStream out = new FileOutputStream(dsFile); out.write(ds1.toXML().getBytes()); out.close(); final MockDynamicContentFinder finder = new MockDynamicContentFinder( dsFile.toURL().toString()); out = new FileOutputStream(dsFile); out.write(ds2.toXML().getBytes()); out.close(); finder.reloadDataSources(); assertTrue( finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_1_NAME)); assertTrue( finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_2_NAME)); assertFalse( finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_0_NAME)); out = new FileOutputStream(dsFile); out.write(ds1.toXML().getBytes()); out.flush(); finder.reloadDataSources(); assertTrue( finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_0_NAME)); assertTrue( finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_1_NAME)); assertFalse( finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_2_NAME)); finder.shutdown(); } public void testAutoReloadDataSources() throws Exception { DataSourcesConfig.DataSources ds1 = getDataSources(CATALOG_0_DEFINITION, CATALOG_1_DEFINITION); DataSourcesConfig.DataSources ds2 = getDataSources(CATALOG_1_DEFINITION, CATALOG_2_DEFINITION); File dsFile = File.createTempFile( getClass().getName() + "-datasources", ".xml"); dsFile.deleteOnExit(); OutputStream out = new FileOutputStream(dsFile); out.write(ds1.toXML().getBytes()); out.flush(); final MockDynamicContentFinder finder = new MockDynamicContentFinder( dsFile.toURL().toString()); out = new FileOutputStream(dsFile); out.write(ds2.toXML().getBytes()); out.close(); finder.reloadDataSources(); assertTrue( finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_1_NAME)); assertTrue( finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_2_NAME)); assertFalse( finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_0_NAME)); out = new FileOutputStream(dsFile); out.write(ds1.toXML().getBytes()); out.close(); // Wait for it to auto-reload. Thread.sleep( MondrianProperties.instance().XmlaSchemaRefreshInterval.get() + 1000); assertTrue( finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_0_NAME)); assertTrue( finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_1_NAME)); assertFalse( finder.containsCatalog(DATASOURCE_1_NAME, CATALOG_2_NAME)); finder.shutdown(); } private static class MockDynamicContentFinder extends DynamicContentFinder { private List flushCatalogList = new ArrayList(); public MockDynamicContentFinder(String dataSources) { super(dataSources); } protected void flushCatalog(String catalogName) { flushCatalogList.add(catalogName); } public List flushCatalogList() { return flushCatalogList; } public boolean containsCatalog( String datasourceName, String catalogName) { return locateCatalog(datasourceName, catalogName) != null; } public synchronized DataSourcesConfig.Catalog locateCatalog( String datasourceName, String catalogName) { for (DataSourcesConfig.DataSource ds : dataSources.dataSources) { if (ds.name.equals(datasourceName)) { for (DataSourcesConfig.Catalog catalog : ds.catalogs.catalogs) { if (catalog.name.equals(catalogName)) { return catalog; } } } } return null; } } } // End DynamicDatasourceXmlaServletTest.java mondrian-3.4.1/testsrc/main/mondrian/xmla/XmlaExcel2007Test.java0000644000175000017500000001433311735330606024313 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2010 Pentaho and others // All Rights Reserved. */ package mondrian.xmla; import mondrian.olap.Util; import mondrian.spi.Dialect; import mondrian.test.DiffRepository; import mondrian.test.TestContext; /** * Test suite for compatibility of Mondrian XMLA with Excel 2007. * * @author Richard M. Emberson */ public class XmlaExcel2007Test extends XmlaBaseTestCase { protected String getSessionId(Action action) { return getSessionId("XmlaExcel2000Test", action); } static class Callback extends XmlaRequestCallbackImpl { Callback() { super("XmlaExcel2000Test"); } } public XmlaExcel2007Test() { } public XmlaExcel2007Test(String name) { super(name); } protected Class getServletCallbackClass() { return Callback.class; } protected DiffRepository getDiffRepos() { return DiffRepository.lookup(XmlaExcel2007Test.class); } protected String filter( String testCaseName, String filename, String content) { if ((testCaseName.startsWith("testMemberPropertiesAndSlicer") || testCaseName.equals("testBugMondrian761")) && filename.equals("response")) { Dialect dialect = TestContext.instance().getDialect(); switch (dialect.getDatabaseProduct()) { case MYSQL: content = foo(content, "Has_x0020_coffee_x0020_bar", "1", "true"); content = foo(content, "Has_x0020_coffee_x0020_bar", "0", "false"); break; case ACCESS: content = foo(content, "Has_x0020_coffee_x0020_bar", "1", "true"); content = foo(content, "Has_x0020_coffee_x0020_bar", "0", "false"); content = foo(content, "Store_x0020_Sqft", "23688", "23688.0"); content = foo( content, "Grocery_x0020_Sqft", "15337", "15336.753169821777"); content = foo( content, "Frozen_x0020_Sqft", "5011", "5010.748098106934"); content = foo( content, "Meat_x0020_Sqft", "3340", "3340.4987320712894"); content = foo(content, "Store_x0020_Sqft", "23598", "23598.0"); content = foo( content, "Grocery_x0020_Sqft", "14210", "14210.378025591175"); content = foo( content, "Frozen_x0020_Sqft", "5633", "5632.5731846452945"); content = foo( content, "Meat_x0020_Sqft", "3755", "3755.0487897635303"); break; } } return content; } private String foo(String content, String tag, String from, String to) { String start = "<" + tag + ">"; String end = ""; final String s = Util.replace( content, start + from + end, start + to + end); assert !s.contains(start + from + end); return s; } /** *

Testcase for * bug MONDRIAN-679, "VisualTotals gives ClassCastException when called via * XMLA". */ public void test01() { helperTest(false); } /** * Test that checks that (a) member properties are in correct format for * Excel 2007, (b) the slicer axis is in the correct format for Excel 2007. */ public void testMemberPropertiesAndSlicer() { helperTestExpect(false); } /** * Test that executes MDSCHEMA_PROPERTIES with * {@link org.olap4j.metadata.Property.TypeFlag#MEMBER}. */ public void testMdschemaPropertiesMember() { helperTest(false); } /** * Test that executes MDSCHEMA_PROPERTIES with * {@link org.olap4j.metadata.Property.TypeFlag#CELL}. * * @throws Exception on error */ public void testMdschemaPropertiesCell() { helperTest(false); } /** * Tests that mondrian can correctly answer the extra queries generated by * Excel 2007 in bug * MONDRIAN-726, "Change 13509 is not Excel 2007 compatible". */ public void testUniqueName() { assertQueryReturns( "WITH MEMBER [Store].[XL_PT0] AS 'strtomember(\"[Store].[USA].[CA]\").UniqueName' SELECT {[Store].[XL_PT0]} ON 0 FROM \n" + "[HR] CELL PROPERTIES VALUE ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[XL_PT0]}\n" + "Row #0: [Store].[USA].[CA]\n"); assertQueryReturns( "WITH MEMBER [Store].[XL_PT0] AS 'strtomember(\"[Store].[All Stores].[USA].[CA]\").UniqueName' SELECT {[Store].[XL_PT0]} ON 0 FROM \n" + "[HR] CELL PROPERTIES VALUE ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[XL_PT0]}\n" + "Row #0: [Store].[USA].[CA]\n"); } /** * Tests that executed MDX query with CELL PROPERTIES included; bug * MONDRIAN-708, * "After change 13351 all Excel pivots fail to update. CellInfo element in * XMLA response is wrong". * *

CellInfo element should always contain all requested cell properties. * Cell itself can contain fewer properties than requested. * *

Currently most properties are not implemented or not defined. * If they get implemented then test needs to be changed. */ public void testCellInfo() { helperTest(false); } /** *

Testcase for * bug MONDRIAN-761, "VisualTotalMember cannot be cast to * RolapCubeMember". */ public void testBugMondrian761() { helperTest(false); } } // End XmlaExcel2007Test.java mondrian-3.4.1/testsrc/main/mondrian/xmla/XmlaExcel2000Test.java0000644000175000017500000000633111735330606024303 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho and others // All Rights Reserved. // // jhyde, 29 March, 2002 */ package mondrian.xmla; import mondrian.test.DiffRepository; /** * Test suite for compatibility of Mondrian XMLA with Excel 2000. * Simba (the maker of the O2X bridge) supplied captured request/response * soap messages between Excel 2000 and SQL Server. These form the * basis of the output files in the excel_2000 directory. * * @author Richard M. Emberson */ public class XmlaExcel2000Test extends XmlaBaseTestCase { public XmlaExcel2000Test() { super(); } public XmlaExcel2000Test(String name) { super(name); } protected DiffRepository getDiffRepos() { return DiffRepository.lookup(XmlaExcel2000Test.class); } protected Class getServletCallbackClass() { return Callback.class; } static class Callback extends XmlaRequestCallbackImpl { Callback() { super("XmlaExcel2000Test"); } } public void test01() { helperTest(false); } // BeginSession public void test02() { helperTest(false); } public void test03() { helperTest(true); } public void test04() { helperTest(true); } public void test05() { helperTest(true); } public void test06() { helperTest(true); } // BeginSession public void test07() { helperTest(false); } public void test08() { helperTest(true); } public void test09() { helperTest(true); } public void test10() { helperTest(true); } public void test11() { helperTest(true); } public void test12() { helperTest(true); } public void testMdschemaMeasures() { helperTest(true); } public void testMdschemaMeasuresEmitInvisible() { helperTest(true); } public void test14() { helperTest(true); } public void test15() { helperTest(true); } public void test16() { helperTest(true); } public void test17() { helperTest(true); } public void test18() { helperTest(true); } public void testExpect01() { helperTestExpect(false); } public void testExpect02() { helperTestExpect(false); } public void testExpect03() { helperTestExpect(true); } public void testExpect04() { helperTestExpect(true); } public void testExpect05() { helperTestExpect(true); } public void testExpect06() { helperTestExpect(true); } ///////////////////////////////////////////////////////////////////////// // helpers ///////////////////////////////////////////////////////////////////////// protected String getSessionId(Action action) { return getSessionId("XmlaExcel2000Test", action); } } // End XmlaExcel2000Test.java mondrian-3.4.1/testsrc/main/mondrian/xmla/XmlaErrorTest.java0000644000175000017500000010150511735330606024031 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2010 Pentaho and others // All Rights Reserved. */ package mondrian.xmla; import mondrian.olap.Util; import mondrian.rolap.RolapConnectionProperties; import mondrian.test.DiffRepository; import mondrian.test.TestContext; import mondrian.tui.*; import mondrian.util.Base64; import org.w3c.dom.*; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.*; import java.util.Enumeration; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Test of the XMLA Fault generation - errors occur/are-detected in * in Mondrian XMLA and a SOAP Fault is returned. * *

There is a set of tests dealing with Authorization and HTTP Header * Expect and Continue dialog. These are normally done at the webserver * level and can be removed here if desired. (I wrote them before I * realized that Mondrian XMLA would not handle any Authorization issues * if it were in a webserver.) * * @author Richard M. Emberson */ @SuppressWarnings({"ThrowableInstanceNeverThrown"}) public class XmlaErrorTest extends XmlaBaseTestCase implements XmlaConstants { static boolean doAuthorization = false; static String user = null; static String password = null; static class Callback implements XmlaRequestCallback { static String MY_SESSION_ID = "my_session_id"; Callback() { } public void init(ServletConfig servletConfig) throws ServletException { } public boolean processHttpHeader( HttpServletRequest request, HttpServletResponse response, Map context) throws Exception { // look for authorization // Authorization: Basic ZWRnZTphYmNkMTIzNC4= // tjones:abcd1234$$. if (DEBUG) { System.out.println("doAuthorization=" + doAuthorization); System.out.println("AUTH_TYPE=" + request.getAuthType()); } if (doAuthorization) { Enumeration values = request.getHeaders(AUTHORIZATION); if ((values == null) || (! values.hasMoreElements())) { throw XmlaRequestCallback.Helper.authorizationException( new Exception("Authorization: no header value")); } String authScheme = (String) values.nextElement(); if (DEBUG) { System.out.println("authScheme=" + authScheme); } if (! values.hasMoreElements()) { throw XmlaRequestCallback.Helper.authorizationException( new Exception("Authorization: too few header value")); } String encoded = (String) values.nextElement(); if (DEBUG) { System.out.println("encoded=" + encoded); } byte[] bytes = Base64.decode(encoded); String userPass = new String(bytes); if (DEBUG) { System.out.println("userPass=" + userPass); } if (! authScheme.equals(HttpServletRequest.BASIC_AUTH)) { throw XmlaRequestCallback.Helper.authorizationException( new Exception( "Authorization: bad schema: " + authScheme)); } int index = userPass.indexOf(':'); if (index == -1) { throw XmlaRequestCallback.Helper.authorizationException( new Exception( "Authorization: badly formed userPass in encoding: " + encoded)); } String userid = userPass.substring(0, index); String password = userPass.substring(index + 1, userPass.length()); if (DEBUG) { System.out.println("userid=" + userid); System.out.println("password=" + password); } if (!Util.equals(userid, XmlaErrorTest.user)) { throw XmlaRequestCallback.Helper.authorizationException( new Exception( "Authorization: bad userid: " + userid + " should be: " + XmlaErrorTest.user)); } if (!Util.equals(password, XmlaErrorTest.password)) { throw XmlaRequestCallback.Helper.authorizationException( new Exception( "Authorization: bad password: " + password + " should be: " + XmlaErrorTest.password)); } } String expect = request.getHeader(EXPECT); if ((expect != null) && expect.equalsIgnoreCase(EXPECT_100_CONTINUE)) { XmlaRequestCallback.Helper.generatedExpectResponse( request, response, context); return false; } else { return true; } } public void preAction( HttpServletRequest request, Element[] requestSoapParts, Map context) throws Exception { context.put( MY_SESSION_ID, getSessionId("XmlaExcelXPTest", Action.CREATE)); } public String generateSessionId(Map context) { return (String) context.get(MY_SESSION_ID); } public void postAction( HttpServletRequest request, HttpServletResponse response, byte[][] responseSoapParts, Map context) throws Exception { } } static Element[] getChildElements(Node node) { List list = new ArrayList(); NodeList nlist = node.getChildNodes(); int len = nlist.getLength(); for (int i = 0; i < len; i++) { Node child = nlist.item(i); if (child instanceof Element) { list.add((Element) child); } } return list.toArray(new Element[list.size()]); } static CharacterData getCharacterData(Node node) { NodeList nlist = node.getChildNodes(); int len = nlist.getLength(); for (int i = 0; i < len; i++) { Node child = nlist.item(i); if (child instanceof CharacterData) { return (CharacterData) child; } } return null; } static String getNodeContent(Node n) { CharacterData cd = getCharacterData(n); return (cd != null) ? cd.getData() : null; } private static class Fault { final String faultCode; final String faultString; final String faultActor; final String errorNS; final String errorCode; final String errorDesc; Fault( String faultCode, String faultString, String faultActor, String errorNS, String errorCode, String errorDesc) { this.faultCode = faultCode; this.faultString = faultString; this.faultActor = faultActor; this.errorNS = errorNS; this.errorCode = errorCode; this.errorDesc = errorDesc; } Fault(Node[] faultNodes) throws Exception { if (faultNodes.length < 3 || faultNodes.length > 4) { throw new Exception( "SOAP Fault node has " + faultNodes.length + " children"); } // fault code element Node node = faultNodes[0]; faultCode = getNodeContent(node); // fault string element node = faultNodes[1]; faultString = getNodeContent(node); // actor element node = faultNodes[2]; faultActor = getNodeContent(node); if (faultNodes.length > 3) { // detail element node = faultNodes[3]; faultNodes = getChildElements(node); if (faultNodes.length != 1) { throw new Exception( "SOAP Fault detail node has " + faultNodes.length + " children"); } // error element node = faultNodes[0]; errorNS = node.getNamespaceURI(); faultNodes = getChildElements(node); if (faultNodes.length != 2) { throw new Exception( "SOAP Fault detail error node has " + faultNodes.length + " children"); } // error code element node = faultNodes[0]; errorCode = getNodeContent(node); // error desc element node = faultNodes[1]; errorDesc = getNodeContent(node); } else { errorNS = errorCode = errorDesc = null; } } String getFaultCode() { return faultCode; } String getFaultString() { return faultString; } String getFaultActor() { return faultActor; } boolean hasDetailError() { return (errorCode != null); } String getDetailErrorCode() { return errorCode; } String getDetailErrorDesc() { return errorDesc; } public String toString() { return "faultCode=" + faultCode + ", faultString=" + faultString + ", faultActor=" + faultActor + ", errorNS=" + errorNS + ", errorCode=" + errorCode + ", errorDesc=" + errorDesc; } void checkSame(Fault expectedFault) throws Exception { if (!Util.equals(this.faultCode, expectedFault.faultCode)) { notSame("faultcode", expectedFault.faultCode, this.faultCode); } if (!Util.equals(this.faultString, expectedFault.faultString)) { notSame( "faultstring", expectedFault.faultString, this.faultString); } if (!Util.equals(this.faultActor, expectedFault.faultActor)) { notSame( "faultactor", expectedFault.faultActor, this.faultActor); } if (!Util.equals(this.errorNS, expectedFault.errorNS)) { throw new Exception( "For error element namespace " + " Expected " + expectedFault.errorNS + " but Got " + this.errorNS); } if (!Util.equals(this.errorCode, expectedFault.errorCode)) { notSame("error.code", expectedFault.errorCode, this.errorCode); } } private void notSame(String elementName, String expected, String got) throws Exception { throw new Exception( "For element " + elementName + " expected [" + expected + "] but got [" + got + "]"); } } private static PrintStream systemErr; public XmlaErrorTest() { } public XmlaErrorTest(String name) { super(name); } protected void setUp() throws Exception { super.setUp(); // NOTE jvs 27-Feb-2007: Since this test produces errors // intentionally, squelch the ones that SAX produces on stderr systemErr = System.err; System.setErr(new PrintStream(new ByteArrayOutputStream())); } protected void tearDown() throws Exception { // Restore stderr System.setErr(systemErr); super.tearDown(); } protected DiffRepository getDiffRepos() { return DiffRepository.lookup(XmlaErrorTest.class); } protected Class getServletCallbackClass() { return Callback.class; } protected Map getCatalogNameUrls(TestContext testContext) { if (catalogNameUrls == null) { String connectString = testContext.getConnectString(); Util.PropertyList connectProperties = Util.parseConnectString(connectString); String catalog = connectProperties.get( RolapConnectionProperties.Catalog.name()); catalogNameUrls = new TreeMap(); catalogNameUrls.put("FoodMart", catalog); } return catalogNameUrls; } ///////////////////////////////////////////////////////////////////////// // tests ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// // bad XML ///////////////////////////////////////////////////////////////////////// // junk rather than xml public void testJunk() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, USM_DOM_PARSE_CODE), USM_DOM_PARSE_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, USM_DOM_PARSE_CODE, null); doTest(expectedFault); } // bad soap envolope element tag public void testBadXml01() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, USM_DOM_PARSE_CODE), USM_DOM_PARSE_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, USM_DOM_PARSE_CODE, null); doTest(expectedFault); } // bad soap namespace public void testBadXml02() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, USM_DOM_PARSE_CODE), USM_DOM_PARSE_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, USM_DOM_PARSE_CODE, null); doTest(expectedFault); } ///////////////////////////////////////////////////////////////////////// // bad action ///////////////////////////////////////////////////////////////////////// public void testBadAction01() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_BAD_SOAP_BODY_CODE), HSB_BAD_SOAP_BODY_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_BAD_SOAP_BODY_CODE, null); doTest(expectedFault); } public void testBadAction02() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_BAD_SOAP_BODY_CODE), HSB_BAD_SOAP_BODY_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_BAD_SOAP_BODY_CODE, null); doTest(expectedFault); } public void testBadAction03() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_BAD_SOAP_BODY_CODE), HSB_BAD_SOAP_BODY_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_BAD_SOAP_BODY_CODE, null); doTest(expectedFault); } ///////////////////////////////////////////////////////////////////////// // bad soap structure ///////////////////////////////////////////////////////////////////////// public void testBadSoap01() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, USM_DOM_PARSE_CODE), USM_DOM_PARSE_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, USM_DOM_PARSE_CODE, null); doTest(expectedFault); } public void testBadSoap02() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, USM_DOM_PARSE_CODE), USM_DOM_PARSE_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, USM_DOM_PARSE_CODE, null); doTest(expectedFault); } ///////////////////////////////////////////////////////////////////////// // authorization ///////////////////////////////////////////////////////////////////////// // no authorization field in header public void testAuth01() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, CHH_AUTHORIZATION_CODE), CHH_AUTHORIZATION_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, CHH_AUTHORIZATION_CODE, null); doAuthorization = true; try { doTest(expectedFault); } finally { doAuthorization = false; } } // the user/password is not base64 encode and no ':' character public void testAuth02() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, CHH_AUTHORIZATION_CODE), CHH_AUTHORIZATION_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, CHH_AUTHORIZATION_CODE, null); doAuthorization = true; String requestText = fileToString("request"); byte[] reqBytes = requestText.getBytes(); MockHttpServletRequest req = new MockHttpServletRequest(reqBytes); req.setMethod("POST"); req.setContentType("text/xml"); req.setAuthType(HttpServletRequest.BASIC_AUTH); req.setHeader( XmlaRequestCallback.AUTHORIZATION, HttpServletRequest.BASIC_AUTH); req.setHeader(XmlaRequestCallback.AUTHORIZATION, "FOOBAR"); try { doTest(req, expectedFault); } finally { doAuthorization = false; } } // this should work public void testAuth03() throws Exception { Fault expectedFault = null; doAuthorization = true; String requestText = fileToString("request"); byte[] reqBytes = requestText.getBytes(); MockHttpServletRequest req = new MockHttpServletRequest(reqBytes); req.setMethod("POST"); req.setContentType("text/xml"); req.setAuthType(HttpServletRequest.BASIC_AUTH); req.setHeader( XmlaRequestCallback.AUTHORIZATION, HttpServletRequest.BASIC_AUTH); String user = "MY_USER"; String password = "MY_PASSWORD"; XmlaErrorTest.user = user; XmlaErrorTest.password = password; String credential = user + ':' + password; String encoded = Base64.encodeBytes(credential.getBytes()); req.setHeader(XmlaRequestCallback.AUTHORIZATION, encoded); try { doTest(req, expectedFault); req.setHeader( XmlaRequestCallback.EXPECT, XmlaRequestCallback.EXPECT_100_CONTINUE); if (DEBUG) { System.out.println("DO IT AGAIN"); } doTest(req, expectedFault); } finally { XmlaErrorTest.doAuthorization = false; XmlaErrorTest.user = null; XmlaErrorTest.password = null; } } // fail: bad user name public void testAuth04() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, CHH_AUTHORIZATION_CODE), CHH_AUTHORIZATION_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, CHH_AUTHORIZATION_CODE, null); doAuthorization = true; String requestText = fileToString("request"); byte[] reqBytes = requestText.getBytes(); MockHttpServletRequest req = new MockHttpServletRequest(reqBytes); req.setMethod("POST"); req.setContentType("text/xml"); req.setAuthType(HttpServletRequest.BASIC_AUTH); req.setHeader( XmlaRequestCallback.AUTHORIZATION, HttpServletRequest.BASIC_AUTH); String user = "MY_USER"; String password = "MY_PASSWORD"; XmlaErrorTest.user = user + "FOO"; XmlaErrorTest.password = password; String credential = user + ':' + password; String encoded = Base64.encodeBytes(credential.getBytes()); req.setHeader(XmlaRequestCallback.AUTHORIZATION, encoded); try { doTest(req, expectedFault); } finally { XmlaErrorTest.doAuthorization = false; XmlaErrorTest.user = null; XmlaErrorTest.password = null; } } // fail: bad password public void testAuth05() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, CHH_AUTHORIZATION_CODE), CHH_AUTHORIZATION_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, CHH_AUTHORIZATION_CODE, null); doAuthorization = true; String requestText = fileToString("request"); byte[] reqBytes = requestText.getBytes(); MockHttpServletRequest req = new MockHttpServletRequest(reqBytes); req.setMethod("POST"); req.setContentType("text/xml"); req.setAuthType(HttpServletRequest.BASIC_AUTH); req.setHeader( XmlaRequestCallback.AUTHORIZATION, HttpServletRequest.BASIC_AUTH); String user = "MY_USER"; String password = "MY_PASSWORD"; XmlaErrorTest.user = user; XmlaErrorTest.password = password + "FOO"; String credential = user + ':' + password; String encoded = Base64.encodeBytes(credential.getBytes()); req.setHeader(XmlaRequestCallback.AUTHORIZATION, encoded); try { doTest(req, expectedFault); } finally { XmlaErrorTest.doAuthorization = false; XmlaErrorTest.user = null; XmlaErrorTest.password = null; } } // bad header public void testBadHeader01() throws Exception { // remember, errors in headers do not have detail sections Fault expectedFault = new Fault( XmlaException.formatFaultCode( MUST_UNDERSTAND_FAULT_FC, HSH_MUST_UNDERSTAND_CODE), HSH_MUST_UNDERSTAND_FAULT_FS, FAULT_ACTOR, null, null, null); doTest(expectedFault); } // bad body public void testBadBody01() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_BAD_SOAP_BODY_CODE), HSB_BAD_SOAP_BODY_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_BAD_SOAP_BODY_CODE, null); doTest(expectedFault); } public void testBadBody02() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_BAD_SOAP_BODY_CODE), HSB_BAD_SOAP_BODY_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_BAD_SOAP_BODY_CODE, null); doTest(expectedFault); } public void testBadBody03() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_BAD_SOAP_BODY_CODE), HSB_BAD_SOAP_BODY_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_BAD_SOAP_BODY_CODE, null); doTest(expectedFault); } public void testBadBody04() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_BAD_REQUEST_TYPE_CODE), HSB_BAD_REQUEST_TYPE_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_BAD_REQUEST_TYPE_CODE, null); doTest(expectedFault); } public void testBadBody05() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_BAD_RESTRICTIONS_CODE), HSB_BAD_RESTRICTIONS_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_BAD_RESTRICTIONS_CODE, null); doTest(expectedFault); } public void testBadBody06() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_BAD_PROPERTIES_CODE), HSB_BAD_PROPERTIES_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_BAD_PROPERTIES_CODE, null); doTest(expectedFault); } public void testBadBody07() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_BAD_COMMAND_CODE), HSB_BAD_COMMAND_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_BAD_COMMAND_CODE, null); doTest(expectedFault); } public void testBadBody08() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_BAD_PROPERTIES_CODE), HSB_BAD_PROPERTIES_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_BAD_PROPERTIES_CODE, null); doTest(expectedFault); } public void testBadBody09() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_BAD_RESTRICTION_LIST_CODE), HSB_BAD_RESTRICTION_LIST_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_BAD_RESTRICTION_LIST_CODE, null); doTest(expectedFault); } public void testBadBody10() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_BAD_PROPERTIES_LIST_CODE), HSB_BAD_PROPERTIES_LIST_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_BAD_PROPERTIES_LIST_CODE, null); doTest(expectedFault); } public void testBadBody11() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_BAD_PROPERTIES_LIST_CODE), HSB_BAD_PROPERTIES_LIST_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_BAD_PROPERTIES_LIST_CODE, null); doTest(expectedFault); } public void testBadBody12() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_BAD_STATEMENT_CODE), HSB_BAD_STATEMENT_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_BAD_STATEMENT_CODE, null); doTest(expectedFault); } public void testBadBody13() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_DRILL_THROUGH_FORMAT_CODE), HSB_DRILL_THROUGH_FORMAT_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_DRILL_THROUGH_FORMAT_CODE, null); doTest(expectedFault); } public void testBadBody14() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_DRILL_THROUGH_FORMAT_CODE), HSB_DRILL_THROUGH_FORMAT_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_DRILL_THROUGH_FORMAT_CODE, null); doTest(expectedFault); } public void testBadBody15() throws Exception { Fault expectedFault = new Fault( XmlaException.formatFaultCode( CLIENT_FAULT_FC, HSB_DRILL_THROUGH_FORMAT_CODE), HSB_DRILL_THROUGH_FORMAT_FAULT_FS, FAULT_ACTOR, MONDRIAN_NAMESPACE, HSB_DRILL_THROUGH_FORMAT_CODE, null); doTest(expectedFault); } ///////////////////////////////////////////////////////////////////////// // helper ///////////////////////////////////////////////////////////////////////// protected void doTest( MockHttpServletRequest req, Fault expectedFault) throws Exception { MockHttpServletResponse res = new MockHttpServletResponse(); res.setCharacterEncoding("UTF-8"); Servlet servlet = getServlet(getTestContext()); servlet.service(req, res); int statusCode = res.getStatusCode(); if (statusCode == HttpServletResponse.SC_OK) { byte[] bytes = res.toByteArray(); processResults(bytes, expectedFault); } else if (statusCode == HttpServletResponse.SC_UNAUTHORIZED) { byte[] bytes = res.toByteArray(); processResults(bytes, expectedFault); } else if (statusCode == HttpServletResponse.SC_CONTINUE) { // remove the Expect header from request and try again if (DEBUG) { System.out.println("Got CONTINUE"); } req.clearHeader(XmlaRequestCallback.EXPECT); req.clearHeader(XmlaRequestCallback.AUTHORIZATION); doAuthorization = false; servlet.service(req, res); statusCode = res.getStatusCode(); if (statusCode == HttpServletResponse.SC_OK) { byte[] bytes = res.toByteArray(); processResults(bytes, expectedFault); } else { fail("Bad status code: " + statusCode); } } else { fail("Bad status code: " + statusCode); } } protected void doTest( Fault expectedFault) throws Exception { String requestText = fileToString("request"); Servlet servlet = getServlet(getTestContext()); // do SOAP-XMLA byte[] bytes = XmlaSupport.processSoapXmla(requestText, servlet); processResults(bytes, expectedFault); } protected void processResults(byte[] results, Fault expectedFault) throws Exception { if (DEBUG) { String response = new String(results); System.out.println("response=" + response); } Node[] fnodes = XmlaSupport.extractFaultNodesFromSoap(results); if ((fnodes == null) || (fnodes.length == 0)) { if (expectedFault != null) { // error fail("Failed to get SOAP Fault element in SOAP Body node"); } } if (expectedFault != null) { Fault fault = new Fault(fnodes); if (DEBUG) { System.out.println("fault=" + fault); System.out.println("expectedFault=" + expectedFault); } fault.checkSame(expectedFault); } } protected String getSessionId(Action action) { return getSessionId("XmlaExcelXPTest", action); } } // End XmlaErrorTest.java mondrian-3.4.1/testsrc/main/mondrian/olap4j/0000755000175000017500000000000011735330606020641 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/olap4j/MondrianInprocProxy.java0000644000175000017500000000524311735330606025474 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2008-2011 Pentaho // All Rights Reserved. */ package mondrian.olap4j; import mondrian.olap.Util; import mondrian.tui.XmlaSupport; import org.apache.commons.collections.map.ReferenceMap; import org.olap4j.driver.xmla.XmlaOlap4jServerInfos; import org.olap4j.driver.xmla.proxy.XmlaOlap4jProxy; import java.util.*; import java.util.concurrent.*; /** * Proxy which implements XMLA requests by talking to mondrian * in-process. This is more convenient to debug than an inter-process * request using HTTP. * * @author jhyde */ public class MondrianInprocProxy implements XmlaOlap4jProxy { private final ExecutorService executor = Util.getExecutorService( 1, 1, 1, -1, "MondrianInprocProxy$executor"); private final Map catalogNameUrls; private final String urlString; private final Map servletCache = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.WEAK); /** * Creates and initializes a MondrianInprocProxy. * * @param catalogNameUrls Collection of catalog names and the URL where * their catalog is to be found. For testing purposes, this should contain * a catalog called "FoodMart". * * @param urlString JDBC connect string; must begin with "jdbc:mondrian:" */ public MondrianInprocProxy( Map catalogNameUrls, String urlString) { this.catalogNameUrls = catalogNameUrls; if (!urlString.startsWith("jdbc:mondrian:")) { throw new IllegalArgumentException(); } this.urlString = urlString.substring("jdbc:mondrian:".length()); } public byte[] get( XmlaOlap4jServerInfos infos, String request) { try { return XmlaSupport.processSoapXmla( request, urlString, catalogNameUrls, null, null, servletCache); } catch (Exception e) { throw new RuntimeException( "Error while reading '" + infos.getUrl() + "'", e); } } public Future submit( final XmlaOlap4jServerInfos infos, final String request) { return this.executor.submit( new Callable() { public byte[] call() throws Exception { return get(infos, request); } }); } public String getEncodingCharsetName() { return "UTF-8"; } } // End MondrianInprocProxy.java mondrian-3.4.1/testsrc/main/mondrian/test/0000755000175000017500000000000011742752424020433 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/test/BasicQueryTest.java0000644000175000017500000116476511735330606024225 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho // All Rights Reserved. // // jhyde, Feb 14, 2003 */ package mondrian.test; import mondrian.calc.ResultStyle; import mondrian.olap.Axis; import mondrian.olap.Cell; import mondrian.olap.Connection; import mondrian.olap.*; import mondrian.olap.Position; import mondrian.olap.type.NumericType; import mondrian.olap.type.Type; import mondrian.spi.*; import mondrian.util.Bug; import junit.framework.Assert; import org.olap4j.*; import org.olap4j.layout.RectangularCellSetFormatter; import java.io.PrintWriter; import java.io.StringWriter; import java.sql.*; import java.util.*; import java.util.concurrent.*; import java.util.regex.Pattern; /** * BasicQueryTest is a test case which tests simple queries * against the FoodMart database. * * @author jhyde * @since Feb 14, 2003 */ public class BasicQueryTest extends FoodMartTestCase { static final String EmptyResult = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "Axis #2:\n"; private static final String timeWeekly = TestContext.hierarchyName("Time", "Weekly"); private MondrianProperties props = MondrianProperties.instance(); public BasicQueryTest() { super(); } public BasicQueryTest(String name) { super(name); } private static final QueryAndResult[] sampleQueries = { // 0 new QueryAndResult( "select {[Measures].[Unit Sales]} on columns\n" + " from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 266,773\n"), // 1 new QueryAndResult( "select\n" + " {[Measures].[Unit Sales]} on columns,\n" + " order(except([Promotion Media].[Media Type].members,{[Promotion Media].[Media Type].[No Media]}),[Measures].[Unit Sales],DESC) on rows\n" + "from Sales ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Promotion Media].[Daily Paper, Radio, TV]}\n" + "{[Promotion Media].[Daily Paper]}\n" + "{[Promotion Media].[Product Attachment]}\n" + "{[Promotion Media].[Daily Paper, Radio]}\n" + "{[Promotion Media].[Cash Register Handout]}\n" + "{[Promotion Media].[Sunday Paper, Radio]}\n" + "{[Promotion Media].[Street Handout]}\n" + "{[Promotion Media].[Sunday Paper]}\n" + "{[Promotion Media].[Bulk Mail]}\n" + "{[Promotion Media].[In-Store Coupon]}\n" + "{[Promotion Media].[TV]}\n" + "{[Promotion Media].[Sunday Paper, Radio, TV]}\n" + "{[Promotion Media].[Radio]}\n" + "Row #0: 9,513\n" + "Row #1: 7,738\n" + "Row #2: 7,544\n" + "Row #3: 6,891\n" + "Row #4: 6,697\n" + "Row #5: 5,945\n" + "Row #6: 5,753\n" + "Row #7: 4,339\n" + "Row #8: 4,320\n" + "Row #9: 3,798\n" + "Row #10: 3,607\n" + "Row #11: 2,726\n" + "Row #12: 2,454\n"), // 2 new QueryAndResult( "select\n" + " { [Measures].[Units Shipped], [Measures].[Units Ordered] } on columns,\n" + " NON EMPTY [Store].[Store Name].members on rows\n" + "from Warehouse", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Units Shipped]}\n" + "{[Measures].[Units Ordered]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Store].[USA].[WA].[Bellingham].[Store 2]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "{[Store].[USA].[WA].[Walla Walla].[Store 22]}\n" + "{[Store].[USA].[WA].[Yakima].[Store 23]}\n" + "Row #0: 10759.0\n" + "Row #0: 11699.0\n" + "Row #1: 24587.0\n" + "Row #1: 26463.0\n" + "Row #2: 23835.0\n" + "Row #2: 26270.0\n" + "Row #3: 1696.0\n" + "Row #3: 1875.0\n" + "Row #4: 8515.0\n" + "Row #4: 9109.0\n" + "Row #5: 32393.0\n" + "Row #5: 35797.0\n" + "Row #6: 2348.0\n" + "Row #6: 2454.0\n" + "Row #7: 22734.0\n" + "Row #7: 24610.0\n" + "Row #8: 24110.0\n" + "Row #8: 26703.0\n" + "Row #9: 11889.0\n" + "Row #9: 12828.0\n" + "Row #10: 32411.0\n" + "Row #10: 35930.0\n" + "Row #11: 1860.0\n" + "Row #11: 2074.0\n" + "Row #12: 10589.0\n" + "Row #12: 11426.0\n"), // 3 new QueryAndResult( "with member [Measures].[Store Sales Last Period] as " + " '([Measures].[Store Sales], Time.[Time].PrevMember)',\n" + " format='#,###.00'\n" + "select\n" + " {[Measures].[Store Sales Last Period]} on columns,\n" + " {TopCount([Product].[Product Department].members,5, [Measures].[Store Sales Last Period])} on rows\n" + "from Sales\n" + "where ([Time].[1998])", "Axis #0:\n" + "{[Time].[1998]}\n" + "Axis #1:\n" + "{[Measures].[Store Sales Last Period]}\n" + "Axis #2:\n" + "{[Product].[Food].[Produce]}\n" + "{[Product].[Food].[Snack Foods]}\n" + "{[Product].[Non-Consumable].[Household]}\n" + "{[Product].[Food].[Frozen Foods]}\n" + "{[Product].[Food].[Canned Foods]}\n" + "Row #0: 82,248.42\n" + "Row #1: 67,609.82\n" + "Row #2: 60,469.89\n" + "Row #3: 55,207.50\n" + "Row #4: 39,774.34\n"), // 4 new QueryAndResult( "with member [Measures].[Total Store Sales] as 'Sum(YTD(),[Measures].[Store Sales])', format_string='#.00'\n" + "select\n" + " {[Measures].[Total Store Sales]} on columns,\n" + " {TopCount([Product].[Product Department].members,5, [Measures].[Total Store Sales])} on rows\n" + "from Sales\n" + "where ([Time].[1997].[Q2].[4])", "Axis #0:\n" + "{[Time].[1997].[Q2].[4]}\n" + "Axis #1:\n" + "{[Measures].[Total Store Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Produce]}\n" + "{[Product].[Food].[Snack Foods]}\n" + "{[Product].[Non-Consumable].[Household]}\n" + "{[Product].[Food].[Frozen Foods]}\n" + "{[Product].[Food].[Canned Foods]}\n" + "Row #0: 26526.67\n" + "Row #1: 21897.10\n" + "Row #2: 19980.90\n" + "Row #3: 17882.63\n" + "Row #4: 12963.23\n"), // 5 new QueryAndResult( "with member [Measures].[Store Profit Rate] as '([Measures].[Store Sales]-[Measures].[Store Cost])/[Measures].[Store Cost]', format = '#.00%'\n" + "select\n" + " {[Measures].[Store Cost],[Measures].[Store Sales],[Measures].[Store Profit Rate]} on columns,\n" + " Order([Product].[Product Department].members, [Measures].[Store Profit Rate], BDESC) on rows\n" + "from Sales\n" + "where ([Time].[1997])", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Store Profit Rate]}\n" + "Axis #2:\n" + "{[Product].[Food].[Breakfast Foods]}\n" + "{[Product].[Non-Consumable].[Carousel]}\n" + "{[Product].[Food].[Canned Products]}\n" + "{[Product].[Food].[Baking Goods]}\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "{[Product].[Non-Consumable].[Health and Hygiene]}\n" + "{[Product].[Food].[Snack Foods]}\n" + "{[Product].[Food].[Baked Goods]}\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Food].[Frozen Foods]}\n" + "{[Product].[Non-Consumable].[Periodicals]}\n" + "{[Product].[Food].[Produce]}\n" + "{[Product].[Food].[Seafood]}\n" + "{[Product].[Food].[Deli]}\n" + "{[Product].[Food].[Meat]}\n" + "{[Product].[Food].[Canned Foods]}\n" + "{[Product].[Non-Consumable].[Household]}\n" + "{[Product].[Food].[Starchy Foods]}\n" + "{[Product].[Food].[Eggs]}\n" + "{[Product].[Food].[Snacks]}\n" + "{[Product].[Food].[Dairy]}\n" + "{[Product].[Drink].[Dairy]}\n" + "{[Product].[Non-Consumable].[Checkout]}\n" + "Row #0: 2,756.80\n" + "Row #0: 6,941.46\n" + "Row #0: 151.79%\n" + "Row #1: 595.97\n" + "Row #1: 1,500.11\n" + "Row #1: 151.71%\n" + "Row #2: 1,317.13\n" + "Row #2: 3,314.52\n" + "Row #2: 151.65%\n" + "Row #3: 15,370.61\n" + "Row #3: 38,670.41\n" + "Row #3: 151.59%\n" + "Row #4: 5,576.79\n" + "Row #4: 14,029.08\n" + "Row #4: 151.56%\n" + "Row #5: 12,972.99\n" + "Row #5: 32,571.86\n" + "Row #5: 151.07%\n" + "Row #6: 26,963.34\n" + "Row #6: 67,609.82\n" + "Row #6: 150.75%\n" + "Row #7: 6,564.09\n" + "Row #7: 16,455.43\n" + "Row #7: 150.69%\n" + "Row #8: 11,069.53\n" + "Row #8: 27,748.53\n" + "Row #8: 150.67%\n" + "Row #9: 22,030.66\n" + "Row #9: 55,207.50\n" + "Row #9: 150.59%\n" + "Row #10: 3,614.55\n" + "Row #10: 9,056.76\n" + "Row #10: 150.56%\n" + "Row #11: 32,831.33\n" + "Row #11: 82,248.42\n" + "Row #11: 150.52%\n" + "Row #12: 1,520.70\n" + "Row #12: 3,809.14\n" + "Row #12: 150.49%\n" + "Row #13: 10,108.87\n" + "Row #13: 25,318.93\n" + "Row #13: 150.46%\n" + "Row #14: 1,465.42\n" + "Row #14: 3,669.89\n" + "Row #14: 150.43%\n" + "Row #15: 15,894.53\n" + "Row #15: 39,774.34\n" + "Row #15: 150.24%\n" + "Row #16: 24,170.73\n" + "Row #16: 60,469.89\n" + "Row #16: 150.18%\n" + "Row #17: 4,705.91\n" + "Row #17: 11,756.07\n" + "Row #17: 149.82%\n" + "Row #18: 3,684.90\n" + "Row #18: 9,200.76\n" + "Row #18: 149.69%\n" + "Row #19: 5,827.58\n" + "Row #19: 14,550.05\n" + "Row #19: 149.68%\n" + "Row #20: 12,228.85\n" + "Row #20: 30,508.85\n" + "Row #20: 149.48%\n" + "Row #21: 2,830.92\n" + "Row #21: 7,058.60\n" + "Row #21: 149.34%\n" + "Row #22: 1,525.04\n" + "Row #22: 3,767.71\n" + "Row #22: 147.06%\n"), // 6 new QueryAndResult( "with\n" + " member [Product].[All Products].[Drink].[Percent of Alcoholic Drinks] as '[Product].[All Products].[Drink].[Alcoholic Beverages]/[Product].[All Products].[Drink]',\n" + " format_string = '#.00%'\n" + "select\n" + " { [Product].[All Products].[Drink].[Percent of Alcoholic Drinks] } on columns,\n" + " order([Customers].[All Customers].[USA].[WA].Children, [Product].[All Products].[Drink].[Percent of Alcoholic Drinks],BDESC) on rows\n" + "from Sales\n" + "where ([Measures].[Unit Sales])", "Axis #0:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #1:\n" + "{[Product].[Drink].[Percent of Alcoholic Drinks]}\n" + "Axis #2:\n" + "{[Customers].[USA].[WA].[Seattle]}\n" + "{[Customers].[USA].[WA].[Kirkland]}\n" + "{[Customers].[USA].[WA].[Marysville]}\n" + "{[Customers].[USA].[WA].[Anacortes]}\n" + "{[Customers].[USA].[WA].[Olympia]}\n" + "{[Customers].[USA].[WA].[Ballard]}\n" + "{[Customers].[USA].[WA].[Bremerton]}\n" + "{[Customers].[USA].[WA].[Puyallup]}\n" + "{[Customers].[USA].[WA].[Yakima]}\n" + "{[Customers].[USA].[WA].[Tacoma]}\n" + "{[Customers].[USA].[WA].[Everett]}\n" + "{[Customers].[USA].[WA].[Renton]}\n" + "{[Customers].[USA].[WA].[Issaquah]}\n" + "{[Customers].[USA].[WA].[Bellingham]}\n" + "{[Customers].[USA].[WA].[Port Orchard]}\n" + "{[Customers].[USA].[WA].[Redmond]}\n" + "{[Customers].[USA].[WA].[Spokane]}\n" + "{[Customers].[USA].[WA].[Burien]}\n" + "{[Customers].[USA].[WA].[Lynnwood]}\n" + "{[Customers].[USA].[WA].[Walla Walla]}\n" + "{[Customers].[USA].[WA].[Edmonds]}\n" + "{[Customers].[USA].[WA].[Sedro Woolley]}\n" + "Row #0: 44.05%\n" + "Row #1: 34.41%\n" + "Row #2: 34.20%\n" + "Row #3: 32.93%\n" + "Row #4: 31.05%\n" + "Row #5: 30.84%\n" + "Row #6: 30.69%\n" + "Row #7: 29.81%\n" + "Row #8: 28.82%\n" + "Row #9: 28.70%\n" + "Row #10: 28.37%\n" + "Row #11: 26.67%\n" + "Row #12: 26.60%\n" + "Row #13: 26.47%\n" + "Row #14: 26.42%\n" + "Row #15: 26.28%\n" + "Row #16: 25.96%\n" + "Row #17: 24.70%\n" + "Row #18: 21.89%\n" + "Row #19: 21.47%\n" + "Row #20: 17.47%\n" + "Row #21: 13.79%\n"), // 7 new QueryAndResult( "with member [Measures].[Accumulated Sales] as 'Sum(YTD(),[Measures].[Store Sales])'\n" + "select\n" + " {[Measures].[Store Sales],[Measures].[Accumulated Sales]} on columns,\n" + " {Descendants([Time].[1997],[Time].[Month])} on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Accumulated Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1].[1]}\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[5]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q3].[7]}\n" + "{[Time].[1997].[Q3].[8]}\n" + "{[Time].[1997].[Q3].[9]}\n" + "{[Time].[1997].[Q4].[10]}\n" + "{[Time].[1997].[Q4].[11]}\n" + "{[Time].[1997].[Q4].[12]}\n" + "Row #0: 45,539.69\n" + "Row #0: 45,539.69\n" + "Row #1: 44,058.79\n" + "Row #1: 89,598.48\n" + "Row #2: 50,029.87\n" + "Row #2: 139,628.35\n" + "Row #3: 42,878.25\n" + "Row #3: 182,506.60\n" + "Row #4: 44,456.29\n" + "Row #4: 226,962.89\n" + "Row #5: 45,331.73\n" + "Row #5: 272,294.62\n" + "Row #6: 50,246.88\n" + "Row #6: 322,541.50\n" + "Row #7: 46,199.04\n" + "Row #7: 368,740.54\n" + "Row #8: 43,825.97\n" + "Row #8: 412,566.51\n" + "Row #9: 42,342.27\n" + "Row #9: 454,908.78\n" + "Row #10: 53,363.71\n" + "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() { assertQueryReturns(sampleQueries[0].query, sampleQueries[0].result); } public void testSample1() { assertQueryReturns(sampleQueries[1].query, sampleQueries[1].result); } public void testSample2() { assertQueryReturns(sampleQueries[2].query, sampleQueries[2].result); } public void testSample3() { assertQueryReturns(sampleQueries[3].query, sampleQueries[3].result); } public void testSample4() { assertQueryReturns(sampleQueries[4].query, sampleQueries[4].result); } public void testSample5() { assertQueryReturns(sampleQueries[5].query, sampleQueries[5].result); } public void testSample5Snowflake() { propSaver.set( MondrianProperties.instance().FilterChildlessSnowflakeMembers, false); final TestContext context = getTestContext().withFreshConnection(); try { context.assertQueryReturns( sampleQueries[5].query, "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Store Profit Rate]}\n" + "Axis #2:\n" + "{[Product].[Food].[Breakfast Foods]}\n" + "{[Product].[Non-Consumable].[Carousel]}\n" + "{[Product].[Food].[Canned Products]}\n" + "{[Product].[Food].[Baking Goods]}\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "{[Product].[Non-Consumable].[Health and Hygiene]}\n" + "{[Product].[Food].[Snack Foods]}\n" + "{[Product].[Food].[Baked Goods]}\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Food].[Frozen Foods]}\n" + "{[Product].[Non-Consumable].[Periodicals]}\n" + "{[Product].[Food].[Produce]}\n" + "{[Product].[Food].[Seafood]}\n" + "{[Product].[Food].[Deli]}\n" + "{[Product].[Food].[Meat]}\n" + "{[Product].[Food].[Canned Foods]}\n" + "{[Product].[Non-Consumable].[Household]}\n" + "{[Product].[Food].[Starchy Foods]}\n" + "{[Product].[Food].[Eggs]}\n" + "{[Product].[Food].[Snacks]}\n" + "{[Product].[Food].[Dairy]}\n" + "{[Product].[Drink].[Dairy]}\n" + "{[Product].[Non-Consumable].[Checkout]}\n" + "{[Product].[Drink].[Baking Goods]}\n" + "{[Product].[Food].[Packaged Foods]}\n" + "Row #0: 2,756.80\n" + "Row #0: 6,941.46\n" + "Row #0: 151.79%\n" + "Row #1: 595.97\n" + "Row #1: 1,500.11\n" + "Row #1: 151.71%\n" + "Row #2: 1,317.13\n" + "Row #2: 3,314.52\n" + "Row #2: 151.65%\n" + "Row #3: 15,370.61\n" + "Row #3: 38,670.41\n" + "Row #3: 151.59%\n" + "Row #4: 5,576.79\n" + "Row #4: 14,029.08\n" + "Row #4: 151.56%\n" + "Row #5: 12,972.99\n" + "Row #5: 32,571.86\n" + "Row #5: 151.07%\n" + "Row #6: 26,963.34\n" + "Row #6: 67,609.82\n" + "Row #6: 150.75%\n" + "Row #7: 6,564.09\n" + "Row #7: 16,455.43\n" + "Row #7: 150.69%\n" + "Row #8: 11,069.53\n" + "Row #8: 27,748.53\n" + "Row #8: 150.67%\n" + "Row #9: 22,030.66\n" + "Row #9: 55,207.50\n" + "Row #9: 150.59%\n" + "Row #10: 3,614.55\n" + "Row #10: 9,056.76\n" + "Row #10: 150.56%\n" + "Row #11: 32,831.33\n" + "Row #11: 82,248.42\n" + "Row #11: 150.52%\n" + "Row #12: 1,520.70\n" + "Row #12: 3,809.14\n" + "Row #12: 150.49%\n" + "Row #13: 10,108.87\n" + "Row #13: 25,318.93\n" + "Row #13: 150.46%\n" + "Row #14: 1,465.42\n" + "Row #14: 3,669.89\n" + "Row #14: 150.43%\n" + "Row #15: 15,894.53\n" + "Row #15: 39,774.34\n" + "Row #15: 150.24%\n" + "Row #16: 24,170.73\n" + "Row #16: 60,469.89\n" + "Row #16: 150.18%\n" + "Row #17: 4,705.91\n" + "Row #17: 11,756.07\n" + "Row #17: 149.82%\n" + "Row #18: 3,684.90\n" + "Row #18: 9,200.76\n" + "Row #18: 149.69%\n" + "Row #19: 5,827.58\n" + "Row #19: 14,550.05\n" + "Row #19: 149.68%\n" + "Row #20: 12,228.85\n" + "Row #20: 30,508.85\n" + "Row #20: 149.48%\n" + "Row #21: 2,830.92\n" + "Row #21: 7,058.60\n" + "Row #21: 149.34%\n" + "Row #22: 1,525.04\n" + "Row #22: 3,767.71\n" + "Row #22: 147.06%\n" + "Row #23: \n" + "Row #23: \n" + "Row #23: \n" + "Row #24: \n" + "Row #24: \n" + "Row #24: \n"); } finally { context.close(); } } public void testSample6() { assertQueryReturns(sampleQueries[6].query, sampleQueries[6].result); } public void testSample7() { assertQueryReturns(sampleQueries[7].query, sampleQueries[7].result); } public void testSample8() { if (TestContext.instance().getDialect().getDatabaseProduct() == Dialect.DatabaseProduct.INFOBRIGHT) { // Skip this test on Infobright, because [Promotion Sales] is // defined wrong. return; } assertQueryReturns(sampleQueries[8].query, sampleQueries[8].result); } public void testGoodComments() { assertQueryReturns( "SELECT {} ON ROWS, {} ON COLUMNS FROM [Sales]/* trailing comment*/", EmptyResult); String[] comments = { "-- a basic comment\n", "// another basic comment\n", "/* yet another basic comment */", "-- a more complicated comment test\n", "-- to make it more intesting, -- we'll nest this comment\n", "-- also, \"we can put a string in the comment\"\n", "-- also, 'even a single quote string'\n", "---- and, the comment delimiter is looong\n", "/*\n" + " * next, how about a comment block?\n" + " * with several lines.\n" + " * also, \"we can put a string in the comment\"\n" + " * also, 'even a single quote string'\n" + " * also, -- another style comment is happy\n" + " */\n", "/* a simple /* nested comment, only needs to be closed once */", "/*\n" + " * a multiline /* nested comment\n" + "*/", "/**\n" + " * a multiline\n" + " * /* multiline\n" + " * * nested comment\n" + "*/", "-- single-line comment containing /* multiline */ comment\n", "/* multi-line comment containing -- single-line comment */", }; List allCommentList = new ArrayList(); for (String comment : comments) { allCommentList.add(comment); if (comment.indexOf("\n") >= 0) { allCommentList.add(comment.replaceAll("\n", "\r\n")); allCommentList.add(comment.replaceAll("\n", "\n\r")); allCommentList.add(comment.replaceAll("\n", " \n \n ")); } } allCommentList.add(""); final String[] allComments = allCommentList.toArray(new String[allCommentList.size()]); // The last element of the array is the concatenation of all other // comments. StringBuilder buf = new StringBuilder(); for (String allComment : allComments) { buf.append(allComment); } allComments[allComments.length - 1] = buf.toString(); // Comment at start of query. for (String comment : allComments) { assertQueryReturns( comment + "SELECT {} ON ROWS, {} ON COLUMNS FROM [Sales]", EmptyResult); } // Comment after SELECT. for (String comment : allComments) { assertQueryReturns( "SELECT" + comment + "{} ON ROWS, {} ON COLUMNS FROM [Sales]", EmptyResult); } // Comment within braces. for (String comment : allComments) { assertQueryReturns( "SELECT {" + comment + "} ON ROWS, {} ON COLUMNS FROM [Sales]", EmptyResult); } // Comment after axis name. for (String comment : allComments) { assertQueryReturns( "SELECT {} ON ROWS" + comment + ", {} ON COLUMNS FROM [Sales]", EmptyResult); } // Comment before slicer. for (String comment : allComments) { assertQueryReturns( "SELECT {} ON ROWS, {} ON COLUMNS FROM [Sales] WHERE" + comment + "([Gender].[F])", "Axis #0:\n" + "{[Gender].[F]}\n" + "Axis #1:\n" + "Axis #2:\n"); } // Comment after query. for (String comment : allComments) { assertQueryReturns( "SELECT {} ON ROWS, {} ON COLUMNS FROM [Sales]" + comment, EmptyResult); } assertQueryReturns( "-- a comment test with carriage returns at the end of the lines\r\n" + "-- first, more than one single-line comment in a row\r\n" + "-- and, to make it more intesting, -- we'll nest this comment\r\n" + "-- also, \"we can put a string in the comment\"\r\n" + "-- also, 'even a single quote string'\r\n" + "---- and, the comment delimiter is looong\r\n" + "/*\r\n" + " * next, now about a comment block?\r\n" + " * with several lines.\r\n" + " * also, \"we can put a string in the comment\"\r\n" + " * also, 'even a single quote comment'\r\n" + " * also, -- another style comment is heppy\r\n" + " * also, // another style comment is heppy\r\n" + " */\r\n" + "SELECT {} ON ROWS, {} ON COLUMNS FROM [Sales]\r", EmptyResult); assertQueryReturns( "-- an entire select statement commented out\n" + "-- SELECT {} ON ROWS, {} ON COLUMNS FROM [Sales];\n" + "/*SELECT {} ON ROWS, {} ON COLUMNS FROM [Sales];*/\n" + "// SELECT {} ON ROWS, {} ON COLUMNS FROM [Sales];\n" + "SELECT {} ON ROWS, {} ON COLUMNS FROM [Sales]", EmptyResult); assertQueryReturns( "// now for some comments in a larger command\n" + "with // create calculate measure [Product].[All Products].[Drink].[Percent of Alcoholic Drinks]\n" + " member [Product].[All Products].[Drink].[Percent of Alcoholic Drinks]/*the measure name*/as ' // begin the definition of the measure next\n" + " [Product]./****this is crazy****/[All Products].[Drink].[Alcoholic Beverages]/[Product].[All Products].[Drink]', // divide number of alcoholic drinks by total # of drinks\n" + " format_string = '#.00%' // a custom format for our measure\n" + "select\n" + " { [Product]/**** still crazy ****/.[All Products].[Drink].[Percent of Alcoholic Drinks] } on columns,\n" + " order(/****do not put a comment inside square brackets****/[Customers].[All Customers].[USA].[WA].Children, [Product].[All Products].[Drink].[Percent of Alcoholic Drinks],BDESC) on rows\n" + "from Sales\n" + "where ([Measures].[Unit Sales] /****,[Time].[1997]****/) -- a comment at the end of the command", "Axis #0:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #1:\n" + "{[Product].[Drink].[Percent of Alcoholic Drinks]}\n" + "Axis #2:\n" + "{[Customers].[USA].[WA].[Seattle]}\n" + "{[Customers].[USA].[WA].[Kirkland]}\n" + "{[Customers].[USA].[WA].[Marysville]}\n" + "{[Customers].[USA].[WA].[Anacortes]}\n" + "{[Customers].[USA].[WA].[Olympia]}\n" + "{[Customers].[USA].[WA].[Ballard]}\n" + "{[Customers].[USA].[WA].[Bremerton]}\n" + "{[Customers].[USA].[WA].[Puyallup]}\n" + "{[Customers].[USA].[WA].[Yakima]}\n" + "{[Customers].[USA].[WA].[Tacoma]}\n" + "{[Customers].[USA].[WA].[Everett]}\n" + "{[Customers].[USA].[WA].[Renton]}\n" + "{[Customers].[USA].[WA].[Issaquah]}\n" + "{[Customers].[USA].[WA].[Bellingham]}\n" + "{[Customers].[USA].[WA].[Port Orchard]}\n" + "{[Customers].[USA].[WA].[Redmond]}\n" + "{[Customers].[USA].[WA].[Spokane]}\n" + "{[Customers].[USA].[WA].[Burien]}\n" + "{[Customers].[USA].[WA].[Lynnwood]}\n" + "{[Customers].[USA].[WA].[Walla Walla]}\n" + "{[Customers].[USA].[WA].[Edmonds]}\n" + "{[Customers].[USA].[WA].[Sedro Woolley]}\n" + "Row #0: 44.05%\n" + "Row #1: 34.41%\n" + "Row #2: 34.20%\n" + "Row #3: 32.93%\n" + "Row #4: 31.05%\n" + "Row #5: 30.84%\n" + "Row #6: 30.69%\n" + "Row #7: 29.81%\n" + "Row #8: 28.82%\n" + "Row #9: 28.70%\n" + "Row #10: 28.37%\n" + "Row #11: 26.67%\n" + "Row #12: 26.60%\n" + "Row #13: 26.47%\n" + "Row #14: 26.42%\n" + "Row #15: 26.28%\n" + "Row #16: 25.96%\n" + "Row #17: 24.70%\n" + "Row #18: 21.89%\n" + "Row #19: 21.47%\n" + "Row #20: 17.47%\n" + "Row #21: 13.79%\n"); } public void testBadComments() { // Comments cannot appear inside identifiers. assertQueryThrows( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Gender].MEMBERS} ON ROWS\n" + "FROM [Sales]\n" + "WHERE {[/***an illegal comment****/Marital Status].[S]}", "Failed to parse query"); // Nested comments only need to be closed once. assertQueryThrows( "/* a simple /* nested */ comment */\n" + "SELECT {} ON ROWS, {} ON COLUMNS FROM [Sales]", "Failed to parse query"); // We support \r as a line-end delimiter. assertQueryReturns( "SELECT {} ON COLUMNS -- comment terminated by CR only\r, {} ON ROWS FROM [Sales]", EmptyResult); } /** * Tests that a query whose axes are empty works; bug * MONDRIAN-52. */ public void testBothAxesEmpty() { assertQueryReturns( "SELECT {} ON ROWS, {} ON COLUMNS FROM [Sales]", EmptyResult); // expression which evaluates to empty set assertQueryReturns( "SELECT Filter({[Gender].MEMBERS}, 1 = 0) ON COLUMNS, \n" + "{} ON ROWS\n" + "FROM [Sales]", EmptyResult); // with slicer assertQueryReturns( "SELECT {} ON ROWS, {} ON COLUMNS \n" + "FROM [Sales] WHERE ([Gender].[F])", "Axis #0:\n" + "{[Gender].[F]}\n" + "Axis #1:\n" + "Axis #2:\n"); } /** * Used to test that a slicer with multiple values gives an error; bug * MONDRIAN-96. * But now compound slicers are valid. */ public void testCompoundSlicer() { // two tuples assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Gender].MEMBERS} ON ROWS\n" + "FROM [Sales]\n" + "WHERE {([Marital Status].[S]),\n" + " ([Marital Status].[M])}", "Axis #0:\n" + "{[Marital Status].[S]}\n" + "{[Marital Status].[M]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 266,773\n" + "Row #1: 131,558\n" + "Row #2: 135,215\n"); // set with incompatible members assertQueryThrows( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Gender].MEMBERS} ON ROWS\n" + "FROM [Sales]\n" + "WHERE {[Marital Status].[S],\n" + " [Product]}", "All arguments to function '{}' must have same hierarchy."); // expression which evaluates to a set with zero members used to be an // error - now it's ok; cells are null because they are aggregating over // nothing assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Gender].MEMBERS} ON ROWS\n" + "FROM [Sales]\n" + "WHERE Filter({[Marital Status].MEMBERS}, 1 = 0)", "Axis #0:\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: \n"); // expression which evaluates to a not-null member is ok assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Gender].MEMBERS} ON ROWS\n" + "FROM [Sales]\n" + "WHERE ({Filter({[Marital Status].MEMBERS}, [Measures].[Unit Sales] = 266773)}.Item(0))", "Axis #0:\n" + "{[Marital Status].[All Marital Status]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 266,773\n" + "Row #1: 131,558\n" + "Row #2: 135,215\n"); // Expression which evaluates to a null member used to be an error; now // it is an unsatisfiable condition, so cells come out empty. // Confirmed with SSAS 2005. assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Gender].MEMBERS} ON ROWS\n" + "FROM [Sales]\n" + "WHERE [Marital Status].Parent", "Axis #0:\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: \n"); // expression which evaluates to a set with one member is ok assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Gender].MEMBERS} ON ROWS\n" + "FROM [Sales]\n" + "WHERE Filter({[Marital Status].MEMBERS}, [Measures].[Unit Sales] = 266773)", "Axis #0:\n" + "{[Marital Status].[All Marital Status]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 266,773\n" + "Row #1: 131,558\n" + "Row #2: 135,215\n"); // Slicer expression which evaluates to three tuples is ok now we // support compound slicers. But the results must not double-count if, // as in this case, a member and its parent both appear in the list. // In that respect, an MDX WHERE clause behaves more like a SQL WHERE // clause (applying the condition to each cell) than the MDX Aggregate // function (summing over all cells that pass). assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Gender].MEMBERS} ON ROWS\n" + "FROM [Sales]\n" + "WHERE Filter({[Marital Status].MEMBERS}, [Measures].[Unit Sales] <= 266773)", "Axis #0:\n" + "{[Marital Status].[All Marital Status]}\n" + "{[Marital Status].[M]}\n" + "{[Marital Status].[S]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 266,773\n" + "Row #1: 131,558\n" + "Row #2: 135,215\n"); // set with incompatible members assertQueryThrows( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Gender].MEMBERS} ON ROWS\n" + "FROM [Sales]\n" + "WHERE {[Marital Status].[S],\n" + " [Product]}", "All arguments to function '{}' must have same hierarchy."); // two members of same dimension in columns and rows assertQueryThrows( "SELECT CrossJoin(\n" + " {[Measures].[Unit Sales]},\n" + " {[Gender].[M]}) ON COLUMNS,\n" + " {[Gender].MEMBERS} ON ROWS\n" + "FROM [Sales]", "Hierarchy '[Gender]' appears in more than one independent axis."); // two members of same dimension in rows and filter assertQueryThrows( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Gender].MEMBERS} ON ROWS\n" + "FROM [Sales]" + "WHERE ([Marital Status].[S], [Gender].[F])", "Hierarchy '[Gender]' appears in more than one independent axis."); // members of different hierarchies of the same dimension in rows and // filter assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Time].[1997].Children} ON ROWS\n" + "FROM [Sales]" + "WHERE ([Marital Status].[S], " + timeWeekly + ".[1997].[20])", "Axis #0:\n" + "{[Marital Status].[S], [Time].[Weekly].[1997].[20]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q4]}\n" + "Row #0: \n" + "Row #1: 3,523\n" + "Row #2: \n" + "Row #3: \n"); // two members of same dimension in slicer tuple assertQueryThrows( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Gender].MEMBERS} ON ROWS\n" + "FROM [Sales]" + "WHERE ([Marital Status].[S], [Marital Status].[M])", "Tuple contains more than one member of hierarchy '[Marital Status]'."); // two members of different hierarchies of the same dimension in the // slicer tuple assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Gender].MEMBERS} ON ROWS\n" + "FROM [Sales]" + "WHERE ([Time].[1997].[Q1], " + timeWeekly + ".[1997].[4])", "Axis #0:\n" + "{[Time].[1997].[Q1], [Time].[Weekly].[1997].[4]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 4,908\n" + "Row #1: 2,354\n" + "Row #2: 2,554\n"); // testcase for bug MONDRIAN-68, "Member appears in slicer and other // axis should be illegal" assertQueryThrows( "select\n" + "{[Measures].[Unit Sales]} on columns,\n" + "{([Product].[All Products], [Time].[1997])} ON rows\n" + "from Sales\n" + "where ([Time].[1997])", "Hierarchy '[Time]' appears in more than one independent axis."); // different hierarchies of same dimension on slicer and other axis assertQueryReturns( "select\n" + "{[Measures].[Unit Sales]} on columns,\n" + "{([Product].[All Products], " + timeWeekly + ".[1997])} ON rows\n" + "from Sales\n" + "where ([Time].[1997])", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[All Products], [Time].[Weekly].[1997]}\n" + "Row #0: 266,773\n"); } /** * Test case for * MONDRIAN-814, * "MDX with specific where clause doesn't work" . This test case * was as close as I could get to the original test case on the foodmart * data set, but it did not reproduce the bug. */ public void testCompoundSlicerNonEmpty() { // With MONDRIAN-814, cell totals would be about a factor of 4 smaller, // and the number of rows returned would be the same (1220) if 21, 22 // and 23 were removed from the slicer. final String mdx = "select non empty [Measures].[Sales Count] on 0,\n" + " non empty hierarchize(\n" + " Crossjoin(\n" + " Union(\n" + " [Gender].CurrentMember,\n" + " [Gender].Children),\n" + " Union(\n" + " [Product].CurrentMember,\n" + " [Product].[Brand Name].Members))) on 1\n" + "from [Sales]\n" + "where { " + timeWeekly + ".[1997].[20]" + " , " + timeWeekly + ".[1997].[21]" + " , " + timeWeekly + ".[1997].[22]" + " , " + timeWeekly + ".[1997].[23]" + " }"; if (false) { // Output too large to check in. assertQueryReturns(mdx, "xxxx"); } Result result = getTestContext().executeQuery(mdx); assertEquals(1477, result.getAxes()[1].getPositions().size()); assertEquals( "5,896", result.getCell(new int[] {0, 0}).getFormattedValue()); } public void testEmptyTupleSlicerFails() { assertQueryThrows( "select [Measures].[Unit Sales] on 0,\n" + "[Product].Children on 1\n" + "from [Warehouse and Sales]\n" + "where ()", "Syntax error at line 4, column 8, token ')'"); } /** * Requires the use of a sparse segment, because the product dimension * has 6 atttributes, the product of whose cardinalities is ~8M. If we * use a dense segment, we run out of memory trying to allocate a huge * array. */ public void testBigQuery() { Result result = executeQuery( "SELECT {[Measures].[Unit Sales]} on columns,\n" + " {[Product].members} on rows\n" + "from Sales"); final int rowCount = result.getAxes()[1].getPositions().size(); assertEquals( MondrianProperties.instance().FilterChildlessSnowflakeMembers.get() ? 2256 : 2266, rowCount); assertEquals( "152", result.getCell( new int[]{ 0, rowCount - 1 }).getFormattedValue()); } /** * Unit test for the {@link Cell#getContextMember(mondrian.olap.Hierarchy)} * method. */ public void testGetContext() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } Result result = getTestContext().executeQuery( "select [Gender].Members on 0,\n" + "[Time].[Weekly].[1997].[6].Children on 1\n" + "from [Sales]\n" + "where [Marital Status].[S]"); final Cell cell = result.getCell(new int[]{0, 0}); final Map hierarchyMap = new HashMap(); for (Dimension dimension : result.getQuery().getCube().getDimensions()) { for (Hierarchy hierarchy : dimension.getHierarchies()) { hierarchyMap.put(hierarchy.getUniqueName(), hierarchy); } } assertEquals( "[Measures].[Unit Sales]", cell.getContextMember(hierarchyMap.get("[Measures]")) .getUniqueName()); assertEquals( "[Time].[1997]", cell.getContextMember(hierarchyMap.get("[Time]")) .getUniqueName()); assertEquals( "[Time].[Weekly].[1997].[6].[1]", cell.getContextMember(hierarchyMap.get("[Time].[Weekly]")) .getUniqueName()); assertEquals( "[Gender].[All Gender]", cell.getContextMember(hierarchyMap.get("[Gender]")) .getUniqueName()); assertEquals( "[Marital Status].[S]", cell.getContextMember(hierarchyMap.get("[Marital Status]")) .getUniqueName()); } public void testNonEmpty1() { assertSize( "select\n" + " NON EMPTY CrossJoin({[Product].[All Products].[Drink].Children},\n" + " {[Customers].[All Customers].[USA].[WA].[Bellingham]}) on rows,\n" + " CrossJoin(\n" + " {[Measures].[Unit Sales], [Measures].[Store Sales]},\n" + " { [Promotion Media].[All Media].[Radio],\n" + " [Promotion Media].[All Media].[TV],\n" + " [Promotion Media].[All Media].[Sunday Paper],\n" + " [Promotion Media].[All Media].[Street Handout] }\n" + " ) on columns\n" + "from Sales\n" + "where ([Time].[1997])", 8, 2); } public void testNonEmpty2() { assertSize( "select\n" + " NON EMPTY CrossJoin(\n" + " {[Product].[All Products].Children},\n" + " {[Customers].[All Customers].[USA].[WA].[Bellingham]}) on rows,\n" + " NON EMPTY CrossJoin(\n" + " {[Measures].[Unit Sales]},\n" + " { [Promotion Media].[All Media].[Cash Register Handout],\n" + " [Promotion Media].[All Media].[Sunday Paper],\n" + " [Promotion Media].[All Media].[Street Handout] }\n" + " ) on columns\n" + "from Sales\n" + "where ([Time].[1997])", 2, 2); } public void testOneDimensionalQueryWithTupleAsSlicer() { Result result = executeQuery( "select\n" + " [Product].[All Products].[Drink].children on columns\n" + "from Sales\n" + "where ([Measures].[Unit Sales], [Promotion Media].[All Media].[Street Handout], [Time].[1997])"); assertTrue(result.getAxes().length == 1); assertTrue(result.getAxes()[0].getPositions().size() == 3); assertTrue(result.getSlicerAxis().getPositions().size() == 1); assertTrue(result.getSlicerAxis().getPositions().get(0).size() == 3); } public void testSlicerIsEvaluatedBeforeAxes() { // about 10 products exceeded 20000 units in 1997, only 2 for Q1 assertSize( "SELECT {[Measures].[Unit Sales]} on columns,\n" + " filter({[Product].members}, [Measures].[Unit Sales] > 20000) on rows\n" + "FROM Sales\n" + "WHERE [Time].[1997].[Q1]", 1, 2); } public void testSlicerWithCalculatedMembers() { assertSize( "WITH Member [Time].[Time].[1997].[H1] as ' Aggregate({[Time].[1997].[Q1], [Time].[1997].[Q2]})' \n" + " MEMBER [Measures].[Store Margin] as '[Measures].[Store Sales] - [Measures].[Store Cost]'\n" + "SELECT {[Gender].members} on columns,\n" + " filter({[Product].members}, [Gender].[F] > 10000) on rows\n" + "FROM Sales\n" + "WHERE ([Time].[1997].[H1], [Measures].[Store Margin])", 3, 6); } public void _testEver() { assertQueryReturns( "select\n" + " {[Measures].[Unit Sales], [Measures].[Ever]} on columns,\n" + " [Gender].members on rows\n" + "from Sales", "xxx"); } public void _testDairy() { assertQueryReturns( "with\n" + " member [Product].[Non dairy] as '[Product].[All Products] - [Product].[Food].[Dairy]'\n" + " member [Measures].[Dairy ever] as 'sum([Time].members, ([Measures].[Unit Sales],[Product].[Food].[Dairy]))'\n" + " set [Customers who never bought dairy] as 'filter([Customers].members, [Measures].[Dairy ever] = 0)'\n" + "select\n" + " {[Measures].[Unit Sales], [Measures].[Dairy ever]} on columns,\n" + " [Customers who never bought dairy] on rows\n" + "from Sales", "xxx"); } public void testSolveOrder() { assertQueryReturns( "WITH\n" + " MEMBER [Measures].[StoreType] AS \n" + " '[Store].CurrentMember.Properties(\"Store Type\")',\n" + " SOLVE_ORDER = 2\n" + " MEMBER [Measures].[ProfitPct] AS \n" + " '(Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales]',\n" + " SOLVE_ORDER = 1, FORMAT_STRING = '##.00%'\n" + "SELECT\n" + " { Descendants([Store].[USA], [Store].[Store Name])} ON COLUMNS,\n" + " { [Measures].[Store Sales], [Measures].[Store Cost], [Measures].[StoreType],\n" + " [Measures].[ProfitPct] } ON ROWS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA].[Alameda].[HQ]}\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Store].[USA].[WA].[Bellingham].[Store 2]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "{[Store].[USA].[WA].[Walla Walla].[Store 22]}\n" + "{[Store].[USA].[WA].[Yakima].[Store 23]}\n" + "Axis #2:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[StoreType]}\n" + "{[Measures].[ProfitPct]}\n" + "Row #0: \n" + "Row #0: 45,750.24\n" + "Row #0: 54,545.28\n" + "Row #0: 54,431.14\n" + "Row #0: 4,441.18\n" + "Row #0: 55,058.79\n" + "Row #0: 87,218.28\n" + "Row #0: 4,739.23\n" + "Row #0: 52,896.30\n" + "Row #0: 52,644.07\n" + "Row #0: 49,634.46\n" + "Row #0: 74,843.96\n" + "Row #0: 4,705.97\n" + "Row #0: 24,329.23\n" + "Row #1: \n" + "Row #1: 18,266.44\n" + "Row #1: 21,771.54\n" + "Row #1: 21,713.53\n" + "Row #1: 1,778.92\n" + "Row #1: 21,948.94\n" + "Row #1: 34,823.56\n" + "Row #1: 1,896.62\n" + "Row #1: 21,121.96\n" + "Row #1: 20,956.80\n" + "Row #1: 19,795.49\n" + "Row #1: 29,959.28\n" + "Row #1: 1,880.34\n" + "Row #1: 9,713.81\n" + "Row #2: HeadQuarters\n" + "Row #2: Gourmet Supermarket\n" + "Row #2: Supermarket\n" + "Row #2: Supermarket\n" + "Row #2: Small Grocery\n" + "Row #2: Supermarket\n" + "Row #2: Deluxe Supermarket\n" + "Row #2: Small Grocery\n" + "Row #2: Supermarket\n" + "Row #2: Supermarket\n" + "Row #2: Supermarket\n" + "Row #2: Deluxe Supermarket\n" + "Row #2: Small Grocery\n" + "Row #2: Mid-Size Grocery\n" + "Row #3: \n" + "Row #3: 60.07%\n" + "Row #3: 60.09%\n" + "Row #3: 60.11%\n" + "Row #3: 59.94%\n" + "Row #3: 60.14%\n" + "Row #3: 60.07%\n" + "Row #3: 59.98%\n" + "Row #3: 60.07%\n" + "Row #3: 60.19%\n" + "Row #3: 60.12%\n" + "Row #3: 59.97%\n" + "Row #3: 60.04%\n" + "Row #3: 60.07%\n"); } public void testSolveOrderNonMeasure() { assertQueryReturns( "WITH\n" + " MEMBER [Product].[ProdCalc] as '1', SOLVE_ORDER=1\n" + " MEMBER [Measures].[MeasuresCalc] as '2', SOLVE_ORDER=2\n" + " Member [Time].[Time].[1997].[TimeCalc] as '3', SOLVE_ORDER=3\n" + "SELECT\n" + " { [Product].[ProdCalc] } ON columns,\n" + " {([Time].[1997].[TimeCalc],\n" + " [Measures].[MeasuresCalc])} ON rows\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[ProdCalc]}\n" + "Axis #2:\n" + "{[Time].[1997].[TimeCalc], [Measures].[MeasuresCalc]}\n" + "Row #0: 3\n"); } public void testSolveOrderNonMeasure2() { assertQueryReturns( "WITH\n" + " MEMBER [Store].[StoreCalc] as '0', SOLVE_ORDER=0\n" + " MEMBER [Product].[ProdCalc] as '1', SOLVE_ORDER=1\n" + "SELECT\n" + " { [Product].[ProdCalc] } ON columns,\n" + " { [Store].[StoreCalc] } ON rows\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[ProdCalc]}\n" + "Axis #2:\n" + "{[Store].[StoreCalc]}\n" + "Row #0: 1\n"); } /** * Test what happens when the solve orders are the same. According to * http://msdn.microsoft.com/library/en-us/olapdmad/agmdxadvanced_6jn7.asp * if solve orders are the same then the dimension specified first * when defining the cube wins. * *

In the first test, the answer should be 1 because Promotions * comes before Customers in the FoodMart.xml schema. */ public void testSolveOrderAmbiguous1() { assertQueryReturns( "WITH\n" + " MEMBER [Promotions].[Calc] AS '1'\n" + " MEMBER [Customers].[Calc] AS '2'\n" + "SELECT\n" + " { [Promotions].[Calc] } ON COLUMNS,\n" + " { [Customers].[Calc] } ON ROWS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Promotions].[Calc]}\n" + "Axis #2:\n" + "{[Customers].[Calc]}\n" + "Row #0: 1\n"); } /** * In the second test, the answer should be 2 because Product comes before * Promotions in the FoodMart.xml schema. */ public void testSolveOrderAmbiguous2() { assertQueryReturns( "WITH\n" + " MEMBER [Promotions].[Calc] AS '1'\n" + " MEMBER [Product].[Calc] AS '2'\n" + "SELECT\n" + " { [Promotions].[Calc] } ON COLUMNS,\n" + " { [Product].[Calc] } ON ROWS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Promotions].[Calc]}\n" + "Axis #2:\n" + "{[Product].[Calc]}\n" + "Row #0: 2\n"); } public void testCalculatedMemberWhichIsNotAMeasure() { assertQueryReturns( "WITH MEMBER [Product].[BigSeller] AS\n" + " 'IIf([Product].[Drink].[Alcoholic Beverages].[Beer and Wine] > 100, \"Yes\",\"No\")'\n" + "SELECT {[Product].[BigSeller],[Product].children} ON COLUMNS,\n" + " {[Store].[USA].[CA].children} ON ROWS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[BigSeller]}\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[Alameda]}\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "Row #0: No\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #1: Yes\n" + "Row #1: 1,945\n" + "Row #1: 15,438\n" + "Row #1: 3,950\n" + "Row #2: Yes\n" + "Row #2: 2,422\n" + "Row #2: 18,294\n" + "Row #2: 4,947\n" + "Row #3: Yes\n" + "Row #3: 2,560\n" + "Row #3: 18,369\n" + "Row #3: 4,706\n" + "Row #4: No\n" + "Row #4: 175\n" + "Row #4: 1,555\n" + "Row #4: 387\n"); } public void testMultipleCalculatedMembersWhichAreNotMeasures() { assertQueryReturns( "WITH\n" + " MEMBER [Store].[x] AS '1'\n" + " MEMBER [Product].[x] AS '1'\n" + "SELECT {[Store].[x]} ON COLUMNS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[x]}\n" + "Row #0: 1\n"); } /** * Testcase for bug * MONDRIAN-77, "Calculated member name conflict". * *

There used to be something wrong with non-measure calculated members * where the ordering of the WITH MEMBER would determine whether or not * the member would be found in the cube. This test would fail but the * previous one would work ok. */ public void testMultipleCalculatedMembersWhichAreNotMeasures2() { assertQueryReturns( "WITH\n" + " MEMBER [Product].[x] AS '1'\n" + " MEMBER [Store].[x] AS '1'\n" + "SELECT {[Store].[x]} ON COLUMNS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[x]}\n" + "Row #0: 1\n"); } /** * This one had the same problem. It wouldn't find the * [Store].[x] member because it has the same leaf * name as [Product].[x]. (See MONDRIAN-77.) */ public void testMultipleCalculatedMembersWhichAreNotMeasures3() { assertQueryReturns( "WITH\n" + " MEMBER [Product].[x] AS '1'\n" + " MEMBER [Store].[x] AS '1'\n" + "SELECT {[Store].[x]} ON COLUMNS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[x]}\n" + "Row #0: 1\n"); } public void testConstantString() { String s = executeExpr(" \"a string\" "); assertEquals("a string", s); } public void testConstantNumber() { String s = executeExpr(" 1234 "); assertEquals("1,234", s); } public void testCyclicalCalculatedMembers() { Util.discard( executeQuery( "WITH\n" + " MEMBER [Product].[X] AS '[Product].[Y]'\n" + " MEMBER [Product].[Y] AS '[Product].[X]'\n" + "SELECT\n" + " {[Product].[X]} ON COLUMNS,\n" + " {Store.[Store Name].Members} ON ROWS\n" + "FROM Sales")); } /** * Disabled test. It used throw an 'infinite loop' error (which is what * Plato does). But now we revert to the context of the default member when * calculating calculated members (we used to stay in the context of the * calculated member), and we get a result. */ public void testCycle() { if (false) { assertExprThrows("[Time].[1997].[Q4]", "infinite loop"); } else { String s = executeExpr("[Time].[1997].[Q4]"); assertEquals("72,024", s); } } public void testHalfYears() { Util.discard( executeQuery( "WITH MEMBER [Measures].[ProfitPercent] AS\n" + " '([Measures].[Store Sales]-[Measures].[Store Cost])/([Measures].[Store Cost])',\n" + " FORMAT_STRING = '#.00%', SOLVE_ORDER = 1\n" + " Member [Time].[Time].[1997].[First Half] AS '[Time].[1997].[Q1] + [Time].[1997].[Q2]'\n" + " Member [Time].[Time].[1997].[Second Half] AS '[Time].[1997].[Q3] + [Time].[1997].[Q4]'\n" + " SELECT {[Time].[1997].[First Half],\n" + " [Time].[1997].[Second Half],\n" + " [Time].[1997].CHILDREN} ON COLUMNS,\n" + " {[Store].[Store Country].[USA].CHILDREN} ON ROWS\n" + " FROM [Sales]\n" + " WHERE ([Measures].[ProfitPercent])")); } public void _testHalfYearsTrickyCase() { Util.discard( executeQuery( "WITH MEMBER MEASURES.ProfitPercent AS\n" + " '([Measures].[Store Sales]-[Measures].[Store Cost])/([Measures].[Store Cost])',\n" + " FORMAT_STRING = '#.00%', SOLVE_ORDER = 1\n" + " Member [Time].[Time].[First Half 97] AS '[Time].[1997].[Q1] + [Time].[1997].[Q2]'\n" + " Member [Time].[Time].[Second Half 97] AS '[Time].[1997].[Q3] + [Time].[1997].[Q4]'\n" + " SELECT {[Time].[First Half 97],\n" + " [Time].[Second Half 97],\n" + " [Time].[1997].CHILDREN} ON COLUMNS,\n" + " {[Store].[Store Country].[USA].CHILDREN} ON ROWS\n" + " FROM [Sales]\n" + " WHERE (MEASURES.ProfitPercent)")); } public void testAsSample7ButUsingVirtualCube() { Util.discard( executeQuery( "with member [Measures].[Accumulated Sales] as 'Sum(YTD(),[Measures].[Store Sales])'\n" + "select\n" + " {[Measures].[Store Sales],[Measures].[Accumulated Sales]} on columns,\n" + " {Descendants([Time].[1997],[Time].[Month])} on rows\n" + "from [Warehouse and Sales]")); } public void testVirtualCube() { assertQueryReturns( // Note that Unit Sales is independent of Warehouse. "select CrossJoin(\n" + " {[Warehouse].DefaultMember, [Warehouse].[USA].children},\n" + " {[Measures].[Unit Sales], [Measures].[Store Sales], [Measures].[Units Shipped]}) on columns,\n" + " [Time].[Time].children on rows\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Warehouse].[All Warehouses], [Measures].[Unit Sales]}\n" + "{[Warehouse].[All Warehouses], [Measures].[Store Sales]}\n" + "{[Warehouse].[All Warehouses], [Measures].[Units Shipped]}\n" + "{[Warehouse].[USA].[CA], [Measures].[Unit Sales]}\n" + "{[Warehouse].[USA].[CA], [Measures].[Store Sales]}\n" + "{[Warehouse].[USA].[CA], [Measures].[Units Shipped]}\n" + "{[Warehouse].[USA].[OR], [Measures].[Unit Sales]}\n" + "{[Warehouse].[USA].[OR], [Measures].[Store Sales]}\n" + "{[Warehouse].[USA].[OR], [Measures].[Units Shipped]}\n" + "{[Warehouse].[USA].[WA], [Measures].[Unit Sales]}\n" + "{[Warehouse].[USA].[WA], [Measures].[Store Sales]}\n" + "{[Warehouse].[USA].[WA], [Measures].[Units Shipped]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q4]}\n" + "Row #0: 66,291\n" + "Row #0: 139,628.35\n" + "Row #0: 50951.0\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 8539.0\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 7994.0\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 34418.0\n" + "Row #1: 62,610\n" + "Row #1: 132,666.27\n" + "Row #1: 49187.0\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: 15726.0\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: 7575.0\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: 25886.0\n" + "Row #2: 65,848\n" + "Row #2: 140,271.89\n" + "Row #2: 57789.0\n" + "Row #2: \n" + "Row #2: \n" + "Row #2: 20821.0\n" + "Row #2: \n" + "Row #2: \n" + "Row #2: 8673.0\n" + "Row #2: \n" + "Row #2: \n" + "Row #2: 28295.0\n" + "Row #3: 72,024\n" + "Row #3: 152,671.62\n" + "Row #3: 49799.0\n" + "Row #3: \n" + "Row #3: \n" + "Row #3: 15791.0\n" + "Row #3: \n" + "Row #3: \n" + "Row #3: 16666.0\n" + "Row #3: \n" + "Row #3: \n" + "Row #3: 17342.0\n"); } public void testUseDimensionAsShorthandForMember() { Util.discard( executeQuery( "select {[Measures].[Unit Sales]} on columns,\n" + " {[Store], [Store].children} on rows\n" + "from [Sales]")); } public void _testMembersFunction() { Util.discard( executeQuery( "select {[Measures].[Unit Sales]} on columns,\n" + " {[Customers].members(0)} on rows\n" + "from [Sales]")); } public void _testProduct2() { final Axis axis = getTestContext().executeAxis("{[Product2].members}"); System.out.println(TestContext.toString(axis.getPositions())); } private static final List taglibQueries = Arrays.asList( // 0 new QueryAndResult( "select\n" + " {[Measures].[Unit Sales], [Measures].[Store Cost], [Measures].[Store Sales]} on columns,\n" + " CrossJoin(\n" + " { [Promotion Media].[All Media].[Radio],\n" + " [Promotion Media].[All Media].[TV],\n" + " [Promotion Media].[All Media].[Sunday Paper],\n" + " [Promotion Media].[All Media].[Street Handout] },\n" + " [Product].[All Products].[Drink].children) on rows\n" + "from Sales\n" + "where ([Time].[1997])", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Promotion Media].[Radio], [Product].[Drink].[Alcoholic Beverages]}\n" + "{[Promotion Media].[Radio], [Product].[Drink].[Beverages]}\n" + "{[Promotion Media].[Radio], [Product].[Drink].[Dairy]}\n" + "{[Promotion Media].[TV], [Product].[Drink].[Alcoholic Beverages]}\n" + "{[Promotion Media].[TV], [Product].[Drink].[Beverages]}\n" + "{[Promotion Media].[TV], [Product].[Drink].[Dairy]}\n" + "{[Promotion Media].[Sunday Paper], [Product].[Drink].[Alcoholic Beverages]}\n" + "{[Promotion Media].[Sunday Paper], [Product].[Drink].[Beverages]}\n" + "{[Promotion Media].[Sunday Paper], [Product].[Drink].[Dairy]}\n" + "{[Promotion Media].[Street Handout], [Product].[Drink].[Alcoholic Beverages]}\n" + "{[Promotion Media].[Street Handout], [Product].[Drink].[Beverages]}\n" + "{[Promotion Media].[Street Handout], [Product].[Drink].[Dairy]}\n" + "Row #0: 75\n" + "Row #0: 70.40\n" + "Row #0: 168.62\n" + "Row #1: 97\n" + "Row #1: 75.70\n" + "Row #1: 186.03\n" + "Row #2: 54\n" + "Row #2: 36.75\n" + "Row #2: 89.03\n" + "Row #3: 76\n" + "Row #3: 70.99\n" + "Row #3: 182.38\n" + "Row #4: 188\n" + "Row #4: 167.00\n" + "Row #4: 419.14\n" + "Row #5: 68\n" + "Row #5: 45.19\n" + "Row #5: 119.55\n" + "Row #6: 148\n" + "Row #6: 128.97\n" + "Row #6: 316.88\n" + "Row #7: 197\n" + "Row #7: 161.81\n" + "Row #7: 399.58\n" + "Row #8: 85\n" + "Row #8: 54.75\n" + "Row #8: 140.27\n" + "Row #9: 158\n" + "Row #9: 121.14\n" + "Row #9: 294.55\n" + "Row #10: 270\n" + "Row #10: 201.28\n" + "Row #10: 520.55\n" + "Row #11: 84\n" + "Row #11: 50.26\n" + "Row #11: 128.32\n"), // 1 new QueryAndResult( "select\n" + " [Product].[All Products].[Drink].children on rows,\n" + " CrossJoin(\n" + " {[Measures].[Unit Sales], [Measures].[Store Sales]},\n" + " { [Promotion Media].[All Media].[Radio],\n" + " [Promotion Media].[All Media].[TV],\n" + " [Promotion Media].[All Media].[Sunday Paper],\n" + " [Promotion Media].[All Media].[Street Handout] }\n" + " ) on columns\n" + "from Sales\n" + "where ([Time].[1997])", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales], [Promotion Media].[Radio]}\n" + "{[Measures].[Unit Sales], [Promotion Media].[TV]}\n" + "{[Measures].[Unit Sales], [Promotion Media].[Sunday Paper]}\n" + "{[Measures].[Unit Sales], [Promotion Media].[Street Handout]}\n" + "{[Measures].[Store Sales], [Promotion Media].[Radio]}\n" + "{[Measures].[Store Sales], [Promotion Media].[TV]}\n" + "{[Measures].[Store Sales], [Promotion Media].[Sunday Paper]}\n" + "{[Measures].[Store Sales], [Promotion Media].[Street Handout]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Drink].[Dairy]}\n" + "Row #0: 75\n" + "Row #0: 76\n" + "Row #0: 148\n" + "Row #0: 158\n" + "Row #0: 168.62\n" + "Row #0: 182.38\n" + "Row #0: 316.88\n" + "Row #0: 294.55\n" + "Row #1: 97\n" + "Row #1: 188\n" + "Row #1: 197\n" + "Row #1: 270\n" + "Row #1: 186.03\n" + "Row #1: 419.14\n" + "Row #1: 399.58\n" + "Row #1: 520.55\n" + "Row #2: 54\n" + "Row #2: 68\n" + "Row #2: 85\n" + "Row #2: 84\n" + "Row #2: 89.03\n" + "Row #2: 119.55\n" + "Row #2: 140.27\n" + "Row #2: 128.32\n"), // 2 new QueryAndResult( "select\n" + " {[Measures].[Unit Sales], [Measures].[Store Sales]} on columns,\n" + " Order([Product].[Product Department].members, [Measures].[Store Sales], DESC) on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Produce]}\n" + "{[Product].[Food].[Snack Foods]}\n" + "{[Product].[Food].[Frozen Foods]}\n" + "{[Product].[Food].[Canned Foods]}\n" + "{[Product].[Food].[Baking Goods]}\n" + "{[Product].[Food].[Dairy]}\n" + "{[Product].[Food].[Deli]}\n" + "{[Product].[Food].[Baked Goods]}\n" + "{[Product].[Food].[Snacks]}\n" + "{[Product].[Food].[Starchy Foods]}\n" + "{[Product].[Food].[Eggs]}\n" + "{[Product].[Food].[Breakfast Foods]}\n" + "{[Product].[Food].[Seafood]}\n" + "{[Product].[Food].[Meat]}\n" + "{[Product].[Food].[Canned Products]}\n" + "{[Product].[Non-Consumable].[Household]}\n" + "{[Product].[Non-Consumable].[Health and Hygiene]}\n" + "{[Product].[Non-Consumable].[Periodicals]}\n" + "{[Product].[Non-Consumable].[Checkout]}\n" + "{[Product].[Non-Consumable].[Carousel]}\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "{[Product].[Drink].[Dairy]}\n" + "Row #0: 37,792\n" + "Row #0: 82,248.42\n" + "Row #1: 30,545\n" + "Row #1: 67,609.82\n" + "Row #2: 26,655\n" + "Row #2: 55,207.50\n" + "Row #3: 19,026\n" + "Row #3: 39,774.34\n" + "Row #4: 20,245\n" + "Row #4: 38,670.41\n" + "Row #5: 12,885\n" + "Row #5: 30,508.85\n" + "Row #6: 12,037\n" + "Row #6: 25,318.93\n" + "Row #7: 7,870\n" + "Row #7: 16,455.43\n" + "Row #8: 6,884\n" + "Row #8: 14,550.05\n" + "Row #9: 5,262\n" + "Row #9: 11,756.07\n" + "Row #10: 4,132\n" + "Row #10: 9,200.76\n" + "Row #11: 3,317\n" + "Row #11: 6,941.46\n" + "Row #12: 1,764\n" + "Row #12: 3,809.14\n" + "Row #13: 1,714\n" + "Row #13: 3,669.89\n" + "Row #14: 1,812\n" + "Row #14: 3,314.52\n" + "Row #15: 27,038\n" + "Row #15: 60,469.89\n" + "Row #16: 16,284\n" + "Row #16: 32,571.86\n" + "Row #17: 4,294\n" + "Row #17: 9,056.76\n" + "Row #18: 1,779\n" + "Row #18: 3,767.71\n" + "Row #19: 841\n" + "Row #19: 1,500.11\n" + "Row #20: 13,573\n" + "Row #20: 27,748.53\n" + "Row #21: 6,838\n" + "Row #21: 14,029.08\n" + "Row #22: 4,186\n" + "Row #22: 7,058.60\n"), // 3 new QueryAndResult( "select\n" + " [Product].[All Products].[Drink].children on columns\n" + "from Sales\n" + "where ([Measures].[Unit Sales], [Promotion Media].[All Media].[Street Handout], [Time].[1997])", "Axis #0:\n" + "{[Measures].[Unit Sales], [Promotion Media].[Street Handout], [Time].[1997]}\n" + "Axis #1:\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Drink].[Dairy]}\n" + "Row #0: 158\n" + "Row #0: 270\n" + "Row #0: 84\n"), // 4 new QueryAndResult( "select\n" + " NON EMPTY CrossJoin([Product].[All Products].[Drink].children, [Customers].[All Customers].[USA].[WA].Children) on rows,\n" + " CrossJoin(\n" + " {[Measures].[Unit Sales], [Measures].[Store Sales]},\n" + " { [Promotion Media].[All Media].[Radio],\n" + " [Promotion Media].[All Media].[TV],\n" + " [Promotion Media].[All Media].[Sunday Paper],\n" + " [Promotion Media].[All Media].[Street Handout] }\n" + " ) on columns\n" + "from Sales\n" + "where ([Time].[1997])", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales], [Promotion Media].[Radio]}\n" + "{[Measures].[Unit Sales], [Promotion Media].[TV]}\n" + "{[Measures].[Unit Sales], [Promotion Media].[Sunday Paper]}\n" + "{[Measures].[Unit Sales], [Promotion Media].[Street Handout]}\n" + "{[Measures].[Store Sales], [Promotion Media].[Radio]}\n" + "{[Measures].[Store Sales], [Promotion Media].[TV]}\n" + "{[Measures].[Store Sales], [Promotion Media].[Sunday Paper]}\n" + "{[Measures].[Store Sales], [Promotion Media].[Street Handout]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Anacortes]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Ballard]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Bellingham]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Bremerton]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Burien]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Everett]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Issaquah]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Kirkland]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Lynnwood]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Marysville]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Olympia]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Port Orchard]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Puyallup]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Redmond]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Renton]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Seattle]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Spokane]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Tacoma]}\n" + "{[Product].[Drink].[Alcoholic Beverages], [Customers].[USA].[WA].[Yakima]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Anacortes]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Ballard]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Bremerton]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Burien]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Edmonds]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Everett]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Issaquah]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Kirkland]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Lynnwood]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Marysville]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Olympia]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Port Orchard]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Puyallup]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Redmond]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Seattle]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Sedro Woolley]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Spokane]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Tacoma]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Walla Walla]}\n" + "{[Product].[Drink].[Beverages], [Customers].[USA].[WA].[Yakima]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Ballard]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Bellingham]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Bremerton]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Burien]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Everett]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Issaquah]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Kirkland]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Lynnwood]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Marysville]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Olympia]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Port Orchard]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Puyallup]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Redmond]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Renton]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Seattle]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Spokane]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Tacoma]}\n" + "{[Product].[Drink].[Dairy], [Customers].[USA].[WA].[Yakima]}\n" + "Row #0: \n" + "Row #0: 2\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 1.14\n" + "Row #0: \n" + "Row #0: \n" + "Row #1: 4\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: 4\n" + "Row #1: 10.40\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: 2.16\n" + "Row #2: \n" + "Row #2: 1\n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: 2.37\n" + "Row #2: \n" + "Row #2: \n" + "Row #3: \n" + "Row #3: \n" + "Row #3: 24\n" + "Row #3: \n" + "Row #3: \n" + "Row #3: \n" + "Row #3: 46.09\n" + "Row #3: \n" + "Row #4: 3\n" + "Row #4: \n" + "Row #4: \n" + "Row #4: 8\n" + "Row #4: 2.10\n" + "Row #4: \n" + "Row #4: \n" + "Row #4: 9.63\n" + "Row #5: 6\n" + "Row #5: \n" + "Row #5: \n" + "Row #5: 5\n" + "Row #5: 8.06\n" + "Row #5: \n" + "Row #5: \n" + "Row #5: 6.21\n" + "Row #6: 3\n" + "Row #6: \n" + "Row #6: \n" + "Row #6: 7\n" + "Row #6: 7.80\n" + "Row #6: \n" + "Row #6: \n" + "Row #6: 15.00\n" + "Row #7: 14\n" + "Row #7: \n" + "Row #7: \n" + "Row #7: \n" + "Row #7: 36.10\n" + "Row #7: \n" + "Row #7: \n" + "Row #7: \n" + "Row #8: 3\n" + "Row #8: \n" + "Row #8: \n" + "Row #8: 16\n" + "Row #8: 10.29\n" + "Row #8: \n" + "Row #8: \n" + "Row #8: 32.20\n" + "Row #9: 3\n" + "Row #9: \n" + "Row #9: \n" + "Row #9: \n" + "Row #9: 10.56\n" + "Row #9: \n" + "Row #9: \n" + "Row #9: \n" + "Row #10: \n" + "Row #10: \n" + "Row #10: 15\n" + "Row #10: 11\n" + "Row #10: \n" + "Row #10: \n" + "Row #10: 34.79\n" + "Row #10: 15.67\n" + "Row #11: \n" + "Row #11: \n" + "Row #11: 7\n" + "Row #11: \n" + "Row #11: \n" + "Row #11: \n" + "Row #11: 17.44\n" + "Row #11: \n" + "Row #12: \n" + "Row #12: \n" + "Row #12: 22\n" + "Row #12: 9\n" + "Row #12: \n" + "Row #12: \n" + "Row #12: 32.35\n" + "Row #12: 17.43\n" + "Row #13: 7\n" + "Row #13: \n" + "Row #13: \n" + "Row #13: 4\n" + "Row #13: 4.77\n" + "Row #13: \n" + "Row #13: \n" + "Row #13: 15.16\n" + "Row #14: 4\n" + "Row #14: \n" + "Row #14: \n" + "Row #14: 4\n" + "Row #14: 3.64\n" + "Row #14: \n" + "Row #14: \n" + "Row #14: 9.64\n" + "Row #15: 2\n" + "Row #15: \n" + "Row #15: \n" + "Row #15: 7\n" + "Row #15: 6.86\n" + "Row #15: \n" + "Row #15: \n" + "Row #15: 8.38\n" + "Row #16: \n" + "Row #16: \n" + "Row #16: \n" + "Row #16: 28\n" + "Row #16: \n" + "Row #16: \n" + "Row #16: \n" + "Row #16: 61.98\n" + "Row #17: \n" + "Row #17: \n" + "Row #17: 3\n" + "Row #17: 4\n" + "Row #17: \n" + "Row #17: \n" + "Row #17: 10.56\n" + "Row #17: 8.96\n" + "Row #18: 6\n" + "Row #18: \n" + "Row #18: \n" + "Row #18: 3\n" + "Row #18: 7.16\n" + "Row #18: \n" + "Row #18: \n" + "Row #18: 8.10\n" + "Row #19: 7\n" + "Row #19: \n" + "Row #19: \n" + "Row #19: \n" + "Row #19: 15.63\n" + "Row #19: \n" + "Row #19: \n" + "Row #19: \n" + "Row #20: 3\n" + "Row #20: \n" + "Row #20: \n" + "Row #20: 13\n" + "Row #20: 6.96\n" + "Row #20: \n" + "Row #20: \n" + "Row #20: 12.22\n" + "Row #21: \n" + "Row #21: \n" + "Row #21: 16\n" + "Row #21: \n" + "Row #21: \n" + "Row #21: \n" + "Row #21: 45.08\n" + "Row #21: \n" + "Row #22: 3\n" + "Row #22: \n" + "Row #22: \n" + "Row #22: 18\n" + "Row #22: 6.39\n" + "Row #22: \n" + "Row #22: \n" + "Row #22: 21.08\n" + "Row #23: \n" + "Row #23: \n" + "Row #23: \n" + "Row #23: 21\n" + "Row #23: \n" + "Row #23: \n" + "Row #23: \n" + "Row #23: 33.22\n" + "Row #24: \n" + "Row #24: \n" + "Row #24: \n" + "Row #24: 9\n" + "Row #24: \n" + "Row #24: \n" + "Row #24: \n" + "Row #24: 22.65\n" + "Row #25: 2\n" + "Row #25: \n" + "Row #25: \n" + "Row #25: 9\n" + "Row #25: 6.80\n" + "Row #25: \n" + "Row #25: \n" + "Row #25: 18.90\n" + "Row #26: 3\n" + "Row #26: \n" + "Row #26: \n" + "Row #26: 9\n" + "Row #26: 1.50\n" + "Row #26: \n" + "Row #26: \n" + "Row #26: 23.01\n" + "Row #27: \n" + "Row #27: \n" + "Row #27: \n" + "Row #27: 22\n" + "Row #27: \n" + "Row #27: \n" + "Row #27: \n" + "Row #27: 50.71\n" + "Row #28: 4\n" + "Row #28: \n" + "Row #28: \n" + "Row #28: \n" + "Row #28: 5.16\n" + "Row #28: \n" + "Row #28: \n" + "Row #28: \n" + "Row #29: \n" + "Row #29: \n" + "Row #29: 20\n" + "Row #29: 14\n" + "Row #29: \n" + "Row #29: \n" + "Row #29: 48.02\n" + "Row #29: 28.80\n" + "Row #30: \n" + "Row #30: \n" + "Row #30: 14\n" + "Row #30: \n" + "Row #30: \n" + "Row #30: \n" + "Row #30: 19.96\n" + "Row #30: \n" + "Row #31: \n" + "Row #31: \n" + "Row #31: 10\n" + "Row #31: 40\n" + "Row #31: \n" + "Row #31: \n" + "Row #31: 26.36\n" + "Row #31: 74.49\n" + "Row #32: 6\n" + "Row #32: \n" + "Row #32: \n" + "Row #32: \n" + "Row #32: 17.01\n" + "Row #32: \n" + "Row #32: \n" + "Row #32: \n" + "Row #33: 4\n" + "Row #33: \n" + "Row #33: \n" + "Row #33: \n" + "Row #33: 2.80\n" + "Row #33: \n" + "Row #33: \n" + "Row #33: \n" + "Row #34: 4\n" + "Row #34: \n" + "Row #34: \n" + "Row #34: \n" + "Row #34: 7.98\n" + "Row #34: \n" + "Row #34: \n" + "Row #34: \n" + "Row #35: \n" + "Row #35: \n" + "Row #35: \n" + "Row #35: 46\n" + "Row #35: \n" + "Row #35: \n" + "Row #35: \n" + "Row #35: 81.71\n" + "Row #36: \n" + "Row #36: \n" + "Row #36: 21\n" + "Row #36: 6\n" + "Row #36: \n" + "Row #36: \n" + "Row #36: 37.93\n" + "Row #36: 14.73\n" + "Row #37: \n" + "Row #37: \n" + "Row #37: 3\n" + "Row #37: \n" + "Row #37: \n" + "Row #37: \n" + "Row #37: 7.92\n" + "Row #37: \n" + "Row #38: 25\n" + "Row #38: \n" + "Row #38: \n" + "Row #38: 3\n" + "Row #38: 51.65\n" + "Row #38: \n" + "Row #38: \n" + "Row #38: 2.34\n" + "Row #39: 3\n" + "Row #39: \n" + "Row #39: \n" + "Row #39: 4\n" + "Row #39: 4.47\n" + "Row #39: \n" + "Row #39: \n" + "Row #39: 9.20\n" + "Row #40: \n" + "Row #40: 1\n" + "Row #40: \n" + "Row #40: \n" + "Row #40: \n" + "Row #40: 1.47\n" + "Row #40: \n" + "Row #40: \n" + "Row #41: \n" + "Row #41: \n" + "Row #41: 15\n" + "Row #41: \n" + "Row #41: \n" + "Row #41: \n" + "Row #41: 18.88\n" + "Row #41: \n" + "Row #42: \n" + "Row #42: \n" + "Row #42: \n" + "Row #42: 3\n" + "Row #42: \n" + "Row #42: \n" + "Row #42: \n" + "Row #42: 3.75\n" + "Row #43: 9\n" + "Row #43: \n" + "Row #43: \n" + "Row #43: 10\n" + "Row #43: 31.41\n" + "Row #43: \n" + "Row #43: \n" + "Row #43: 15.12\n" + "Row #44: 3\n" + "Row #44: \n" + "Row #44: \n" + "Row #44: 3\n" + "Row #44: 7.41\n" + "Row #44: \n" + "Row #44: \n" + "Row #44: 2.55\n" + "Row #45: 3\n" + "Row #45: \n" + "Row #45: \n" + "Row #45: \n" + "Row #45: 1.71\n" + "Row #45: \n" + "Row #45: \n" + "Row #45: \n" + "Row #46: \n" + "Row #46: \n" + "Row #46: \n" + "Row #46: 7\n" + "Row #46: \n" + "Row #46: \n" + "Row #46: \n" + "Row #46: 11.86\n" + "Row #47: \n" + "Row #47: \n" + "Row #47: \n" + "Row #47: 3\n" + "Row #47: \n" + "Row #47: \n" + "Row #47: \n" + "Row #47: 2.76\n" + "Row #48: \n" + "Row #48: \n" + "Row #48: 4\n" + "Row #48: 5\n" + "Row #48: \n" + "Row #48: \n" + "Row #48: 4.50\n" + "Row #48: 7.27\n" + "Row #49: \n" + "Row #49: \n" + "Row #49: 7\n" + "Row #49: \n" + "Row #49: \n" + "Row #49: \n" + "Row #49: 10.01\n" + "Row #49: \n" + "Row #50: \n" + "Row #50: \n" + "Row #50: 5\n" + "Row #50: 4\n" + "Row #50: \n" + "Row #50: \n" + "Row #50: 12.88\n" + "Row #50: 5.28\n" + "Row #51: 2\n" + "Row #51: \n" + "Row #51: \n" + "Row #51: \n" + "Row #51: 2.64\n" + "Row #51: \n" + "Row #51: \n" + "Row #51: \n" + "Row #52: \n" + "Row #52: \n" + "Row #52: \n" + "Row #52: 5\n" + "Row #52: \n" + "Row #52: \n" + "Row #52: \n" + "Row #52: 12.34\n" + "Row #53: \n" + "Row #53: \n" + "Row #53: \n" + "Row #53: 5\n" + "Row #53: \n" + "Row #53: \n" + "Row #53: \n" + "Row #53: 3.41\n" + "Row #54: \n" + "Row #54: \n" + "Row #54: \n" + "Row #54: 4\n" + "Row #54: \n" + "Row #54: \n" + "Row #54: \n" + "Row #54: 2.44\n" + "Row #55: \n" + "Row #55: \n" + "Row #55: 2\n" + "Row #55: \n" + "Row #55: \n" + "Row #55: \n" + "Row #55: 6.92\n" + "Row #55: \n" + "Row #56: 13\n" + "Row #56: \n" + "Row #56: \n" + "Row #56: 7\n" + "Row #56: 23.69\n" + "Row #56: \n" + "Row #56: \n" + "Row #56: 7.07\n"), // 5 new QueryAndResult( "select from Sales\n" + "where ([Measures].[Store Sales], [Time].[1997], [Promotion Media].[All Media].[TV])", "Axis #0:\n" + "{[Measures].[Store Sales], [Time].[1997], [Promotion Media].[TV]}\n" + "7,786.21")); public void testTaglib0() { assertQueryReturns( taglibQueries.get(0).query, taglibQueries.get(0).result); } public void testTaglib1() { assertQueryReturns( taglibQueries.get(1).query, taglibQueries.get(1).result); } public void testTaglib2() { assertQueryReturns( taglibQueries.get(2).query, taglibQueries.get(2).result); } public void testTaglib3() { assertQueryReturns( taglibQueries.get(3).query, taglibQueries.get(3).result); } public void testTaglib4() { assertQueryReturns( taglibQueries.get(4).query, taglibQueries.get(4).result); } public void testTaglib5() { assertQueryReturns( taglibQueries.get(5).query, taglibQueries.get(5).result); } public void testCellValue() { Result result = executeQuery( "select {[Measures].[Unit Sales],[Measures].[Store Sales]} on columns,\n" + " {[Gender].[M]} on rows\n" + "from Sales"); Cell cell = result.getCell(new int[]{0, 0}); Object value = cell.getValue(); assertTrue(value instanceof Number); assertEquals(135215, ((Number) value).intValue()); cell = result.getCell(new int[]{1, 0}); value = cell.getValue(); assertTrue(value instanceof Number); // Plato give 285011.12, Oracle gives 285011, MySQL gives 285964 (bug!) assertEquals(285011, ((Number) value).intValue()); } public void testDynamicFormat() { assertQueryReturns( "with member [Measures].[USales] as [Measures].[Unit Sales],\n" + " format_string = iif([Measures].[Unit Sales] > 50000, \"\\#.00\\<\\/b\\>\", \"\\#.00\\<\\/i\\>\")\n" + "select \n" + " {[Measures].[USales]} on columns,\n" + " {[Store Type].members} on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[USales]}\n" + "Axis #2:\n" + "{[Store Type].[All Store Types]}\n" + "{[Store Type].[Deluxe Supermarket]}\n" + "{[Store Type].[Gourmet Supermarket]}\n" + "{[Store Type].[HeadQuarters]}\n" + "{[Store Type].[Mid-Size Grocery]}\n" + "{[Store Type].[Small Grocery]}\n" + "{[Store Type].[Supermarket]}\n" + "Row #0: 266773.00\n" + "Row #1: 76837.00\n" + "Row #2: 21333.00\n" + "Row #3: \n" + "Row #4: 11491.00\n" + "Row #5: 6557.00\n" + "Row #6: 150555.00\n"); } public void testFormatOfNulls() { assertQueryReturns( "with member [Measures]._Foo as '([Measures].[Store Sales])',\n" + " format_string = '$#,##0.00;($#,##0.00);ZERO;NULL;Nil'\n" + "select\n" + " {[Measures].[_Foo]} on columns,\n" + " {[Customers].[Country].members} on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[_Foo]}\n" + "Axis #2:\n" + "{[Customers].[Canada]}\n" + "{[Customers].[Mexico]}\n" + "{[Customers].[USA]}\n" + "Row #0: NULL\n" + "Row #1: NULL\n" + "Row #2: $565,238.13\n"); // explicit null value assertQueryReturns( "with member [Measures].[Foo] as null,\n" + " format_string = 'a;b;c;d'\n" + "select {[Measures].[Foo]} on columns\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: d\n"); } /** * Test case for bug * MONDRIAN-434, * "Small negative numbers cause exceptions w 2-section format". */ public void testFormatOfNil() { assertQueryReturns( "with member measures.formatTest as '0.000001',\n" + " FORMAT_STRING='#.##;(#.##)' \n" + "select { measures.formatTest } on 0 from sales ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[formatTest]}\n" + "Row #0: .\n"); } /** * If a measure (in this case, [Measures].[Sales Count]) * occurs only within a format expression, bug * MONDRIAN-14. * causes an internal * error ("value not found") when the cell's formatted value is retrieved. */ public void testBugMondrian14() { assertQueryReturns( "with member [Measures].[USales] as '[Measures].[Unit Sales]',\n" + " format_string = iif([Measures].[Sales Count] > 30, \"#.00 good\",\"#.00 bad\")\n" + "select {[Measures].[USales], [Measures].[Store Cost], [Measures].[Store Sales]} ON columns,\n" + " Crossjoin({[Promotion Media].[All Media].[Radio], [Promotion Media].[All Media].[TV], [Promotion Media]. [All Media].[Sunday Paper], [Promotion Media].[All Media].[Street Handout]}, [Product].[All Products].[Drink].Children) ON rows\n" + "from [Sales] where ([Time].[1997])", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[USales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Promotion Media].[Radio], [Product].[Drink].[Alcoholic Beverages]}\n" + "{[Promotion Media].[Radio], [Product].[Drink].[Beverages]}\n" + "{[Promotion Media].[Radio], [Product].[Drink].[Dairy]}\n" + "{[Promotion Media].[TV], [Product].[Drink].[Alcoholic Beverages]}\n" + "{[Promotion Media].[TV], [Product].[Drink].[Beverages]}\n" + "{[Promotion Media].[TV], [Product].[Drink].[Dairy]}\n" + "{[Promotion Media].[Sunday Paper], [Product].[Drink].[Alcoholic Beverages]}\n" + "{[Promotion Media].[Sunday Paper], [Product].[Drink].[Beverages]}\n" + "{[Promotion Media].[Sunday Paper], [Product].[Drink].[Dairy]}\n" + "{[Promotion Media].[Street Handout], [Product].[Drink].[Alcoholic Beverages]}\n" + "{[Promotion Media].[Street Handout], [Product].[Drink].[Beverages]}\n" + "{[Promotion Media].[Street Handout], [Product].[Drink].[Dairy]}\n" + "Row #0: 75.00 bad\n" + "Row #0: 70.40\n" + "Row #0: 168.62\n" + "Row #1: 97.00 good\n" + "Row #1: 75.70\n" + "Row #1: 186.03\n" + "Row #2: 54.00 bad\n" + "Row #2: 36.75\n" + "Row #2: 89.03\n" + "Row #3: 76.00 bad\n" + "Row #3: 70.99\n" + "Row #3: 182.38\n" + "Row #4: 188.00 good\n" + "Row #4: 167.00\n" + "Row #4: 419.14\n" + "Row #5: 68.00 bad\n" + "Row #5: 45.19\n" + "Row #5: 119.55\n" + "Row #6: 148.00 good\n" + "Row #6: 128.97\n" + "Row #6: 316.88\n" + "Row #7: 197.00 good\n" + "Row #7: 161.81\n" + "Row #7: 399.58\n" + "Row #8: 85.00 bad\n" + "Row #8: 54.75\n" + "Row #8: 140.27\n" + "Row #9: 158.00 good\n" + "Row #9: 121.14\n" + "Row #9: 294.55\n" + "Row #10: 270.00 good\n" + "Row #10: 201.28\n" + "Row #10: 520.55\n" + "Row #11: 84.00 bad\n" + "Row #11: 50.26\n" + "Row #11: 128.32\n"); } /** * This bug causes all of the format strings to be the same, because the * required expression [Measures].[Unit Sales] is not in the cache; bug * MONDRIAN-34. */ public void testBugMondrian34() { assertQueryReturns( "with member [Measures].[xxx] as '[Measures].[Store Sales]',\n" + " format_string = IIf([Measures].[Unit Sales] > 100000, \"AAA######.00\",\"BBB###.00\")\n" + "select {[Measures].[xxx]} ON columns,\n" + " {[Product].children} ON rows\n" + "from [Sales] where [Time].[1997]", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[xxx]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: BBB48836.21\n" + "Row #1: AAA409035.59\n" + "Row #2: BBB107366.33\n"); } /** * Tuple as slicer causes {@link ClassCastException}; bug * MONDRIAN-36. */ public void testBugMondrian36() { assertQueryReturns( "select {[Measures].[Unit Sales]} ON columns,\n" + " {[Gender].Children} ON rows\n" + "from [Sales]\n" + "where ([Time].[1997], [Customers])", "Axis #0:\n" + "{[Time].[1997], [Customers].[All Customers]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 131,558\n" + "Row #1: 135,215\n"); } /** * Query with distinct-count measure and no other measures gives * {@link ArrayIndexOutOfBoundsException}; * MONDRIAN-46. */ public void testBugMondrian46() { TestContext.instance().flushSchemaCache(); assertQueryReturns( "select {[Measures].[Customer Count]} ON columns,\n" + " {([Promotion Media].[All Media], [Product].[All Products])} ON rows\n" + "from [Sales]\n" + "where [Time].[1997]", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Promotion Media].[All Media], [Product].[All Products]}\n" + "Row #0: 5,581\n"); } /** * Tests that the "Store" cube is working. * *

The [Fact Count] measure, which is implicitly created because the cube * definition does not include an explicit count measure, is flagged 'not * visible' but is still correctly returned from [Measures].Members. */ public void testStoreCube() { assertQueryReturns( "select {[Measures].members} on columns,\n" + " {[Store Type].members} on rows\n" + "from [Store]" + "where [Store].[USA].[CA]", "Axis #0:\n" + "{[Store].[USA].[CA]}\n" + "Axis #1:\n" + "{[Measures].[Store Sqft]}\n" + "{[Measures].[Grocery Sqft]}\n" + "{[Measures].[Fact Count]}\n" + "Axis #2:\n" + "{[Store Type].[All Store Types]}\n" + "{[Store Type].[Deluxe Supermarket]}\n" + "{[Store Type].[Gourmet Supermarket]}\n" + "{[Store Type].[HeadQuarters]}\n" + "{[Store Type].[Mid-Size Grocery]}\n" + "{[Store Type].[Small Grocery]}\n" + "{[Store Type].[Supermarket]}\n" + "Row #0: 69,764\n" + "Row #0: 44,868\n" + "Row #0: 5\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #2: 23,688\n" + "Row #2: 15,337\n" + "Row #2: 1\n" + "Row #3: \n" + "Row #3: \n" + "Row #3: 1\n" + "Row #4: \n" + "Row #4: \n" + "Row #4: \n" + "Row #5: 22,478\n" + "Row #5: 15,321\n" + "Row #5: 1\n" + "Row #6: 23,598\n" + "Row #6: 14,210\n" + "Row #6: 2\n"); } public void testSchemaLevelTableIsBad() { // todo: } public void testSchemaLevelTableInAnotherHierarchy() { // todo: // // // //
// // // } public void testSchemaLevelWithViewSpecifiesTable() { // todo: // // select * from emp // // // Should get error that tablename is not allowed } public void testSchemaLevelOrdinalInOtherTable() { // todo: // Hierarchy is based upon a join. // Level's name expression is in a different table than its ordinal. } public void testSchemaTopLevelNotUnique() { // todo: // Should get error if the top level of a hierarchy does not have // uniqueNames="true" } /** * Test case for * MONDRIAN-8, * "Problem getting children in hierarchy based on join.". * It happens when getting the children of a member crosses a table * boundary. */ public void testBugMondrian8() { // minimal test case assertQueryReturns( "select {[Measures].[Unit Sales]} ON columns,\n" + "{[Product].[All Products].[Drink].[Beverages].[Drinks].[Flavored Drinks].children} ON rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Excellent]}\n" + "{[Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Fabulous]}\n" + "{[Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Skinner]}\n" + "{[Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Token]}\n" + "{[Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Washington]}\n" + "Row #0: 468\n" + "Row #1: 469\n" + "Row #2: 506\n" + "Row #3: 466\n" + "Row #4: 560\n"); // shorter test case executeQuery( "select {[Measures].[Unit Sales], [Measures].[Store Cost], [Measures].[Store Sales]} ON columns," + "ToggleDrillState({" + "([Promotion Media].[All Media].[Radio], [Product].[All Products].[Drink].[Beverages].[Drinks].[Flavored Drinks])" + "}, {[Product].[All Products].[Drink].[Beverages].[Drinks].[Flavored Drinks]}) ON rows " + "from [Sales] where ([Time].[1997])"); } /** * The bug happened when a cell which was in cache was compared with a cell * which was not in cache. The compare method could not deal with the * {@link RuntimeException} which indicates that the cell is not in cache. */ public void testBug636687() { executeQuery( "select {[Measures].[Unit Sales], [Measures].[Store Cost],[Measures].[Store Sales]} ON columns, " + "Order(" + "{([Store].[All Stores].[USA].[CA], [Product].[All Products].[Drink].[Alcoholic Beverages]), " + "([Store].[All Stores].[USA].[CA], [Product].[All Products].[Drink].[Beverages]), " + "Crossjoin({[Store].[All Stores].[USA].[CA].Children}, {[Product].[All Products].[Drink].[Beverages]}), " + "([Store].[All Stores].[USA].[CA], [Product].[All Products].[Drink].[Dairy]), " + "([Store].[All Stores].[USA].[OR], [Product].[All Products].[Drink].[Alcoholic Beverages]), " + "([Store].[All Stores].[USA].[OR], [Product].[All Products].[Drink].[Beverages]), " + "([Store].[All Stores].[USA].[OR], [Product].[All Products].[Drink].[Dairy]), " + "([Store].[All Stores].[USA].[WA], [Product].[All Products].[Drink].[Alcoholic Beverages]), " + "([Store].[All Stores].[USA].[WA], [Product].[All Products].[Drink].[Beverages]), " + "([Store].[All Stores].[USA].[WA], [Product].[All Products].[Drink].[Dairy])}, " + "[Measures].[Store Cost], BDESC) ON rows " + "from [Sales] " + "where ([Time].[1997])"); executeQuery( "select {[Measures].[Unit Sales], [Measures].[Store Cost], [Measures].[Store Sales]} ON columns, " + "Order(" + "{([Store].[All Stores].[USA].[WA], [Product].[All Products].[Drink].[Beverages]), " + "([Store].[All Stores].[USA].[CA], [Product].[All Products].[Drink].[Beverages]), " + "([Store].[All Stores].[USA].[OR], [Product].[All Products].[Drink].[Beverages]), " + "([Store].[All Stores].[USA].[WA], [Product].[All Products].[Drink].[Alcoholic Beverages]), " + "([Store].[All Stores].[USA].[CA], [Product].[All Products].[Drink].[Alcoholic Beverages]), " + "([Store].[All Stores].[USA].[OR], [Product].[All Products].[Drink].[Alcoholic Beverages]), " + "([Store].[All Stores].[USA].[WA], [Product].[All Products].[Drink].[Dairy]), " + "([Store].[All Stores].[USA].[CA].[San Diego], [Product].[All Products].[Drink].[Beverages]), " + "([Store].[All Stores].[USA].[CA].[Los Angeles], [Product].[All Products].[Drink].[Beverages]), " + "Crossjoin({[Store].[All Stores].[USA].[CA].[Los Angeles]}, {[Product].[All Products].[Drink].[Beverages].Children}), " + "([Store].[All Stores].[USA].[CA].[Beverly Hills], [Product].[All Products].[Drink].[Beverages]), " + "([Store].[All Stores].[USA].[CA], [Product].[All Products].[Drink].[Dairy]), " + "([Store].[All Stores].[USA].[OR], [Product].[All Products].[Drink].[Dairy]), " + "([Store].[All Stores].[USA].[CA].[San Francisco], [Product].[All Products].[Drink].[Beverages])}, " + "[Measures].[Store Cost], BDESC) ON rows " + "from [Sales] " + "where ([Time].[1997])"); } /** * Bug 769114: Internal error ("not found") when executing * Order(TopCount). */ public void testBug769114() { assertQueryReturns( "select {[Measures].[Unit Sales], [Measures].[Store Cost], [Measures].[Store Sales]} ON columns,\n" + " Order(TopCount({[Product].[Product Category].Members}, 10.0, [Measures].[Unit Sales]), [Measures].[Store Sales], ASC) ON rows\n" + "from [Sales]\n" + "where [Time].[1997]", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Baked Goods].[Bread]}\n" + "{[Product].[Food].[Deli].[Meat]}\n" + "{[Product].[Food].[Dairy].[Dairy]}\n" + "{[Product].[Food].[Baking Goods].[Baking Goods]}\n" + "{[Product].[Food].[Baking Goods].[Jams and Jellies]}\n" + "{[Product].[Food].[Canned Foods].[Canned Soup]}\n" + "{[Product].[Food].[Frozen Foods].[Vegetables]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods]}\n" + "{[Product].[Food].[Produce].[Fruit]}\n" + "{[Product].[Food].[Produce].[Vegetables]}\n" + "Row #0: 7,870\n" + "Row #0: 6,564.09\n" + "Row #0: 16,455.43\n" + "Row #1: 9,433\n" + "Row #1: 8,215.81\n" + "Row #1: 20,616.29\n" + "Row #2: 12,885\n" + "Row #2: 12,228.85\n" + "Row #2: 30,508.85\n" + "Row #3: 8,357\n" + "Row #3: 6,123.32\n" + "Row #3: 15,446.69\n" + "Row #4: 11,888\n" + "Row #4: 9,247.29\n" + "Row #4: 23,223.72\n" + "Row #5: 8,006\n" + "Row #5: 6,408.29\n" + "Row #5: 15,966.10\n" + "Row #6: 6,984\n" + "Row #6: 5,885.05\n" + "Row #6: 14,769.82\n" + "Row #7: 30,545\n" + "Row #7: 26,963.34\n" + "Row #7: 67,609.82\n" + "Row #8: 11,767\n" + "Row #8: 10,312.77\n" + "Row #8: 25,816.13\n" + "Row #9: 20,739\n" + "Row #9: 18,048.81\n" + "Row #9: 45,185.41\n"); } /** * Bug 793616: Deeply nested UNION function takes forever to validate. * (Problem was that each argument of a function was validated twice, hence * the validation time was O(2 ^ depth).) */ public void _testBug793616() { if (props.TestExpDependencies.get() > 0) { // Don't run this test if dependency-checking is enabled. // Dependency checking will hugely slow down evaluation, and give // the false impression that the validation performance bug has // returned. return; } final long start = System.currentTimeMillis(); Connection connection = getTestContext().getConnection(); final String queryString = "select {[Measures].[Unit Sales],\n" + " [Measures].[Store Cost],\n" + " [Measures].[Store Sales]} ON columns,\n" + "Hierarchize(Union(Union(Union(Union(Union(Union(Union(Union(Union(Union(Union(Union(Union(Union(Union(Union(Union(Union(Union(Union\n" + "({([Gender].[All Gender],\n" + " [Marital Status].[All Marital Status],\n" + " [Customers].[All Customers],\n" + " [Product].[All Products])},\n" + " Crossjoin ([Gender].[All Gender].Children,\n" + " {([Marital Status].[All Marital Status],\n" + " [Customers].[All Customers],\n" + " [Product].[All Products])})),\n" + " Crossjoin(Crossjoin({[Gender].[All Gender].[F]},\n" + " [Marital Status].[All Marital Status].Children),\n" + " {([Customers].[All Customers],\n" + " [Product].[All Products])})),\n" + " Crossjoin(Crossjoin({([Gender].[All Gender].[F],\n" + " [Marital Status].[All Marital Status].[M])},\n" + " [Customers].[All Customers].Children),\n" + " {[Product].[All Products]})),\n" + " Crossjoin(Crossjoin({([Gender].[All Gender].[F],\n" + " [Marital Status].[All Marital Status].[M])},\n" + " [Customers].[All Customers].[USA].Children),\n" + " {[Product].[All Products]})),\n" + " Crossjoin ({([Gender].[All Gender].[F], [Marital Status].[All Marital Status].[M], [Customers].[All Customers].[USA].[CA])},\n" + " [Product].[All Products].Children)),\n" + " Crossjoin({([Gender].[All Gender].[F], [Marital Status].[All Marital Status].[M], [Customers].[All Customers].[USA].[OR])},\n" + " [Product].[All Products].Children)),\n" + " Crossjoin({([Gender].[All Gender].[F], [Marital Status].[All Marital Status].[M], [Customers].[All Customers].[USA].[WA])},\n" + " [Product].[All Products].Children)),\n" + " Crossjoin ({([Gender].[All Gender].[F], [Marital Status].[All Marital Status].[M], [Customers].[All Customers].[USA])},\n" + " [Product].[All Products].Children)),\n" + " Crossjoin(\n" + " Crossjoin({([Gender].[All Gender].[F], [Marital Status].[All Marital Status].[S])}, [Customers].[All Customers].Children),\n" + " {[Product].[All Products]})),\n" + " Crossjoin(Crossjoin({([Gender].[All Gender].[F],\n" + " [Marital Status].[All Marital Status].[S])},\n" + " [Customers].[All Customers].[USA].Children),\n" + " {[Product].[All Products]})),\n" + " Crossjoin ({([Gender].[All Gender].[F],\n" + " [Marital Status].[All Marital Status].[S],\n" + " [Customers].[All Customers].[USA].[CA])},\n" + " [Product].[All Products].Children)),\n" + " Crossjoin({([Gender].[All Gender].[F],\n" + " [Marital Status].[All Marital Status].[S],\n" + " [Customers].[All Customers].[USA].[OR])},\n" + " [Product].[All Products].Children)),\n" + " Crossjoin({([Gender].[All Gender].[F],\n" + " [Marital Status].[All Marital Status].[S],\n" + " [Customers].[All Customers].[USA].[WA])},\n" + " [Product].[All Products].Children)),\n" + " Crossjoin ({([Gender].[All Gender].[F],\n" + " [Marital Status].[All Marital Status].[S],\n" + " [Customers].[All Customers].[USA])},\n" + " [Product].[All Products].Children)),\n" + " Crossjoin(Crossjoin({([Gender].[All Gender].[F],\n" + " [Marital Status].[All Marital Status])},\n" + " [Customers].[All Customers].Children),\n" + " {[Product].[All Products]})),\n" + " Crossjoin(Crossjoin({([Gender].[All Gender].[F],\n" + " [Marital Status].[All Marital Status])},\n" + " [Customers].[All Customers].[USA].Children),\n" + " {[Product].[All Products]})),\n" + " Crossjoin ({([Gender].[All Gender].[F],\n" + " [Marital Status].[All Marital Status],\n" + " [Customers].[All Customers].[USA].[CA])},\n" + " [Product].[All Products].Children)),\n" + " Crossjoin({([Gender].[All Gender].[F],\n" + " [Marital Status].[All Marital Status],\n" + " [Customers].[All Customers].[USA].[OR])},\n" + " [Product].[All Products].Children)),\n" + " Crossjoin({([Gender].[All Gender].[F],\n" + " [Marital Status].[All Marital Status],\n" + " [Customers].[All Customers].[USA].[WA])},\n" + " [Product].[All Products].Children)),\n" + " Crossjoin ({([Gender].[All Gender].[F],\n" + " [Marital Status].[All Marital Status],\n" + " [Customers].[All Customers].[USA])},\n" + " [Product].[All Products].Children))) ON rows from [Sales] where [Time].[1997]"; Query query = connection.parseQuery(queryString); // If this call took longer than 10 seconds, the performance bug has // probably resurfaced again. final long afterParseMillis = System.currentTimeMillis(); final long afterParseNonDbMillis = afterParseMillis - Util.dbTimeMillis(); final long parseMillis = afterParseMillis - start; assertTrue( "performance problem: parse took " + parseMillis + " milliseconds", parseMillis <= 10000); Result result = connection.execute(query); assertEquals(59, result.getAxes()[1].getPositions().size()); // If this call took longer than 10 seconds, // or 2 seconds exclusing db access, // the performance bug has // probably resurfaced again. final long afterExecMillis = System.currentTimeMillis(); final long afterExecNonDbMillis = afterExecMillis - Util.dbTimeMillis(); final long execNonDbMillis = afterExecNonDbMillis - afterParseNonDbMillis; final long execMillis = (afterExecMillis - afterParseMillis); assertTrue( "performance problem: execute took " + execMillis + " milliseconds, " + execNonDbMillis + " milliseconds excluding db", execNonDbMillis <= 2000 && execMillis <= 30000); } public void testCatalogHierarchyBasedOnView() { // Don't run this test if aggregates are enabled: two levels mapped to // the "gender" column confuse the agg engine. if (props.ReadAggregates.get()) { return; } TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null); if (!testContext.getDialect().allowsFromQuery()) { return; } testContext.assertAxisReturns( "[Gender2].members", "[Gender2].[All Gender]\n" + "[Gender2].[F]\n" + "[Gender2].[M]"); } /** * Run a query against a large hierarchy, to make sure that we can generate * joins correctly. This probably won't work in MySQL. */ public void testCatalogHierarchyBasedOnView2() { // Don't run this test if aggregates are enabled: two levels mapped to // the "gender" column confuse the agg engine. if (props.ReadAggregates.get()) { return; } if (getTestContext().getDialect().allowsFromQuery()) { return; } TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""); testContext.assertQueryReturns( "select {[Measures].[Unit Sales]} on columns,\n" + " {[ProductView].[Drink].[Beverages].children} on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[ProductView].[Drink].[Beverages].[Carbonated Beverages]}\n" + "{[ProductView].[Drink].[Beverages].[Drinks]}\n" + "{[ProductView].[Drink].[Beverages].[Hot Beverages]}\n" + "{[ProductView].[Drink].[Beverages].[Pure Juice Beverages]}\n" + "Row #0: 3,407\n" + "Row #1: 2,469\n" + "Row #2: 4,301\n" + "Row #3: 3,396\n"); } public void testCountDistinct() { assertQueryReturns( "select {[Measures].[Unit Sales], [Measures].[Customer Count]} on columns,\n" + " {[Gender].members} on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 266,773\n" + "Row #0: 5,581\n" + "Row #1: 131,558\n" + "Row #1: 2,755\n" + "Row #2: 135,215\n" + "Row #2: 2,826\n"); } /** * Turn off aggregate caching and run query with both use of aggregate * tables on and off - should result in the same answer. * Note that if the "mondrian.rolap.aggregates.Read" property is not true, * then no aggregate tables is be read in any event. */ public void testCountDistinctAgg() { boolean use_agg_orig = props.UseAggregates.get(); // turn off caching propSaver.set(props.DisableCaching, true); assertQueryReturns( "select {[Measures].[Unit Sales], [Measures].[Customer Count]} on rows,\n" + "NON EMPTY {[Time].[1997].[Q1].[1]} ON COLUMNS\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997].[Q1].[1]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Customer Count]}\n" + "Row #0: 21,628\n" + "Row #1: 1,396\n"); if (use_agg_orig) { propSaver.set(props.UseAggregates, false); } else { propSaver.set(props.UseAggregates, true); } assertQueryReturns( "select {[Measures].[Unit Sales], [Measures].[Customer Count]} on rows,\n" + "NON EMPTY {[Time].[1997].[Q1].[1]} ON COLUMNS\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997].[Q1].[1]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Customer Count]}\n" + "Row #0: 21,628\n" + "Row #1: 1,396\n"); } /** * * There are cross database order issues in this test. * * MySQL and Access show the rows as: * * [Store Size in SQFT].[All Store Size in SQFTs] * [Store Size in SQFT].[null] * [Store Size in SQFT].[] * * Postgres shows: * * [Store Size in SQFT].[All Store Size in SQFTs] * [Store Size in SQFT].[] * [Store Size in SQFT].[null] * * The test failure is due to some inherent differences in the way * Postgres orders NULLs in a result set, * compared with MySQL and Access. * * From the MySQL 4.X manual: * * When doing an ORDER BY, NULL values are presented first if you do * ORDER BY ... ASC and last if you do ORDER BY ... DESC. * * From the Postgres 8.0 manual: * * The null value sorts higher than any other value. In other words, * with ascending sort order, null values sort at the end, and with * descending sort order, null values sort at the beginning. * * Oracle also sorts nulls high by default. * * So, this test has expected results that vary depending on whether * the database is being used sorts nulls high or low. * */ public void testMemberWithNullKey() { if (!isDefaultNullMemberRepresentation()) { return; } Result result = executeQuery( "select {[Measures].[Unit Sales]} on columns,\n" + "{[Store Size in SQFT].members} on rows\n" + "from Sales"); String resultString = TestContext.toString(result); resultString = Pattern.compile("\\.0\\]").matcher(resultString).replaceAll("]"); // The members function hierarchizes its results, so nulls should // sort high, regardless of DBMS. Note that Oracle's driver says that // NULLs sort low, but it's lying. final boolean nullsSortHigh = false; int row = 0; final String expected = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store Size in SQFT].[All Store Size in SQFTs]}\n" // null is at the start in order under DBMSs that sort null low + (!nullsSortHigh ? "{[Store Size in SQFT].[#null]}\n" : "") + "{[Store Size in SQFT].[20319]}\n" + "{[Store Size in SQFT].[21215]}\n" + "{[Store Size in SQFT].[22478]}\n" + "{[Store Size in SQFT].[23112]}\n" + "{[Store Size in SQFT].[23593]}\n" + "{[Store Size in SQFT].[23598]}\n" + "{[Store Size in SQFT].[23688]}\n" + "{[Store Size in SQFT].[23759]}\n" + "{[Store Size in SQFT].[24597]}\n" + "{[Store Size in SQFT].[27694]}\n" + "{[Store Size in SQFT].[28206]}\n" + "{[Store Size in SQFT].[30268]}\n" + "{[Store Size in SQFT].[30584]}\n" + "{[Store Size in SQFT].[30797]}\n" + "{[Store Size in SQFT].[33858]}\n" + "{[Store Size in SQFT].[34452]}\n" + "{[Store Size in SQFT].[34791]}\n" + "{[Store Size in SQFT].[36509]}\n" + "{[Store Size in SQFT].[38382]}\n" + "{[Store Size in SQFT].[39696]}\n" // null is at the end in order for DBMSs that sort nulls high + (nullsSortHigh ? "{[Store Size in SQFT].[#null]}\n" : "") + "Row #" + row++ + ": 266,773\n" + (!nullsSortHigh ? "Row #" + row++ + ": 39,329\n" : "") + "Row #" + row++ + ": 26,079\n" + "Row #" + row++ + ": 25,011\n" + "Row #" + row++ + ": 2,117\n" + "Row #" + row++ + ": \n" + "Row #" + row++ + ": \n" + "Row #" + row++ + ": 25,663\n" + "Row #" + row++ + ": 21,333\n" + "Row #" + row++ + ": \n" + "Row #" + row++ + ": \n" + "Row #" + row++ + ": 41,580\n" + "Row #" + row++ + ": 2,237\n" + "Row #" + row++ + ": 23,591\n" + "Row #" + row++ + ": \n" + "Row #" + row++ + ": \n" + "Row #" + row++ + ": 35,257\n" + "Row #" + row++ + ": \n" + "Row #" + row++ + ": \n" + "Row #" + row++ + ": \n" + "Row #" + row++ + ": \n" + "Row #" + row++ + ": 24,576\n" + (nullsSortHigh ? "Row #" + row++ + ": 39,329\n" : ""); TestContext.assertEqualsVerbose(expected, resultString); } /** * Test case for * MONDRIAN-977, * "NPE in Query with Crossjoin Descendants of Unknown Member". */ public void testCrossjoinWithDescendantsAndUnknownMember() { propSaver.set( MondrianProperties.instance().IgnoreInvalidMembersDuringQuery, true); assertQueryReturns( "select {[Measures].[Unit Sales]} on columns,\n" + "NON EMPTY CrossJoin(\n" + " Descendants([Product].[All Products], [Product].[Product Family]),\n" + " Descendants([Store].[All Stores].[Foo], [Store].[Store State])) on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n"); } /** * Slicer contains [Promotion Media].[Daily Paper], but * filter expression is in terms of [Promotion Media].[Radio]. */ public void testSlicerOverride() { assertQueryReturns( "with member [Measures].[Radio Unit Sales] as \n" + " '([Measures].[Unit Sales], [Promotion Media].[Radio])'\n" + "select {[Measures].[Unit Sales], [Measures].[Radio Unit Sales]} on columns,\n" + " filter([Product].[Product Department].members, [Promotion Media].[Radio] > 50) on rows\n" + "from Sales\n" + "where ([Promotion Media].[Daily Paper], [Time].[1997].[Q1])", "Axis #0:\n" + "{[Promotion Media].[Daily Paper], [Time].[1997].[Q1]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Radio Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Produce]}\n" + "{[Product].[Food].[Snack Foods]}\n" + "Row #0: 692\n" + "Row #0: 87\n" + "Row #1: 447\n" + "Row #1: 63\n"); } public void testMembersOfLargeDimensionTheHardWay() { // Avoid this test if memory is scarce. if (Bug.avoidMemoryOverflow(TestContext.instance().getDialect())) { return; } final Connection connection = TestContext.instance().getConnection(); String queryString = "select {[Measures].[Unit Sales]} on columns,\n" + "{[Customers].members} on rows\n" + "from Sales"; Query query = connection.parseQuery(queryString); Result result = connection.execute(query); assertEquals(10407, result.getAxes()[1].getPositions().size()); } public void testUnparse() { Connection connection = getConnection(); Query query = connection.parseQuery( "with member [Measures].[Rendite] as \n" + " '(([Measures].[Store Sales] - [Measures].[Store Cost])) / [Measures].[Store Cost]',\n" + " format_string = iif(([Measures].[Store Sales] - [Measures].[Store Cost]) / [Measures].[Store Cost] * 100 > \n" + " Parameter (\"UpperLimit\", NUMERIC, 151, \"Obere Grenze\"), \n" + " \"|#.00%|arrow='up'\",\n" + " iif(([Measures].[Store Sales] - [Measures].[Store Cost]) / [Measures].[Store Cost] * 100 < \n" + " Parameter(\"LowerLimit\", NUMERIC, 150, \"Untere Grenze\"),\n" + " \"|#.00%|arrow='down'\",\n" + " \"|#.00%|arrow='right'\"))\n" + "select {[Measures].members} on columns\n" + "from Sales"); final String s = Util.unparse(query); // Parentheses are added to reflect operator precedence, but that's ok. // Note that the doubled parentheses in line #2 of the query have been // reduced to a single level. TestContext.assertEqualsVerbose( "with member [Measures].[Rendite] as '(([Measures].[Store Sales] - [Measures].[Store Cost]) / [Measures].[Store Cost])', " + "format_string = IIf((((([Measures].[Store Sales] - [Measures].[Store Cost]) / [Measures].[Store Cost]) * 100) > Parameter(\"UpperLimit\", NUMERIC, 151, \"Obere Grenze\")), " + "\"|#.00%|arrow='up'\", " + "IIf((((([Measures].[Store Sales] - [Measures].[Store Cost]) / [Measures].[Store Cost]) * 100) < Parameter(\"LowerLimit\", NUMERIC, 150, \"Untere Grenze\")), " + "\"|#.00%|arrow='down'\", \"|#.00%|arrow='right'\"))\n" + "select {[Measures].Members} ON COLUMNS\n" + "from [Sales]\n", s); } public void testUnparse2() { Connection connection = getConnection(); Query query = connection.parseQuery( "with member [Measures].[Foo] as '1', " + "format_string='##0.00', " + "funny=IIf(1=1,\"x\"\"y\",\"foo\") " + "select {[Measures].[Foo]} on columns from Sales"); final String s = query.toString(); // The "format_string" property, a string literal, is now delimited by // double-quotes. This won't work in MSOLAP, but for Mondrian it's // consistent with the fact that property values are expressions, // not enclosed in single-quotes. TestContext.assertEqualsVerbose( "with member [Measures].[Foo] as '1', " + "format_string = \"##0.00\", " + "funny = IIf((1 = 1), \"x\"\"y\", \"foo\")\n" + "select {[Measures].[Foo]} ON COLUMNS\n" + "from [Sales]\n", s); } /** * Basically, the LookupCube function can evaluate a single MDX statement * against a cube other than the cube currently indicated by query context * to retrieve a single string or numeric result. * *

For example, the Budget cube in the FoodMart 2000 database contains * budget information that can be displayed by store. The Sales cube in the * FoodMart 2000 database contains sales information that can be displayed * by store. Since no virtual cube exists in the FoodMart 2000 database that * joins the Sales and Budget cubes together, comparing the two sets of * figures would be difficult at best. * *

Note In many situations a virtual cube can be used to integrate * data from multiple cubes, which will often provide a simpler and more * efficient solution than the LookupCube function. This example uses the * LookupCube function for purposes of illustration. * *

The following MDX query, however, uses the LookupCube function to * retrieve unit sales information for each store from the Sales cube, * presenting it side by side with the budget information from the Budget * cube. */ public void _testLookupCube() { assertQueryReturns( "WITH MEMBER Measures.[Store Unit Sales] AS \n" + " 'LookupCube(\"Sales\", \"(\" + MemberToStr(Store.CurrentMember) + \", Measures.[Unit Sales])\")'\n" + "SELECT\n" + " {Measures.Amount, Measures.[Store Unit Sales]} ON COLUMNS,\n" + " Store.CA.CHILDREN ON ROWS\n" + "FROM Budget", ""); } /** *

Basket analysis is a topic better suited to data mining discussions, * but some basic forms of basket analysis can be handled through the use of * MDX queries. * *

For example, one method of basket analysis groups customers based on * qualification. In the following example, a qualified customer is one who * has more than $10,000 in store sales or more than 10 unit sales. The * following table illustrates such a report, run against the Sales cube in * FoodMart 2000 with qualified customers grouped by the Country and State * Province levels of the Customers dimension. The count and store sales * total of qualified customers is represented by the Qualified Count and * Qualified Sales columns, respectively. * *

To accomplish this basic form of basket analysis, the following MDX * query constructs two calculated members. The first calculated member uses * the MDX Count, Filter, and Descendants functions to create the Qualified * Count column, while the second calculated member uses the MDX Sum, * Filter, and Descendants functions to create the Qualified Sales column. * *

The key to this MDX query is the use of Filter and Descendants * together to screen out non-qualified customers. Once screened out, the * Sum and Count MDX functions can then be used to provide aggregation data * only on qualified customers. */ public void testBasketAnalysis() { assertQueryReturns( "WITH MEMBER [Measures].[Qualified Count] AS\n" + " 'COUNT(FILTER(DESCENDANTS(Customers.CURRENTMEMBER, [Customers].[Name]),\n" + " ([Measures].[Store Sales]) > 10000 OR ([Measures].[Unit Sales]) > 10))'\n" + "MEMBER [Measures].[Qualified Sales] AS\n" + " 'SUM(FILTER(DESCENDANTS(Customers.CURRENTMEMBER, [Customers].[Name]),\n" + " ([Measures].[Store Sales]) > 10000 OR ([Measures].[Unit Sales]) > 10),\n" + " ([Measures].[Store Sales]))'\n" + "SELECT {[Measures].[Qualified Count], [Measures].[Qualified Sales]} ON COLUMNS,\n" + " DESCENDANTS([Customers].[All Customers], [State Province], SELF_AND_BEFORE) ON ROWS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Qualified Count]}\n" + "{[Measures].[Qualified Sales]}\n" + "Axis #2:\n" + "{[Customers].[All Customers]}\n" + "{[Customers].[Canada]}\n" + "{[Customers].[Canada].[BC]}\n" + "{[Customers].[Mexico]}\n" + "{[Customers].[Mexico].[DF]}\n" + "{[Customers].[Mexico].[Guerrero]}\n" + "{[Customers].[Mexico].[Jalisco]}\n" + "{[Customers].[Mexico].[Mexico]}\n" + "{[Customers].[Mexico].[Oaxaca]}\n" + "{[Customers].[Mexico].[Sinaloa]}\n" + "{[Customers].[Mexico].[Veracruz]}\n" + "{[Customers].[Mexico].[Yucatan]}\n" + "{[Customers].[Mexico].[Zacatecas]}\n" + "{[Customers].[USA]}\n" + "{[Customers].[USA].[CA]}\n" + "{[Customers].[USA].[OR]}\n" + "{[Customers].[USA].[WA]}\n" + "Row #0: 4,719.00\n" + "Row #0: 553,587.77\n" + "Row #1: .00\n" + "Row #1: \n" + "Row #2: .00\n" + "Row #2: \n" + "Row #3: .00\n" + "Row #3: \n" + "Row #4: .00\n" + "Row #4: \n" + "Row #5: .00\n" + "Row #5: \n" + "Row #6: .00\n" + "Row #6: \n" + "Row #7: .00\n" + "Row #7: \n" + "Row #8: .00\n" + "Row #8: \n" + "Row #9: .00\n" + "Row #9: \n" + "Row #10: .00\n" + "Row #10: \n" + "Row #11: .00\n" + "Row #11: \n" + "Row #12: .00\n" + "Row #12: \n" + "Row #13: 4,719.00\n" + "Row #13: 553,587.77\n" + "Row #14: 2,149.00\n" + "Row #14: 151,509.69\n" + "Row #15: 1,008.00\n" + "Row #15: 141,899.84\n" + "Row #16: 1,562.00\n" + "Row #16: 260,178.24\n"); } /** * Flushes the cache then runs {@link #testBasketAnalysis}, because this * test has been known to fail when run standalone. */ public void testBasketAnalysisAfterFlush() { TestContext.instance().flushSchemaCache(); testBasketAnalysis(); } /** * How Can I Perform Complex String Comparisons? * *

MDX can handle basic string comparisons, but does not include complex * string comparison and manipulation functions, for example, for finding * 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 (VBA) external function library. * *

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, * 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. * *

The following MDX query demonstrates how to achieve the results * displayed in the previous table. For each member in the Product * dimension, the name of the member is converted to lowercase using the * LCase VBA function. Then, the InStr VBA function is used to discover * whether or not the name contains the word "fruit". This information is * used to then construct a set, using the Filter MDX function, from only * those members from the Product dimension that contain the substring * "fruit" in their names. */ public void testStringComparisons() { assertQueryReturns( "SELECT {Measures.[Unit Sales]} ON COLUMNS,\n" + " FILTER([Product].[Product Name].MEMBERS,\n" + " INSTR(LCASE([Product].CURRENTMEMBER.NAME), \"fruit\") <> 0) ON ROWS \n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Applause].[Applause Canned Mixed Fruit]}\n" + "{[Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Big City].[Big City Canned Mixed Fruit]}\n" + "{[Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Green Ribbon].[Green Ribbon Canned Mixed Fruit]}\n" + "{[Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Swell].[Swell Canned Mixed Fruit]}\n" + "{[Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Toucan].[Toucan Canned Mixed Fruit]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Best Choice].[Best Choice Apple Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Best Choice].[Best Choice Grape Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Best Choice].[Best Choice Raspberry Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Best Choice].[Best Choice Strawberry Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fast].[Fast Apple Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fast].[Fast Grape Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fast].[Fast Raspberry Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fast].[Fast Strawberry Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fort West].[Fort West Apple Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fort West].[Fort West Grape Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fort West].[Fort West Raspberry Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fort West].[Fort West Strawberry Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Horatio].[Horatio Apple Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Horatio].[Horatio Grape Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Horatio].[Horatio Raspberry Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Horatio].[Horatio Strawberry Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Nationeel].[Nationeel Apple Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Nationeel].[Nationeel Grape Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Nationeel].[Nationeel Raspberry Fruit Roll]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Nationeel].[Nationeel Strawberry Fruit Roll]}\n" + "Row #0: 205\n" + "Row #1: 204\n" + "Row #2: 142\n" + "Row #3: 204\n" + "Row #4: 187\n" + "Row #5: 174\n" + "Row #6: 114\n" + "Row #7: 110\n" + "Row #8: 150\n" + "Row #9: 149\n" + "Row #10: 173\n" + "Row #11: 163\n" + "Row #12: 154\n" + "Row #13: 181\n" + "Row #14: 178\n" + "Row #15: 210\n" + "Row #16: 189\n" + "Row #17: 177\n" + "Row #18: 191\n" + "Row #19: 149\n" + "Row #20: 169\n" + "Row #21: 185\n" + "Row #22: 216\n" + "Row #23: 167\n" + "Row #24: 138\n"); } /** * Test case for * MONDRIAN-539, * "Problem with the MID function getting last character in a string.". */ public void testMid() { assertQueryReturns( "with\n" + "member measures.x as 'Mid(\"yahoo\",5, 1)'\n" + "select {measures.x} ON COLUMNS from [Sales] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[x]}\n" + "Row #0: o\n"); } /** * How Can I Show Percentages as Measures? * *

Another common business question easily answered through MDX is the * display of percent values created as available measures. * *

For example, the Sales cube in the FoodMart 2000 database contains * unit sales for each store in a given city, state, and country, organized * along the Sales dimension. A report is requested to show, for * California, the percentage of total unit sales attained by each city * with a store. The results are illustrated in the following table. * *

Because the parent of a member is typically another, aggregated * member in a regular dimension, this is easily achieved by the * construction of a calculated member, as demonstrated in the following MDX * query, using the CurrentMember and Parent MDX functions. */ public void testPercentagesAsMeasures() { assertQueryReturns( // todo: "Store.[USA].[CA]" should be "Store.CA" "WITH MEMBER Measures.[Unit Sales Percent] AS\n" + " '((Store.CURRENTMEMBER, Measures.[Unit Sales]) /\n" + " (Store.CURRENTMEMBER.PARENT, Measures.[Unit Sales])) ',\n" + " FORMAT_STRING = 'Percent'\n" + "SELECT {Measures.[Unit Sales], Measures.[Unit Sales Percent]} ON COLUMNS,\n" + " ORDER(DESCENDANTS(Store.[USA].[CA], Store.[Store City], SELF), \n" + " [Measures].[Unit Sales], ASC) ON ROWS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Unit Sales Percent]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[Alameda]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #1: 2,117\n" + "Row #1: 2.83%\n" + "Row #2: 21,333\n" + "Row #2: 28.54%\n" + "Row #3: 25,635\n" + "Row #3: 34.30%\n" + "Row #4: 25,663\n" + "Row #4: 34.33%\n"); } /** * How Can I Show Cumulative Sums as Measures? * *

Another common business request, cumulative sums, is useful for * business reporting purposes. However, since aggregations are handled in a * hierarchical fashion, cumulative sums present some unique challenges in * Analysis Services. * *

The best way to create a cumulative sum is as a calculated measure in * MDX, using the Rank, Head, Order, and Sum MDX functions together. * *

For example, the following table illustrates a report that shows two * views of employee count in all stores and cities in California, sorted * by employee count. The first column shows the aggregated counts for each * store and city, while the second column shows aggregated counts for each * store, but cumulative counts for each city. * *

The cumulative number of employees for San Diego represents the value * of both Los Angeles and San Diego, the value for Beverly Hills represents * the cumulative total of Los Angeles, San Diego, and Beverly Hills, and so * on. * *

Since the members within the state of California have been ordered * from highest to lowest number of employees, this form of cumulative sum * measure provides a form of pareto analysis within each state. * *

To support this, the Order function is first used to reorder members * accordingly for both the Rank and Head functions. Once reordered, the * Rank function is used to supply the ranking of each tuple within the * reordered set of members, progressing as each member in the Store * dimension is examined. The value is then used to determine the number of * tuples to retrieve from the set of reordered members using the Head * function. Finally, the retrieved members are then added together using * the Sum function to obtain a cumulative sum. The following MDX query * demonstrates how all of this works in concert to provide cumulative * sums. * *

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 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, it would not satisfy the needs of this query. */ public void _testCumlativeSums() { assertQueryReturns( // todo: "[Store].[USA].[CA]" should be "Store.CA"; implement "AS" "WITH MEMBER Measures.[Cumulative No of Employees] AS\n" + " 'SUM(HEAD(ORDER({[Store].Siblings}, [Measures].[Number of Employees], BDESC) AS OrderedSiblings,\n" + " RANK([Store], OrderedSiblings)),\n" + " [Measures].[Number of Employees])'\n" + "SELECT {[Measures].[Number of Employees], [Measures].[Cumulative No of Employees]} ON COLUMNS,\n" + " ORDER(DESCENDANTS([Store].[USA].[CA], [Store State], AFTER), \n" + " [Measures].[Number of Employees], BDESC) ON ROWS\n" + "FROM HR", ""); } /** * How Can I Implement a Logical AND or OR Condition in a WHERE * Clause? * *

For SQL users, the use of AND and OR logical operators in the WHERE * clause of a SQL statement is an essential tool for constructing business * queries. However, the WHERE clause of an MDX statement serves a * slightly different purpose, and understanding how the WHERE clause is * used in MDX can assist in constructing such business queries. * *

The WHERE clause in MDX is used to further restrict the results of * an MDX query, in effect providing another dimension on which the results * of the query are further sliced. As such, only expressions that resolve * to a single tuple are allowed. The WHERE clause implicitly supports a * logical AND operation involving members across different dimensions, by * including the members as part of a tuple. To support logical AND * operations involving members within a single dimensions, as well as * logical OR operations, a calculated member needs to be defined in * addition to the use of the WHERE clause. * *

For example, the following MDX query illustrates the use of a * calculated member to support a logical OR. The query returns unit sales * by quarter and year for all food and drink related products sold in 1997, * run against the Sales cube in the FoodMart 2000 database. * *

The calculated member simply adds the values of the Unit Sales * measure for the Food and the Drink levels of the Product dimension * together. The WHERE clause is then used to restrict return of * information only to the calculated member, effectively implementing a * logical OR to return information for all time periods that contain unit * sales values for either food, drink, or both types of products. * *

You can use the Aggregate function in similar situations where all * measures are not aggregated by summing. To return the same results in the * above example using the Aggregate function, replace the definition for * the calculated member with this definition: * *

* 'Aggregate({[Product].[Food], [Product].[Drink]})' *
*/ public void testLogicalOps() { assertQueryReturns( "WITH MEMBER [Product].[Food OR Drink] AS\n" + " '([Product].[Food], Measures.[Unit Sales]) + ([Product].[Drink], Measures.[Unit Sales])'\n" + "SELECT {Measures.[Unit Sales]} ON COLUMNS,\n" + " DESCENDANTS(Time.[1997], [Quarter], SELF_AND_BEFORE) ON ROWS\n" + "FROM Sales\n" + "WHERE [Product].[Food OR Drink]", "Axis #0:\n" + "{[Product].[Food OR Drink]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q4]}\n" + "Row #0: 216,537\n" + "Row #1: 53,785\n" + "Row #2: 50,720\n" + "Row #3: 53,505\n" + "Row #4: 58,527\n"); } /** *

A logical AND, by contrast, can be supported by using two different * techniques. If the members used to construct the logical AND reside on * different dimensions, all that is required is a WHERE clause that uses * a tuple representing all involved members. The following MDX query uses a * WHERE clause that effectively restricts the query to retrieve unit * sales for drink products in the USA, shown by quarter and year for 1997. */ public void testLogicalAnd() { assertQueryReturns( "SELECT {Measures.[Unit Sales]} ON COLUMNS,\n" + " DESCENDANTS([Time].[1997], [Quarter], SELF_AND_BEFORE) ON ROWS\n" + "FROM Sales\n" + "WHERE ([Product].[Drink], [Store].USA)", "Axis #0:\n" + "{[Product].[Drink], [Store].[USA]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q4]}\n" + "Row #0: 24,597\n" + "Row #1: 5,976\n" + "Row #2: 5,895\n" + "Row #3: 6,065\n" + "Row #4: 6,661\n"); } /** *

The WHERE clause in the previous MDX query effectively provides a * logical AND operator, in which all unit sales for 1997 are returned only * for drink products and only for those sold in stores in the USA. * *

If the members used to construct the logical AND condition reside on * the same dimension, you can use a calculated member or a named set to * filter out the unwanted members, as demonstrated in the following MDX * query. * *

The named set, [Good AND Pearl Stores], restricts the displayed unit * sales totals only to those stores that have sold both Good products and * Pearl products. */ public void _testSet() { assertQueryReturns( "WITH SET [Good AND Pearl Stores] AS\n" + " 'FILTER(Store.Members,\n" + " ([Product].[Good], Measures.[Unit Sales]) > 0 AND \n" + " ([Product].[Pearl], Measures.[Unit Sales]) > 0)'\n" + "SELECT DESCENDANTS([Time].[1997], [Quarter], SELF_AND_BEFORE) ON COLUMNS,\n" + " [Good AND Pearl Stores] ON ROWS\n" + "FROM Sales", ""); } /** * How Can I Use Custom Member Properties in MDX? * *

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 * typical MDX query. * *

Member properties can be retrieved in one of two ways. The easiest * and most used method of retrieving member properties is to use the * DIMENSION PROPERTIES MDX statement when constructing an axis in an MDX * query. * *

For example, a member property in the Store dimension in the FoodMart * 2000 database details the total square feet for each store. The following * MDX query can retrieve this member property as part of the returned * cellset. */ public void testCustomMemberProperties() { assertQueryReturns( "SELECT {[Measures].[Units Shipped], [Measures].[Units Ordered]} ON COLUMNS,\n" + " NON EMPTY [Store].[Store Name].MEMBERS\n" + " DIMENSION PROPERTIES [Store].[Store Name].[Store Sqft] ON ROWS\n" + "FROM Warehouse", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Units Shipped]}\n" + "{[Measures].[Units Ordered]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Store].[USA].[WA].[Bellingham].[Store 2]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "{[Store].[USA].[WA].[Walla Walla].[Store 22]}\n" + "{[Store].[USA].[WA].[Yakima].[Store 23]}\n" + "Row #0: 10759.0\n" + "Row #0: 11699.0\n" + "Row #1: 24587.0\n" + "Row #1: 26463.0\n" + "Row #2: 23835.0\n" + "Row #2: 26270.0\n" + "Row #3: 1696.0\n" + "Row #3: 1875.0\n" + "Row #4: 8515.0\n" + "Row #4: 9109.0\n" + "Row #5: 32393.0\n" + "Row #5: 35797.0\n" + "Row #6: 2348.0\n" + "Row #6: 2454.0\n" + "Row #7: 22734.0\n" + "Row #7: 24610.0\n" + "Row #8: 24110.0\n" + "Row #8: 26703.0\n" + "Row #9: 11889.0\n" + "Row #9: 12828.0\n" + "Row #10: 32411.0\n" + "Row #10: 35930.0\n" + "Row #11: 1860.0\n" + "Row #11: 2074.0\n" + "Row #12: 10589.0\n" + "Row #12: 11426.0\n"); } /** *

The drawback to using the DIMENSION PROPERTIES statement is that, * for most client applications, the member property is not readily * apparent. If the previous MDX query is executed in the MDX sample * application shipped with SQL Server 2000 Analysis Services, for example, * you must double-click the name of the member in the grid to open the * Member Properties dialog box, which displays all of the member properties * shipped as part of the cellset, including the [Store].[Store Name].[Store * Sqft] member property. * *

The other method of retrieving member properties involves the creation * of a calculated member based on the member property. The following MDX * query brings back the total square feet for each store as a measure, * included in the COLUMNS axis. * *

The [Store SqFt] measure is constructed with the Properties MDX * function to retrieve the [Store SQFT] member property for each member in * the Store dimension. The benefit to this technique is that the calculated * member is readily apparent and easily accessible in client applications * that do not support member properties. */ public void _testMemberPropertyAsCalcMember() { assertQueryReturns( // todo: implement .PROPERTIES "WITH MEMBER Measures.[Store SqFt] AS '[Store].CURRENTMEMBER.PROPERTIES(\"Store SQFT\")'\n" + "SELECT { [Measures].[Store SQFT], [Measures].[Units Shipped], [Measures].[Units Ordered] } ON COLUMNS,\n" + " [Store].[Store Name].MEMBERS ON ROWS\n" + "FROM Warehouse", ""); } /** * How Can I Drill Down More Than One Level Deep, or Skip Levels When * Drilling Down? * *

Drilling down is an essential ability for most OLAP products, and * Analysis Services is no exception. Several functions exist that support * drilling up and down the hierarchy of dimensions within a cube. * Typically, drilling up and down the hierarchy is done one level at a * time; think of this functionality as a zoom feature for OLAP data. * *

There are times, though, when the need to drill down more than one * level at the same time, or even skip levels when displaying information * about multiple levels, exists for a business scenario. * *

For example, you would like to show report results from a query of * the Sales cube in the FoodMart 2000 sample database showing sales totals * for individual cities and the subtotals for each country, as shown in the * following table. * *

The Customers dimension, however, has Country, State Province, and * City levels. In order to show the above report, you would have to show * the Country level and then drill down two levels to show the City * level, skipping the State Province level entirely. * *

However, the MDX ToggleDrillState and DrillDownMember functions * provide drill down functionality only one level below a specified set. To * drill down more than one level below a specified set, you need to use a * combination of MDX functions, including Descendants, Generate, and * Except. This technique essentially constructs a large set that includes * all levels between both upper and lower desired levels, then uses a * smaller set representing the undesired level or levels to remove the * appropriate members from the larger set. * *

The MDX Descendants function is used to construct a set consisting of * the descendants of each member in the Customers dimension. The * descendants are determined using the MDX Descendants function, with the * descendants of the City level and the level above, the State Province * level, for each member of the Customers dimension being added to the * set. * *

The MDX Generate function now creates a set consisting of all members * at the Country level as well as the members of the set generated by the * MDX Descendants function. Then, the MDX Except function is used to * exclude all members at the State Province level, so the returned set * contains members at the Country and City levels. * *

Note, however, that the previous MDX query will still order the * members according to their hierarchy. Although the returned set contains * members at the Country and City levels, the Country, State Province, * and City levels determine the order of the members. */ public void _testDrillingDownMoreThanOneLevel() { assertQueryReturns( // todo: implement "GENERATE" "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " EXCEPT(GENERATE([Customers].[Country].MEMBERS,\n" + " {DESCENDANTS([Customers].CURRENTMEMBER, [Customers].[City], SELF_AND_BEFORE)}),\n" + " {[Customers].[State Province].MEMBERS}) ON ROWS\n" + "FROM Sales", ""); } /** * How Do I Get the Topmost Members of a Level Broken Out by an Ancestor * Level? * *

This type of MDX query is common when only the facts for the lowest * level of a dimension within a cube are needed, but information about * other levels within the same dimension may also be required to satisfy a * specific business scenario. * *

For example, a report that shows the unit sales for the store with * the highest unit sales from each country is needed for marketing * purposes. The following table provides an example of this report, run * against the Sales cube in the FoodMart 2000 sample database. * *

This looks simple enough, but the Country Name column provides * unexpected difficulty. The values for the Store Country column are taken * from the Store Country level of the Store dimension, so the Store * Country column is constructed as a calculated member as part of the MDX * query, using the MDX Ancestor and Name functions to return the country * names for each store. * *

A combination of the MDX Generate, TopCount, and Descendants * functions are used to create a set containing the top stores in unit * sales for each country. */ public void _testTopmost() { assertQueryReturns( // todo: implement "GENERATE" "WITH MEMBER Measures.[Country Name] AS \n" + " 'Ancestor(Store.CurrentMember, [Store Country]).Name'\n" + "SELECT {Measures.[Country Name], Measures.[Unit Sales]} ON COLUMNS,\n" + " GENERATE([Store Country].MEMBERS, \n" + " TOPCOUNT(DESCENDANTS([Store].CURRENTMEMBER, [Store].[Store Name]),\n" + " 1, [Measures].[Unit Sales])) ON ROWS\n" + "FROM Sales", ""); } /** *

The MDX Descendants function is used to construct a set consisting of * only those members at the Store Name level in the Store dimension. Then, * the MDX TopCount function is used to return only the topmost store based * on the Unit Sales measure. The MDX Generate function then constructs a * set based on the topmost stores, following the hierarchy of the Store * dimension. * *

Alternate techniques, such as using the MDX Crossjoin function, may * not provide the desired results because non-related joins can occur. * Since the Store Country and Store Name levels are within the same * dimension, they cannot be cross-joined. Another dimension that provides * the same regional hierarchy structure, such as the Customers dimension, * can be employed with the Crossjoin function. But, using this technique * can cause non-related joins and return unexpected results. * *

For example, the following MDX query uses the Crossjoin function to * attempt to return the same desired results. * *

However, some unexpected surprises occur because the topmost member * in the Store dimension is cross-joined with all of the children of the * Customers dimension, as shown in the following table. * *

In this instance, the use of a calculated member to provide store * country names is easier to understand and debug than attempting to * cross-join across unrelated members */ public void testTopmost2() { assertQueryReturns( "SELECT {Measures.[Unit Sales]} ON COLUMNS,\n" + " CROSSJOIN(Customers.CHILDREN,\n" + " TOPCOUNT(DESCENDANTS([Store].CURRENTMEMBER, [Store].[Store Name]),\n" + " 1, [Measures].[Unit Sales])) ON ROWS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Customers].[Canada], [Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Customers].[Mexico], [Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Customers].[USA], [Store].[USA].[OR].[Salem].[Store 13]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: 41,580\n"); } /** * How Can I Rank or Reorder Members? * *

One of the issues commonly encountered in business scenarios is the * need to rank the members of a dimension according to their corresponding * measure values. The Order MDX function allows you to order a set based on * a string or numeric expression evaluated against the members of a set. * Combined with other MDX functions, the Order function can support * several different types of ranking. * *

For example, the Sales cube in the FoodMart 2000 database can be used * to show unit sales for each store. However, the business scenario * requires a report that ranks the stores from highest to lowest unit * sales, individually, of nonconsumable products. * *

Because of the requirement that stores be sorted individually, the * hierarchy must be broken (in other words, ignored) for the purpose of * ranking the stores. The Order function is capable of sorting within the * hierarchy, based on the topmost level represented in the set to be * sorted, or, by breaking the hierarchy, sorting all of the members of the * set as if they existed on the same level, with the same parent. * *

The following MDX query illustrates the use of the Order function to * rank the members according to unit sales. */ public void testRank() { assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS, \n" + " ORDER([Store].[Store Name].MEMBERS, (Measures.[Unit Sales]), BDESC) ON ROWS\n" + "FROM Sales\n" + "WHERE [Product].[Non-Consumable]", "Axis #0:\n" + "{[Product].[Non-Consumable]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16]}\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[WA].[Yakima].[Store 23]}\n" + "{[Store].[USA].[WA].[Bellingham].[Store 2]}\n" + "{[Store].[USA].[WA].[Walla Walla].[Store 22]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "{[Store].[Canada].[BC].[Vancouver].[Store 19]}\n" + "{[Store].[Canada].[BC].[Victoria].[Store 20]}\n" + "{[Store].[Mexico].[DF].[Mexico City].[Store 9]}\n" + "{[Store].[Mexico].[DF].[San Andres].[Store 21]}\n" + "{[Store].[Mexico].[Guerrero].[Acapulco].[Store 1]}\n" + "{[Store].[Mexico].[Jalisco].[Guadalajara].[Store 5]}\n" + "{[Store].[Mexico].[Veracruz].[Orizaba].[Store 10]}\n" + "{[Store].[Mexico].[Yucatan].[Merida].[Store 8]}\n" + "{[Store].[Mexico].[Zacatecas].[Camacho].[Store 4]}\n" + "{[Store].[Mexico].[Zacatecas].[Hidalgo].[Store 12]}\n" + "{[Store].[Mexico].[Zacatecas].[Hidalgo].[Store 18]}\n" + "{[Store].[USA].[CA].[Alameda].[HQ]}\n" + "Row #0: 7,940\n" + "Row #1: 6,712\n" + "Row #2: 5,076\n" + "Row #3: 4,947\n" + "Row #4: 4,706\n" + "Row #5: 4,639\n" + "Row #6: 4,479\n" + "Row #7: 4,428\n" + "Row #8: 3,950\n" + "Row #9: 2,140\n" + "Row #10: 442\n" + "Row #11: 390\n" + "Row #12: 387\n" + "Row #13: \n" + "Row #14: \n" + "Row #15: \n" + "Row #16: \n" + "Row #17: \n" + "Row #18: \n" + "Row #19: \n" + "Row #20: \n" + "Row #21: \n" + "Row #22: \n" + "Row #23: \n" + "Row #24: \n"); } /** * How Can I Use Different Calculations for Different Levels in a * Dimension? * *

This type of MDX query frequently occurs when different aggregations * are needed at different levels in a dimension. One easy way to support * such functionality is through the use of a calculated measure, created as * part of the query, which uses the MDX Descendants function in conjunction * with one of the MDX aggregation functions to provide results. * *

For example, the Warehouse cube in the FoodMart 2000 database * supplies the [Units Ordered] measure, aggregated through the Sum * function. But, you would also like to see the average number of units * ordered per store. The following table demonstrates the desired results. * *

By using the following MDX query, the desired results can be * achieved. The calculated measure, [Average Units Ordered], supplies the * average number of ordered units per store by using the Avg, * CurrentMember, and Descendants MDX functions. */ public void testDifferentCalculationsForDifferentLevels() { assertQueryReturns( "WITH MEMBER Measures.[Average Units Ordered] AS\n" + " 'AVG(DESCENDANTS([Store].CURRENTMEMBER, [Store].[Store Name]), [Measures].[Units Ordered])',\n" + " FORMAT_STRING='#.00'\n" + "SELECT {[Measures].[Units ordered], Measures.[Average Units Ordered]} ON COLUMNS,\n" + " [Store].[Store State].MEMBERS ON ROWS\n" + "FROM Warehouse", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Units Ordered]}\n" + "{[Measures].[Average Units Ordered]}\n" + "Axis #2:\n" + "{[Store].[Canada].[BC]}\n" + "{[Store].[Mexico].[DF]}\n" + "{[Store].[Mexico].[Guerrero]}\n" + "{[Store].[Mexico].[Jalisco]}\n" + "{[Store].[Mexico].[Veracruz]}\n" + "{[Store].[Mexico].[Yucatan]}\n" + "{[Store].[Mexico].[Zacatecas]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #1: \n" + "Row #1: \n" + "Row #2: \n" + "Row #2: \n" + "Row #3: \n" + "Row #3: \n" + "Row #4: \n" + "Row #4: \n" + "Row #5: \n" + "Row #5: \n" + "Row #6: \n" + "Row #6: \n" + "Row #7: 66307.0\n" + "Row #7: 16576.75\n" + "Row #8: 44906.0\n" + "Row #8: 22453.00\n" + "Row #9: 116025.0\n" + "Row #9: 16575.00\n"); } /** *

This calculated measure is more powerful than it seems; if, for * example, you then want to see the average number of units ordered for * beer products in all of the stores in the California area, the following * MDX query can be executed with the same calculated measure. */ public void testDifferentCalculations2() { assertQueryReturns( // todo: "[Store].[USA].[CA]" should be "[Store].CA", // "[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer]" // should be "[Product].[Beer]" "WITH MEMBER Measures.[Average Units Ordered] AS\n" + " 'AVG(DESCENDANTS([Store].CURRENTMEMBER, [Store].[Store Name]), [Measures].[Units Ordered])'\n" + "SELECT {[Measures].[Units ordered], Measures.[Average Units Ordered]} ON COLUMNS,\n" + " [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].CHILDREN ON ROWS\n" + "FROM Warehouse\n" + "WHERE [Store].[USA].[CA]", "Axis #0:\n" + "{[Store].[USA].[CA]}\n" + "Axis #1:\n" + "{[Measures].[Units Ordered]}\n" + "{[Measures].[Average Units Ordered]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Pearl]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Top Measure]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Walrus]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #1: 151.0\n" + "Row #1: 75.5\n" + "Row #2: 95.0\n" + "Row #2: 95.0\n" + "Row #3: \n" + "Row #3: \n" + "Row #4: 211.0\n" + "Row #4: 105.5\n"); } /** * How Can I Use Different Calculations for Different Dimensions? * *

Each measure in a cube uses the same aggregation function across all * dimensions. However, there are times where a different aggregation * function may be needed to represent a measure for reporting purposes. Two * basic cases involve aggregating a single dimension using a different * aggregation function than the one used for other dimensions.

    * *
  • Aggregating minimums, maximums, or averages along a time * dimension
  • * *
  • Aggregating opening and closing period values along a time * dimension
* *

The first case involves some knowledge of the behavior of the time * dimension specified in the cube. For instance, to create a calculated * measure that contains the average, along a time dimension, of measures * aggregated as sums along other dimensions, the average of the aggregated * measures must be taken over the set of averaging time periods, * constructed through the use of the Descendants MDX function. Minimum and * maximum values are more easily calculated through the use of the Min and * Max MDX functions, also combined with the Descendants function. * *

For example, the Warehouse cube in the FoodMart 2000 database * contains information on ordered and shipped inventory; from it, a report * is requested to show the average number of units shipped, by product, to * each store. Information on units shipped is added on a monthly basis, so * the aggregated measure [Units Shipped] is divided by the count of * descendants, at the Month level, of the current member in the Time * dimension. This calculation provides a measure representing the average * number of units shipped per month, as demonstrated in the following MDX * query. */ public void _testDifferentCalculationsForDifferentDimensions() { assertQueryReturns( // todo: implement "NONEMPTYCROSSJOIN" "WITH MEMBER [Measures].[Avg Units Shipped] AS\n" + " '[Measures].[Units Shipped] / \n" + " COUNT(DESCENDANTS([Time].CURRENTMEMBER, [Time].[Month], SELF))'\n" + "SELECT {Measures.[Units Shipped], Measures.[Avg Units Shipped]} ON COLUMNS,\n" + "NONEMPTYCROSSJOIN(Store.CA.Children, Product.MEMBERS) ON ROWS\n" + "FROM Warehouse", ""); } /** *

The second case is easier to resolve, because MDX provides the * OpeningPeriod and ClosingPeriod MDX functions specifically to support * opening and closing period values. * *

For example, the Warehouse cube in the FoodMart 2000 database * contains information on ordered and shipped inventory; from it, a report * is requested to show on-hand inventory at the end of every month. Because * the inventory on hand should equal ordered inventory minus shipped * inventory, the ClosingPeriod MDX function can be used to create a * calculated measure to supply the value of inventory on hand, as * demonstrated in the following MDX query. */ public void _testDifferentCalculationsForDifferentDimensions2() { assertQueryReturns( "WITH MEMBER Measures.[Closing Balance] AS\n" + " '([Measures].[Units Ordered], \n" + " CLOSINGPERIOD([Time].[Month], [Time].CURRENTMEMBER)) -\n" + " ([Measures].[Units Shipped], \n" + " CLOSINGPERIOD([Time].[Month], [Time].CURRENTMEMBER))'\n" + "SELECT {[Measures].[Closing Balance]} ON COLUMNS,\n" + " Product.MEMBERS ON ROWS\n" + "FROM Warehouse", ""); } /** * How Can I Use Date Ranges in MDX? * *

Date ranges are a frequently encountered problem. Business questions * use ranges of dates, but OLAP objects provide aggregated information in * date levels. * *

Using the technique described here, you can establish date ranges in * MDX queries at the level of granularity provided by a time dimension. * Date ranges cannot be established below the granularity of the dimension * without additional information. For example, if the lowest level of a * time dimension represents months, you will not be able to establish a * two-week date range without other information. Member properties can be * added to supply specific dates for members; using such member properties, * you can take advantage of the date and time functions provided by VBA and * Excel external function libraries to establish date ranges. * *

The easiest way to specify a static date range is by using the colon * (:) operator. This operator creates a naturally ordered set, using the * members specified on either side of the operator as the endpoints for the * ordered set. For example, to specify the first six months of 1998 from * the Time dimension in FoodMart 2000, the MDX syntax would resemble: * *

[Time].[1998].[1]:[Time].[1998].[6]
* *

For example, the Sales cube uses a time dimension that supports Year, * Quarter, and Month levels. To add a six-month and nine-month total, two * calculated members are created in the following MDX query. */ public void _testDateRange() { assertQueryReturns( // todo: implement "AddCalculatedMembers" "WITH Member [Time].[Time].[1997].[Six Month] AS\n" + " 'SUM([Time].[1]:[Time].[6])'\n" + "Member [Time].[Time].[1997].[Nine Month] AS\n" + " 'SUM([Time].[1]:[Time].[9])'\n" + "SELECT AddCalculatedMembers([Time].[1997].Children) ON COLUMNS,\n" + " [Product].Children ON ROWS\n" + "FROM Sales", ""); } /** * How Can I Use Rolling Date Ranges in MDX? * *

There are several techniques that can be used in MDX to support * rolling date ranges. All of these techniques tend to fall into two * groups. The first group involves the use of relative hierarchical * functions to construct named sets or calculated members, and the second * group involves the use of absolute date functions from external function * libraries to construct named sets or calculated members. Both groups are * applicable in different business scenarios. * *

In the first group of techniques, typically a named set is * constructed which contains a number of periods from a time dimension. For * example, the following table illustrates a 12-month rolling period, in * which the figures for unit sales of the previous 12 months are shown. * *

The following MDX query accomplishes this by using a number of MDX * functions, including LastPeriods, Tail, Filter, Members, and Item, to * construct a named set containing only those members across all other * dimensions that share data with the time dimension at the Month level. * The example assumes that there is at least one measure, such as [Unit * Sales], with a value greater than zero in the current period. The Filter * function creates a set of months with unit sales greater than zero, while * the Tail function returns the last month in this set, the current month. * The LastPeriods function, finally, is then used to retrieve the last 12 * periods at this level, including the current period. */ public void _testRolling() { assertQueryReturns( "WITH SET Rolling12 AS\n" + " 'LASTPERIODS(12, TAIL(FILTER([Time].[Month].MEMBERS, \n" + " ([Customers].[All Customers], \n" + " [Education Level].[All Education Level],\n" + " [Gender].[All Gender],\n" + " [Marital Status].[All Marital Status],\n" + " [Product].[All Products], \n" + " [Promotion Media].[All Media],\n" + " [Promotions].[All Promotions],\n" + " [Store].[All Stores],\n" + " [Store Size in SQFT].[All Store Size in SQFT],\n" + " [Store Type].[All Store Type],\n" + " [Yearly Income].[All Yearly Income],\n" + " Measures.[Unit Sales]) >0),\n" + " 1).ITEM(0).ITEM(0))'\n" + "SELECT {[Measures].[Unit Sales]} ON COLUMNS, \n" + " Rolling12 ON ROWS\n" + "FROM Sales", ""); } /** * How Can I Use Different Calculations for Different Time Periods? * *

A few techniques can be used, depending on the structure of the cube * being queried, to support different calculations for members depending * on the time period. The following example includes the MDX IIf function, * and is easy to use but difficult to maintain. This example works well for * ad hoc queries, but is not the ideal technique for client applications in * a production environment. * *

For example, the following table illustrates a standard and dynamic * 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 sales, while the forecast for July is 260 percent of previous * sales. * *

The most flexible way of handling this type of report is the use of * nested MDX IIf functions to return a multiplier to be used on the members * of the Products dimension, at the Drinks level. The following MDX query * demonstrates this technique. */ public void testDifferentCalcsForDifferentTimePeriods() { assertQueryReturns( // note: "[Product].[Drink Forecast - Standard]" // was "[Drink Forecast - Standard]" "WITH MEMBER [Product].[Drink Forecast - Standard] AS\n" + " '[Product].[All Products].[Drink] * 2'\n" + "MEMBER [Product].[Drink Forecast - Dynamic] AS \n" + " '[Product].[All Products].[Drink] * \n" + " IIF([Time].[Time].CurrentMember.Name = \"1\", 1.2,\n" + " IIF([Time].[Time].CurrentMember.Name = \"2\", 1.3,\n" + " IIF([Time].[Time].CurrentMember.Name = \"3\", 1.4,\n" + " IIF([Time].[Time].CurrentMember.Name = \"4\", 1.6,\n" + " IIF([Time].[Time].CurrentMember.Name = \"5\", 2.1,\n" + " IIF([Time].[Time].CurrentMember.Name = \"6\", 2.4,\n" + " IIF([Time].[Time].CurrentMember.Name = \"7\", 2.6,\n" + " IIF([Time].[Time].CurrentMember.Name = \"8\", 2.3,\n" + " IIF([Time].[Time].CurrentMember.Name = \"9\", 1.9,\n" + " IIF([Time].[Time].CurrentMember.Name = \"10\", 1.5,\n" + " IIF([Time].[Time].CurrentMember.Name = \"11\", 1.4,\n" + " IIF([Time].[Time].CurrentMember.Name = \"12\", 1.2, 1.0))))))))))))'\n" + "SELECT DESCENDANTS(Time.[1997], [Month], SELF) ON COLUMNS, \n" + " {[Product].CHILDREN, [Product].[Drink Forecast - Standard], [Product].[Drink Forecast - Dynamic]} ON ROWS\n" + "FROM Warehouse", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997].[Q1].[1]}\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[5]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q3].[7]}\n" + "{[Time].[1997].[Q3].[8]}\n" + "{[Time].[1997].[Q3].[9]}\n" + "{[Time].[1997].[Q4].[10]}\n" + "{[Time].[1997].[Q4].[11]}\n" + "{[Time].[1997].[Q4].[12]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "{[Product].[Drink Forecast - Standard]}\n" + "{[Product].[Drink Forecast - Dynamic]}\n" + "Row #0: 881.847\n" + "Row #0: 579.051\n" + "Row #0: 476.292\n" + "Row #0: 618.722\n" + "Row #0: 778.886\n" + "Row #0: 636.935\n" + "Row #0: 937.842\n" + "Row #0: 767.332\n" + "Row #0: 920.707\n" + "Row #0: 1,007.764\n" + "Row #0: 820.808\n" + "Row #0: 792.167\n" + "Row #1: 8,383.446\n" + "Row #1: 4,851.406\n" + "Row #1: 5,353.188\n" + "Row #1: 6,061.829\n" + "Row #1: 6,039.282\n" + "Row #1: 5,259.242\n" + "Row #1: 6,902.01\n" + "Row #1: 5,790.772\n" + "Row #1: 8,167.053\n" + "Row #1: 6,188.732\n" + "Row #1: 5,344.845\n" + "Row #1: 5,025.744\n" + "Row #2: 2,040.396\n" + "Row #2: 1,269.816\n" + "Row #2: 1,460.686\n" + "Row #2: 1,696.757\n" + "Row #2: 1,397.035\n" + "Row #2: 1,578.136\n" + "Row #2: 1,671.046\n" + "Row #2: 1,609.447\n" + "Row #2: 2,059.617\n" + "Row #2: 1,617.493\n" + "Row #2: 1,909.713\n" + "Row #2: 1,382.364\n" + "Row #3: 1,763.693\n" + "Row #3: 1,158.102\n" + "Row #3: 952.584\n" + "Row #3: 1,237.444\n" + "Row #3: 1,557.773\n" + "Row #3: 1,273.87\n" + "Row #3: 1,875.685\n" + "Row #3: 1,534.665\n" + "Row #3: 1,841.414\n" + "Row #3: 2,015.528\n" + "Row #3: 1,641.615\n" + "Row #3: 1,584.334\n" + "Row #4: 1,058.216\n" + "Row #4: 752.766\n" + "Row #4: 666.809\n" + "Row #4: 989.955\n" + "Row #4: 1,635.661\n" + "Row #4: 1,528.644\n" + "Row #4: 2,438.39\n" + "Row #4: 1,764.865\n" + "Row #4: 1,749.343\n" + "Row #4: 1,511.646\n" + "Row #4: 1,149.13\n" + "Row #4: 950.601\n"); } /** *

Other techniques, such as the addition of member properties to the * Time or Product dimensions to support such calculations, are not as * flexible but are much more efficient. The primary drawback to using such * techniques is that the calculations are not easily altered for * speculative analysis purposes. For client applications, however, where * the calculations are static or slowly changing, using a member property * is an excellent way of supplying such functionality to clients while * keeping maintenance of calculation variables at the server level. The * same MDX query, for example, could be rewritten to use a member property * named [Dynamic Forecast Multiplier] as shown in the following MDX query. */ public void _testDc4dtp2() { assertQueryReturns( "WITH MEMBER [Product].[Drink Forecast - Standard] AS\n" + " '[Product].[All Products].[Drink] * 2'\n" + "MEMBER [Product].[Drink Forecast - Dynamic] AS \n" + " '[Product].[All Products].[Drink] * \n" + " [Time].CURRENTMEMBER.PROPERTIES(\"Dynamic Forecast Multiplier\")'\n" + "SELECT DESCENDANTS(Time.[1997], [Month], SELF) ON COLUMNS, \n" + " {[Product].CHILDREN, [Drink Forecast - Standard], [Drink Forecast - Dynamic]} ON ROWS\n" + "FROM Warehouse", ""); } public void _testWarehouseProfit() { assertQueryReturns( "select \n" + "{[Measures].[Warehouse Cost], [Measures].[Warehouse Sales], [Measures].[Warehouse Profit]}\n" + " ON COLUMNS from [Warehouse]", ""); } /** * How Can I Compare Time Periods in MDX? * *

To answer such a common business question, MDX provides a number of * functions specifically designed to navigate and aggregate information * across time periods. For example, year-to-date (YTD) totals are directly * supported through the YTD function in MDX. In combination with the MDX * ParallelPeriod function, you can create calculated members to support * direct comparison of totals across time periods. * *

For example, the following table represents a comparison of YTD unit * sales between 1997 and 1998, run against the Sales cube in the FoodMart * 2000 database. * *

The following MDX query uses three calculated members to illustrate * how to use the YTD and ParallelPeriod functions in combination to compare * time periods. */ public void _testYtdGrowth() { assertQueryReturns( // todo: implement "ParallelPeriod" "WITH MEMBER [Measures].[YTD Unit Sales] AS\n" + " 'COALESCEEMPTY(SUM(YTD(), [Measures].[Unit Sales]), 0)'\n" + "MEMBER [Measures].[Previous YTD Unit Sales] AS\n" + " '(Measures.[YTD Unit Sales], PARALLELPERIOD([Time].[Year]))'\n" + "MEMBER [Measures].[YTD Growth] AS\n" + " '[Measures].[YTD Unit Sales] - ([Measures].[Previous YTD Unit Sales])'\n" + "SELECT {[Time].[1998]} ON COLUMNS,\n" + " {[Measures].[YTD Unit Sales], [Measures].[Previous YTD Unit Sales], [Measures].[YTD Growth]} ON ROWS\n" + "FROM Sales ", ""); } /* * takes quite long */ public void dont_testParallelMutliple() { for (int i = 0; i < 5; i++) { runParallelQueries(1, 1, false); runParallelQueries(3, 2, false); runParallelQueries(4, 6, true); runParallelQueries(6, 10, false); } } public void dont_testParallelNot() { runParallelQueries(1, 1, false); } public void dont_testParallelSomewhat() { runParallelQueries(3, 2, false); } public void dont_testParallelFlushCache() { runParallelQueries(4, 6, true); } public void dont_testParallelVery() { runParallelQueries(6, 10, false); } private void runParallelQueries( final int threadCount, final int iterationCount, final boolean flush) { // 10 minute per query long timeoutMs = (long) threadCount * iterationCount * 600 * 1000; final int[] executeCount = new int[] {0}; final List queries = new ArrayList(); queries.addAll(Arrays.asList(sampleQueries)); queries.addAll(taglibQueries); TestCaseForker threaded = new TestCaseForker( this, timeoutMs, threadCount, new ChooseRunnable() { public void run(int i) { for (int j = 0; j < iterationCount; j++) { int queryIndex = (i * 2 + j) % queries.size(); try { QueryAndResult query = queries.get(queryIndex); assertQueryReturns(query.query, query.result); if (flush && i == 0) { TestContext.instance().flushSchemaCache(); } synchronized (executeCount) { executeCount[0]++; } } catch (Throwable e) { e.printStackTrace(); throw Util.newInternal( e, "Thread #" + i + " failed while executing query #" + queryIndex); } } } }); threaded.run(); assertEquals( "number of executions", threadCount * iterationCount, executeCount[0]); } /** * Makes sure that the expression * * [Measures].[Unit Sales] / ([Measures].[Unit Sales], [Product].[All * Products]) * * depends on the current member of the Product dimension, although * [Product].[All Products] is referenced from the expression. */ public void testDependsOn() { assertQueryReturns( "with member [Customers].[my] as \n" + " 'Aggregate(Filter([Customers].[City].Members, (([Measures].[Unit Sales] / ([Measures].[Unit Sales], [Product].[All Products])) > 0.1)))' \n" + "select \n" + " {[Measures].[Unit Sales]} ON columns, \n" + " {[Product].[All Products].[Food].[Deli], [Product].[All Products].[Food].[Frozen Foods]} ON rows \n" + "from [Sales] \n" + "where ([Customers].[my], [Time].[1997])\n", "Axis #0:\n" + "{[Customers].[my], [Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Deli]}\n" + "{[Product].[Food].[Frozen Foods]}\n" + "Row #0: 13\n" + "Row #1: 15,111\n"); } /** * Testcase for bug 1755778, "CrossJoin / Filter query returns null row in * result set" * * @throws Exception on error */ public void testFilterWithCrossJoin() throws Exception { String queryWithFilter = "WITH SET [#DataSet#] AS 'Filter(Crossjoin({[Store].[All Stores]}, {[Customers].[All Customers]}), " + "[Measures].[Unit Sales] > 5)' " + "MEMBER [Customers].[#GT#] as 'Aggregate({[#DataSet#]})' " + "MEMBER [Store].[#GT#] as 'Aggregate({[#DataSet#]})' " + "SET [#GrandTotalSet#] as 'Crossjoin({[Store].[#GT#]}, {[Customers].[#GT#]})' " + "SELECT {[Measures].[Unit Sales]} " + "on columns, Union([#GrandTotalSet#], Hierarchize({[#DataSet#]})) on rows FROM [Sales]"; String queryWithoutFilter = "WITH SET [#DataSet#] AS 'Crossjoin({[Store].[All Stores]}, {[Customers].[All Customers]})' " + "SET [#GrandTotalSet#] as 'Crossjoin({[Store].[All Stores]}, {[Customers].[All Customers]})' " + "SELECT {[Measures].[Unit Sales]} on columns, Union([#GrandTotalSet#], Hierarchize({[#DataSet#]})) " + "on rows FROM [Sales]"; String wrongResultWithFilter = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[#GT#], [Customers].[#GT#]}\n" + "Row #0: \n"; String expectedResultWithFilter = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[#GT#], [Customers].[#GT#]}\n" + "{[Store].[All Stores], [Customers].[All Customers]}\n" + "Row #0: 266,773\n" + "Row #1: 266,773\n"; String expectedResultWithoutFilter = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[All Stores], [Customers].[All Customers]}\n" + "Row #0: 266,773\n"; // With bug 1755778, the following test below fails because it returns // only row that have a null value (see "wrongResultWithFilter"). // It should return the "expectedResultWithFilter" value. assertQueryReturns(queryWithFilter, expectedResultWithFilter); // To see the test case return the correct result comment out the line // above and uncomment out the lines below following. If a similar // query without the filter is executed (queryWithoutFilter) prior to // running the query with the filter then the correct result set is // returned assertQueryReturns( queryWithoutFilter, expectedResultWithoutFilter); assertQueryReturns( queryWithFilter, expectedResultWithFilter); } /** * This resulted in {@link OutOfMemoryError} when the * BatchingCellReader did not know the values for the tuples that * were used in filters. */ public void testFilteredCrossJoin() { TestContext.instance().flushSchemaCache(); Result result = executeQuery( "select {[Measures].[Store Sales]} on columns,\n" + " NON EMPTY Crossjoin(\n" + " Filter([Customers].[Name].Members,\n" + " (([Measures].[Store Sales],\n" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14],\n" + " [Time].[1997].[Q1].[1]) > 5.0)),\n" + " Filter([Product].[Product Name].Members,\n" + " (([Measures].[Store Sales],\n" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14],\n" + " [Time].[1997].[Q1].[1]) > 5.0))\n" + " ) ON rows\n" + "from [Sales]\n" + "where (\n" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14],\n" + " [Time].[1997].[Q1].[1]\n" + ")\n"); // ok if no OutOfMemoryError occurs Axis a = result.getAxes()[1]; assertEquals(12, a.getPositions().size()); } /** * Tests a query with a CrossJoin so large that we run out of memory unless * we can push down evaluation to SQL. */ public void testNonEmptyCrossJoin() { if (!props.EnableNativeCrossJoin.get()) { // If we try to evaluate the crossjoin in memory we run out of // memory. return; } TestContext.instance().flushSchemaCache(); Result result = executeQuery( "select {[Measures].[Store Sales]} on columns,\n" + " NON EMPTY Crossjoin(\n" + " [Customers].[Name].Members,\n" + " [Product].[Product Name].Members\n" + " ) ON rows\n" + "from [Sales]\n" + "where (\n" + " [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14],\n" + " [Time].[1997].[Q1].[1]\n" + ")\n"); // ok if no OutOfMemoryError occurs Axis a = result.getAxes()[1]; assertEquals(67, a.getPositions().size()); } /** * NonEmptyCrossJoin() is not the same as NON EMPTY CrossJoin() * because it's evaluated independently of the other axes. * (see http://blogs.msdn.com/bi_systems/articles/162841.aspx) */ public void testNonEmptyNonEmptyCrossJoin1() { TestContext.instance().flushSchemaCache(); Result result = executeQuery( "select {[Education Level].[All Education Levels].[Graduate Degree]} on columns,\n" + " CrossJoin(\n" + " {[Store Type].[Store Type].members},\n" + " {[Promotions].[Promotion Name].members})\n" + " on rows\n" + "from Sales\n" + "where ([Customers].[All Customers].[USA].[WA].[Anacortes])\n"); Axis a = result.getAxes()[1]; assertEquals(306, a.getPositions().size()); } public void testNonEmptyNonEmptyCrossJoin2() { TestContext.instance().flushSchemaCache(); Result result = executeQuery( "select {[Education Level].[All Education Levels].[Graduate Degree]} on columns,\n" + " NonEmptyCrossJoin(\n" + " {[Store Type].[Store Type].members},\n" + " {[Promotions].[Promotion Name].members})\n" + " on rows\n" + "from Sales\n" + "where ([Customers].[All Customers].[USA].[WA].[Anacortes])\n"); Axis a = result.getAxes()[1]; assertEquals(10, a.getPositions().size()); } public void testNonEmptyNonEmptyCrossJoin3() { TestContext.instance().flushSchemaCache(); Result result = executeQuery( "select {[Education Level].[All Education Levels].[Graduate Degree]} on columns,\n" + " Non Empty CrossJoin(\n" + " {[Store Type].[Store Type].members},\n" + " {[Promotions].[Promotion Name].members})\n" + " on rows\n" + "from Sales\n" + "where ([Customers].[All Customers].[USA].[WA].[Anacortes])\n"); Axis a = result.getAxes()[1]; assertEquals(1, a.getPositions().size()); } public void testNonEmptyNonEmptyCrossJoin4() { TestContext.instance().flushSchemaCache(); Result result = executeQuery( "select {[Education Level].[All Education Levels].[Graduate Degree]} on columns,\n" + " Non Empty NonEmptyCrossJoin(\n" + " {[Store Type].[Store Type].members},\n" + " {[Promotions].[Promotion Name].members})\n" + " on rows\n" + "from Sales\n" + "where ([Customers].[All Customers].[USA].[WA].[Anacortes])\n"); Axis a = result.getAxes()[1]; assertEquals(1, a.getPositions().size()); } /** * description of this testcase: * A calculated member is created on the time.month level. * On Hierarchize this member is compared to a month. * The month has a numeric key, while the calculated members * key type is string. * No exeception must be thrown. */ public void testHierDifferentKeyClass() { Result result = executeQuery( "with member [Time].[Time].[1997].[Q1].[xxx] as\n" + "'Aggregate({[Time].[1997].[Q1].[1], [Time].[1997].[Q1].[2]})'\n" + "select {[Measures].[Unit Sales], [Measures].[Store Cost],\n" + "[Measures].[Store Sales]} ON columns,\n" + "Hierarchize(Union(Union({[Time].[1997], [Time].[1998],\n" + "[Time].[1997].[Q1].[xxx]}, [Time].[1997].Children),\n" + "[Time].[1997].[Q1].Children)) ON rows from [Sales]"); Axis a = result.getAxes()[1]; assertEquals(10, a.getPositions().size()); } /** * Bug #1005995 - many totals of various dimensions */ public void testOverlappingCalculatedMembers() { assertQueryReturns( "WITH MEMBER [Store].[Total] AS 'SUM([Store].[Store Country].MEMBERS)' " + "MEMBER [Store Type].[Total] AS 'SUM([Store Type].[Store Type].MEMBERS)' " + "MEMBER [Gender].[Total] AS 'SUM([Gender].[Gender].MEMBERS)' " + "MEMBER [Measures].[x] AS '[Measures].[Store Sales]' " + "SELECT {[Measures].[x]} ON COLUMNS , " + "{ ([Store].[Total], [Store Type].[Total], [Gender].[Total]) } ON ROWS " + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[x]}\n" + "Axis #2:\n" + "{[Store].[Total], [Store Type].[Total], [Gender].[Total]}\n" + "Row #0: 565,238.13\n"); } /** * the following query raised a classcast exception because * an empty property evaluated as "NullMember" * note: Store "HQ" does not have a "Store Manager" */ public void testEmptyProperty() { assertQueryReturns( "select {[Measures].[Unit Sales]} on columns, " + "filter([Store].[Store Name].members," + "[Store].currentmember.properties(\"Store Manager\")=\"Smith\") on rows" + " from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[WA].[Bellingham].[Store 2]}\n" + "Row #0: 2,237\n"); } /** * This test modifies the Sales cube to contain both the regular usage * of the [Store] shared dimension, and another usage called [Other Store] * which is connected to the [Unit Sales] column */ public void _testCubeWhichUsesSameSharedDimTwice() { // Create a second usage of the "Store" shared dimension called "Other // Store". Attach it to the "unit_sales" column (which has values [1, // 6] whereas store has values [1, 24]. TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", ""); Axis axis = testContext.executeAxis("[Other Store].members"); assertEquals(63, axis.getPositions().size()); axis = testContext.executeAxis("[Store].members"); assertEquals(63, axis.getPositions().size()); final String q1 = "select {[Measures].[Unit Sales]} on columns,\n" + " NON EMPTY {[Other Store].members} on rows\n" + "from [Sales]"; testContext.assertQueryReturns( q1, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Other Store].[All Other Stores]}\n" + "{[Other Store].[Mexico]}\n" + "{[Other Store].[USA]}\n" + "{[Other Store].[Mexico].[Guerrero]}\n" + "{[Other Store].[Mexico].[Jalisco]}\n" + "{[Other Store].[Mexico].[Zacatecas]}\n" + "{[Other Store].[USA].[CA]}\n" + "{[Other Store].[USA].[WA]}\n" + "{[Other Store].[Mexico].[Guerrero].[Acapulco]}\n" + "{[Other Store].[Mexico].[Jalisco].[Guadalajara]}\n" + "{[Other Store].[Mexico].[Zacatecas].[Camacho]}\n" + "{[Other Store].[USA].[CA].[Beverly Hills]}\n" + "{[Other Store].[USA].[WA].[Bellingham]}\n" + "{[Other Store].[USA].[WA].[Bremerton]}\n" + "{[Other Store].[Mexico].[Guerrero].[Acapulco].[Store 1]}\n" + "{[Other Store].[Mexico].[Jalisco].[Guadalajara].[Store 5]}\n" + "{[Other Store].[Mexico].[Zacatecas].[Camacho].[Store 4]}\n" + "{[Other Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Other Store].[USA].[WA].[Bellingham].[Store 2]}\n" + "{[Other Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "Row #0: 266,773\n" + "Row #1: 110,822\n" + "Row #2: 155,951\n" + "Row #3: 1,827\n" + "Row #4: 14,915\n" + "Row #5: 94,080\n" + "Row #6: 222\n" + "Row #7: 155,729\n" + "Row #8: 1,827\n" + "Row #9: 14,915\n" + "Row #10: 94,080\n" + "Row #11: 222\n" + "Row #12: 39,362\n" + "Row #13: 116,367\n" + "Row #14: 1,827\n" + "Row #15: 14,915\n" + "Row #16: 94,080\n" + "Row #17: 222\n" + "Row #18: 39,362\n" + "Row #19: 116,367\n"); final String q2 = "select {[Measures].[Unit Sales]} on columns,\n" + " CrossJoin(\n" + " {[Store].[USA], [Store].[USA].[CA], [Store].[USA].[OR].[Portland]}, \n" + " {[Other Store].[USA], [Other Store].[USA].[CA], [Other Store].[USA].[OR].[Portland]}) on rows\n" + "from [Sales]"; testContext.assertQueryReturns( q2, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA], [Other Store].[USA]}\n" + "{[Store].[USA], [Other Store].[USA].[CA]}\n" + "{[Store].[USA], [Other Store].[USA].[OR].[Portland]}\n" + "{[Store].[USA].[CA], [Other Store].[USA]}\n" + "{[Store].[USA].[CA], [Other Store].[USA].[CA]}\n" + "{[Store].[USA].[CA], [Other Store].[USA].[OR].[Portland]}\n" + "{[Store].[USA].[OR].[Portland], [Other Store].[USA]}\n" + "{[Store].[USA].[OR].[Portland], [Other Store].[USA].[CA]}\n" + "{[Store].[USA].[OR].[Portland], [Other Store].[USA].[OR].[Portland]}\n" + "Row #0: 155,951\n" + "Row #1: 222\n" + "Row #2: \n" + "Row #3: 43,730\n" + "Row #4: 66\n" + "Row #5: \n" + "Row #6: 15,134\n" + "Row #7: 24\n" + "Row #8: \n"); Result result = executeQuery(q2); final Cell cell = result.getCell(new int[] {0, 0}); String sql = cell.getDrillThroughSQL(false); // the following replacement is for databases in ANSI mode // using '"' to quote identifiers sql = sql.replace('"', '`'); String tableQualifier = "as "; final Dialect dialect = getTestContext().getDialect(); if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.ORACLE) { // " + tableQualifier + " tableQualifier = ""; } assertEquals( "select `store`.`store_country` as `Store Country`," + " `time_by_day`.`the_year` as `Year`," + " `store_1`.`store_country` as `x0`," + " `sales_fact_1997`.`unit_sales` as `Unit Sales` " + "from `store` " + tableQualifier + "`store`," + " `sales_fact_1997` " + tableQualifier + "`sales_fact_1997`," + " `time_by_day` " + tableQualifier + "`time_by_day`," + " `store` " + tableQualifier + "`store_1` " + "where `sales_fact_1997`.`store_id` = `store`.`store_id`" + " and `store`.`store_country` = 'USA'" + " and `sales_fact_1997`.`time_id` = `time_by_day`.`time_id`" + " and `time_by_day`.`the_year` = 1997" + " and `sales_fact_1997`.`unit_sales` = `store_1`.`store_id`" + " and `store_1`.`store_country` = 'USA'", sql); } public void testMemberVisibility() { String cubeName = "Sales_MemberVis"; final TestContext testContext = TestContext.instance().create( null, "\n" + "

\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); SchemaReader scr = testContext.getConnection().getSchema().lookupCube( cubeName, true).getSchemaReader(null); Member member = scr.getMemberByUniqueName( Id.Segment.toList( "Measures", "Unit Sales"), true); Object visible = member.getPropertyValue(Property.VISIBLE.name); assertEquals(Boolean.FALSE, visible); member = scr.getMemberByUniqueName( Id.Segment.toList( "Measures", "Store Cost"), true); visible = member.getPropertyValue(Property.VISIBLE.name); assertEquals(Boolean.TRUE, visible); member = scr.getMemberByUniqueName( Id.Segment.toList( "Measures", "Profit"), true); visible = member.getPropertyValue(Property.VISIBLE.name); assertEquals(Boolean.FALSE, visible); } public void testAllMemberCaption() { TestContext testContext = TestContext.instance() .createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + ""); String mdx = "select {[Gender3].[All Gender]} on columns from Sales"; Result result = testContext.executeQuery(mdx); Axis axis0 = result.getAxes()[0]; Position pos0 = axis0.getPositions().get(0); Member allGender = pos0.get(0); String caption = allGender.getCaption(); Assert.assertEquals(caption, "Frauen und Maenner"); } public void testAllLevelName() { TestContext testContext = TestContext.instance() .createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + ""); String mdx = "select {[Gender4].[All Gender]} on columns from Sales"; Result result = testContext.executeQuery(mdx); Axis axis0 = result.getAxes()[0]; Position pos0 = axis0.getPositions().get(0); Member allGender = pos0.get(0); String caption = allGender.getLevel().getName(); Assert.assertEquals(caption, "GenderLevel"); } /** * Bug 1250080 caused a dimension with no 'all' member to be constrained * twice. */ public void testDimWithoutAll() { // Create a test context with a new ""Sales_DimWithoutAll" cube, and // which evaluates expressions against that cube. final String schema = TestContext.instance().getSchema( null, "\n" + "
\n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " " + " \n" + " \n" + "", null, null, null, null); TestContext testContext = TestContext.instance() .withSchema(schema) .withCube("Sales_DimWithoutAll"); // the default member of the Gender dimension is the first member testContext.assertExprReturns("[Gender].CurrentMember.Name", "F"); testContext.assertExprReturns("[Product].CurrentMember.Name", "Drink"); // There is no all member. testContext.assertExprThrows( "([Gender].[All Gender], [Measures].[Unit Sales])", "MDX object '[Gender].[All Gender]' not found in cube 'Sales_DimWithoutAll'"); testContext.assertExprThrows( "([Gender].[All Genders], [Measures].[Unit Sales])", "MDX object '[Gender].[All Genders]' not found in cube 'Sales_DimWithoutAll'"); // evaluated in the default context: [Product].[Drink], [Gender].[F] testContext.assertExprReturns("[Measures].[Unit Sales]", "12,202"); // evaluated in the same context: [Product].[Drink], [Gender].[F] testContext.assertExprReturns( "([Gender].[F], [Measures].[Unit Sales])", "12,202"); // evaluated at in the context: [Product].[Drink], [Gender].[M] testContext.assertExprReturns( "([Gender].[M], [Measures].[Unit Sales])", "12,395"); // evaluated in the context: // [Product].[Food].[Canned Foods], [Gender].[F] testContext.assertExprReturns( "([Product].[Food].[Canned Foods], [Measures].[Unit Sales])", "9,407"); testContext.assertExprReturns( "([Product].[Food].[Dairy], [Measures].[Unit Sales])", "6,513"); testContext.assertExprReturns( "([Product].[Drink].[Dairy], [Measures].[Unit Sales])", "1,987"); } /** * If an axis expression is a member, implicitly convert it to a set. */ public void testMemberOnAxis() { assertQueryReturns( "select [Measures].[Sales Count] on 0, non empty [Store].[Store State].members on 1 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 24,442\n" + "Row #1: 21,611\n" + "Row #2: 40,784\n"); } public void testScalarOnAxisFails() { assertQueryThrows( "select [Measures].[Sales Count] + 1 on 0, non empty [Store].[Store State].members on 1 from [Sales]", "Axis 'COLUMNS' expression is not a set"); } /** * It is illegal for a query to have the same dimension on more than * one axis. */ public void testSameDimOnTwoAxesFails() { assertQueryThrows( "select {[Measures].[Unit Sales]} on columns,\n" + " {[Measures].[Store Sales]} on rows\n" + "from [Sales]", "Hierarchy '[Measures]' appears in more than one independent axis"); // as part of a crossjoin assertQueryThrows( "select {[Measures].[Unit Sales]} on columns,\n" + " CrossJoin({[Product].members}," + " {[Measures].[Store Sales]}) on rows\n" + "from [Sales]", "Hierarchy '[Measures]' appears in more than one independent axis"); // as part of a tuple assertQueryThrows( "select CrossJoin(\n" + " {[Product].children},\n" + " {[Measures].[Unit Sales]}) on columns,\n" + " {([Product],\n" + " [Store].CurrentMember)} on rows\n" + "from [Sales]", "Hierarchy '[Product]' appears in more than one independent axis"); // clash between columns and slicer assertQueryThrows( "select {[Measures].[Unit Sales]} on columns,\n" + " {[Store].Members} on rows\n" + "from [Sales]\n" + "where ([Time].[1997].[Q1], [Measures].[Store Sales])", "Hierarchy '[Measures]' appears in more than one independent axis"); // within aggregate is OK executeQuery( "with member [Measures].[West Coast Total] as " + " ' Aggregate({[Store].[USA].[CA], [Store].[USA].[OR], [Store].[USA].[WA]}) ' \n" + "select " + " {[Measures].[Store Sales], \n" + " [Measures].[Unit Sales]} on Columns,\n" + " CrossJoin(\n" + " {[Product].children},\n" + " {[Store].children}) on Rows\n" + "from [Sales]"); } public void _testSetArgToTupleFails() { assertQueryThrows( "select CrossJoin(\n" + " {[Product].children},\n" + " {[Measures].[Unit Sales]}) on columns,\n" + " {([Product],\n" + " [Store].members)} on rows\n" + "from [Sales]", "Dimension '[Product]' appears in more than one independent axis"); } public void _badArgsToTupleFails() { // clash within slicer assertQueryThrows( "select {[Measures].[Unit Sales]} on columns,\n" + " {[Store].Members} on rows\n" + "from [Sales]\n" + "where ([Time].[1997].[Q1], [Product], [Time].[1997].[Q2])", "Dimension '[Time]' more than once in same tuple"); // ditto assertQueryThrows( "select {[Measures].[Unit Sales]} on columns,\n" + " CrossJoin({[Time].[1997].[Q1],\n" + " {[Product]},\n" + " {[Time].[1997].[Q2]}) on rows\n" + "from [Sales]", "Dimension '[Time]' more than once in same tuple"); } public void testNullMember() { if (isDefaultNullMemberRepresentation()) { assertQueryReturns( "SELECT \n" + "{[Measures].[Store Cost]} ON columns, \n" + "{[Store Size in SQFT].[All Store Size in SQFTs].[#null]} ON rows \n" + "FROM [Sales] \n" + "WHERE [Time].[1997]", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Store Cost]}\n" + "Axis #2:\n" + "{[Store Size in SQFT].[#null]}\n" + "Row #0: 33,307.69\n"); } } public void testNullMemberWithOneNonNull() { if (isDefaultNullMemberRepresentation()) { assertQueryReturns( "SELECT \n" + "{[Measures].[Store Cost]} ON columns, \n" + "{[Store Size in SQFT].[All Store Size in SQFTs].[#null]," + "[Store Size in SQFT].[ALL Store Size in SQFTs].[39696]} ON rows \n" + "FROM [Sales] \n" + "WHERE [Time].[1997]", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Store Cost]}\n" + "Axis #2:\n" + "{[Store Size in SQFT].[#null]}\n" + "{[Store Size in SQFT].[39696]}\n" + "Row #0: 33,307.69\n" + "Row #1: 21,121.96\n"); } } /** * Tests whether the agg mgr behaves correctly if a cell request causes * a column to be constrained multiple times. This happens if two levels * map to the same column via the same join-path. If the constraints are * inconsistent, no data will be returned. */ public void testMultipleConstraintsOnSameColumn() { final String cubeName = "Sales_withCities"; final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " " + " \n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select {\n" + " [Customers].[All Customers].[USA],\n" + " [Customers].[All Customers].[USA].[OR],\n" + " [Customers].[All Customers].[USA].[CA],\n" + " [Customers].[All Customers].[USA].[CA].[Altadena],\n" + " [Customers].[All Customers].[USA].[CA].[Burbank],\n" + " [Customers].[All Customers].[USA].[CA].[Burbank].[Alma Son]} ON COLUMNS\n" + "from [" + cubeName + "] \n" + "where ([Cities].[All Cities].[Burbank], [Measures].[Store Sales])", "Axis #0:\n" + "{[Cities].[Burbank], [Measures].[Store Sales]}\n" + "Axis #1:\n" + "{[Customers].[USA]}\n" + "{[Customers].[USA].[OR]}\n" + "{[Customers].[USA].[CA]}\n" + "{[Customers].[USA].[CA].[Altadena]}\n" + "{[Customers].[USA].[CA].[Burbank]}\n" + "{[Customers].[USA].[CA].[Burbank].[Alma Son]}\n" + "Row #0: 6,577.33\n" + "Row #0: \n" + "Row #0: 6,577.33\n" + "Row #0: \n" + "Row #0: 6,577.33\n" + "Row #0: 36.50\n"); } public void testOverrideDimension() { assertQueryReturns( "with member [Gender].[test] as '\n" + " aggregate(\n" + " filter (crossjoin( [Gender].[Gender].members, [Time].[Time].members), \n" + " [time].[Time].CurrentMember = [Time].[1997].[Q1] AND\n" + "[measures].[unit sales] > 50) )\n" + "'\n" + "select \n" + " { [time].[year].members } on 0,\n" + " { [gender].[test] }\n" + " on 1 \n" + "from [sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "{[Time].[1998]}\n" + "Axis #2:\n" + "{[Gender].[test]}\n" + "Row #0: 66,291\n" + "Row #0: 66,291\n"); } public void testBadMeasure1() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + "", null, null, null, null); Throwable throwable = null; try { testContext.assertSimpleQuery(); } 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"); } public void testBadMeasure2() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " unit_sales\n" + " \n" + " \n" + " \n" + "", null, null, null, null); Throwable throwable = null; try { testContext.assertSimpleQuery(); } 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"); } public void testInvalidMembersInQuery() { String mdx = "select {[Measures].[Unit Sales]} on columns,\n" + " {[Time].[1997].[Q1], [Time].[1997].[QTOO]} on rows\n" + "from [Sales]"; String mdx2 = "select {[Measures].[Unit Sales]} on columns,\n" + "nonemptycrossjoin(\n" + "{[Time].[1997].[Q1], [Time].[1997].[QTOO]},\n" + "[Customers].[All Customers].[USA].children) on rows\n" + "from [Sales]"; String mdx3 = "select {[Measures].[Unit Sales]} on columns\n" + "from [Sales]\n" + "where ([Time].[1997].[QTOO])"; // By default, reference to invalid member should cause // query failure. assertQueryThrows( mdx, "MDX object '[Time].[1997].[QTOO]' not found in cube 'Sales'"); assertQueryThrows( mdx3, "MDX object '[Time].[1997].[QTOO]' not found in cube 'Sales'"); // Now set property propSaver.set( props.IgnoreInvalidMembersDuringQuery, true); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1]}\n" + "Row #0: 66,291\n"); // Illegal member in slicer assertQueryReturns( mdx3, "Axis #0:\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: \n"); // Verify that invalid members in query do NOT prevent // usage of native NECJ (LER-5165). propSaver.set( props.AlertNativeEvaluationUnsupported, "ERROR"); assertQueryReturns( mdx2, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1], [Customers].[USA].[CA]}\n" + "{[Time].[1997].[Q1], [Customers].[USA].[OR]}\n" + "{[Time].[1997].[Q1], [Customers].[USA].[WA]}\n" + "Row #0: 16,890\n" + "Row #1: 19,287\n" + "Row #2: 30,114\n"); } public void testMemberOrdinalCaching() { propSaver.set(props.CompareSiblingsByOrderKey, true); // Use a fresh connection to make sure bad member ordinals haven't // been assigned by previous tests. final TestContext context = getTestContext().withFreshConnection(); try { tryMemberOrdinalCaching(context); } finally { context.close(); } } private void tryMemberOrdinalCaching(TestContext context) { // NOTE jvs 20-Feb-2007: If you change the calculated measure // definition below from zero to // [Customers].[Name].currentmember.Properties(\"MEMBER_ORDINAL\"), you // can see that the absolute ordinals returned are incorrect due to bug // 1660383 (http://tinyurl.com/3xb56f). For now, this test just // verifies that the member sorting is correct when using relative // order key rather than absolute ordinal value. If absolute ordinals // get fixed, replace zero with the MEMBER_ORDINAL property. context.assertQueryReturns( "with member [Measures].[o] as 0\n" + "set necj as nonemptycrossjoin(\n" + "[Store].[Store State].members, [Customers].[Name].members)\n" + "select tail(necj,5) on rows,\n" + "{[Measures].[o]} on columns\n" + "from [Sales]\n", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[o]}\n" + "Axis #2:\n" + "{[Store].[USA].[WA], [Customers].[USA].[WA].[Yakima].[Tracy Meyer]}\n" + "{[Store].[USA].[WA], [Customers].[USA].[WA].[Yakima].[Vanessa Thompson]}\n" + "{[Store].[USA].[WA], [Customers].[USA].[WA].[Yakima].[Velma Lykes]}\n" + "{[Store].[USA].[WA], [Customers].[USA].[WA].[Yakima].[William Battaglia]}\n" + "{[Store].[USA].[WA], [Customers].[USA].[WA].[Yakima].[Wilma Fink]}\n" + "Row #0: 0\n" + "Row #1: 0\n" + "Row #2: 0\n" + "Row #3: 0\n" + "Row #4: 0\n"); // The query above primed the cache with bad absolute ordinals; // verify that this doesn't interfere with subsequent queries. context.assertQueryReturns( "with member [Measures].[o] as 0\n" + "select tail([Customers].[Name].members, 5)\n" + "on rows,\n" + "{[Measures].[o]} on columns\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[o]}\n" + "Axis #2:\n" + "{[Customers].[USA].[WA].[Yakima].[Tracy Meyer]}\n" + "{[Customers].[USA].[WA].[Yakima].[Vanessa Thompson]}\n" + "{[Customers].[USA].[WA].[Yakima].[Velma Lykes]}\n" + "{[Customers].[USA].[WA].[Yakima].[William Battaglia]}\n" + "{[Customers].[USA].[WA].[Yakima].[Wilma Fink]}\n" + "Row #0: 0\n" + "Row #1: 0\n" + "Row #2: 0\n" + "Row #3: 0\n" + "Row #4: 0\n"); } public void testCancel() { // the cancel is issued after 2 seconds so the test query needs to // run for at least that long; it will because the query references // a Udf that has a 1 ms sleep in it; and there are enough rows // in the result that the Udf should execute > 2000 times String query = "WITH \n" + " MEMBER [Measures].[Sleepy] \n" + " AS 'SleepUdf([Measures].[Unit Sales])' \n" + "SELECT {[Measures].[Sleepy]} ON COLUMNS,\n" + " {[Product].members} ON ROWS\n" + "FROM [Sales]"; executeAndCancel(query, 2000); } private void executeAndCancel(String queryString, int waitMillis) { final TestContext tc = TestContext.instance().create( null, null, null, null, "", null); Connection connection = tc.getConnection(); final Query query = connection.parseQuery(queryString); final Throwable[] throwables = {null}; if (waitMillis == 0) { // cancel immediately try { query.getStatement().cancel(); } catch (Exception e) { throwables[0] = e; } } else { // Schedule timer to cancel after waitMillis Timer timer = new Timer(true); TimerTask task = new TimerTask() { public void run() { Thread thread = Thread.currentThread(); thread.setName("CancelThread"); try { query.getStatement().cancel(); } catch (Exception e) { throwables[0] = e; } } }; timer.schedule(task, waitMillis); } if (throwables[0] != null) { Assert.fail( "Cancel request failed: " + throwables[0]); } Throwable throwable = null; try { connection.execute(query); } catch (Throwable ex) { throwable = ex; } if (throwables[0] != null) { Assert.fail( "Cancel request failed: " + throwables[0]); } TestContext.checkThrowable(throwable, "canceled"); } public void testQueryTimeout() { // timeout is issued after 2 seconds so the test query needs to // run for at least that long; it will because the query references // a Udf that has a 1 ms sleep in it; and there are enough rows // in the result that the Udf should execute > 2000 times final TestContext tc = TestContext.instance().create( null, null, null, null, "", null); String query = "WITH\n" + " MEMBER [Measures].[Sleepy]\n" + " AS 'SleepUdf([Measures].[Unit Sales])'\n" + "SELECT {[Measures].[Sleepy]} ON COLUMNS,\n" + " {[Product].members} ON ROWS\n" + "FROM [Sales]"; Throwable throwable = null; propSaver.set(props.QueryTimeout, 2); try { tc.executeQuery(query); } catch (Throwable ex) { throwable = ex; } TestContext.checkThrowable( throwable, "Query timeout of 2 seconds reached"); } public void testFormatInheritance() { assertQueryReturns( "with member measures.foo as 'measures.bar' " + "member measures.bar as " + "'measures.profit' select {measures.foo} on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[foo]}\n" + "Row #0: $339,610.90\n"); } public void testFormatInheritanceWithIIF() { assertQueryReturns( "with member measures.foo as 'measures.bar' " + "member measures.bar as " + "'iif(not isempty(measures.profit),measures.profit,null)' " + "select from sales where measures.foo", "Axis #0:\n" + "{[Measures].[foo]}\n" + "$339,610.90"); } /** * For a calulated member picks up the format of first member that has a * format. In this particular case foo will use profit's format, i.e * neither [unit sales] nor [customer count] format is used. */ public void testFormatInheritanceWorksWithFirstFormatItFinds() { assertQueryReturns( "with member measures.foo as 'measures.bar' " + "member measures.bar as " + "'iif(measures.profit>3000,measures.[unit sales],measures.[Customer Count])' " + "select {[Store].[All Stores].[USA].[WA].children} on 0 " + "from sales where measures.foo", "Axis #0:\n" + "{[Measures].[foo]}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "{[Store].[USA].[WA].[Bremerton]}\n" + "{[Store].[USA].[WA].[Seattle]}\n" + "{[Store].[USA].[WA].[Spokane]}\n" + "{[Store].[USA].[WA].[Tacoma]}\n" + "{[Store].[USA].[WA].[Walla Walla]}\n" + "{[Store].[USA].[WA].[Yakima]}\n" + "Row #0: $190.00\n" + "Row #0: $24,576.00\n" + "Row #0: $25,011.00\n" + "Row #0: $23,591.00\n" + "Row #0: $35,257.00\n" + "Row #0: $96.00\n" + "Row #0: $11,491.00\n"); } /** * Test format string values. Previously, a bug meant that string values * were printed as is, never passed through the format string. */ public void testFormatStringAppliedToStringValue() { // "23" as an integer value assertQueryReturns( "with member [Measures].[Test] as '23', FORMAT_STRING = '|<|arrow=\"up\"'\n" + "select [Measures].[Test] on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Test]}\n" + "Row #0: |23|arrow=up\n"); // "23" as a string value: converted to lower case assertQueryReturns( "with member [Measures].[Test] as '\"23\"', FORMAT_STRING = '|<|arrow=\"up\"'\n" + "select [Measures].[Test] on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Test]}\n" + "Row #0: |23|arrow=up\n"); // string value "Foo Bar" -- converted to lower case assertQueryReturns( "with member [Measures].[Test] as '\"Foo \" || \"Bar\"', FORMAT_STRING = '|<|arrow=\"up\"'\n" + "select [Measures].[Test] on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Test]}\n" + "Row #0: |foo bar|arrow=up\n"); } /** * This tests a fix for bug #1603653 */ public void testAvgCastProblem() { assertQueryReturns( "with member measures.bar as " + "'iif(measures.profit>3000,min([Education Level].[Education Level].Members),min([Education Level].[Education Level].Members))' " + "select {[Store].[All Stores].[USA].[WA].children} on 0 " + "from sales where measures.bar", "Axis #0:\n" + "{[Measures].[bar]}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "{[Store].[USA].[WA].[Bremerton]}\n" + "{[Store].[USA].[WA].[Seattle]}\n" + "{[Store].[USA].[WA].[Spokane]}\n" + "{[Store].[USA].[WA].[Tacoma]}\n" + "{[Store].[USA].[WA].[Walla Walla]}\n" + "{[Store].[USA].[WA].[Yakima]}\n" + "Row #0: $95.00\n" + "Row #0: $1,835.00\n" + "Row #0: $1,277.00\n" + "Row #0: $1,434.00\n" + "Row #0: $1,084.00\n" + "Row #0: $129.00\n" + "Row #0: $958.00\n"); } /** * Test format inheritance to pickup format from second measure when the * first does not have one. */ public void testFormatInheritanceUseSecondIfFirstHasNoFormat() { assertQueryReturns( "with member measures.foo as 'measures.bar+measures.blah'" + " member measures.bar as '10'" + " member measures.blah as '20',format_string='$##.###.00' " + "select from sales where measures.foo", "Axis #0:\n" + "{[Measures].[foo]}\n" + "$30.00"); } /** * Tests format inheritance with complex expression to assert that the * format of the first member that has a valid format is used. */ public void testFormatInheritanceUseFirstValid() { assertQueryReturns( "with member measures.foo as '13+31*measures.[Unit Sales]/" + "iif(measures.profit>0,measures.profit,measures.[Customer Count])'" + " select {[Store].[All Stores].[USA].[CA].children} on 0 " + "from sales where measures.foo", "Axis #0:\n" + "{[Measures].[foo]}\n" + "Axis #1:\n" + "{[Store].[USA].[CA].[Alameda]}\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "Row #0: 13\n" + "Row #0: 37\n" + "Row #0: 37\n" + "Row #0: 37\n" + "Row #0: 38\n"); } public void testQueryIterationLimit() { // Query will need to iterate 4*3 times to compute aggregates, // so set iteration limit to 11 String queryString = "With Set [*NATIVE_CJ_SET] as " + "'NonEmptyCrossJoin([*BASE_MEMBERS_Dates], [*BASE_MEMBERS_Stores])' " + "Set [*BASE_MEMBERS_Dates] as '{[Time].[1997].[Q1], [Time].[1997].[Q2], [Time].[1997].[Q3], [Time].[1997].[Q4]}' " + "Set [*GENERATED_MEMBERS_Dates] as 'Generate([*NATIVE_CJ_SET], {[Time].[Time].CurrentMember})' " + "Set [*GENERATED_MEMBERS_Measures] as '{[Measures].[*SUMMARY_METRIC_0]}' " + "Set [*BASE_MEMBERS_Stores] as '{[Store].[USA].[CA], [Store].[USA].[WA], [Store].[USA].[OR]}' " + "Set [*GENERATED_MEMBERS_Stores] as 'Generate([*NATIVE_CJ_SET], {[Store].CurrentMember})' " + "Member [Time].[Time].[*SM_CTX_SEL] as 'Aggregate([*GENERATED_MEMBERS_Dates])' " + "Member [Measures].[*SUMMARY_METRIC_0] as '[Measures].[Unit Sales]/([Measures].[Unit Sales],[Time].[*SM_CTX_SEL])' " + "Member [Time].[Time].[*SUBTOTAL_MEMBER_SEL~SUM] as 'sum([*GENERATED_MEMBERS_Dates])' " + "Member [Store].[*SUBTOTAL_MEMBER_SEL~SUM] as 'sum([*GENERATED_MEMBERS_Stores])' " + "select crossjoin({[Time].[*SUBTOTAL_MEMBER_SEL~SUM]}, {[Store].[*SUBTOTAL_MEMBER_SEL~SUM]}) " + "on columns from [Sales]"; propSaver.set(props.IterationLimit, 11); Throwable throwable = null; try { Connection connection = getConnection(); Query query = connection.parseQuery(queryString); query.setResultStyle(ResultStyle.LIST); connection.execute(query); } catch (Throwable ex) { throwable = ex; } TestContext.checkThrowable( throwable, "Number of iterations exceeded limit of 11"); // make sure the query runs without the limit set propSaver.reset(); executeQuery(queryString); } public void testGetCaptionUsingMemberDotCaption() { assertQueryReturns( "SELECT Filter(Store.allmembers, " + "[store].currentMember.caption = \"USA\") on 0 FROM SALES", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA]}\n" + "Row #0: 266,773\n"); } public void testGetCaptionUsingMemberDotPropertiesCaption() { assertQueryReturns( "SELECT Filter(Store.allmembers, " + "[store].currentMember.properties(\"caption\") = \"USA\") " + "on 0 FROM SALES", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA]}\n" + "Row #0: 266,773\n"); } public void testDefaultMeasureInCube() { TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); String queryWithoutFilter = "select store.members on 0 from " + "DefaultMeasureTesting"; String queryWithDeflaultMeasureFilter = "select store.members on 0 " + "from DefaultMeasureTesting where [measures].[Supply Time]"; assertQueriesReturnSimilarResults( queryWithoutFilter, queryWithDeflaultMeasureFilter, testContext); } public void testDefaultMeasureInCubeForIncorrectMeasureName() { TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); String queryWithoutFilter = "select store.members on 0 from " + "DefaultMeasureTesting"; String queryWithFirstMeasure = "select store.members on 0 " + "from DefaultMeasureTesting where [measures].[Store Invoice]"; assertQueriesReturnSimilarResults( queryWithoutFilter, queryWithFirstMeasure, testContext); } public void testDefaultMeasureInCubeForCaseSensitivity() { TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); String queryWithoutFilter = "select store.members on 0 from " + "DefaultMeasureTesting"; String queryWithFirstMeasure = "select store.members on 0 " + "from DefaultMeasureTesting where [measures].[Store Invoice]"; String queryWithDefaultMeasureFilter = "select store.members on 0 " + "from DefaultMeasureTesting where [measures].[Supply Time]"; if (props.CaseSensitive.get()) { assertQueriesReturnSimilarResults( queryWithoutFilter, queryWithFirstMeasure, testContext); } else { assertQueriesReturnSimilarResults( queryWithoutFilter, queryWithDefaultMeasureFilter, testContext); } } /** * This tests for bug #1706434, * the ability to convert numeric types to logical (boolean) types. */ public void testNumericToLogicalConversion() { assertQueryReturns( "select " + "{[Measures].[Unit Sales]} on columns, " + "Filter(Descendants(" + "[Product].[Food].[Baked Goods].[Bread]), " + "Count([Product].currentMember.children)) on Rows " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Baked Goods].[Bread]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Bagels]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Bagels].[Colony]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Bagels].[Fantastic]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Bagels].[Great]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Bagels].[Modell]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Bagels].[Sphinx]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Muffins]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Muffins].[Colony]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Muffins].[Fantastic]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Muffins].[Great]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Muffins].[Modell]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Muffins].[Sphinx]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Sliced Bread]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Colony]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Fantastic]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Great]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Modell]}\n" + "{[Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Sphinx]}\n" + "Row #0: 7,870\n" + "Row #1: 815\n" + "Row #2: 163\n" + "Row #3: 160\n" + "Row #4: 145\n" + "Row #5: 165\n" + "Row #6: 182\n" + "Row #7: 3,497\n" + "Row #8: 740\n" + "Row #9: 798\n" + "Row #10: 605\n" + "Row #11: 719\n" + "Row #12: 635\n" + "Row #13: 3,558\n" + "Row #14: 737\n" + "Row #15: 815\n" + "Row #16: 638\n" + "Row #17: 653\n" + "Row #18: 715\n"); } public void testRollupQuery() { assertQueryReturns( "SELECT {[Product].[Product Department].MEMBERS} ON AXIS(0),\n" + "{{[Gender].[Gender].MEMBERS}, {[Gender].[All Gender]}} ON AXIS(1)\n" + "FROM [Sales 2] WHERE {[Measures].[Unit Sales]}", "Axis #0:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #1:\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Drink].[Dairy]}\n" + "{[Product].[Food].[Baked Goods]}\n" + "{[Product].[Food].[Baking Goods]}\n" + "{[Product].[Food].[Breakfast Foods]}\n" + "{[Product].[Food].[Canned Foods]}\n" + "{[Product].[Food].[Canned Products]}\n" + "{[Product].[Food].[Dairy]}\n" + "{[Product].[Food].[Deli]}\n" + "{[Product].[Food].[Eggs]}\n" + "{[Product].[Food].[Frozen Foods]}\n" + "{[Product].[Food].[Meat]}\n" + "{[Product].[Food].[Produce]}\n" + "{[Product].[Food].[Seafood]}\n" + "{[Product].[Food].[Snack Foods]}\n" + "{[Product].[Food].[Snacks]}\n" + "{[Product].[Food].[Starchy Foods]}\n" + "{[Product].[Non-Consumable].[Carousel]}\n" + "{[Product].[Non-Consumable].[Checkout]}\n" + "{[Product].[Non-Consumable].[Health and Hygiene]}\n" + "{[Product].[Non-Consumable].[Household]}\n" + "{[Product].[Non-Consumable].[Periodicals]}\n" + "Axis #2:\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "{[Gender].[All Gender]}\n" + "Row #0: 3,439\n" + "Row #0: 6,776\n" + "Row #0: 1,987\n" + "Row #0: 3,771\n" + "Row #0: 9,841\n" + "Row #0: 1,821\n" + "Row #0: 9,407\n" + "Row #0: 867\n" + "Row #0: 6,513\n" + "Row #0: 5,990\n" + "Row #0: 2,001\n" + "Row #0: 13,011\n" + "Row #0: 841\n" + "Row #0: 18,713\n" + "Row #0: 947\n" + "Row #0: 14,936\n" + "Row #0: 3,459\n" + "Row #0: 2,696\n" + "Row #0: 368\n" + "Row #0: 887\n" + "Row #0: 7,841\n" + "Row #0: 13,278\n" + "Row #0: 2,168\n" + "Row #1: 3,399\n" + "Row #1: 6,797\n" + "Row #1: 2,199\n" + "Row #1: 4,099\n" + "Row #1: 10,404\n" + "Row #1: 1,496\n" + "Row #1: 9,619\n" + "Row #1: 945\n" + "Row #1: 6,372\n" + "Row #1: 6,047\n" + "Row #1: 2,131\n" + "Row #1: 13,644\n" + "Row #1: 873\n" + "Row #1: 19,079\n" + "Row #1: 817\n" + "Row #1: 15,609\n" + "Row #1: 3,425\n" + "Row #1: 2,566\n" + "Row #1: 473\n" + "Row #1: 892\n" + "Row #1: 8,443\n" + "Row #1: 13,760\n" + "Row #1: 2,126\n" + "Row #2: 6,838\n" + "Row #2: 13,573\n" + "Row #2: 4,186\n" + "Row #2: 7,870\n" + "Row #2: 20,245\n" + "Row #2: 3,317\n" + "Row #2: 19,026\n" + "Row #2: 1,812\n" + "Row #2: 12,885\n" + "Row #2: 12,037\n" + "Row #2: 4,132\n" + "Row #2: 26,655\n" + "Row #2: 1,714\n" + "Row #2: 37,792\n" + "Row #2: 1,764\n" + "Row #2: 30,545\n" + "Row #2: 6,884\n" + "Row #2: 5,262\n" + "Row #2: 841\n" + "Row #2: 1,779\n" + "Row #2: 16,284\n" + "Row #2: 27,038\n" + "Row #2: 4,294\n"); } /** * Tests for bug #1630754. In Mondrian 2.2.2 the SqlTupleReader.readTuples * method would create a SQL having an in-clause with more that 1000 * entities under some circumstances. This exceeded the limit for Oracle * resulting in an ORA-01795 error. */ public void testBug1630754() { // In order to reproduce this bug a dimension with 2 levels with more // than 1000 member each was necessary. The customer_id column has more // than 1000 distinct members so it was used for this test. TestContext testContext = TestContext.instance() .createSubstitutingCube( "Sales", " \n" + " \n" + "
\n" + " " + " \n" + " \n" + " "); Result result = testContext.executeQuery( "WITH SET [#DataSet#] AS " + " 'NonEmptyCrossjoin({Descendants([Customer_2].[All Customers], 2)}, " + " {[Product].[All Products]})' " + "SELECT {[Measures].[Unit Sales], [Measures].[Store Sales]} on columns, " + "Hierarchize({[#DataSet#]}) on rows FROM [Sales]"); final int rowCount = result.getAxes()[1].getPositions().size(); assertEquals(5581, rowCount); } /** * Tests a query which uses filter and crossjoin. This query caused * problems when the retrowoven version of mondrian was used in jdk1.5, * specifically a {@link ClassCastException} trying to cast a {@link List} * to a {@link Iterable}. */ public void testNonEmptyCrossjoinFilter() { String desiredResult = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[All Products], [Time].[1997].[Q2].[5]}\n" + "Row #0: 21,081\n"; assertQueryReturns( "select NON EMPTY {[Measures].[Unit Sales]} ON COLUMNS,\n" + "NON EMPTY Crossjoin(" + " {Product.[All Products]},\n" + " Filter(" + " Descendants(Time.[Time], [Time].[Month]), " + " Time.[Time].CurrentMember.Name = '5')) ON ROWS\n" + "from [Sales] ", desiredResult); assertQueryReturns( "select NON EMPTY {[Measures].[Unit Sales]} ON COLUMNS,\n" + "NON EMPTY Filter(" + " Crossjoin(" + " {Product.[All Products]},\n" + " Descendants(Time.[Time], [Time].[Month]))," + " Time.[Time].CurrentMember.Name = '5') ON ROWS\n" + "from [Sales] ", desiredResult); } public void testDuplicateAxisFails() { assertQueryThrows( "select [Gender].Members on columns," + " [Measures].Members on columns " + "from [Sales]", "Duplicate axis name 'COLUMNS'."); } public void testInvalidAxisFails() { assertQueryThrows( "select [Gender].Members on 0," + " [Measures].Members on 10 " + "from [Sales]", "Axis numbers specified in a query must be sequentially specified," + " and cannot contain gaps. Axis 1 (ROWS) is missing."); assertQueryThrows( "select [Gender].Members on columns," + " [Measures].Members on foobar\n" + "from [Sales]", "Syntax error at line 1, column 59, token 'foobar'"); assertQueryThrows( "select [Gender].Members on columns," + " [Measures].Members on slicer\n" + "from [Sales]", "Syntax error at line 1, column 59, token 'slicer'"); assertQueryThrows( "select [Gender].Members on columns," + " [Measures].Members on filter\n" + "from [Sales]", "Syntax error at line 1, column 59, token 'filter'"); } /** * Tests various ways to sum the properties of the descendants of a member, * inspired by forum post * summing * properties. */ public void testSummingProperties() { final String expected = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA]}\n" + "{[Store].[USA].[CA]}\n" + "Axis #2:\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 131,558\n" + "Row #0: 36,759\n" + "Row #1: 135,215\n" + "Row #1: 37,989\n"; assertQueryReturns( "with member [Measures].[Sum Sqft] as '" + "sum(" + " Descendants([Store].CurrentMember, [Store].Levels(5))," + " [Store].CurrentMember.Properties(\"Store Sqft\")) '\n" + "select {[Store].[USA], [Store].[USA].[CA]} on 0,\n" + " [Gender].Children on 1\n" + "from [Sales]", expected); // same query, except get level by name not ordinal, should give same // result assertQueryReturns( "with member [Measures].[Sum Sqft] as '" + "sum(" + " Descendants([Store].CurrentMember, [Store].Levels(\"Store Name\"))," + " [Store].CurrentMember.Properties(\"Store Sqft\")) '\n" + "select {[Store].[USA], [Store].[USA].[CA]} on 0,\n" + " [Gender].Children on 1\n" + "from [Sales]", expected); // same query, except level is hard-coded; same result again assertQueryReturns( "with member [Measures].[Sum Sqft] as '" + "sum(" + " Descendants([Store].CurrentMember, [Store].[Store Name])," + " [Store].CurrentMember.Properties(\"Store Sqft\")) '\n" + "select {[Store].[USA], [Store].[USA].[CA]} on 0,\n" + " [Gender].Children on 1\n" + "from [Sales]", expected); // same query, except using the level-less form of the DESCENDANTS // function; same result again assertQueryReturns( "with member [Measures].[Sum Sqft] as '" + "sum(" + " Descendants([Store].CurrentMember, , LEAVES)," + " [Store].CurrentMember.Properties(\"Store Sqft\")) '\n" + "select {[Store].[USA], [Store].[USA].[CA]} on 0,\n" + " [Gender].Children on 1\n" + "from [Sales]", expected); } public void testIifWithTupleFirstAndMemberNextWithMeasure() { assertQueryReturns( "WITH\n" + "MEMBER [Gender].agg " + "AS 'IIF(1=1, ([Gender].[All Gender],measures.[unit sales])," + "([Gender].[All Gender]))', SOLVE_ORDER = 4 " + "SELECT {[Measures].[unit sales]} ON 0, " + "{{[Gender].[Gender].MEMBERS},{([Gender].agg)}} on 1 FROM sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "{[Gender].[agg]}\n" + "Row #0: 131,558\n" + "Row #1: 135,215\n" + "Row #2: 266,773\n"); } public void testIifWithMemberFirstAndTupleNextWithMeasure() { assertQueryReturns( "WITH\n" + "MEMBER [Gender].agg " + "AS 'IIF(1=1, ([Gender].[All Gender])," + "([Gender].[All Gender],measures.[unit sales]))', SOLVE_ORDER = 4 " + "SELECT {[Measures].[unit sales]} ON 0, " + "{{[Gender].[Gender].MEMBERS},{([Gender].agg)}} on 1 FROM sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "{[Gender].[agg]}\n" + "Row #0: 131,558\n" + "Row #1: 135,215\n" + "Row #2: 266,773\n"); } public void testIifWithMemberFirstAndTupleNextWithoutMeasure() { assertQueryReturns( "WITH\n" + "MEMBER [Gender].agg " + "AS 'IIF(1=1, ([Gender].[All Gender])," + "([Gender].[All Gender],[Time].[1997]))', SOLVE_ORDER = 4 " + "SELECT {[Measures].[unit sales]} ON 0, " + "{{[Gender].[Gender].MEMBERS},{([Gender].agg)}} on 1 FROM sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "{[Gender].[agg]}\n" + "Row #0: 131,558\n" + "Row #1: 135,215\n" + "Row #2: 266,773\n"); } public void testIifWithTupleFirstAndMemberNextWithoutMeasure() { assertQueryReturns( "WITH\n" + "MEMBER [Gender].agg " + "AS 'IIF(1=1, " + "([Store].[All Stores].[USA], [Gender].[All Gender]), " + "([Gender].[All Gender]))', " + "SOLVE_ORDER = 4 " + "SELECT {[Measures].[unit sales]} ON 0, " + "{([Gender].agg)} on 1 FROM sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[agg]}\n" + "Row #0: 266,773\n"); } public void testIifWithTuplesOfUnequalSizes() { assertQueryReturns( "WITH\n" + "MEMBER [Gender].agg " + "AS 'IIF(Measures.currentMember is [Measures].[Unit Sales], " + "([Store].[All Stores],[Gender].[All Gender],measures.[unit sales])," + "([Store].[All Stores],[Gender].[All Gender]))', SOLVE_ORDER = 4 " + "SELECT {[Measures].[unit sales]} ON 0, " + "{{[Gender].[Gender].MEMBERS},{([Gender].agg)}} on 1 FROM sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "{[Gender].[agg]}\n" + "Row #0: 131,558\n" + "Row #1: 135,215\n" + "Row #2: 266,773\n"); } public void testIifWithTuplesOfUnequalSizesAndOrder() { assertQueryReturns( "WITH\n" + "MEMBER [Gender].agg " + "AS 'IIF(Measures.currentMember is [Measures].[Unit Sales], " + "([Store].[All Stores],[Gender].[M],measures.[unit sales])," + "([Gender].[M],[Store].[All Stores]))', SOLVE_ORDER = 4 " + "SELECT {[Measures].[unit sales]} ON 0, " + "{{[Gender].[Gender].MEMBERS},{([Gender].agg)}} on 1 FROM sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "{[Gender].[agg]}\n" + "Row #0: 131,558\n" + "Row #1: 135,215\n" + "Row #2: 135,215\n"); } public void testEmptyAggregationListDueToFilterDoesNotThrowException() { propSaver.set(props.IgnoreMeasureForNonJoiningDimension, true); assertQueryReturns( "WITH \n" + "MEMBER [GENDER].[AGG] " + "AS 'AGGREGATE(FILTER([S1], (NOT ISEMPTY([MEASURES].[STORE SALES]))))' " + "SET [S1] " + "AS 'CROSSJOIN({[GENDER].[GENDER].MEMBERS},{[STORE].[CANADA].CHILDREN})' " + "SELECT\n" + "{[MEASURES].[STORE SALES]} ON COLUMNS,\n" + "{[GENDER].[AGG]} ON ROWS\n" + "FROM [WAREHOUSE AND SALES]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Gender].[AGG]}\n" + "Row #0: \n"); } /** * Testcase for Pentaho bug * BISERVER-1323, * empty SQL query generated when crossjoining more than two sets each * containing just the 'all' member. */ public void testEmptySqlBug() { final String expectedResult = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[All Stores], [Product].[All Products], [Customers].[All Customers]}\n" + "Row #0: 266,773\n"; assertQueryReturns( "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NON EMPTY Crossjoin({[Store].[All Stores]}" + ", Crossjoin({[Product].[All Products]}, {[Customers].[All Customers]})) ON ROWS " + "from [Sales]", expectedResult); // without NON EMPTY assertQueryReturns( "select {[Measures].[Unit Sales]} ON COLUMNS, " + " Crossjoin({[Store].[All Stores]}" + ", Crossjoin({[Product].[All Products]}, {[Customers].[All Customers]})) ON ROWS " + "from [Sales]", expectedResult); // using * operator assertQueryReturns( "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NON EMPTY [Store].[All Stores] " + " * [Product].[All Products]" + " * [Customers].[All Customers] ON ROWS " + "from [Sales]", expectedResult); // combining tuple assertQueryReturns( "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NON EMPTY [Store].[All Stores] " + " * {([Product].[All Products]," + " [Customers].[All Customers])} ON ROWS " + "from [Sales]", expectedResult); // combining two members with tuple final String expectedResult4 = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[All Stores], [Product].[All Products], [Customers].[All Customers], [Gender].[All Gender]}\n" + "Row #0: 266,773\n"; assertQueryReturns( "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NON EMPTY [Store].[All Stores] " + " * [Product].[All Products]" + " * {([Customers].[All Customers], [Gender].[All Gender])} ON ROWS " + "from [Sales]", expectedResult4); assertQueryReturns( "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NON EMPTY [Store].[All Stores] " + " * {([Product].[All Products], [Customers].[All Customers])}" + " * [Gender].[All Gender] ON ROWS " + "from [Sales]", expectedResult4); assertQueryReturns( "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NON EMPTY {([Store].[All Stores], [Product].[All Products])}" + " * [Customers].[All Customers]" + " * [Gender].[All Gender] ON ROWS " + "from [Sales]", expectedResult4); } /** * Tests bug MONDRIAN-7, * "Heterogeneous axis gives wrong results". The bug is a misnomer; * heterogeneous axes should give an error. */ public void testHeterogeneousAxis() { // SSAS2005 gives error: // Query (1, 8) Two sets specified in the function have different // dimensionality. assertQueryThrows( "select {[Measures].[Unit Sales], [Gender].Members} on 0,\n" + " [Store].[USA].Children on 1\n" + "from [Sales]", "All arguments to function '{}' must have same hierarchy."); assertQueryThrows( "select {[Marital Status].Members, [Gender].Members} on 0,\n" + " [Store].[USA].Children on 1\n" + "from [Sales]", "All arguments to function '{}' must have same hierarchy."); } /** * Tests hierarchies of the same dimension on different axes. */ public void testHierarchiesOfSameDimensionOnDifferentAxes() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } assertQueryReturns( "select [Time].[Year].Members on columns,\n" + "[Time].[Weekly].[1997].[6].Children on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "{[Time].[1998]}\n" + "Axis #2:\n" + "{[Time].[Weekly].[1997].[6].[1]}\n" + "{[Time].[Weekly].[1997].[6].[26]}\n" + "{[Time].[Weekly].[1997].[6].[27]}\n" + "{[Time].[Weekly].[1997].[6].[28]}\n" + "{[Time].[Weekly].[1997].[6].[29]}\n" + "{[Time].[Weekly].[1997].[6].[30]}\n" + "{[Time].[Weekly].[1997].[6].[31]}\n" + "Row #0: 404\n" + "Row #0: \n" + "Row #1: 593\n" + "Row #1: \n" + "Row #2: 422\n" + "Row #2: \n" + "Row #3: 382\n" + "Row #3: \n" + "Row #4: 731\n" + "Row #4: \n" + "Row #5: \n" + "Row #5: \n" + "Row #6: \n" + "Row #6: \n"); } /** * A simple user-defined function which adds one to its argument, but * sleeps 1 ms before doing so. */ public static class SleepUdf implements UserDefinedFunction { public String getName() { return "SleepUdf"; } public String getDescription() { return "Returns its argument plus one but sleeps 1 ms first"; } public Syntax getSyntax() { return Syntax.Function; } public Type getReturnType(Type[] parameterTypes) { return new NumericType(); } public Type[] getParameterTypes() { return new Type[] {new NumericType()}; } public Object execute(Evaluator evaluator, Argument[] arguments) { final Object argValue = arguments[0].evaluateScalar(evaluator); if (argValue instanceof Number) { try { Thread.sleep(1); } catch (Exception ex) { return null; } return ((Number) argValue).doubleValue() + 1; } else { // Argument might be a RuntimeException indicating that // the cache does not yet have the required cell value. The // function will be called again when the cache is loaded. return null; } } public String[] getReservedWords() { return null; } } /** * This unit test would cause connection leaks without a fix for bug * MONDRIAN-571, * "HighCardSqlTupleReader does not close SQL Connections". * It would be better if there was a way to verify that no leaks occurred in * the data source. */ public void testHighCardSqlTupleReaderLeakingConnections() { assertQueryReturns( "WITH MEMBER [Measures].[NegativeSales] AS '- [Measures].[Store Sales]' " + "MEMBER [Product].[SameName] AS 'Aggregate(Filter(" + "[Product].[Product Name].members,([Measures].[Store Sales] > 0)))' " + "MEMBER [Measures].[SameName] AS " + "'([Measures].[Store Sales],[Product].[SameName])' " + "select {[Measures].[Store Sales], [Measures].[NegativeSales], " + "[Measures].[SameName]} ON COLUMNS, " + "[Store].[Store Country].members ON ROWS " + "from [Sales] " + "where [Time].[1997]", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[NegativeSales]}\n" + "{[Measures].[SameName]}\n" + "Axis #2:\n" + "{[Store].[Canada]}\n" + "{[Store].[Mexico]}\n" + "{[Store].[USA]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #2: 565,238.13\n" + "Row #2: -565,238.13\n" + "Row #2: 565,238.13\n"); } public void testZeroValuesAreNotTreatedAsNull() { String mdx = "select" + " {" + " (" + " [Product].[All Products].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Tomatos]," + " [Warehouse].[All Warehouses].[USA].[WA].[Seattle].[Quality Warehousing and Trucking]," + " [Store].[All Stores].[USA].[WA].[Seattle].[Store 15]," + " [Time.Weekly].[All Time.Weeklys].[1997].[24].[3]" + " )" + " }" + " on 0," + " [Measures].[units shipped] on 1" + " from warehouse"; assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables]" + ".[Tell Tale].[Tell Tale Tomatos], " + "[Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and " + "Trucking], " + "[Store].[USA].[WA].[Seattle].[Store 15], " + "[Time].[Weekly].[1997].[24].[3]}\n" + "Axis #2:\n" + "{[Measures].[Units Shipped]}\n" + "Row #0: .0\n"); } public void testDirectMemberReferenceOnDimensionWithCalculationsDefined() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", null, "" + "([Gender].LastChild)" + ""); testContext.assertQueryReturns( "select {[Gender].[M]} on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[M]}\n" + "Row #0: 135,215\n"); } public void testExplain() throws SQLException { if (Util.PreJdk15) { // Cannot use explain before JDK 1.5. EmptyResultSet relies on // javax.sql.rowset.RowSetMetaDataImpl, which arrived in JDK 1.5. return; } OlapConnection connection = TestContext.instance().getOlap4jConnection(); final OlapStatement statement = connection.createStatement(); final ResultSet resultSet = statement.executeQuery( "explain plan for\n" + "select [Measures].[Unit Sales] on 0,\n" + " Filter([Product].Children, [Measures].[Unit Sales] > 100) on 1\n" + "from [Sales]"); assertTrue(resultSet.next()); assertEquals(1, resultSet.getMetaData().getColumnCount()); assertEquals("PLAN", resultSet.getMetaData().getColumnName(1)); assertEquals(Types.VARCHAR, resultSet.getMetaData().getColumnType(1)); String s = resultSet.getString(1); TestContext.assertEqualsVerbose( "Axis (COLUMNS):\n" + "SetListCalc(name=SetListCalc, class=class mondrian.olap.fun.SetFunDef$SetListCalc, type=SetType>, resultStyle=MUTABLE_LIST)\n" + " 2(name=2, class=class mondrian.olap.fun.SetFunDef$SetListCalc$2, type=MemberType, resultStyle=VALUE)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Measures].[Unit Sales])\n" + "\n" + "Axis (ROWS):\n" + "ImmutableIterCalc(name=ImmutableIterCalc, class=class mondrian.olap.fun.FilterFunDef$ImmutableIterCalc, type=SetType>, resultStyle=ITERABLE)\n" + " Children(name=Children, class=class mondrian.olap.fun.BuiltinFunTable$22$1, type=SetType>, resultStyle=LIST)\n" + " CurrentMemberFixed(hierarchy=[Product], name=CurrentMemberFixed, class=class mondrian.olap.fun.HierarchyCurrentMemberFunDef$FixedCalcImpl, type=MemberType, resultStyle=VALUE)\n" + " >(name=>, class=class mondrian.olap.fun.BuiltinFunTable$63$1, type=BOOLEAN, resultStyle=VALUE)\n" + " MemberValueCalc(name=MemberValueCalc, class=class mondrian.calc.impl.MemberValueCalc, type=SCALAR, resultStyle=VALUE)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Measures].[Unit Sales])\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=NUMERIC, resultStyle=VALUE_NOT_NULL, value=100.0)\n" + "\n", s); } public void testExplainComplex() throws SQLException { if (Util.PreJdk15) { // Cannot use explain before JDK 1.5. EmptyResultSet relies on // javax.sql.rowset.RowSetMetaDataImpl, which arrived in JDK 1.5. return; } OlapConnection connection = TestContext.instance().getOlap4jConnection(); final OlapStatement statement = connection.createStatement(); final String mdx = "with member [Time].[Time].[1997].[H1] as\n" + " Aggregate({[Time].[1997].[Q1], [Time].[1997].[Q2]})\n" + " member [Measures].[Store Margin] as\n" + " [Measures].[Store Sales] - [Measures].[Store Cost],\n" + " format_string =\n" + " iif(\n" + " [Measures].[Unit Sales] > 50000,\n" + " \"\\#.00\\<\\/b\\>\",\n" + " \"\\#.00\\<\\/i\\>\")\n" + " set [Hi Val Products] as\n" + " Filter(\n" + " Descendants([Product].[Drink], , LEAVES),\n" + " [Measures].[Unit Sales] > 100)\n" + "select\n" + " {[Measures].[Unit Sales], [Measures].[Store Margin]} on 0,\n" + " [Hi Val Products] * [Marital Status].Members on 1\n" + "from [Sales]\n" + "where [Gender].[F]"; // Plan before execution. final ResultSet resultSet = statement.executeQuery("explain plan for\n" + mdx); assertTrue(resultSet.next()); String s = resultSet.getString(1); TestContext.assertEqualsVerbose( "Axis (FILTER):\n" + "SetListCalc(name=SetListCalc, class=class mondrian.olap.fun.SetFunDef$SetListCalc, type=SetType>, resultStyle=MUTABLE_LIST)\n" + " ()(name=(), class=class mondrian.olap.fun.SetFunDef$SetListCalc$2, type=MemberType, resultStyle=VALUE)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Gender].[F])\n" + "\n" + "Axis (COLUMNS):\n" + "SetListCalc(name=SetListCalc, class=class mondrian.olap.fun.SetFunDef$SetListCalc, type=SetType>, resultStyle=MUTABLE_LIST)\n" + " 2(name=2, class=class mondrian.olap.fun.SetFunDef$SetListCalc$2, type=MemberType, resultStyle=VALUE)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Measures].[Unit Sales])\n" + " 2(name=2, class=class mondrian.olap.fun.SetFunDef$SetListCalc$2, type=MemberType, resultStyle=VALUE)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Measures].[Store Margin])\n" + "\n" + "Axis (ROWS):\n" + "CrossJoinIterCalc(name=CrossJoinIterCalc, class=class mondrian.olap.fun.CrossJoinFunDef$CrossJoinIterCalc, type=SetType, MemberType>>, resultStyle=ITERABLE)\n" + " 1(name=1, class=class mondrian.mdx.NamedSetExpr$1, type=SetType>, resultStyle=ITERABLE)\n" + " Members(name=Members, class=class mondrian.olap.fun.BuiltinFunTable$27$1, type=SetType>, resultStyle=MUTABLE_LIST)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=HierarchyType, resultStyle=VALUE_NOT_NULL, value=[Marital Status])\n" + "\n", s); // Plan after execution, including profiling. final String[] strings = {null, null}; ((mondrian.server.Statement) statement).enableProfiling( new ProfileHandler() { public void explain(String plan, QueryTiming timing) { strings[0] = plan; strings[1] = String.valueOf(timing); } } ); final CellSet cellSet = statement.executeOlapQuery(mdx); new RectangularCellSetFormatter(true).format( cellSet, new PrintWriter(new StringWriter())); cellSet.close(); final String actual = strings[0].replaceAll( "callMillis=[0-9]+", "callMillis=nnn") .replaceAll( "[0-9]+ms", "nnnms"); TestContext.assertEqualsVerbose( "Axis (FILTER):\n" + "SetListCalc(name=SetListCalc, class=class mondrian.olap.fun.SetFunDef$SetListCalc, type=SetType>, resultStyle=MUTABLE_LIST, callCount=2, callMillis=nnn, elementCount=2, elementSquaredCount=2)\n" + " ()(name=(), class=class mondrian.olap.fun.SetFunDef$SetListCalc$2, type=MemberType, resultStyle=VALUE)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Gender].[F], callCount=2, callMillis=nnn)\n" + "\n" + "Axis (COLUMNS):\n" + "SetListCalc(name=SetListCalc, class=class mondrian.olap.fun.SetFunDef$SetListCalc, type=SetType>, resultStyle=MUTABLE_LIST, callCount=2, callMillis=nnn, elementCount=4, elementSquaredCount=8)\n" + " 2(name=2, class=class mondrian.olap.fun.SetFunDef$SetListCalc$2, type=MemberType, resultStyle=VALUE)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Measures].[Unit Sales], callCount=2, callMillis=nnn)\n" + " 2(name=2, class=class mondrian.olap.fun.SetFunDef$SetListCalc$2, type=MemberType, resultStyle=VALUE)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=MemberType, resultStyle=VALUE_NOT_NULL, value=[Measures].[Store Margin], callCount=2, callMillis=nnn)\n" + "\n" + "Axis (ROWS):\n" + "CrossJoinIterCalc(name=CrossJoinIterCalc, class=class mondrian.olap.fun.CrossJoinFunDef$CrossJoinIterCalc, type=SetType, MemberType>>, resultStyle=ITERABLE, callCount=2, callMillis=nnn, elementCount=0, elementSquaredCount=0)\n" + " 1(name=1, class=class mondrian.mdx.NamedSetExpr$1, type=SetType>, resultStyle=ITERABLE)\n" + " Members(name=Members, class=class mondrian.olap.fun.BuiltinFunTable$27$1, type=SetType>, resultStyle=MUTABLE_LIST)\n" + " Literal(name=Literal, class=class mondrian.calc.impl.ConstantCalc, type=HierarchyType, resultStyle=VALUE_NOT_NULL, value=[Marital Status], callCount=2, callMillis=nnn)\n" + "\n", actual); assertTrue( strings[1], strings[1].contains( "SqlStatement-SqlTupleReader.readTuples [[Product].[Product " + "Category]] invoked 1 times for total of ")); } public void testExplainInvalid() throws SQLException { OlapConnection connection = TestContext.instance().getOlap4jConnection(); final OlapStatement statement = connection.createStatement(); try { final ResultSet resultSet = statement.executeQuery( "select\n" + " {[Measures].[Unit Sales], [Measures].[Store Margin]} on 0,\n" + " [Hi Val Products] * [Marital Status].Members on 1\n" + "from [Sales]\n" + "where [Gender].[F]"); fail("expected error, got " + resultSet); } catch (SQLException e) { TestContext.checkThrowable( e, "MDX object '[Measures].[Store Margin]' not found in cube 'Sales'"); } } /** * This is a test for MONDRIAN-1014. Executing a statement * twice concurrently would fail because the statement wasn't * cleaning up properly its execution context. */ public void testConcurrentStatementRun() throws Exception { final OlapConnection olapConnection = TestContext.instance().getOlap4jConnection(); final String mdxQuery = "select {TopCount([Customers].Members, 10, [Measures].[Unit Sales])} on columns from [Sales]"; final ExecutorService es = Executors.newCachedThreadPool( new ThreadFactory() { public Thread newThread(Runnable r) { final Thread thread = Executors.defaultThreadFactory().newThread(r); thread.setName( "mondrian.test.BasicQueryTest.testConcurrentStatementRun"); thread.setDaemon(true); return thread; } }); final OlapStatement stmt = olapConnection.createStatement(); es.submit( new Callable() { public CellSet call() throws Exception { return stmt.executeOlapQuery(mdxQuery); } }); // Give some time to the first query so it enters a "running" state. Thread.sleep(100); es.submit( new Callable() { public CellSet call() throws Exception { return stmt.executeOlapQuery(mdxQuery); } }).get(); es.shutdownNow(); } public void testRollup() { switch (2) { case 0: assertQueryReturns( "select [Gender].Children * [Product].Children on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[F], [Product].[Drink]}\n" + "{[Gender].[F], [Product].[Food]}\n" + "{[Gender].[F], [Product].[Non-Consumable]}\n" + "{[Gender].[M], [Product].[Drink]}\n" + "{[Gender].[M], [Product].[Food]}\n" + "{[Gender].[M], [Product].[Non-Consumable]}\n" + "Row #0: 12,202\n" + "Row #0: 94,814\n" + "Row #0: 24,542\n" + "Row #0: 12,395\n" + "Row #0: 97,126\n" + "Row #0: 25,694\n"); // now, should be able to answer this one by rolling up gender assertQueryReturns( "select [Product].Children on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 24,597\n" + "Row #0: 191,940\n" + "Row #0: 50,236\n"); break; case 1: assertQueryReturns( "select [Gender].[M] * [Product].Children on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[M], [Product].[Drink]}\n" + "{[Gender].[M], [Product].[Food]}\n" + "{[Gender].[M], [Product].[Non-Consumable]}\n" + "Row #0: 12,395\n" + "Row #0: 97,126\n" + "Row #0: 25,694\n"); assertQueryReturns( "select [Gender].[F] * [Product].Children on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[F], [Product].[Drink]}\n" + "{[Gender].[F], [Product].[Food]}\n" + "{[Gender].[F], [Product].[Non-Consumable]}\n" + "Row #0: 12,202\n" + "Row #0: 94,814\n" + "Row #0: 24,542\n"); // now, should be able to answer this one by rolling up gender assertQueryReturns( "select [Product].Children on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 24,597\n" + "Row #0: 191,940\n" + "Row #0: 50,236\n"); break; case 2: String[] genders = {"M", "F"}; String[] states = {"USA", "Canada", "Mexico"}; for (String state : states) { for (String gender : genders) { getTestContext().executeQuery( "select [Gender].[" + gender + "] * [Store].[" + state + "] * [Product].Children on 0\n" + "from [Sales]"); } } // now, should be able to answer this one by rolling up gender assertQueryReturns( "select [Product].Children on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 24,597\n" + "Row #0: 191,940\n" + "Row #0: 50,236\n"); break; case 3: // Test case for MONDRIAN-1021. // First, read {Mexico}. // Now, query {USA, Canada, Mexico}. Should just read {USA, Canada}. break; case 4: } } } // End BasicQueryTest.java mondrian-3.4.1/testsrc/main/mondrian/test/PropertySaver.java0000644000175000017500000001234711735330606024126 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho // All Rights Reserved. // // jhyde, Feb 21, 2003 */ package mondrian.test; import mondrian.olap.MondrianProperties; import mondrian.rolap.RolapUtil; import org.apache.log4j.Level; import org.apache.log4j.Logger; import org.eigenbase.util.property.*; import java.util.HashMap; import java.util.Map; /** * Sets properties and logging levels, and remembers the original values so they * can be reverted at the end of the test. * * @author jhyde * @since Oct 28, 2008 */ public class PropertySaver { public final MondrianProperties properties = MondrianProperties.instance(); private final Map originalValues = new HashMap(); private final Map originalLoggerLevels = new HashMap(); // wacky initializer to prevent compiler from internalizing the // string (we don't want it to be == other occurrences of "NOT_SET") private static final String NOT_SET = new StringBuffer("NOT_" + "SET").toString(); /** * Sets a boolean property and remembers its previous value. * * @param property Property * @param value New value */ public void set(BooleanProperty property, boolean value) { if (!originalValues.containsKey(property)) { final String originalValue = properties.containsKey(property.getPath()) ? properties.getProperty(property.getPath()) : NOT_SET; originalValues.put( property, originalValue); } property.set(value); } /** * Sets an integer property and remembers its previous value. * * @param property Property * @param value New value */ public void set(IntegerProperty property, int value) { if (!originalValues.containsKey(property)) { final String originalValue = properties.containsKey(property.getPath()) ? properties.getProperty(property.getPath()) : NOT_SET; originalValues.put( property, originalValue); } property.set(value); } /** * Sets a string property and remembers its previous value. * * @param property Property * @param value New value */ public void set(StringProperty property, String value) { if (!originalValues.containsKey(property)) { final String originalValue = properties.containsKey(property.getPath()) ? properties.getProperty(property.getPath()) : NOT_SET; originalValues.put( property, originalValue); } property.set(value); } /** * Sets a double property and remembers its previous value. * * @param property Property * @param value New value */ public void set(DoubleProperty property, Double value) { if (!originalValues.containsKey(property)) { final String originalValue = properties.containsKey(property.getPath()) ? properties.getProperty(property.getPath()) : NOT_SET; originalValues.put( property, originalValue); } property.set(value); } /** * Sets all properties back to their original values. */ public void reset() { for (Map.Entry entry : originalValues.entrySet()) { final String value = entry.getValue(); //noinspection StringEquality if (value == NOT_SET) { properties.remove(entry.getKey().getPath()); } else { properties.setProperty(entry.getKey().getPath(), value); } if (entry.getKey() == MondrianProperties.instance().NullMemberRepresentation) { RolapUtil.reloadNullLiteral(); } } for (Map.Entry entry : originalLoggerLevels.entrySet()) { entry.getKey().setLevel(entry.getValue()); } } /** * Sets a logger's level. * * @param logger Logger * @param level Logging level */ public void set(Logger logger, Level level) { final Level prevLevel = logger.getLevel(); if (!originalLoggerLevels.containsKey(logger)) { originalLoggerLevels.put(logger, prevLevel); } logger.setLevel(level); } /** * Sets a logger's level to at least the given level. * * @param logger Logger * @param level Logging level */ public void setAtLeast(Logger logger, Level level) { final Level prevLevel = logger.getLevel(); if (prevLevel == null || !prevLevel.isGreaterOrEqual(level)) { set(logger, level); } } } // End PropertySaver.java mondrian-3.4.1/testsrc/main/mondrian/test/IgnoreUnrelatedDimensionsTest.java0000644000175000017500000004415611735330606027264 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.olap.MondrianProperties; /** * Test case to * push unrelatedDimensions to top level when ignoreUnrelatedDimensions property * is set to true on a base cube usage. * * @author ajoglekar * @since Dec 03, 2007 */ public class IgnoreUnrelatedDimensionsTest extends FoodMartTestCase { // TODO: use propSaver to restore property values boolean originalNonEmptyFlag; private final MondrianProperties prop = MondrianProperties.instance(); private static final String cubeSales3 = "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""; private static final String cubeWarehouseAndSales3 = "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""; protected void setUp() throws Exception { super.setUp(); originalNonEmptyFlag = prop.EnableNonEmptyOnAllAxis.get(); prop.EnableNonEmptyOnAllAxis.set(true); } protected void tearDown() throws Exception { prop.EnableNonEmptyOnAllAxis.set(originalNonEmptyFlag); super.tearDown(); } public TestContext getTestContext() { return TestContext.instance().create( null, null, "\n" + " " + " \n" + " \n" + " " + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " [Measures].[Profit] / [Measures].[Units Shipped]\n" + " \n" + "", null, null, null); } public void testTotalingOnCrossJoinOfJoiningAndNonJoiningDimensions() { assertQueryReturns( "WITH MEMBER [Measures].[Unit Sales VM] AS " + "'ValidMeasure([Measures].[Unit Sales])', SOLVE_ORDER = 3000 " + "MEMBER Gender.G AS 'AGGREGATE(CROSSJOIN({[Gender].[Gender].MEMBERS}," + "[WAREHOUSE].[STATE PROVINCE].MEMBERS))'" + "SELECT " + "{[MEASURES].[Unit Sales VM]} ON 0," + "{Gender.G} ON 1 " + "FROM [WAREHOUSE AND SALES2]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales VM]}\n" + "Axis #2:\n" + "{[Gender].[G]}\n" + "Row #0: 266,773\n"); } public void testVMShouldNotPushUpAggMemberDefinedOnNonJoiningDimension() { assertQueryReturns( "WITH MEMBER [Measures].[Total Sales] AS " + "'ValidMeasure(Measures.[Warehouse Sales]) + [Measures].[Unit Sales]'," + "SOLVE_ORDER = 3000 " + "MEMBER Gender.G AS " + "'AGGREGATE(CROSSJOIN({GENDER.[M]},{[Product].[All Products].[Drink]}))'," + "SOLVE_ORDER = 4 " + "SELECT " + "{[MEASURES].[Total Sales]} ON 0," + "{Gender.G} ON 1 FROM [WAREHOUSE AND SALES2]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Total Sales]}\n" + "Axis #2:\n" + "{[Gender].[G]}\n" + "Row #0: 30,405.602\n"); } public void testAggMemberDefinedOnNonJoiningDimensionWithNonAllDefltMember() { // Gender dim to have Gender.F as default member final TestContext context = TestContext.instance().create( null, cubeSales3, cubeWarehouseAndSales3, null, null, null); context.assertQueryReturns( "WITH MEMBER [Measures].[Total Sales] AS " + "'ValidMeasure(Measures.[Warehouse Sales]) + [Measures].[Unit Sales]'," + "SOLVE_ORDER = 3000 " + "MEMBER Gender.G AS " + "'AGGREGATE(CROSSJOIN({GENDER.[M]},{[Product].[All Products].[Drink]}))'," + "SOLVE_ORDER = 4 " + "SELECT " + "{[MEASURES].[Total Sales]} ON 0," + "{Gender.G} ON 1 FROM [WAREHOUSE AND SALES 3]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Total Sales]}\n" + "Axis #2:\n" + "{[Gender].[G]}\n" + "Row #0: 30,405.602\n"); } public void testTotalingForValidAndNonValidMeasuresWithJoiningDimensions() { assertQueryReturns( "WITH MEMBER [Measures].[Unit Sales VM] AS " + "'ValidMeasure([Measures].[Unit Sales])'," + "SOLVE_ORDER = 3000 " + "MEMBER PRODUCT.G AS 'AGGREGATE(CROSSJOIN({PRODUCT.[PRODUCT NAME].MEMBERS}," + "[STORE].[STORE NAME].MEMBERS))'" + "SELECT " + "{[MEASURES].[Unit Sales VM], [MEASURES].[STORE COST]} ON 0," + "{PRODUCT.G} ON 1 FROM [WAREHOUSE AND SALES2]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales VM]}\n" + "{[Measures].[Store Cost]}\n" + "Axis #2:\n" + "{[Product].[G]}\n" + "Row #0: 266,773\n" + "Row #0: 225,627.23\n"); } public void testTotalingWhenIgnoreUnrelatedDimensionsPropertyIsTrue() { assertQueryReturns( "WITH MEMBER [Measures].[Unit Sales VM] AS " + "'ValidMeasure([Measures].[Unit Sales])', SOLVE_ORDER = 3000 " + "MEMBER [Gender].[COG_OQP_USR_Aggregate(Gender SET)] AS " + "'IIF([Measures].CURRENTMEMBER IS [Measures].[Unit Sales VM], " + "([Gender].[COG_OQP_INT_m2], [Measures].[Unit Sales VM]), " + "AGGREGATE([COG_OQP_INT_s1]))', SOLVE_ORDER = 4 " + "MEMBER [Gender].[COG_OQP_INT_m2] AS " + "'AGGREGATE([COG_OQP_INT_s1])', SOLVE_ORDER = 4 " + "MEMBER [WAREHOUSE].[COG_OQP_USR_Aggregate(WAREHOUSE SET)] AS " + "'IIF([Measures].CURRENTMEMBER IS [Measures].[Unit Sales VM], " + "([WAREHOUSE].[COG_OQP_INT_m1], [Measures].[Unit Sales VM]), " + "AGGREGATE({[Warehouse].[State Province].&[DF].[Mexico City].[Freeman And Co], " + "[Warehouse].[State Province].&[BC].[Vancouver].[Bellmont Distributing]}))', " + "SOLVE_ORDER = 8 " + "MEMBER [WAREHOUSE].[COG_OQP_INT_m1] AS " + "'AGGREGATE({[Warehouse].[State Province].&[DF].[Mexico City].[Freeman And Co], " + "[Warehouse].[State Province].&[BC].[Vancouver].[Bellmont Distributing]})', " + "SOLVE_ORDER = 8 " + "SET [COG_OQP_INT_s2] AS " + "'CROSSJOIN({[Gender].[All Gender].[M], [Gender].[All Gender].[F]}, " + "{{[Warehouse].[State Province].&[DF].[Mexico City].[Freeman And Co], " + "[Warehouse].[State Province].&[BC].[Vancouver].[Bellmont Distributing]}, " + "{([Warehouse].[COG_OQP_USR_Aggregate(Warehouse SET)])}})' " + "SET [COG_OQP_INT_s1] AS " + "'CROSSJOIN({[Gender].[All Gender].[M], [Gender].[All Gender].[F]}, " + "{[Warehouse].[State Province].&[DF].[Mexico City].[Freeman And Co], " + "[Warehouse].[State Province].&[BC].[Vancouver].[Bellmont Distributing]})' " + "SELECT " + "{[Measures].[Unit Sales VM]} ON AXIS(0), " + "{[COG_OQP_INT_s2], HEAD({([Gender].[COG_OQP_USR_Aggregate(Gender SET)], " + "[WAREHOUSE].DEFAULTMEMBER)}, " + "IIF(COUNT([COG_OQP_INT_s1], INCLUDEEMPTY) > 0, 1, 0))} ON AXIS(1) " + "FROM [WAREHOUSE AND SALES2]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales VM]}\n" + "Axis #2:\n" + "{[Gender].[M], [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co]}\n" + "{[Gender].[M], [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing]}\n" + "{[Gender].[M], [Warehouse].[COG_OQP_USR_Aggregate(WAREHOUSE SET)]}\n" + "{[Gender].[F], [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co]}\n" + "{[Gender].[F], [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing]}\n" + "{[Gender].[F], [Warehouse].[COG_OQP_USR_Aggregate(WAREHOUSE SET)]}\n" + "{[Gender].[COG_OQP_USR_Aggregate(Gender SET)], [Warehouse].[All Warehouses]}\n" + "Row #0: 135,215\n" + "Row #1: 135,215\n" + "Row #2: 135,215\n" + "Row #3: 131,558\n" + "Row #4: 131,558\n" + "Row #5: 131,558\n" + "Row #6: 266,773\n"); } public void testTotalingOnNonJoiningDimension() { assertQueryReturns( "WITH MEMBER [Measures].[Unit Sales VM] AS " + "'ValidMeasure([Measures].[Unit Sales])', SOLVE_ORDER =3000" + "MEMBER MEASURES.[VirtualMeasure] AS " + "'[Measures].[Store Invoice]/[Measures].[Unit Sales VM]', SOLVE_ORDER=3000 " + "MEMBER [Warehouse].[COG_OQP_USR_Aggregate(Warehouse set)] AS " + "'IIF([Measures].CURRENTMEMBER IS " + "[Measures].[VirtualMeasure], ([Warehouse].[COG_OQP_INT_m1], " + "[Measures].[VirtualMeasure]), " + "AGGREGATE({[Warehouse].[All Warehouses].[USA].[OR]," + "[Warehouse].[All Warehouses].[USA].[WA]}))', SOLVE_ORDER = 8 " + "MEMBER [Warehouse].[COG_OQP_INT_m1] AS " + "'AGGREGATE({[Warehouse].[All Warehouses].[USA].[OR]," + "[Warehouse].[All Warehouses].[USA].[WA]})', SOLVE_ORDER = 8 " + "MEMBER [Product].[COG_OQP_USR_Aggregate(Product Set)1] " + "AS 'IIF([Measures].CURRENTMEMBER IS [Measures].[VirtualMeasure], " + "([Product].[COG_OQP_INT_m2], [Measures].[VirtualMeasure]), " + "AGGREGATE([COG_OQP_INT_s3]))', SOLVE_ORDER = 4 " + "MEMBER [Product].[COG_OQP_INT_m2] " + "AS 'AGGREGATE([COG_OQP_INT_s3])', SOLVE_ORDER = 4 " + "SET [COG_OQP_INT_s4] AS " + "'CROSSJOIN({[Product].[All Products].[Drink],[Product].[All Products].[Food]}, " + "{{[Warehouse].[All Warehouses].[USA].[OR],[Warehouse].[All Warehouses].[USA].[WA]}, " + "{([Warehouse].[COG_OQP_USR_Aggregate(Warehouse set)])}})' " + "SET [COG_OQP_INT_s3] AS " + "'CROSSJOIN({[Product].[All Products].[Drink],[Product].[All Products].[Food]}," + "{{[Warehouse].[All Warehouses].[USA].[OR],[Warehouse].[All Warehouses].[USA].[WA]}})' " + "SET [COG_OQP_INT_s2] AS " + "'{[Measures].[Store Invoice],[Measures].[Unit Sales VM],[Measures].[VirtualMeasure]}' " + "SELECT " + "[COG_OQP_INT_s2] DIMENSION PROPERTIES PARENT_LEVEL, " + "PARENT_UNIQUE_NAME ON AXIS(0), " + "{[COG_OQP_INT_s4], HEAD({([Product].[COG_OQP_USR_Aggregate(Product Set)1], " + "[Warehouse].DEFAULTMEMBER)}, " + "IIF(COUNT([COG_OQP_INT_s3], INCLUDEEMPTY) > 0, 1, 0))} " + "DIMENSION PROPERTIES PARENT_LEVEL, PARENT_UNIQUE_NAME ON AXIS(1) " + "FROM [WAREHOUSE AND SALES2]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Invoice]}\n" + "{[Measures].[Unit Sales VM]}\n" + "{[Measures].[VirtualMeasure]}\n" + "Axis #2:\n" + "{[Product].[Drink], [Warehouse].[USA].[OR]}\n" + "{[Product].[Drink], [Warehouse].[USA].[WA]}\n" + "{[Product].[Drink], [Warehouse].[COG_OQP_USR_Aggregate(Warehouse set)]}\n" + "{[Product].[Food], [Warehouse].[USA].[OR]}\n" + "{[Product].[Food], [Warehouse].[USA].[WA]}\n" + "{[Product].[Food], [Warehouse].[COG_OQP_USR_Aggregate(Warehouse set)]}\n" + "{[Product].[COG_OQP_USR_Aggregate(Product Set)1], [Warehouse].[All Warehouses]}\n" + "Row #0: 2,057.232\n" + "Row #0: 24,597\n" + "Row #0: 0.084\n" + "Row #1: 4,868.471\n" + "Row #1: 24,597\n" + "Row #1: 0.198\n" + "Row #2: 6,925.702\n" + "Row #2: 24,597\n" + "Row #2: 0.282\n" + "Row #3: 13,726.825\n" + "Row #3: 191,940\n" + "Row #3: 0.072\n" + "Row #4: 37,712.692\n" + "Row #4: 191,940\n" + "Row #4: 0.196\n" + "Row #5: 51,439.517\n" + "Row #5: 191,940\n" + "Row #5: 0.268\n" + "Row #6: 58,365.22\n" + "Row #6: 216,537\n" + "Row #6: 0.27\n"); } public void testUnrelatedDimPropOverridesIgnoreMeasure() { boolean origIgnoreMeasure = prop.IgnoreMeasureForNonJoiningDimension.get(); prop.IgnoreMeasureForNonJoiningDimension.set(true); assertQueryReturns( "WITH\n" + "MEMBER [Measures].[Total Sales] AS '[Measures].[Store Sales] + " + "[Measures].[Warehouse Sales]'\n" + "MEMBER [Product].[AggSP1_1] AS\n" + "'IIF([Measures].CURRENTMEMBER IS [Measures].[Total Sales],\n" + "[Warehouse].[All Warehouses],\n" + "[Warehouse].[All Warehouses])'\n" + "MEMBER [Product].[AggSP1_2] AS\n" + "'IIF([Measures].CURRENTMEMBER IS [Measures].[Total Sales],\n" + "([Warehouse].[All Warehouses]),\n" + "([Warehouse].[All Warehouses]))'\n" + "\n" + "SELECT\n" + "{[Measures].[Total Sales]} ON AXIS(0),\n" + "{[Product].[AggSP1_1], [Product].[AggSP1_2]} ON AXIS(1)\n" + "FROM\n" + "[Warehouse and Sales2]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Total Sales]}\n" + "Axis #2:\n" + "{[Product].[AggSP1_1]}\n" + "{[Product].[AggSP1_2]}\n" + "Row #0: 762,009.02\n" + "Row #1: 762,009.02\n"); prop.IgnoreMeasureForNonJoiningDimension.set(origIgnoreMeasure); } } // End IgnoreUnrelatedDimensionsTest.java mondrian-3.4.1/testsrc/main/mondrian/test/FoodMartTestCase.java0000644000175000017500000003773511735330606024460 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. // // jhyde, 29 March, 2002 */ package mondrian.test; import mondrian.calc.TupleList; import mondrian.calc.impl.UnaryTupleList; import mondrian.olap.*; import junit.framework.Assert; import junit.framework.TestCase; import java.util.*; /** * FoodMartTestCase is a unit test which runs against the FoodMart * database. * * @author jhyde * @since 29 March, 2002 */ public class FoodMartTestCase extends TestCase { /** * Access properties via this object and their values will be reset on * {@link #tearDown()}. */ protected final PropertySaver propSaver = new PropertySaver(); public FoodMartTestCase(String name) { super(name); } public FoodMartTestCase() { } protected void tearDown() throws Exception { // revert any properties that have been set during this test propSaver.reset(); } /** * Returns the test context. Override this method if you wish to use a * different source for your FoodMart connection. */ public TestContext getTestContext() { return TestContext.instance(); } protected Connection getConnection() { return getTestContext().getConnection(); } /** * Runs a query, and asserts that the result has a given number of columns * and rows. */ protected void assertSize( String queryString, int columnCount, int rowCount) { Result result = executeQuery(queryString); Axis[] axes = result.getAxes(); Assert.assertTrue(axes.length == 2); Assert.assertTrue(axes[0].getPositions().size() == columnCount); Assert.assertTrue(axes[1].getPositions().size() == rowCount); } /** * Runs a query, and asserts that it throws an exception which contains * the given pattern. */ public void assertQueryThrows(String queryString, String pattern) { getTestContext().assertQueryThrows(queryString, pattern); } /** * Executes a query in a given connection. */ public Result execute(Connection connection, String queryString) { Query query = connection.parseQuery(queryString); return connection.execute(query); } /** * Executes a set expression which is expected to return 0 or 1 members. * It is an error if the expression returns tuples (as opposed to members), * or if it returns two or more members. * * @param expression Expression * @return Null if axis returns the empty set, member if axis returns one * member. Throws otherwise. */ public Member executeSingletonAxis(String expression) { return getTestContext().executeSingletonAxis(expression); } /** * Runs a query and checks that the result is a given string. */ public void assertQueryReturns(String query, String desiredResult) { getTestContext().assertQueryReturns(query, desiredResult); } /** * Runs a query. */ public Result executeQuery(String queryString) { return getTestContext().executeQuery(queryString); } /** * Runs a query with a given expression on an axis, and asserts that it * throws an error which matches a particular pattern. The expression * is evaulated against the Sales cube. */ public void assertAxisThrows(String expression, String pattern) { getTestContext().assertAxisThrows(expression, pattern); } /** * Runs a query on the "Sales" cube with a given expression on an axis, and * asserts that it returns the expected string. */ public void assertAxisReturns(String expression, String expected) { getTestContext().assertAxisReturns(expression, expected); } /** * Executes an expression against the Sales cube in the FoodMart database * to form a single cell result set, then returns that cell's formatted * value. */ public String executeExpr(String expression) { return getTestContext().executeExprRaw(expression).getFormattedValue(); } /** * Executes an expression which yields a boolean result, and asserts that * the result is the expected one. */ public void assertBooleanExprReturns(String expression, boolean expected) { final String iifExpression = "Iif (" + expression + ",\"true\",\"false\")"; final String actual = executeExpr(iifExpression); final String expectedString = expected ? "true" : "false"; assertEquals(expectedString, actual); } /** * Runs an expression, and asserts that it gives an error which contains * a particular pattern. The error might occur during parsing, or might * be contained within the cell value. */ public void assertExprThrows(String expression, String pattern) { getTestContext().assertExprThrows(expression, pattern); } /** * Runs an expression and asserts that it returns a given result. */ public void assertExprReturns(String expression, String expected) { getTestContext().assertExprReturns(expression, expected); } /** * Executes query1 and query2 and Compares the obtained measure values. */ protected void assertQueriesReturnSimilarResults( String query1, String query2, TestContext testContext) { String resultString1 = TestContext.toString(testContext.executeQuery(query1)); String resultString2 = TestContext.toString(testContext.executeQuery(query2)); assertEquals( measureValues(resultString1), measureValues(resultString2)); } /** * Truncates the query result to return only measure values. */ private static String measureValues(String resultString) { int index = resultString.indexOf("}"); return index != -1 ? resultString.substring(index) : resultString; } protected boolean isGroupingSetsSupported() { return MondrianProperties.instance().EnableGroupingSets.get() && getTestContext().getDialect().supportsGroupingSets(); } protected boolean isDefaultNullMemberRepresentation() { return MondrianProperties.instance().NullMemberRepresentation.get() .equals("#null"); } protected Member member( List segmentList, SchemaReader salesCubeSchemaReader) { return salesCubeSchemaReader.getMemberByUniqueName(segmentList, true); } protected TupleList storeMembersCAAndOR( SchemaReader salesCubeSchemaReader) { return new UnaryTupleList(Arrays.asList( member( Id.Segment.toList( "Store", "All Stores", "USA", "CA", "Alameda"), salesCubeSchemaReader), member( Id.Segment.toList( "Store", "All Stores", "USA", "CA", "Alameda", "HQ"), salesCubeSchemaReader), member( Id.Segment.toList( "Store", "All Stores", "USA", "CA", "Beverly Hills"), salesCubeSchemaReader), member( Id.Segment.toList( "Store", "All Stores", "USA", "CA", "Beverly Hills", "Store 6"), salesCubeSchemaReader), member( Id.Segment.toList( "Store", "All Stores", "USA", "CA", "Los Angeles"), salesCubeSchemaReader), member( Id.Segment.toList( "Store", "All Stores", "USA", "OR", "Portland"), salesCubeSchemaReader), member( Id.Segment.toList( "Store", "All Stores", "USA", "OR", "Portland", "Store 11"), salesCubeSchemaReader), member( Id.Segment.toList( "Store", "All Stores", "USA", "OR", "Salem"), salesCubeSchemaReader), member( Id.Segment.toList( "Store", "All Stores", "USA", "OR", "Salem", "Store 13"), salesCubeSchemaReader))); } protected TupleList productMembersPotScrubbersPotsAndPans( SchemaReader salesCubeSchemaReader) { return new UnaryTupleList(Arrays.asList( member( Id.Segment.toList( "Product", "All Products", "Non-Consumable", "Household", "Kitchen Products", "Pot Scrubbers", "Cormorant"), salesCubeSchemaReader), member( Id.Segment.toList( "Product", "All Products", "Non-Consumable", "Household", "Kitchen Products", "Pot Scrubbers", "Denny"), salesCubeSchemaReader), member( Id.Segment.toList( "Product", "All Products", "Non-Consumable", "Household", "Kitchen Products", "Pot Scrubbers", "Red Wing"), salesCubeSchemaReader), member( Id.Segment.toList( "Product", "All Products", "Non-Consumable", "Household", "Kitchen Products", "Pots and Pans", "Cormorant"), salesCubeSchemaReader), member( Id.Segment.toList( "Product", "All Products", "Non-Consumable", "Household", "Kitchen Products", "Pots and Pans", "Denny"), salesCubeSchemaReader), member( Id.Segment.toList( "Product", "All Products", "Non-Consumable", "Household", "Kitchen Products", "Pots and Pans", "High Quality"), salesCubeSchemaReader), member( Id.Segment.toList( "Product", "All Products", "Non-Consumable", "Household", "Kitchen Products", "Pots and Pans", "Red Wing"), salesCubeSchemaReader), member( Id.Segment.toList( "Product", "All Products", "Non-Consumable", "Household", "Kitchen Products", "Pots and Pans", "Sunset"), salesCubeSchemaReader))); } protected TupleList genderMembersIncludingAll( boolean includeAllMember, SchemaReader salesCubeSchemaReader, Cube salesCube) { Member maleMember = member( Id.Segment.toList("Gender", "All Gender", "M"), salesCubeSchemaReader); Member femaleMember = member( Id.Segment.toList("Gender", "All Gender", "F"), salesCubeSchemaReader); Member [] members; if (includeAllMember) { members = new Member[] { allMember("Gender", salesCube), maleMember, femaleMember}; } else { members = new Member[] {maleMember, femaleMember}; } return new UnaryTupleList(Arrays.asList(members)); } protected Member allMember(String dimensionName, Cube salesCube) { Dimension genderDimension = getDimension(dimensionName, salesCube); return genderDimension.getHierarchy().getAllMember(); } private Dimension getDimension(String dimensionName, Cube salesCube) { return getDimensionWithName(dimensionName, salesCube.getDimensions()); } protected Dimension getDimensionWithName( String name, Dimension[] dimensions) { Dimension resultDimension = null; for (Dimension dimension : dimensions) { if (dimension.getName().equals(name)) { resultDimension = dimension; break; } } return resultDimension; } protected List warehouseMembersCanadaMexicoUsa(SchemaReader reader) { return Arrays.asList( member(Id.Segment.toList( "Warehouse", "All Warehouses", "Canada"), reader), member(Id.Segment.toList( "Warehouse", "All Warehouses", "Mexico"), reader), member(Id.Segment.toList( "Warehouse", "All Warehouses", "USA"), reader)); } protected Cube cubeByName(Connection connection, String cubeName) { SchemaReader reader = connection.getSchemaReader().withLocus(); Cube[] cubes = reader.getCubes(); return cubeByName(cubeName, cubes); } private Cube cubeByName(String cubeName, Cube[] cubes) { Cube resultCube = null; for (Cube cube : cubes) { if (cubeName.equals(cube.getName())) { resultCube = cube; break; } } return resultCube; } protected TupleList storeMembersUsaAndCanada( boolean includeAllMember, SchemaReader salesCubeSchemaReader, Cube salesCube) { Member usaMember = member( Id.Segment.toList("Store", "All Stores", "USA"), salesCubeSchemaReader); Member canadaMember = member( Id.Segment.toList("Store", "All Stores", "CANADA"), salesCubeSchemaReader); Member [] members; if (includeAllMember) { members = new Member[]{ allMember("Store", salesCube), usaMember, canadaMember}; } else { members = new Member[] {usaMember, canadaMember}; } return new UnaryTupleList(Arrays.asList(members)); } static class QueryAndResult { final String query; final String result; QueryAndResult(String query, String result) { this.query = query; this.result = result; } } } /** * Similar to {@link Runnable}, except classes which implement * ChooseRunnable choose what to do based upon an integer * parameter. */ interface ChooseRunnable { void run(int i); } /** * Runs a test case in several parallel threads, catching exceptions from * each one, and succeeding only if they all succeed. */ class TestCaseForker { BasicQueryTest testCase; long timeoutMs; Thread[] threads; List failures = new ArrayList(); ChooseRunnable chooseRunnable; public TestCaseForker( BasicQueryTest testCase, long timeoutMs, int threadCount, ChooseRunnable chooseRunnable) { this.testCase = testCase; this.timeoutMs = timeoutMs; this.threads = new Thread[threadCount]; this.chooseRunnable = chooseRunnable; } public void run() { ThreadGroup threadGroup = null; for (int i = 0; i < threads.length; i++) { final int threadIndex = i; threads[i] = new Thread(threadGroup, "thread #" + threadIndex) { public void run() { try { chooseRunnable.run(threadIndex); } catch (Throwable e) { e.printStackTrace(); failures.add(e); } } }; } for (Thread thread : threads) { thread.start(); } for (Thread thread : threads) { try { thread.join(timeoutMs); } catch (InterruptedException e) { failures.add( Util.newInternal( e, "Interrupted after " + timeoutMs + "ms")); break; } } if (failures.size() > 0) { for (Throwable throwable : failures) { throwable.printStackTrace(); } TestCase.fail(failures.size() + " threads failed"); } } } // End FoodMartTestCase.java mondrian-3.4.1/testsrc/main/mondrian/test/DiffRepository.java0000644000175000017500000006503011735330606024246 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.olap.Util; import junit.framework.*; import org.eigenbase.xom.XMLOutput; import org.w3c.dom.*; import org.xml.sax.SAXException; import java.io.*; import java.net.URL; import java.util.*; import javax.xml.parsers.*; /** * A collection of resources used by tests. * *

Loads files containing test input and output into memory. * If there are differences, writes out a log file containing the actual * output. * *

Typical usage is as follows:

    *
  1. A testcase class defines a method
     *
     * package com.acme.test;
     *
     * public class MyTest extends TestCase {
     *     public DiffRepository getDiffRepos() {
     *         return DiffRepository.lookup(MyTest.class);
     *     }
     *
     *     public void testToUpper() {
     *          getDiffRepos().assertEquals("${result}", "${string}");
     *     }
    
     *     public void testToLower() {
     *          getDiffRepos().assertEquals("Multi-line\nstring", "${string}");
     *     }
     * }
    * * There is an accompanying reference file named after the class, * com/acme/test/MyTest.ref.xml: *
     * <Root>
     *     <TestCase name="testToUpper">
     *         <Resource name="string">
     *             <![CDATA[String to be converted to upper case]]>
     *         </Resource>
     *         <Resource name="result">
     *             <![CDATA[STRING TO BE CONVERTED TO UPPER CASE]]>
     *         </Resource>
     *     </TestCase>
     *     <TestCase name="testToLower">
     *         <Resource name="result">
     *             <![CDATA[multi-line
     * string]]>
     *         </Resource>
     *     </TestCase>
     * </Root>
     * 
    * *

    If any of the testcases fails, a log file is generated, called * com/acme/test/MyTest.log.xml containing the actual output. * The log file is otherwise identical to the reference log, so once the * log file has been verified, it can simply be copied over to become the new * reference log.

    * *

    If a resource or testcase does not exist, DiffRepository * creates them in the log file. Because DiffRepository is so forgiving, it is * very easy to create new tests and testcases.

    * *

    The {@link #lookup} method ensures that all test cases share the same * instance of the repository. This is important more than one one test case * fails. The shared instance ensures that the generated .log.xml * file contains the actual for both test cases. * * @author jhyde */ public class DiffRepository { private final DiffRepository baseRepos; private final DocumentBuilder docBuilder; private Document doc; private final Element root; private final File refFile; private final File logFile; /* Example XML document: */ private static final String RootTag = "Root"; private static final String TestCaseTag = "TestCase"; private static final String TestCaseNameAttr = "name"; private static final String ResourceTag = "Resource"; private static final String ResourceNameAttr = "name"; private static final String ResourceSqlDialectAttr = "dialect"; private static final ThreadLocal CurrentTestCaseName = new ThreadLocal(); /** * Holds one diff-repository per class. It is necessary for all testcases * in the same class to share the same diff-repository: if the * repos gets loaded once per testcase, then only one diff is recorded. */ private static final Map mapClassToRepos = new HashMap(); /** * Default prefix directories. */ private static final String[] DefaultPrefixes = {"testsrc", "main"}; private static File findFile( Class clazz, String[] prefixes, final String suffix) { // The reference file for class "com.foo.Bar" is "com/foo/Bar.ref.xml" String rest = clazz.getName().replace('.', File.separatorChar) + suffix; File fileBase = getFileBase(clazz, prefixes); return new File(fileBase, rest); } /** * Returns the base directory relative to which test logs are stored. * *

    Deduces the directory based upon the current directory. * If the current directory is "/home/jhyde/open/mondrian/intellij", * returns "/home/jhyde/open/mondrian/testsrc". */ private static File getFileBase(Class clazz, String[] prefixes) { String javaFileName = clazz.getName().replace('.', File.separatorChar) + ".java"; File file = new File(System.getProperty("user.dir")); while (true) { File file2 = file; for (String prefix : prefixes) { file2 = new File(file2, prefix); } if (file2.isDirectory() && new File(file2, javaFileName).exists()) { return file2; } file = file.getParentFile(); if (file == null) { throw new RuntimeException("cannot find base dir"); } } } /** * Creates a DiffRepository from a pair of files. * * @param refFile File containing reference results * @param logFile File to contain the actual output of the test run * @param baseRepos Base repository to inherit from, or null * @param prefixes List of directories to search in, or null */ public DiffRepository( File refFile, File logFile, DiffRepository baseRepos, String[] prefixes) { if (prefixes == null) { prefixes = DefaultPrefixes; } this.baseRepos = baseRepos; if (refFile == null) { throw new IllegalArgumentException("url must not be null"); } this.refFile = refFile; Util.discard(this.refFile); this.logFile = logFile; // Load the document. DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance(); try { this.docBuilder = fac.newDocumentBuilder(); if (refFile.exists()) { // Parse the reference file. this.doc = docBuilder.parse(new FileInputStream(refFile)); // Don't write a log file yet -- as far as we know, it's still // identical. } else { // There's no reference file. Create and write a log file. this.doc = docBuilder.newDocument(); this.doc.appendChild( doc.createElement(RootTag)); flushDoc(); } this.root = doc.getDocumentElement(); if (!root.getNodeName().equals(RootTag)) { throw new RuntimeException( "expected root element of type '" + RootTag + "', but found '" + root.getNodeName() + "'"); } } catch (ParserConfigurationException e) { throw Util.newInternal(e, "error while creating xml parser"); } catch (IOException e) { throw Util.newInternal(e, "error while creating xml parser"); } catch (SAXException e) { throw Util.newInternal(e, "error while creating xml parser"); } } /** * Creates a read-only repository reading from a URL. * * @param refUrl URL pointing to reference file */ public DiffRepository(URL refUrl) { this.refFile = null; this.logFile = null; this.baseRepos = null; // Load the document. DocumentBuilderFactory fac = DocumentBuilderFactory.newInstance(); try { this.docBuilder = fac.newDocumentBuilder(); // Parse the reference file. this.doc = docBuilder.parse(refUrl.openStream()); this.root = doc.getDocumentElement(); if (!root.getNodeName().equals(RootTag)) { throw new RuntimeException( "expected root element of type '" + RootTag + "', but found '" + root.getNodeName() + "'"); } } catch (ParserConfigurationException e) { throw Util.newInternal(e, "error while creating xml parser"); } catch (IOException e) { throw Util.newInternal(e, "error while creating xml parser"); } catch (SAXException e) { throw Util.newInternal(e, "error while creating xml parser"); } } /** * Expands a string containing one or more variables. * (Currently only works if there is one variable.) */ public String expand(String tag, String text) { if (text == null) { return null; } else if (text.startsWith("${") && text.endsWith("}")) { final String testCaseName = getCurrentTestCaseName(true); final String token = text.substring(2, text.length() - 1); if (tag == null) { tag = token; } assert token.startsWith(tag) : "token '" + token + "' does not match tag '" + tag + "'"; final String expanded = get(testCaseName, token); if (expanded == null) { // Token is not specified. Return the original text: this will // cause a diff, and the actual value will be written to the // log file. return text; } return expanded; } else { // Make sure what appears in the resource file is consistent with // what is in the Java. It helps to have a redundant copy in the // resource file. final String testCaseName = getCurrentTestCaseName(true); if (baseRepos != null && baseRepos.get(testCaseName, tag) != null) { // set in base repos; don't override } else { set(tag, text); } return text; } } /** * Sets the value of a given resource of the current testcase. * * @param resourceName Name of the resource, e.g. "sql" * @param value Value of the resource */ public synchronized void set(String resourceName, String value) { assert resourceName != null; final String testCaseName = getCurrentTestCaseName(true); update(testCaseName, resourceName, value); } public void amend(String expected, String actual) { if (expected.startsWith("${") && expected.endsWith("}")) { String token = expected.substring(2, expected.length() - 1); set(token, actual); } else { // do nothing } } public synchronized String get( final String testCaseName, String resourceName) { return get(testCaseName, resourceName, null); } /** * Returns a given resource from a given testcase. * * @param testCaseName Name of test case, e.g. "testFoo" * @param resourceName Name of resource, e.g. "sql", "plan" * @param dialectName Name of sql dialect, e.g. "MYSQL", "LUCIDDB" * @return The value of the resource, or null if not found */ public synchronized String get( final String testCaseName, String resourceName, String dialectName) { Element testCaseElement = getTestCaseElement(root, testCaseName); if (testCaseElement == null) { if (baseRepos != null) { return baseRepos.get(testCaseName, resourceName, dialectName); } else { return null; } } final Element resourceElement = getResourceElement(testCaseElement, resourceName, dialectName); if (resourceElement != null) { return getText(resourceElement); } return null; } /** * Returns the text under an element. */ private static String getText(Element element) { // If there is a child, return its text and ignore // all other child elements. final NodeList childNodes = element.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node node = childNodes.item(i); if (node instanceof CDATASection) { return node.getNodeValue(); } } // Otherwise return all the text under this element (including // whitespace). StringBuilder buf = new StringBuilder(); for (int i = 0; i < childNodes.getLength(); i++) { Node node = childNodes.item(i); if (node instanceof Text) { buf.append(((Text) node).getData()); } } return buf.toString(); } /** * Returns the <TestCase> element corresponding to the current * test case. * * @param root Root element of the document * @param testCaseName Name of test case * @return TestCase element, or null if not found */ private static Element getTestCaseElement( final Element root, final String testCaseName) { final NodeList childNodes = root.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node child = childNodes.item(i); if (child.getNodeName().equals(TestCaseTag)) { Element testCase = (Element) child; if (testCaseName.equals( testCase.getAttribute(TestCaseNameAttr))) { return testCase; } } } return null; } /** * @return a list of the names of all test cases defined in the * repository file */ public List getTestCaseNames() { List list = new ArrayList(); final NodeList childNodes = root.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node child = childNodes.item(i); if (child.getNodeName().equals(TestCaseTag)) { Element testCase = (Element) child; list.add(testCase.getAttribute(TestCaseNameAttr)); } } return list; } /** * Sets the name of the current test case. For use in * tests created via dynamic suite() methods. Caller should pass * test case name from setUp(), and null from tearDown() to clear. * * @param testCaseName name of test case to set as current, * or null to clear */ public void setCurrentTestCaseName(String testCaseName) { CurrentTestCaseName.set(testCaseName); } /** * Returns the name of the current testcase by looking up the call * stack for a method whose name starts with "test", for example * "testFoo". * * @param fail Whether to fail if no method is found * @return Name of current testcase, or null if not found */ public String getCurrentTestCaseName(boolean fail) { // check thread-local first String testCaseName = CurrentTestCaseName.get(); if (testCaseName != null) { return testCaseName; } // Clever, this. Dump the stack and look up it for a method which // looks like a testcase name, e.g. "testFoo". final StackTraceElement[] stackTrace; //noinspection ThrowableInstanceNeverThrown Throwable runtimeException = new Throwable(); runtimeException.fillInStackTrace(); stackTrace = runtimeException.getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { final String methodName = stackTraceElement.getMethodName(); if (methodName.startsWith("test")) { return methodName; } } if (fail) { throw new RuntimeException("no testcase on current callstack"); } else { return null; } } public void assertEquals(String tag, String expected, String actual) { final String testCaseName = getCurrentTestCaseName(true); String expected2 = expand(tag, expected); if (expected2 == null) { update(testCaseName, expected, actual); throw new AssertionFailedError( "reference file does not contain resource '" + expected + "' for testcase '" + testCaseName + "'"); } else { try { // TODO jvs 25-Apr-2006: reuse bulk of // DiffTestCase.diffTestLog here; besides newline // insensitivity, it can report on the line // at which the first diff occurs, which is useful // for largish snippets String expected2Canonical = Util.replace(expected2, Util.nl, "\n"); String actualCanonical = Util.replace(actual, Util.nl, "\n"); Assert.assertEquals( expected2Canonical, actualCanonical); } catch (ComparisonFailure e) { amend(expected, actual); throw e; } } } /** * Creates a new document with a given resource. * *

    This method is synchronized, in case two threads are running * test cases of this test at the same time. * * @param testCaseName Test case name * @param resourceName Resource name * @param value New value of resource */ private synchronized void update( String testCaseName, String resourceName, String value) { Element testCaseElement = getTestCaseElement(root, testCaseName); if (testCaseElement == null) { testCaseElement = doc.createElement(TestCaseTag); testCaseElement.setAttribute(TestCaseNameAttr, testCaseName); root.appendChild(testCaseElement); } Element resourceElement = getResourceElement(testCaseElement, resourceName); if (resourceElement == null) { resourceElement = doc.createElement(ResourceTag); resourceElement.setAttribute(ResourceNameAttr, resourceName); testCaseElement.appendChild(resourceElement); } else { removeAllChildren(resourceElement); } resourceElement.appendChild(doc.createCDATASection(value)); // Write out the document. flushDoc(); } /** * Flush the reference document to the file system. */ private void flushDoc() { FileWriter w = null; try { w = new FileWriter(logFile); write(doc, w); } catch (IOException e) { throw Util.newInternal( e, "error while writing test reference log '" + logFile + "'"); } finally { if (w != null) { try { w.close(); } catch (IOException e) { // ignore } } } } /** * Returns a given resource from a given testcase. * * @param testCaseElement The enclosing TestCase element, * e.g. <TestCase name="testFoo">. * @param resourceName Name of resource, e.g. "sql", "plan" * @return The value of the resource, or null if not found */ private static Element getResourceElement( Element testCaseElement, String resourceName) { return getResourceElement(testCaseElement, resourceName, null); } private static Element getResourceElement( Element testCaseElement, String resourceName, String resourceAttribute1) { final NodeList childNodes = testCaseElement.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node child = childNodes.item(i); if (child.getNodeName().equals(ResourceTag) && resourceName.equals( ((Element) child).getAttribute(ResourceNameAttr)) && ((resourceAttribute1 == null) || resourceAttribute1.equals( ((Element) child).getAttribute( ResourceSqlDialectAttr)))) { return (Element) child; } } return null; } private static void removeAllChildren(Element element) { final NodeList childNodes = element.getChildNodes(); while (childNodes.getLength() > 0) { element.removeChild(childNodes.item(0)); } } /** * Serializes an XML document as text. * *

    FIXME: I'm sure there's a library call to do this, but I'm danged * if I can find it. -- jhyde, 2006/2/9. */ private static void write(Document doc, Writer w) { final XMLOutput out = new XMLOutput(w); out.setIndentString(" "); writeNode(doc, out); } private static void writeNode(Node node, XMLOutput out) { final NodeList childNodes; switch (node.getNodeType()) { case Node.DOCUMENT_NODE: out.print("" + Util.nl); childNodes = node.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node child = childNodes.item(i); writeNode(child, out); } // writeNode(((Document) node).getDocumentElement(), out); break; case Node.ELEMENT_NODE: Element element = (Element) node; final String tagName = element.getTagName(); out.beginBeginTag(tagName); // Attributes. final NamedNodeMap attributeMap = element.getAttributes(); for (int i = 0; i < attributeMap.getLength(); i++) { final Node att = attributeMap.item(i); out.attribute(att.getNodeName(), att.getNodeValue()); } out.endBeginTag(tagName); // Write child nodes, ignoring attributes but including text. childNodes = node.getChildNodes(); for (int i = 0; i < childNodes.getLength(); i++) { Node child = childNodes.item(i); if (child.getNodeType() == Node.ATTRIBUTE_NODE) { continue; } writeNode(child, out); } out.endTag(tagName); break; case Node.ATTRIBUTE_NODE: out.attribute(node.getNodeName(), node.getNodeValue()); break; case Node.CDATA_SECTION_NODE: CDATASection cdata = (CDATASection) node; out.cdata(cdata.getNodeValue(), true); break; case Node.TEXT_NODE: Text text = (Text) node; final String wholeText = text.getNodeValue(); if (!isWhitespace(wholeText)) { out.cdata(wholeText, false); } break; case Node.COMMENT_NODE: Comment comment = (Comment) node; out.print("" + Util.nl); break; default: throw new RuntimeException( "unexpected node type: " + node.getNodeType() + " (" + node + ")"); } } /** * Returns whether a given piece of text is solely whitespace. * * @param text Text * @return Whether text is whitespace */ private static boolean isWhitespace(String text) { for (int i = 0, count = text.length(); i < count; ++i) { final char c = text.charAt(i); switch (c) { case ' ': case '\t': case '\n': break; default: return false; } } return true; } /** * Finds the repository instance for a given class, using the default * prefixes, and with no parent repository. * * @see #lookup(Class, DiffRepository, String[]) */ public static DiffRepository lookup(Class clazz) { return lookup(clazz, null, null); } /** * Finds the repository instance for a given class. * *

    It is important that all testcases in a class share the same * repository instance. This ensures that, if two or more testcases fail, * the log file will contains the actual results of both testcases. * *

    The baseRepos parameter is useful if the test is an * extension to a previous test. If the test class has a base class which * also has a repository, specify the repository here. DiffRepository will * look for resources in the base class if it cannot find them in this * repository. If test resources from testcases in the base class are * missing or incorrect, it will not write them to the log file -- you * probably need to fix the base test. * * @param clazz Testcase class * @param baseRepos Base class of test class * @param prefixes Array of directory names to look in; if null, the * default {"testsrc", "main"} is used * @return The diff repository shared between testcases in this class. */ public static DiffRepository lookup( Class clazz, DiffRepository baseRepos, String[] prefixes) { DiffRepository diffRepos = mapClassToRepos.get(clazz); if (diffRepos == null) { if (prefixes == null) { prefixes = DefaultPrefixes; } final File refFile = findFile(clazz, prefixes, ".ref.xml"); final File logFile = findFile(clazz, prefixes, ".log.xml"); diffRepos = new DiffRepository(refFile, logFile, baseRepos, null); mapClassToRepos.put(clazz, diffRepos); } return diffRepos; } } // End DiffRepository.java mondrian-3.4.1/testsrc/main/mondrian/test/CmdRunnerTest.ref.xml0000644000175000017500000000201511735330606024457 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/test/build/0000755000175000017500000000000011735330606021526 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/test/build/package.html0000644000175000017500000000011711735330606024006 0ustar drazzibdrazzib Regression tests relating to the build process. mondrian-3.4.1/testsrc/main/mondrian/test/build/AntTestBase.java0000644000175000017500000001156211735330606024553 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2008 SQLstream, Inc. // Copyright (C) 2009-2010 Pentaho // All Rights Reserved. */ package mondrian.test.build; import mondrian.olap.Util; import junit.framework.TestCase; import java.io.*; /** * Base class for tests that execute Ant targets. Sub-classes * should invoke {@link #runAntTest(String)} to run an Ant target. If * the Ant sub-process cannot be started of if it returns an exit code that * indicates error, the test fails. * *

    * AntTestBase makes the following assumptions about its run-time environment: *

      *
    1. Ant can be invoked by executing ant. That is, ant is * on the current PATH.
    2. *
    3. The version of Ant on the PATH is new enough to execute the * build.xml script.
    4. *
    5. The test is being invoked in the root directory (e.g. * //open/mondrian) as the current directory or a subdirectory of it.
    6. *
    * *
     * REVIEW: SWZ: 3/11/2006: This class is not portable to Windows.  Potential
     * solutions:
     * 1) Check for Windows via System properties and invoke
     *    "command.com ant.bat [target]" (or whatever's necessary) when the OS is
     *    Windows.
     * 2) Require Ant libraries be on the classpath and invoke Ant's API
     *    directly.  This is preferred, since it should be OS neutral.
     * 
    * * @author Stephan Zuercher * @since Mar 11, 2006 */ abstract class AntTestBase extends TestCase { private static final boolean DEBUG = false; /** * Creates an AntTestBase. * * @param name Test name */ AntTestBase(String name) { super(name); } /** * Runs an ant task. * * @param target Name of ant target * @throws IOException * @throws InterruptedException */ protected void runAntTest(String target) throws IOException, InterruptedException { if (Util.PreJdk15) { // Cannot invoke ant in JDK 1.4 or ealier. // ant gives "Unknown argument: -cp" return; } // On hudson, ant is not on the path but is at /opt/ant1.7. If that // file exists, assume that we are on hudson. Otherwise, require ant // to be on the path. String antCommand = "ant"; final String[] paths = { "/opt/ant1.7/bin/ant", "/opt/apache-ant-1.7.1/bin/ant" }; for (String path : paths) { final File antFile = new File(path); if (antFile.exists()) { antCommand = antFile.getAbsolutePath(); break; } } Runtime runtime = Runtime.getRuntime(); Process proc = runtime.exec( new String[] { antCommand, "-find", "build.xml", target }); final Sucker outSucker = new Sucker(proc.getInputStream(), DEBUG ? System.out : null); final Sucker errSucker = new Sucker(proc.getErrorStream(), DEBUG ? System.err : null); outSucker.start(); errSucker.start(); int exitCode = proc.waitFor(); if (exitCode != 0) { final String lineSep = System.getProperty("line.separator"); fail( "Error running 'ant " + target + "'" + lineSep + "Stdout:" + lineSep + outSucker.toString() + "Stderr:" + lineSep + errSucker.toString()); } } /** * Thread that reads from an input stream, stores in a buffer, and also * writes to a given output stream. * *

    Useful for ensuring that processes don't hang up because one of their * outputs (stdout or stderr) is full. */ private static class Sucker extends Thread { private final InputStream stream; private final PrintStream out; private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); /** * Creates a Sucker. * * @param stream Input stream * @param out Output stream */ Sucker(InputStream stream, PrintStream out) { this.stream = stream; this.out = out; } public void run() { byte[] buf = new byte[1000]; int x; try { while ((x = stream.read(buf)) >= 0) { baos.write(buf, 0, x); if (out != null) { out.write(buf, 0, x); } } } catch (IOException e) { throw new RuntimeException(e); } } public String toString() { return baos.toString(); } } } // End AntTestBase.java mondrian-3.4.1/testsrc/main/mondrian/test/build/CodeComplianceTest.java0000644000175000017500000000223311735330606026076 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2009 SQLstream, Inc. // Copyright (C) 2009-2009 Pentaho // All Rights Reserved. */ package mondrian.test.build; /** * Omnibus code compliance test to wrap various ant tasks that check the code * base, such checkFile, as macker, Javadoc, preambles, and so on. * * @author Chard Nelson * @since Sep 8, 2009 */ public class CodeComplianceTest extends AntTestBase { /** * Creates a CodeComplianceTest. * * @param name Test name */ public CodeComplianceTest(String name) { super(name); } /** * Checks source code file formatting. */ public void testCodeFormatting() throws Exception { runAntTest("checkCodeFormatting"); } /** * Checks that javadoc can be generated without errors. */ public void testJavadoc() throws Exception { runAntTest("checkJavadoc"); } } // End CodeComplianceTest.java mondrian-3.4.1/testsrc/main/mondrian/test/ParallelTest.java0000644000175000017500000000653511735330606023677 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; import java.util.Random; /** * A ParameterTest is a test suite for functionality relating to * parameters. * * @author jhyde * @since Jun 26, 2006 */ public class ParallelTest extends FoodMartTestCase { public ParallelTest(String name) { super(name); } public void testParallelSchemaFlush() { // 5 threads, 8 cycles each, flush cache 1/10 of the time checkSchemaFlush(5, 8, 10); } /** * Tests several threads, each of which is creating connections and * periodically flushing the schema cache. * * @param count * @param cycleCount * @param flushInverseFrequency */ private void checkSchemaFlush( final int count, final int cycleCount, final int flushInverseFrequency) { final Random random = new Random(123456); Worker[] workers = new Worker[count]; Thread[] threads = new Thread[count]; for (int i = 0; i < count; i++) { workers[i] = new Worker() { public void runSafe() { for (int i = 0; i < cycleCount; ++i) { cycle(); try { // Sleep up to 100ms. Thread.sleep(random.nextInt(100)); } catch (InterruptedException e) { throw Util.newInternal(e, "interrupted"); } } } private void cycle() { Connection connection = getConnection(); Query query = connection.parseQuery( "select {[Measures].[Unit Sales]} on columns," + " {[Product].Members} on rows " + "from [Sales]"); Result result = connection.execute(query); String s = result.toString(); assertNotNull(s); // 20% of the time, flush the schema cache. if (random.nextInt(flushInverseFrequency) == 0) { final CacheControl cacheControl = connection.getCacheControl(null); cacheControl.flushSchemaCache(); } } }; threads[i] = new Thread(workers[i]); } for (int i = 0; i < count; i++) { threads[i].start(); } for (int i = 0; i < count; i++) { try { threads[i].join(); } catch (InterruptedException e) { throw Util.newInternal(e, "while joining thread #" + i); } } } private static abstract class Worker implements Runnable { Throwable throwable; public void run() { try { runSafe(); } catch (Throwable e) { throwable = e; } } public abstract void runSafe(); } } // End ParallelTest.java mondrian-3.4.1/testsrc/main/mondrian/test/QueryRunner.java0000644000175000017500000002763011735330606023601 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; import java.io.PrintStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; /** * Thread which runs an MDX query and checks it against an expected result. * It is used for concurrency testing. */ public class QueryRunner extends Thread { private long mRunTime; private long mStartTime; private long mStopTime; private List mExceptions = new ArrayList(); private int mMyId; private int mRunCount; private int mSuccessCount; private boolean mRandomQueries; static final String[] Queries = new String[]{ // #0 "select {[Measures].[Unit Sales]} on columns\n" + " from Sales", // mdx sample #1 "select\n" + " {[Measures].[Unit Sales]} on columns,\n" + " order(except([Promotion Media].[Media Type].members,{[Promotion Media].[Media Type].[No Media]}),[Measures].[Unit Sales],DESC) on rows\n" + "from Sales", // mdx sample #2 "select\n" + " { [Measures].[Units Shipped], [Measures].[Units Ordered] } on columns,\n" + " NON EMPTY [Store].[Store Name].members on rows\n" + "from Warehouse", // mdx sample #3 "with member [Measures].[Store Sales Last Period] as '([Measures].[Store Sales], Time.PrevMember)'\n" + "select\n" + " {[Measures].[Store Sales Last Period]} on columns,\n" + " {TopCount([Product].[Product Department].members,5, [Measures].[Store Sales Last Period])} on rows\n" + "from Sales\n" + "where ([Time].[1998])", // mdx sample #4 "with member [Measures].[Total Store Sales] as 'Sum(YTD(),[Measures].[Store Sales])'\n" + "select\n" + " {[Measures].[Total Store Sales]} on columns,\n" + " {TopCount([Product].[Product Department].members,5, [Measures].[Total Store Sales])} on rows\n" + "from Sales\n" + "where ([Time].[1997].[Q2].[4])", // mdx sample #5 "with member [Measures].[Store Profit Rate] as '([Measures].[Store Sales]-[Measures].[Store Cost])/[Measures].[Store Cost]', format = '#.00%'\n" + "select\n" + " {[Measures].[Store Cost],[Measures].[Store Sales],[Measures].[Store Profit Rate]} on columns,\n" + " Order([Product].[Product Department].members, [Measures].[Store Profit Rate], BDESC) on rows\n" + "from Sales\n" + "where ([Time].[1997])", // mdx sample #6 "with\n" + " member [Product].[All Products].[Drink].[Percent of Alcoholic Drinks] as '[Product].[All Products].[Drink].[Alcoholic Beverages]/[Product].[All Products].[Drink]', format = '#.00%'\n" + "select\n" + " { [Product].[All Products].[Drink].[Percent of Alcoholic Drinks] } on columns,\n" + " order([Customers].[All Customers].[USA].[WA].Children, [Product].[All Products].[Drink].[Percent of Alcoholic Drinks],BDESC) on rows\n" + "from Sales\n" + "where ([Measures].[Unit Sales])", // mdx sample #7 "with member [Measures].[Accumulated Sales] as 'Sum(YTD(),[Measures].[Store Sales])'\n" + "select\n" + " {[Measures].[Store Sales],[Measures].[Accumulated Sales]} on columns,\n" + " {Descendants([Time].[1997],[Time].[Month])} on rows\n" + "from Sales", // #8 "select\n" + " {[Measures].[Unit Sales]} on columns,\n" + " [Gender].members on rows\n" + "from Sales", // // #9 // "with\n" // + " member [Product].[Non dairy] as" // + " '[Product].[All Products] - [Product].[Food].[Dairy]'\n" // + " member [Measures].[Dairy ever] as" // + " 'sum([Time].members, " // + "([Measures].[Unit Sales],[Product].[Food].[Dairy]))'\n" // + " set [Customers who never bought dairy] as" // + " 'filter([Customers].members, [Measures].[Dairy ever] = 0)'\n" // + "select\n" // + " {[Measures].[Unit Sales], [Measures].[Dairy ever]} on columns,\n" // + " [Customers who never bought dairy] on rows\n" // + "from Sales\r\n", // #10 "select {[Has bought dairy].members} on columns,\n" + " {[Customers].[USA]} on rows\n" + "from Sales\n" + "where ([Measures].[Unit Sales])", // #11 "WITH\n" + " MEMBER [Measures].[StoreType] AS \n" + " '[Store].CurrentMember.Properties(\"Store Type\")',\n" + " SOLVE_ORDER = 2\n" + " MEMBER [Measures].[ProfitPct] AS \n" + " '((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',\n" + " SOLVE_ORDER = 1, FORMAT_STRING = 'Percent'\n" + "SELECT\n" + " { [Store].[Store Name].Members} ON COLUMNS,\n" + " { [Measures].[Store Sales], [Measures].[Store Cost], [Measures].[StoreType],\n" + " [Measures].[ProfitPct] } ON ROWS\n" + "FROM Sales", // #12 "WITH\n" + " MEMBER [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[BigSeller] AS\n" + " 'IIf([Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine] > 100, \"Yes\",\"No\")'\n" + "SELECT\n" + " {[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[BigSeller]} ON COLUMNS,\n" + " {Store.[Store Name].Members} ON ROWS\n" + "FROM Sales", // #13 "WITH\n" + " MEMBER [Measures].[ProfitPct] AS \n" + " '((Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales])',\n" + " SOLVE_ORDER = 1, FORMAT_STRING = 'Percent'\n" + " MEMBER [Measures].[ProfitValue] AS \n" + " '[Measures].[Store Sales] * [Measures].[ProfitPct]',\n" + " SOLVE_ORDER = 2, FORMAT_STRING = 'Currency'\n" + "SELECT\n" + " { [Store].[Store Name].Members} ON COLUMNS,\n" + " { [Measures].[Store Sales], [Measures].[Store Cost], [Measures].[ProfitValue],\n" + " [Measures].[ProfitPct] } ON ROWS\n" + "FROM Sales", // #14: cyclical calculated members "WITH\n" + " MEMBER [Product].[X] AS '[Product].[Y]'\n" + " MEMBER [Product].[Y] AS '[Product].[X]'\n" + "SELECT\n" + " {[Product].[X]} ON COLUMNS,\n" + " {Store.[Store Name].Members} ON ROWS\n" + "FROM Sales", // #15 "WITH MEMBER MEASURES.ProfitPercent AS\n" + " '([Measures].[Store Sales]-[Measures].[Store Cost])/([Measures].[Store Cost])',\n" + " FORMAT_STRING = '#.00%', SOLVE_ORDER = 1\n" + " MEMBER [Time].[Time].[First Half 97] AS '[Time].[1997].[Q1] + [Time].[1997].[Q2]'\n" + " MEMBER [Time].[Time].[Second Half 97] AS '[Time].[1997].[Q3] + [Time].[1997].[Q4]'\n" + " SELECT {[Time].[First Half 97],\n" + " [Time].[Second Half 97],\n" + " [Time].[1997].CHILDREN} ON COLUMNS,\n" + " {[Store].[Store Country].[USA].CHILDREN} ON ROWS\n" + " FROM [Sales]\n" + " WHERE ([Measures].[ProfitPercent])", // #16 (= mdx sample #7, but uses virtual cube) "with member [Measures].[Accumulated Sales] as 'Sum(YTD(),[Measures].[Store Sales])'\n" + "select\n" + " {[Measures].[Store Sales],[Measures].[Accumulated Sales]} on columns,\n" + " {Descendants([Time].[1997],[Time].[Month])} on rows\n" + "from [Warehouse and Sales]", // #17 Virtual cube. Note that Unit Sales is independent of Warehouse. "select CrossJoin(\r\n" + " {[Warehouse].DefaultMember, [Warehouse].[USA].children},\n" + " {[Measures].[Unit Sales], [Measures].[Units Shipped]}) on columns,\n" + " [Time].children on rows\n" + "from [Warehouse and Sales]", // #18 bug: should allow dimension to be used as shorthand for member "select {[Measures].[Unit Sales]} on columns,\n" + " {[Store], [Store].children} on rows\n" + "from [Sales]", // #19 bug: should allow 'members(n)' (and do it efficiently) "select {[Measures].[Unit Sales]} on columns,\n" + " {[Customers].members} on rows\n" + "from [Sales]", // #20 crossjoins on rows and columns, and a slicer "select\n" + " CrossJoin(\n" + " {[Measures].[Unit Sales], [Measures].[Store Sales]},\n" + " {[Time].[1997].[Q2].children}) on columns, \n" + " CrossJoin(\n" + " CrossJoin(\n" + " [Gender].members,\n" + " [Marital Status].members),\n" + " {[Store], [Store].children}) on rows\n" + "from [Sales]\n" + "where (\n" + " [Product].[Food],\n" + " [Education Level].[High School Degree],\n" + " [Promotions].DefaultMember)", }; public QueryRunner(int id, int numSeconds, boolean useRandomQuery) { mRunTime = (long) numSeconds * 1000; mMyId = id; mRandomQueries = useRandomQuery; } public void run() { mStartTime = System.currentTimeMillis(); try { Connection cxn = DriverManager.getConnection( StandAlone.ConnectionString, null); int queryIndex = -1; while (System.currentTimeMillis() - mStartTime < mRunTime) { try { if (mRandomQueries) { queryIndex = (int)(Math.random() * Queries.length); } else { queryIndex = mRunCount % Queries.length; } mRunCount++; Query query = cxn.parseQuery(Queries[queryIndex]); cxn.execute(query); mSuccessCount++; } catch (Exception e) { mExceptions.add( new Exception( "Exception occurred on iteration " + queryIndex, e)); } } mStopTime = System.currentTimeMillis(); } catch (Exception e) { mExceptions.add(e); } } public void report(PrintStream out) { String message = MessageFormat.format( "Thread {0} ran {1} queries, {2} successfully in {3} milliseconds", mMyId, mRunCount, mSuccessCount, mStopTime - mStartTime); out.println(message); for (Exception throwable : mExceptions) { throwable.printStackTrace(out); } } private static void runTest( int numThreads, int seconds, boolean randomQueries) { QueryRunner[] runners = new QueryRunner[numThreads]; for (int idx = 0; idx < runners.length; idx++) { runners[idx] = new QueryRunner(idx, seconds, randomQueries); } for (int idx = 0; idx < runners.length; idx++) { runners[idx].start(); } for (int idx = 0; idx < runners.length; idx++) { try { runners[idx].join(); } catch (InterruptedException e) { e.printStackTrace(); } } for (int idx = 0; idx < runners.length; idx++) { runners[idx].report(System.out); } } public static void main(String[] args) { if (args.length != 3) { System.err.println( "usage: QueryRunner numThreads seconds randomQueries"); } else { runTest( Integer.parseInt(args[0]), Integer.parseInt(args[1]), Boolean.valueOf(args[2])); } } } // End QueryRunner.java mondrian-3.4.1/testsrc/main/mondrian/test/CacheTest.java0000644000175000017500000001123011735330606023132 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; import mondrian.server.monitor.Monitor; import mondrian.server.monitor.ServerInfo; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; /** * Cunning tests to discover whether the cache manager is working to spec * and is thread-safe. * * @author Julian Hyde */ public class CacheTest extends FoodMartTestCase { /** * Tests that if N queries are executed at the same time, only one segment * request will be sent. The query that arrives second should see that there * is a pending segment in the aggregation manager, and should wait for * that. * *

    If the test fails, look at segmentCreateViaSqlCount. If it has * increased by more than one between before and after, the clients have not * managed to share work. If it has not increased, the cache was probably * not flushed correctly.

    */ public void testNQueriesWaitingForSameSegmentRepeat() throws ExecutionException, InterruptedException { final int parallel = 10; final ThreadPoolExecutor executor = new ThreadPoolExecutor( 2, parallel, 1, TimeUnit.SECONDS, new ArrayBlockingQueue(parallel * 2)); final int repeatCount = 20; for (int i = 0; i < repeatCount; i++) { checkNQueriesWaitingForSameSegment( executor, parallel, "iteration #" + i + " of " + repeatCount); } executor.shutdown(); } private void checkNQueriesWaitingForSameSegment( ThreadPoolExecutor executor, int parallel, String iteration) throws InterruptedException, ExecutionException { final MondrianServer server = MondrianServer.forConnection(getConnection()); final CacheControl cacheControl = getConnection().getCacheControl(null); cacheControl.flush( cacheControl.createMeasuresRegion( getCubeWithName( "Sales", getConnection().getSchema().getCubes()))); Thread.sleep(2000); // wait for flush to propagate final Monitor monitor = server.getMonitor(); final ServerInfo serverBefore = monitor.getServer(); final List> futures = new ArrayList>(); for (int i = 0; i < parallel; i++) { Callable runnable = new Callable() { public Boolean call() { assertQueryReturns( "select [Gender].Children * [Product].Children on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[F], [Product].[Drink]}\n" + "{[Gender].[F], [Product].[Food]}\n" + "{[Gender].[F], [Product].[Non-Consumable]}\n" + "{[Gender].[M], [Product].[Drink]}\n" + "{[Gender].[M], [Product].[Food]}\n" + "{[Gender].[M], [Product].[Non-Consumable]}\n" + "Row #0: 12,202\n" + "Row #0: 94,814\n" + "Row #0: 24,542\n" + "Row #0: 12,395\n" + "Row #0: 97,126\n" + "Row #0: 25,694\n"); return true; } }; futures.add(executor.submit(runnable)); } for (Future future : futures) { assertTrue(future.get() == Boolean.TRUE); } final ServerInfo serverAfter = monitor.getServer(); final String beforeAfter = "before: " + serverBefore + "\n" + "after: " + serverAfter + "\n" + iteration; assertTrue( beforeAfter, serverAfter.segmentCreateCount == serverBefore.segmentCreateCount + 1 && serverAfter.segmentCreateViaSqlCount == serverBefore.segmentCreateViaSqlCount + 1); } private Cube getCubeWithName(String cubeName, Cube[] cubes) { for (Cube cube : cubes) { if (cubeName.equals(cube.getName())) { return cube; } } return null; } } // End CacheTest.java mondrian-3.4.1/testsrc/main/mondrian/test/package.html0000644000175000017500000000007211735330606022707 0ustar drazzibdrazzib Suite of regression tests. mondrian-3.4.1/testsrc/main/mondrian/test/TupleListTest.java0000644000175000017500000002167711735330606024074 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2011 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.calc.TupleCollections; import mondrian.calc.TupleList; import mondrian.calc.impl.*; import mondrian.olap.*; import mondrian.rolap.RolapConnection; import mondrian.server.Locus; import java.util.*; /** * Unit test for {@link TupleList} and common implementations. * * @author jhyde */ public class TupleListTest extends FoodMartTestCase { public TupleListTest(String name) { super(name); } public TupleListTest() { super(); } public void testTupleList() { assertTrue(TupleCollections.createList(1) instanceof UnaryTupleList); assertTrue(TupleCollections.createList(2) instanceof ArrayTupleList); } public void testUnaryTupleList() { // empty list final TupleList list0 = new UnaryTupleList(); assertTrue(list0.isEmpty()); assertEquals(0, list0.size()); assertEquals(list0, TupleCollections.emptyList(1)); TupleList list1 = new UnaryTupleList(); assertEquals(list0, list1); final Member storeUsaMember = xxx("[Store].[USA]"); list1.add(Collections.singletonList(storeUsaMember)); assertFalse(list1.isEmpty()); assertEquals(1, list1.size()); assertNotSame(list0, list1); TupleList list2 = new UnaryTupleList(); list2.addTuple(new Member[]{storeUsaMember}); assertFalse(list2.isEmpty()); assertEquals(1, list2.size()); assertEquals(list1, list2); list2.clear(); assertEquals(list0, list2); assertEquals(list2, list0); // For various lists, sublist returns the whole thing. for (TupleList list : Arrays.asList(list0, list1, list2)) { assertEquals(list, list.subList(0, list.size())); assertNotSame(list, list.subList(0, list.size())); } // Null members OK (at least for TupleList). list1.addTuple(new Member[]{null}); list1.add(Collections.singletonList(null)); } public void testArrayTupleList() { final Member genderFMember = xxx("[Gender].[F]"); final Member genderMMember = xxx("[Gender].[M]"); // empty list final TupleList list0 = new ArrayTupleList(2); assertTrue(list0.isEmpty()); assertEquals(0, list0.size()); assertEquals(list0, TupleCollections.emptyList(2)); TupleList list1 = new ArrayTupleList(2); assertEquals(list0, list1); final Member storeUsaMember = xxx("[Store].[USA]"); list1.add(Arrays.asList(storeUsaMember, genderFMember)); assertFalse(list1.isEmpty()); assertEquals(1, list1.size()); assertNotSame(list0, list1); try { list1.add(Arrays.asList(storeUsaMember)); fail("expected error"); } catch (IllegalArgumentException e) { assertEquals("Tuple length does not match arity", e.getMessage()); } try { list1.addTuple(new Member[] {storeUsaMember}); fail("expected error"); } catch (IllegalArgumentException e) { assertEquals("Tuple length does not match arity", e.getMessage()); } try { list1.add( Arrays.asList(storeUsaMember, genderFMember, genderFMember)); fail("expected error"); } catch (IllegalArgumentException e) { assertEquals("Tuple length does not match arity", e.getMessage()); } try { list1.addTuple( new Member[]{storeUsaMember, genderFMember, genderFMember}); fail("expected error"); } catch (IllegalArgumentException e) { assertEquals("Tuple length does not match arity", e.getMessage()); } TupleList list2 = new ArrayTupleList(2); list2.addTuple(new Member[]{storeUsaMember, genderFMember}); assertFalse(list2.isEmpty()); assertEquals(1, list2.size()); assertEquals(list1, list2); list2.clear(); assertEquals(list0, list2); assertEquals(list2, list0); assertEquals("[]", list0.toString()); assertEquals("[[[Store].[USA], [Gender].[F]]]", list1.toString()); assertEquals("[]", list2.toString()); // For various lists, sublist returns the whole thing. for (TupleList list : Arrays.asList(list0, list1, list2)) { final TupleList sublist = list.subList(0, list.size()); assertNotNull(sublist); assertNotNull(sublist.toString()); assertEquals(sublist.isEmpty(), list.isEmpty()); assertEquals(list, sublist); assertNotSame(list, sublist); } // Null members OK (at least for TupleList). list1.addTuple(storeUsaMember, null); list1.add(Arrays.asList(storeUsaMember, null)); TupleList fm = new ArrayTupleList(2); fm.addTuple(genderFMember, storeUsaMember); fm.addTuple(genderMMember, storeUsaMember); checkProject(fm); } public void testDelegatingTupleList() { final Member genderFMember = xxx("[Gender].[F]"); final Member genderMMember = xxx("[Gender].[M]"); final Member storeUsaMember = xxx("[Store].[USA]"); final List> arrayList = new ArrayList>(); TupleList fm = new DelegatingTupleList(2, arrayList); fm.addTuple(genderFMember, storeUsaMember); fm.addTuple(genderMMember, storeUsaMember); assertEquals(2, fm.size()); assertEquals(2, fm.getArity()); assertEquals( "[[[Gender].[F], [Store].[USA]], [[Gender].[M], [Store].[USA]]]", fm.toString()); checkProject(fm); } /** * This is a test for MONDRIAN-1040. The DelegatingTupleList.slice() * method was mixing up the column and index variables. */ public void testDelegatingTupleListSlice() { // Functional test. assertQueryReturns( "select {[Measures].[Store Sales]} ON COLUMNS, Hierarchize(Except({[Customers].[All Customers], [Customers].[All Customers].Children}, {[Customers].[All Customers]})) ON ROWS from [Sales] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Customers].[Canada]}\n" + "{[Customers].[Mexico]}\n" + "{[Customers].[USA]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: 565,238.13\n"); Locus.execute( (RolapConnection)getTestContext().getConnection(), "testDelegatingTupleListSlice", new Locus.Action() { public Void execute() { // Unit test final Member genderFMember = xxx("[Gender].[F]"); final Member storeUsaMember = xxx("[Store].[USA]"); final List> arrayList = new ArrayList>(); final TupleList fm = new DelegatingTupleList(2, arrayList); fm.addTuple(genderFMember, storeUsaMember); final List sliced = fm.slice(0); assertEquals(2, sliced.size()); assertEquals(1, fm.size()); return null; } }); } private void checkProject(TupleList fm) { assertEquals(2, fm.size()); assertEquals(2, fm.getArity()); assertEquals(2, fm.project(new int[] {0}).size()); assertEquals(fm.slice(0), fm.project(new int[] {0}).slice(0)); assertEquals(fm.slice(1), fm.project(new int[] {1}).slice(0)); assertEquals( "[[[Gender].[F]], [[Gender].[M]]]", fm.project(new int[] {0}).toString()); assertEquals( "[[[Store].[USA]], [[Store].[USA]]]", fm.project(new int[] {1}).toString()); // Also check cloneList. assertEquals(0, fm.cloneList(100).size()); assertEquals(fm.size(), fm.cloneList(-1).size()); assertEquals(fm, fm.cloneList(-1)); assertNotSame(fm, fm.cloneList(-1)); } /** * Queries a member of the Sales cube. * * @param memberName Unique name of member * @return The member */ private Member xxx(String memberName) { Schema schema = getConnection().getSchema(); final boolean fail = true; Cube salesCube = schema.lookupCube("Sales", fail); final SchemaReader schemaReader = salesCube.getSchemaReader(null); // unrestricted return schemaReader.getMemberByUniqueName( Util.parseIdentifier(memberName), true); } } // End TupleListTest.java mondrian-3.4.1/testsrc/main/mondrian/test/ParentChildHierarchyTest.java0000644000175000017500000016770611735330606026207 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho // All Rights Reserved. // // jhyde, Mar 6, 2003 */ package mondrian.test; import mondrian.olap.*; import mondrian.util.Bug; import junit.framework.Assert; import java.util.List; /** * Tests for parent-child hierarchies. * * @author jhyde * @since Mar 6, 2003 */ public class ParentChildHierarchyTest extends FoodMartTestCase { public ParentChildHierarchyTest(String name) { super(name); } // -- Helper methods ------------------------------------------------------- /** * Returns a TestContext in which the "HR" cube contains an extra dimension, * "EmployeesClosure", which is an explicit closure of [Employees]. * *

    [Employees] is a parent/child hierarchy (along the relationship * supervisor_id/employee_id). The table employee_closure expresses the * closure of the parent/child relation, ie it represents * ancestor/descendant, having a row for each ancestor/descendant pair. * *

    The closed hierarchy has two levels: the detail level (here named * [Employee]) is equivalent to the base hierarchy; the [Closure] level * relates each descendant to all its ancestors. */ private TestContext getEmpClosureTestContext() { return TestContext.instance().createSubstitutingCube( "HR", " \n" + " \n" + " \n" + "

\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " "); } /** * Returns a TestContext in which the "HR" cube contains an extra dimension, * "EmployeesSnowFlake", which is a joined hierarchy with a closure. * this is almost identical to employee, except we do a join with store * to validate joins with closures work */ private TestContext getEmpSnowFlakeClosureTestContext() { return TestContext.instance().createSubstitutingCube( "HR", "" + "" + " " + "
" + "
" + " " + " " + " " + " " + "
" + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""); } /** * Returns a TestContext in which the "HR" cube contains an extra dimension, * "EmployeesSnowFlake", which is a joined hierarchy with a closure. * this is almost identical to employee, except we do a join with store * to validate joins with closures work */ private TestContext getEmpSharedClosureTestContext() { String sharedClosureDimension = "" + "" + " " + "
" + "
" + " " + " " + " " + "
" + " " + " " + " " + " " + " " + " " + " " + " " + "" + ""; String cube = "\n" + "
\n" + " \n" + " " + " " + "
" + " " + " " + " " + " \n" + " " + " " + ""; return TestContext.instance().create( sharedClosureDimension, cube, null, null, null, null); } /** * Returns a TestContext in which the "HR" cube contains an extra dimension, * "EmployeesNonClosure", which is a joined parent child hierarchy with no * closure. this is almost identical to employee, except we removed the * closure to validate that non-closures work */ private TestContext getEmpNonClosureTestContext() { return TestContext.instance().createSubstitutingCube( "HR", "" + "" + "
" + " " + " " + " " + " " + " " + " " + " " + " " + "" + "", null); } /** * Tests snow flake closure combination. * *

Test case for * MONDRIAN-266, * "Closure tables do not work in a Snowflake Dimension". */ public void testSnowflakeClosure() { getEmpSnowFlakeClosureTestContext().assertQueryReturns( "select {[Measures].[Count], [Measures].[Org Salary], \n" + "[Measures].[Number Of Employees], [Measures].[Avg Salary]} on columns,\n" + "{[EmployeeSnowFlake]} on rows\n" + "from [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Count]}\n" + "{[Measures].[Org Salary]}\n" + "{[Measures].[Number of Employees]}\n" + "{[Measures].[Avg Salary]}\n" + "Axis #2:\n" + "{[EmployeeSnowFlake].[All Employees]}\n" + "Row #0: 7,392\n" + "Row #0: $39,431.67\n" + "Row #0: 616\n" + "Row #0: $64.01\n"); } public void testSharedClosureParentChildHierarchy() { TestContext context = getEmpSharedClosureTestContext(); context.assertQueryReturns( "Select " + "{[SharedEmployee].[All SharedEmployees].[Sheri Nowmer].[Derrick Whelply].children} on columns " // + [SharedEmployee].[Sheri Nowmer].children + "from EmployeeSharedClosureCube", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[SharedEmployee].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker]}\n" + "{[SharedEmployee].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo]}\n" + "{[SharedEmployee].[Sheri Nowmer].[Derrick Whelply].[Laurie Borges]}\n" + "Row #0: $10,256.30\n" + "Row #0: $29,121.55\n" + "Row #0: $35,487.69\n"); } /** * Test case for * MONDRIAN-284, * "Parent child hierarchies without closures are broken". */ public void _testNonClosureParentChildHierarchy() { getEmpNonClosureTestContext().assertQueryReturns( "Select " + "{[EmployeesNonClosure].[Sheri Nowmer].children} on columns," + "{[Time].[1997]} ON rows " + "from HR", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[EmployeesNonClosure].[Sheri Nowmer].[Derrick Whelply]}\n" + "{[EmployeesNonClosure].[Sheri Nowmer].[Michael Spence]}\n" + "{[EmployeesNonClosure].[Sheri Nowmer].[Maya Gutierrez]}\n" + "{[EmployeesNonClosure].[Sheri Nowmer].[Roberta Damstra]}\n" + "{[EmployeesNonClosure].[Sheri Nowmer].[Rebecca Kanagaki]}\n" + "{[EmployeesNonClosure].[Sheri Nowmer].[Darren Stanz]}\n" + "{[EmployeesNonClosure].[Sheri Nowmer].[Donna Arnold]}\n" + "Axis #2:\n" + "{[Time].[1997]}\n" + "Row #0: $36,494.07\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: $428.76\n" + "Row #0: $234.36\n" + "Row #0: $832.68\n" + "Row #0: $577.80\n"); } public void testAll() { assertQueryReturns( "select {[Measures].[Org Salary], [Measures].[Count]} on columns,\n" + " {[Employees]} on rows\n" + "from [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Org Salary]}\n" + "{[Measures].[Count]}\n" + "Axis #2:\n" + "{[Employees].[All Employees]}\n" + "Row #0: $39,431.67\n" + "Row #0: 7,392\n"); } public void testChildrenOfAll() { assertQueryReturns( "select {[Measures].[Org Salary], [Measures].[Count]} on columns,\n" + " {[Employees].children} on rows\n" + "from [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Org Salary]}\n" + "{[Measures].[Count]}\n" + "Axis #2:\n" + "{[Employees].[Sheri Nowmer]}\n" + "Row #0: $39,431.67\n" + "Row #0: 7,392\n"); } /** * Test case for * MONDRIAN-75, * "'distinct count' measure cause exception in parent/child". */ public void testDistinctAll() { // parent/child dimension not expanded, and the query works assertQueryReturns( "select {[Measures].[Count], [Measures].[Org Salary], \n" + "[Measures].[Number Of Employees], [Measures].[Avg Salary]} on columns,\n" + "{[Employees]} on rows\n" + "from [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Count]}\n" + "{[Measures].[Org Salary]}\n" + "{[Measures].[Number of Employees]}\n" + "{[Measures].[Avg Salary]}\n" + "Axis #2:\n" + "{[Employees].[All Employees]}\n" + "Row #0: 7,392\n" + "Row #0: $39,431.67\n" + "Row #0: 616\n" + "Row #0: $64.01\n"); } public void testDistinctChildrenOfAll() { // parent/child dimension expanded: fails with // java.lang.UnsupportedOperationException at // mondrian.rolap.RolapAggregator$6.aggregate(RolapAggregator.java:72) assertQueryReturns( "select {[Measures].[Count], [Measures].[Org Salary], \n" + "[Measures].[Number Of Employees], [Measures].[Avg Salary]} on columns,\n" + "{[Employees].children} on rows\n" + "from [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Count]}\n" + "{[Measures].[Org Salary]}\n" + "{[Measures].[Number of Employees]}\n" + "{[Measures].[Avg Salary]}\n" + "Axis #2:\n" + "{[Employees].[Sheri Nowmer]}\n" + "Row #0: 7,392\n" + "Row #0: $39,431.67\n" + "Row #0: 616\n" + "Row #0: $64.01\n"); } // same two tests, but on a subtree public void testDistinctSubtree() { // also fails with UnsupportedOperationException assertQueryReturns( "select {[Measures].[Count], [Measures].[Org Salary], \n" + "[Measures].[Number Of Employees], [Measures].[Avg Salary]} on columns,\n" + "{[Employees].[All Employees].[Sheri Nowmer].[Rebecca Kanagaki]} on rows\n" + "from [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Count]}\n" + "{[Measures].[Org Salary]}\n" + "{[Measures].[Number of Employees]}\n" + "{[Measures].[Avg Salary]}\n" + "Axis #2:\n" + "{[Employees].[Sheri Nowmer].[Rebecca Kanagaki]}\n" + "Row #0: 24\n" + "Row #0: $234.36\n" + "Row #0: 2\n" + "Row #0: $117.18\n"); } /** * Verifies that COUNT DISTINCT works against the explict closure of the * parent/child hierarchy. (Repeats the last 4 tests.) */ public void testDistinctAllExplicitClosure() { getEmpClosureTestContext().assertQueryReturns( "select {[Measures].[Count], [Measures].[Org Salary], \n" + "[Measures].[Number Of Employees], [Measures].[Avg Salary]} on columns,\n" + "{[EmployeesClosure]} on rows\n" + "from [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Count]}\n" + "{[Measures].[Org Salary]}\n" + "{[Measures].[Number of Employees]}\n" + "{[Measures].[Avg Salary]}\n" + "Axis #2:\n" + "{[EmployeesClosure].[All Employees]}\n" + "Row #0: 7,392\n" + "Row #0: $39,431.67\n" + "Row #0: 616\n" + "Row #0: $64.01\n"); } public void testDistinctChildrenOfAllExplicitClosure() { // the children of the closed relation are all the descendants, so limit // results getEmpClosureTestContext().assertQueryReturns( "select {[Measures].[Count], [Measures].[Org Salary], \n" + "[Measures].[Number Of Employees], [Measures].[Avg Salary]} on columns,\n" + "{[EmployeesClosure].FirstChild} on rows\n" + "from [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Count]}\n" + "{[Measures].[Org Salary]}\n" + "{[Measures].[Number of Employees]}\n" + "{[Measures].[Avg Salary]}\n" + "Axis #2:\n" + "{[EmployeesClosure].[1]}\n" + "Row #0: 7,392\n" + "Row #0: $39,431.67\n" + "Row #0: 616\n" + "Row #0: $64.01\n"); } public void testDistinctSubtreeExplicitClosure() { getEmpClosureTestContext().assertQueryReturns( "select {[Measures].[Count], [Measures].[Org Salary], \n" + "[Measures].[Number Of Employees], [Measures].[Avg Salary]} on columns,\n" + "{[EmployeesClosure].[All Employees].[7]} on rows\n" + "from [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Count]}\n" + "{[Measures].[Org Salary]}\n" + "{[Measures].[Number of Employees]}\n" + "{[Measures].[Avg Salary]}\n" + "Axis #2:\n" + "{[EmployeesClosure].[7]}\n" + "Row #0: 24\n" + "Row #0: $234.36\n" + "Row #0: 2\n" + "Row #0: $117.18\n"); } public void testLeaf() { // Juanita Sharp has no reports assertQueryReturns( "select {[Measures].[Org Salary], [Measures].[Count]} on columns,\n" + " {[Employees].[All Employees].[Sheri Nowmer].[Rebecca Kanagaki].[Juanita Sharp]} on rows\n" + "from [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Org Salary]}\n" + "{[Measures].[Count]}\n" + "Axis #2:\n" + "{[Employees].[Sheri Nowmer].[Rebecca Kanagaki].[Juanita Sharp]}\n" + "Row #0: $72.36\n" + "Row #0: 12\n"); } public void testOneAboveLeaf() { // Rebecca Kanagaki has 2 direct reports, and they have no reports assertQueryReturns( "select {[Measures].[Org Salary], [Measures].[Count]} on columns,\n" + " {[Employees].[All Employees].[Sheri Nowmer].[Rebecca Kanagaki]} on rows\n" + "from [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Org Salary]}\n" + "{[Measures].[Count]}\n" + "Axis #2:\n" + "{[Employees].[Sheri Nowmer].[Rebecca Kanagaki]}\n" + "Row #0: $234.36\n" + "Row #0: 24\n"); } /** * Script That Uses the LEAVES Flag to Return the Bottom 10 Dimension * Members, from here. */ public void testParentChildDescendantsLeavesBottom() { assertQueryReturns( "WITH SET [NonEmptyEmployees] AS 'FILTER(DESCENDANTS([Employees].[All Employees], 10, LEAVES),\n" + " NOT ISEMPTY([Measures].[Employee Salary]))'\n" + "SELECT { [Measures].[Employee Salary], [Measures].[Number of Employees] } ON COLUMNS,\n" + " BOTTOMCOUNT([NonEmptyEmployees], 10, [Measures].[Employee Salary]) ON ROWS\n" + "FROM HR\n" + "WHERE ([Pay Type].[Hourly])", "Axis #0:\n" + "{[Pay Type].[Hourly]}\n" + "Axis #1:\n" + "{[Measures].[Employee Salary]}\n" + "{[Measures].[Number of Employees]}\n" + "Axis #2:\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Laurie Borges].[Eric Long].[Adam Reynolds].[William Hapke].[Marie Richmeier]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Jose Bernard].[Mary Hunt].[Bonnie Bruno].[Ellen Gray]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Laurie Borges].[Paula Nickell].[Kristine Cleary].[Carla Zubaty].[Hattie Haemon]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lois Wood].[Dell Gras].[Christopher Solano].[Sarah Amole]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Charles Macaluso].[Barbara Wallin].[Kenneth Turner].[Shirley Head]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lois Wood].[Dell Gras].[Christopher Solano].[Mary Hall]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lin Conley].[Paul Tays].[Pat Chin].[Yasmina Brown]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Laurie Borges].[Eric Long].[Adam Reynolds].[Joshua Huff].[Teanna Cobb]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lois Wood].[Dell Gras].[Kristine Aldred].[Kenton Forham]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Laurie Borges].[Mary Solimena].[Matthew Hunter].[Eddie Holmes].[Donald Thompson]}\n" + "Row #0: $39.44\n" + "Row #0: 1\n" + "Row #1: $39.52\n" + "Row #1: 1\n" + "Row #2: $39.52\n" + "Row #2: 1\n" + "Row #3: $39.60\n" + "Row #3: 1\n" + "Row #4: $39.62\n" + "Row #4: 1\n" + "Row #5: $39.62\n" + "Row #5: 1\n" + "Row #6: $39.66\n" + "Row #6: 1\n" + "Row #7: $39.67\n" + "Row #7: 1\n" + "Row #8: $39.75\n" + "Row #8: 1\n" + "Row #9: $39.75\n" + "Row #9: 1\n"); } /** * Script from here. */ public void testParentChildDescendantsLeavesTop() { if (Bug.avoidSlowTestOnLucidDB(getTestContext().getDialect())) { return; } assertQueryReturns( "with set [Leaves] as 'Descendants([Employees].[All Employees], 15, LEAVES)'\n" + " set [Parents] as 'Generate([Leaves], {[Employees].CurrentMember.Parent})'\n" + " set [FirstParents] as 'Filter([Parents], \n" + "Count(Descendants( [Employees].CurrentMember, 2)) = 0 )'\n" + "select {[Measures].[Number of Employees]} on Columns,\n" + " TopCount([FirstParents], 10, [Measures].[Number of Employees]) on Rows\n" + "from HR", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Number of Employees]}\n" + "Axis #2:\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Jacqueline Wyllie].[Ralph Mccoy].[Anne Tuck]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Jacqueline Wyllie].[Ralph Mccoy].[Joy Sincich]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Jacqueline Wyllie].[Ralph Mccoy].[Bertha Jameson]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Laurie Borges].[Mary Solimena].[Matthew Hunter].[Florence Vonholt]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Laurie Borges].[Mary Solimena].[Matthew Hunter].[Eddie Holmes]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Laurie Borges].[Mary Solimena].[Matthew Hunter].[Gerald Drury]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Jose Bernard].[Mary Hunt].[Libby Allen]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Jose Bernard].[Mary Hunt].[Bonnie Bruno]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Jose Bernard].[Mary Hunt].[Angela Bowers]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Charles Macaluso].[Barbara Wallin].[Michael Bruha]}\n" + "Row #0: 23\n" + "Row #1: 23\n" + "Row #2: 23\n" + "Row #3: 23\n" + "Row #4: 23\n" + "Row #5: 23\n" + "Row #6: 19\n" + "Row #7: 19\n" + "Row #8: 19\n" + "Row #9: 19\n"); } public void testAllMembersParent() { final String expected = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Parent]}\n" + "Axis #2:\n" + "{[Employees].[All Employees]}\n" + "{[Employees].[Sheri Nowmer]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Shauna Wyro]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Shauna Wyro].[Bunny McCown]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Shauna Wyro].[Bunny McCown].[Nancy Miller]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Shauna Wyro].[Bunny McCown].[Wanda Hollar]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Jacqueline Wyllie]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Jacqueline Wyllie].[Ralph Mccoy]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Jacqueline Wyllie].[Ralph Mccoy].[Anne Tuck]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Jacqueline Wyllie].[Ralph Mccoy].[Anne Tuck].[Corinne Zugschwert]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Jacqueline Wyllie].[Ralph Mccoy].[Anne Tuck].[Michelle Adams]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Jacqueline Wyllie].[Ralph Mccoy].[Anne Tuck].[Donahue Steen]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Jacqueline Wyllie].[Ralph Mccoy].[Anne Tuck].[John Baker]}\n" + "Row #0: #null\n" + "Row #1: All Employees\n" + "Row #2: Sheri Nowmer\n" + "Row #3: Derrick Whelply\n" + "Row #4: Beverly Baker\n" + "Row #5: Shauna Wyro\n" + "Row #6: Bunny McCown\n" + "Row #7: Bunny McCown\n" + "Row #8: Beverly Baker\n" + "Row #9: Jacqueline Wyllie\n" + "Row #10: Ralph Mccoy\n" + "Row #11: Anne Tuck\n" + "Row #12: Anne Tuck\n" + "Row #13: Anne Tuck\n" + "Row #14: Anne Tuck\n"; // Query contains 'Head' just to keep the number of rows reasonable. We // assume that it does not affect the behavior of .Members. assertQueryReturns( "with member [Measures].[Parent] as '[Employees].CurrentMember.Parent.Name'\n" + "select {[Measures].[Parent]}\n" + "ON COLUMNS,\n" + "Head([Employees].Members, 15)\n" + "ON ROWS from [HR]", expected); // Similar query, using .AllMembers rather than // .Members, returns the same result. assertQueryReturns( "with member [Measures].[Parent] as '[Employees].CurrentMember.Parent.Name'\n" + "select {[Measures].[Parent]}\n" + "ON COLUMNS,\n" + "Head([Employees].AllMembers, 15)\n" + "ON ROWS from [HR]", expected); // Similar query use .Members, same result expected. assertQueryReturns( "with member [Measures].[Parent] as '[Employees].CurrentMember.Parent.Name'\n" + "select {[Measures].[Parent]}\n" + "ON COLUMNS,\n" + "{[Employees], Head([Employees].[Employee Id].Members, 14)}\n" + "ON ROWS from [HR]", expected); } // todo: test DimensionUsage which joins to a level which is not in the // same table as the lowest level. /** * The recursion cyclicity check kicks in when the recursion depth reachs * the number of dimensions in the cube. So create a cube with fewer * dimensions (3) than the depth of the emp dimension (6). */ public void testHierarchyFalseCycle() { if (Bug.avoidSlowTestOnLucidDB(getTestContext().getDialect())) { return; } // On the regular HR cube, this has always worked. assertQueryReturns( "SELECT {[Employees].[All Employees].Children} on columns,\n" + " {[Measures].[Org Salary]} on rows\n" + "FROM [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Employees].[Sheri Nowmer]}\n" + "Axis #2:\n" + "{[Measures].[Org Salary]}\n" + "Row #0: $39,431.67\n"); final TestContext testContext = TestContext.instance().create( null, "\n" + "

\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); // On a cube with fewer dimensions, this gave a false failure. testContext.assertQueryReturns( "SELECT {[Employees].[All Employees].Children} on columns,\n" + " {[Measures].[Org Salary]} on rows\n" + "FROM [HR-fewer-dims]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Employees].[Sheri Nowmer]}\n" + "Axis #2:\n" + "{[Measures].[Org Salary]}\n" + "Row #0: $271,552.44\n"); } public void testGenuineCycle() { Result result = executeQuery( "with member [Measures].[Foo] as \n" + " '([Measures].[Foo], OpeningPeriod([Time].[Month]))'\n" + "select\n" + " {[Measures].[Unit Sales], [Measures].[Foo]} on Columns,\n" + " { [Time].[1997].[Q2]} on rows\n" + "from [Sales]"); String resultString = TestContext.toString(result); // The precise moment when the cycle is detected depends upon the state // of the cache, so this test can throw various errors. Here are come // examples: // // Axis #0: // {} // Axis #1: // {[Measures].[Unit Sales]} // {[Measures].[Foo]} // Axis #2: // {[Time].[1997].[Q2]} // Row #0: 62,610 // Row #0: #ERR: // mondrian.olap.fun.MondrianEvaluationException: Infinite // loop while evaluating calculated member '[Measures].[Foo]'; // context stack is {([Time].[1997].[Q2].[4]), // ([Time].[1997].[Q2].[4]), ([Time].[1997].[Q2].[4]), // ([Time].[1997].[Q2].[4]), ([Time].[1997].[Q2].[4]), // ([Time].[1997].[Q2].[4]), ([Time].[1997].[Q2].[4]), // ([Time].[1997].[Q2].[4]), ([Time].[1997].[Q2].[4]), // ([Time].[1997].[Q2].[4]), ([Time].[1997].[Q2].[4]), // ([Time].[1997].[Q2])} // // Axis #0: // {} // Axis #1: // {[Measures].[Unit Sales]} // {[Measures].[Foo]} // Axis #2: // {[Time].[1997].[Q2]} // Row #0: (null) // Row #0: #ERR: mondrian.olap.fun.MondrianEvaluationException: Infinite // loop while evaluating calculated member '[Measures].[Foo]'; context // stack is {([Store].[Mexico], [Time].[1997].[Q2].[4]), // ([Store].[Mexico], [Time].[1997].[Q2].[4]), // ([Store].[Mexico], [Time].[1997].[Q2].[4]), // ([Store].[Mexico], [Time].[1997].[Q2].[4]), // ([Store].[Mexico], [Time].[1997].[Q2].[4]), // ([Store].[Mexico], [Time].[1997].[Q2].[4]), // ([Store].[Mexico], [Time].[1997].[Q2].[4]), // ([Store].[Mexico], [Time].[1997].[Q2].[4]), // ([Store].[Mexico], [Time].[1997].[Q2].[4]), // ([Store].[Mexico], [Time].[1997].[Q2].[4]), // ([Store].[Mexico], [Time].[1997].[Q2].[4]), // ([Store].[Mexico], [Time].[1997].[Q2].[4]), // ([Store].[Mexico], [Time].[1997].[Q2].[4]), // ([Store].[Mexico], [Time].[1997].[Q2])}" // // So encapsulate the error string as a pattern. final String expectedPattern = "(?s).*Row #0: #ERR: mondrian.olap.fun.MondrianEvaluationException.*: Infinite loop while evaluating calculated member \\'\\[Measures\\].\\[Foo\\]\\'; context stack is.*"; if (!resultString.matches(expectedPattern)) { System.out.println(resultString); Assert.assertEquals(expectedPattern, resultString); } } public void testParentChildDrillThrough() { Result result = executeQuery( "select {[Measures].Members} ON columns,\n" + " {[Employees].Members} ON rows\n" + "from [HR]"); // Drill-through for row #0, Employees.All. // Note that the SQL does not contain the employees or employee_closure // tables. final boolean extendedContext = false; checkDrillThroughSql( result, 0, extendedContext, "[Employees].[All Employees]", "$39,431.67", "select" + " `time_by_day`.`the_year` as `Year`," + " `salary`.`salary_paid` as `Org Salary` " + "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 " + "order by `time_by_day`.`the_year` ASC", 7392); // Drill-through for row #1, [Employees].[All].[Sheri Nowmer] // Note that the SQL does not contain the employee_closure table. // That's because when we drill through, we don't want to roll up // measures along the hierarchy. checkDrillThroughSql( result, 1, extendedContext, "[Employees].[Sheri Nowmer]", "$39,431.67", "select `time_by_day`.`the_year` as `Year`," + " `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 " + "order by `time_by_day`.`the_year` ASC, `employee`.`employee_id` ASC", 12); // 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].[Sheri Nowmer].[Derrick Whelply]", "$36,494.07", "select `time_by_day`.`the_year` as `Year`," + " `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 " + "order by `time_by_day`.`the_year` ASC, `employee`.`employee_id` ASC", 12); } public void testParentChildDrillThroughWithContext() { Result result = executeQuery( "select {[Measures].Members} ON columns,\n" + " {[Employees].Members} ON rows\n" + "from [HR]"); // Now with full context. final boolean extendedContext = true; checkDrillThroughSql( result, 2, extendedContext, "[Employees].[Sheri Nowmer].[Derrick Whelply]", "$36,494.07", "select" + " `time_by_day`.`the_year` as `Year`," + " `time_by_day`.`quarter` as `Quarter`," + " `time_by_day`.`the_month` as `Month`," + " `time_by_day`.`month_of_year` as `Month (Key)`," + " `store`.`store_country` as `Store Country`," + " `store`.`store_state` as `Store State`," + " `store`.`store_city` as `Store City`," + " `store`.`store_name` as `Store Name`," + " `position`.`pay_type` as `Pay Type`," + " `store`.`store_type` as `Store Type`," + " `employee`.`management_role` as `Management Role`," + " `employee`.`position_title` as `Position Title`," + " `department`.`department_id` as `Department Description`," + " `employee`.`full_name` 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`," + " `store` =as= `store`," + " `employee` =as= `employee`," + " `position` =as= `position`," + " `department` =as= `department` " + "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`.`store_id` = `store`.`store_id`" + " and `employee`.`position_id` = `position`.`position_id`" + " and `salary`.`department_id` = `department`.`department_id`" + " and `employee`.`employee_id` = 2 " + "order by" + " `time_by_day`.`the_year` ASC," + " `time_by_day`.`quarter` ASC," + " `time_by_day`.`the_month` ASC," + " `time_by_day`.`month_of_year` ASC," + " `store`.`store_country` ASC," + " `store`.`store_state` ASC," + " `store`.`store_city` ASC," + " `store`.`store_name` ASC," + " `position`.`pay_type` ASC," + " `store`.`store_type` ASC," + " `employee`.`management_role` ASC," + " `employee`.`position_title` ASC," + " `department`.`department_id` ASC," + " `employee`.`full_name` ASC," + " `employee`.`employee_id` ASC", 12); } private void checkDrillThroughSql( Result result, int row, boolean extendedContext, String expectedMember, String expectedCell, String expectedSql, int expectedRows) { final Member empMember = result.getAxes()[1].getPositions().get(row).get(0); assertEquals(expectedMember, empMember.getUniqueName()); // drill through member final Cell cell = result.getCell(new int[]{0, row}); assertEquals(expectedCell, cell.getFormattedValue()); String sql = cell.getDrillThroughSQL(extendedContext); getTestContext().assertSqlEquals(expectedSql, sql, expectedRows); } /** * Test case for * MONDRIAN-168, * "NullPointerException in RolapEvaluator.setContext(....)". */ public void testBugMondrian168() { assertQueryReturns( "select \n" + " {[Employee Salary]} on columns, \n" + " {[Employees]} on rows \n" + "from [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Employee Salary]}\n" + "Axis #2:\n" + "{[Employees].[All Employees]}\n" + "Row #0: \n"); assertQueryReturns( "select \n" + " {[Position]} on columns,\n" + " {[Employee Salary]} on rows\n" + "from [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Position].[All Position]}\n" + "Axis #2:\n" + "{[Measures].[Employee Salary]}\n" + "Row #0: \n"); } /** * Tests that a parent-child hierarchy is sorted correctly if the * "ordinalColumn" attribute is included in its definition. Testcase for * MONDRIAN-203, * "Sorting of Parent/Child Hierarchy is wrong". */ public void testParentChildOrdinal() { if (Bug.avoidSlowTestOnLucidDB(getTestContext().getDialect())) { return; } final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + " \n" + " \n" + "", null, null, null, null); // Make sure .CHILDREN is sorted. testContext.assertQueryReturns( "select {[Employees].[All Employees].[Sheri Nowmer].[Rebecca Kanagaki].Children} on columns from [HR-ordered]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Employees].[Sheri Nowmer].[Rebecca Kanagaki].[Sandra Brunner]}\n" + "{[Employees].[Sheri Nowmer].[Rebecca Kanagaki].[Juanita Sharp]}\n" + "Row #0: $60.00\n" + "Row #0: $152.76\n"); // Make sure .DESCENDANTS is sorted. testContext.assertQueryReturns( "select {HEAD(DESCENDANTS([Employees].[Sheri Nowmer], [Employees].[Employee Id], LEAVES), 6)} on columns from [HR-ordered]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Employees].[Sheri Nowmer].[Donna Arnold].[Howard Bechard]}\n" + "{[Employees].[Sheri Nowmer].[Donna Arnold].[Doris Carter]}\n" + "{[Employees].[Sheri Nowmer].[Roberta Damstra].[Phyllis Burchett]}\n" + "{[Employees].[Sheri Nowmer].[Roberta Damstra].[Jennifer Cooper]}\n" + "{[Employees].[Sheri Nowmer].[Roberta Damstra].[Jessica Olguin]}\n" + "{[Employees].[Sheri Nowmer].[Roberta Damstra].[Peggy Petty]}\n" + "Row #0: $193.80\n" + "Row #0: $60.00\n" + "Row #0: $120.00\n" + "Row #0: $152.76\n" + "Row #0: $120.00\n" + "Row #0: $182.40\n"); } public void testLevelMembers() { final TestContext testContext = new TestContext() { public String getDefaultCubeName() { return "HR"; } }; // .MEMBERS testContext.assertExprReturns("[Employees].Members.Count", "1,156"); // .MEMBERS testContext.assertExprReturns( "[Employees].[Employee Id].Members.Count", "1,155"); // .CHILDREN testContext.assertExprReturns( "[Employees].[Sheri Nowmer].Children.Count", "7"); // Make sure that members of the [Employee] hierarachy don't // as calculated (even though they are calculated, internally) // but that real calculated members are counted as calculated. testContext.assertQueryReturns( "with member [Employees].[Foo] as ' Sum([Employees].[All Employees].[Sheri Nowmer].[Donna Arnold].Children) '\n" + "member [Measures].[Count1] AS [Employees].MEMBERS.Count\n" + "member [Measures].[Count2] AS [Employees].ALLMEMBERS.COUNT\n" + "select {[Measures].[Count1], [Measures].[Count2]} ON COLUMNS\n" + "from [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Count1]}\n" + "{[Measures].[Count2]}\n" + "Row #0: 1,156\n" + "Row #0: 1,157\n"); } /** * Test case for * MONDRIAN-488, * "Closure Tables not working with Virtual Cubes". */ public void testClosureTableInVirtualCube() { final TestContext testContext = TestContext.instance().create( "" + " " + "
" + " " + " " + "
" + " " + " " + " " + "", null, "" + "
" + " " + " " + "" + "" + "
" + " " + " " + "" + "" + "" + "" + "" + "" + "[Measures].[Store Sales] / [Measures].[Org Salary]" + "" + "", null, null, null); testContext.assertQueryReturns( "select " + "[Employees].[All Employees].[Sheri Nowmer].[Rebecca Kanagaki].Children" + " ON COLUMNS, " + "{[Measures].[Org Salary]} ON ROWS from [CustomSalesAndHR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Employees].[Sheri Nowmer].[Rebecca Kanagaki].[Juanita Sharp]}\n" + "{[Employees].[Sheri Nowmer].[Rebecca Kanagaki].[Sandra Brunner]}\n" + "Axis #2:\n" + "{[Measures].[Org Salary]}\n" + "Row #0: 152.76\n" + "Row #0: 60\n"); } /** * Verifies the fix for * MONDRIAN-519, * a class cast exception when using non-closure parent child hierarchies. */ public void testClosureVsNoClosure() { if (Bug.avoidSlowTestOnLucidDB(getTestContext().getDialect())) { return; } String cubestart = "\n" + "
\n" + " \n" + " \n" + "
\n" + " \n"; String closure = " \n" + "
\n" + " \n"; String cubeend = " \n" + " \n" + " \n" + "\n" + " \n" + "\n"; final TestContext testClosureContext = TestContext.instance().create( null, cubestart + closure + cubeend, null, null, null, null); final TestContext testNoClosureContext = TestContext.instance().create( null, cubestart + cubeend, null, null, null, null); String mdx; String expected; // 1. Run a big query on both contexts and check that both give same. mdx = "select {[Measures].[Count]} ON COLUMNS,\n" + " NON EMPTY {[Employees].AllMembers} ON ROWS\n" + "from [HR4C]"; expected = TestContext.toString(testClosureContext.executeQuery(mdx)); assertTrue( expected, TestContext.unfold(expected).contains("Row #0: 21,252\n")); // Need to unfold because 'expect' has platform-specific line-endings, // yet assertQueryReturns assumes that it contains linefeeds. testNoClosureContext.assertQueryReturns( mdx, TestContext.unfold(expected)); // 2. Run a small query with known results on both contexts. // Note in particular the total for [All] is 21,252, same as for // [Sheri Nowmer]. There was a bug where [All] had a much higher total. mdx = "select {[Measures].[Count]} ON COLUMNS,\n" + " Descendants([Employees], 2, SELF_AND_BEFORE) ON ROWS\n" + "from [HR4C]"; expected = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Count]}\n" + "Axis #2:\n" + "{[Employees].[All]}\n" + "{[Employees].[Sheri Nowmer]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply]}\n" + "{[Employees].[Sheri Nowmer].[Michael Spence]}\n" + "{[Employees].[Sheri Nowmer].[Maya Gutierrez]}\n" + "{[Employees].[Sheri Nowmer].[Roberta Damstra]}\n" + "{[Employees].[Sheri Nowmer].[Rebecca Kanagaki]}\n" + "{[Employees].[Sheri Nowmer].[Darren Stanz]}\n" + "{[Employees].[Sheri Nowmer].[Donna Arnold]}\n" + "Row #0: 21,252\n" + "Row #1: 21,252\n" + "Row #2: 14,472\n" + "Row #3: 1,128\n" + "Row #4: 5,244\n" + "Row #5: 96\n" + "Row #6: 60\n" + "Row #7: 168\n" + "Row #8: 60\n"; testClosureContext.assertQueryReturns(mdx, expected); testNoClosureContext.assertQueryReturns(mdx, expected); } public void testSchemaReaderLevelMembers() { final SchemaReader schemaReader = TestContext.instance().getConnection() .getSchemaReader().withLocus(); int found = 0; for (Cube cube : schemaReader.getCubes()) { if (!cube.getName().equals("HR")) { continue; } for (Dimension dimension : schemaReader.getCubeDimensions(cube)) { for (Hierarchy hierarchy : schemaReader.getDimensionHierarchies(dimension)) { if (!hierarchy.getName().equals("Employees")) { continue; } ++found; final Level level = hierarchy.getLevels()[1]; assertEquals("Employee Id", level.getName()); final List memberList = schemaReader.getLevelMembers(level, true); assertEquals(1155, memberList.size()); assertEquals( "[Employees].[Sheri Nowmer]", memberList.get(0).getUniqueName()); assertEquals( "[Employees].[Sheri Nowmer].[Derrick Whelply]", memberList.get(1).getUniqueName()); } } } assertEquals(1, found); } /** * Test case for * MONDRIAN-441, * "Parent-child hierarchies: <Join> used in dimension". */ public void testBridgeTable() { if (!Bug.BugMondrian441Fixed) { return; } // The test case in the bug has a new table "bri_store_employee". For // convenience of configuration, we replace that with an InlineTable // here. final TestContext testContext = getTestContext().withSchema( "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 2\n" + " o\n" + " \n" + " \n" + " 2\n" + " 1\n" + " \n" + " \n" + " 2\n" + " 2\n" + " \n" + " \n" + " 2\n" + " 22\n" + " \n" + " \n" + " 2\n" + " 22\n" + " \n" + " \n" + " 2\n" + " 32\n" + " \n" + " \n" + " 2\n" + " 484\n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + ""); testContext.assertQueryReturns( "select\n" + " NON EMPTY {[Measures].[Store Sales]} ON COLUMNS,\n" + " {[Employee].[Sheri Nowmer]} on ROWS\n" + "from Sales_Bug_441", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Employee].[Sheri Nowmer]}\n" + "Row #0: 28,435.38\n"); } } // End ParentChildHierarchyTest.java mondrian-3.4.1/testsrc/main/mondrian/test/MonitorTest.java0000644000175000017500000001311711735330606023564 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.olap.MondrianServer; import mondrian.server.monitor.*; import org.olap4j.CellSet; import org.olap4j.OlapStatement; import org.olap4j.layout.RectangularCellSetFormatter; import java.io.PrintWriter; import java.io.StringWriter; import java.sql.SQLException; import java.util.List; /** * Unit test for monitoring, including {@link mondrian.server.monitor.Monitor}. * * @author jhyde */ public class MonitorTest extends FoodMartTestCase { private void println(Object x) { // Enable for debugging, but not for checked-in code. if (false) { System.out.println(x); } } /** * Exercises as many fields of the monitoring stats classes as possible. * So that we can check that they are being populated. */ public void testMe() throws SQLException { String queryString = "WITH MEMBER [Measures].[Foo] AS\n" + " [Measures].[Unit Sales]" + " + case when [Measures].[Unit Sales] > 0\n" + " then CInt( ([Measures].[Foo], [Time].PrevMember) )\n" + " end\n" + "SELECT [Measures].[Foo] on 0\n" + "from [Sales]\n" + "where [Time].[1997].[Q3].[9]"; final OlapStatement statement1 = getTestContext().getOlap4jConnection().createStatement(); CellSet cellSet = statement1.executeOlapQuery(queryString); StringWriter stringWriter = new StringWriter(); new RectangularCellSetFormatter(true).format( cellSet, new PrintWriter(stringWriter)); statement1.close(); println(stringWriter); final MondrianServer mondrianServer = MondrianServer.forConnection(getConnection()); final Monitor monitor = mondrianServer.getMonitor(); final ServerInfo server = monitor.getServer(); println( "# stmts open: " + server.statementCurrentlyOpenCount()); println( "# connections open: " + server.connectionCurrentlyOpenCount()); println("# rows fetched: " + server.sqlStatementRowFetchCount); println( "# sql stmts open: " + server.sqlStatementCurrentlyOpenCount()); // # sql stmts by category (cell query, member query, other) // -- if you want to do this, capture sql statement events // cell cache requests // cell cache misses // cell cache hits final List connections = monitor.getConnections(); ConnectionInfo lastConnection = connections.get(connections.size() - 1); final List statements = monitor.getStatements(); StatementInfo lastStatement = statements.get(statements.size() - 1); println( "# cell cache requests, misses, hits; " + "by server, connection, mdx statement: " + server.cellCacheRequestCount + ", " + server.cellCacheMissCount() + ", " + server.cellCacheHitCount + "; " + lastConnection.cellCacheRequestCount + ", " + (lastConnection.cellCacheRequestCount - lastConnection.cellCacheHitCount) + ", " + lastConnection.cellCacheHitCount + "; " + lastStatement.cellCacheRequestCount + ", " + lastStatement.cellCacheMissCount + ", " + lastStatement.cellCacheHitCount); // cache misses in the last minute // cache hits in the last minute // -- build a layer on top of monitor that polls say every 15 seconds, // and keeps results for a few minutes println( "number of mdx statements currently open: " + server.statementCurrentlyOpenCount()); println( "number of mdx statements currently executing: " + server.statementCurrentlyExecutingCount()); println( "jvm memory: " + server.jvmHeapBytesUsed + ", max: " + server.jvmHeapBytesMax + ", committed: " + server.jvmHeapBytesCommitted); println( "number of segments: " + server.segmentCount + ", ever created: " + server.segmentCreateCount + ", number of cells: " + server.cellCount + ", number of cell coordinates: " + server.cellCoordinateCount + ", average cell dimensionality: " + ((float) server.cellCoordinateCount / (float) server.cellCount)); println("Statement: " + lastStatement); println("Connection: " + lastConnection); println("Server: " + server); // number of mdx function calls cumulative // how many operations have been evaluated in sql? // number of members in cache // number of cells in segments // mdx query time // sql query time // sql rows // olap4j connection pool size // sql connection pool size // thread count // # schemas in schema cache // cells fulfilled by sql statements // mondrian server count (other stats relate to just one server) // // Events: // // SQL statement start // SQL statment stop // external cache call // sort // (other expensive operations similar to sort?) } } // End MonitorTest.java mondrian-3.4.1/testsrc/main/mondrian/test/MondrianTestRunner.java0000644000175000017500000001456011735330606025101 0ustar drazzibdrazzib/* // Modified from junit's TestRunner class. Original code is covered by // the junit license and modifications are covered as follows: // // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 SAS Institute, Inc. // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. // // sasebb, 14 December, 2004 */ package mondrian.test; import junit.framework.Test; import junit.framework.TestResult; import junit.runner.*; import java.io.PrintStream; import java.util.Timer; import java.util.TimerTask; public class MondrianTestRunner extends BaseTestRunner { private MondrianResultPrinter fPrinter; private int fIterations = 1; private int fVUsers = 1; private int fTimeLimit = 0; // seconds public static final int SUCCESS_EXIT = 0; public static final int FAILURE_EXIT = 1; public static final int EXCEPTION_EXIT = 2; private String stopReason = "Normal termination."; /** * Constructs a TestRunner. */ public MondrianTestRunner() { this(System.out); } /** * Constructs a TestRunner using the given stream for all the output */ public MondrianTestRunner(PrintStream writer) { this(new MondrianResultPrinter(writer)); } /** * Constructs a TestRunner using the given ResultPrinter all the output */ public MondrianTestRunner(MondrianResultPrinter printer) { fPrinter = printer; } /** * Always use the StandardTestSuiteLoader. Overridden from * BaseTestRunner. */ public TestSuiteLoader getLoader() { return new StandardTestSuiteLoader(); } public void testFailed(int status, Test test, Throwable t) { } public void testStarted(String testName) { } public void testEnded(String testName) { } /** * Creates the TestResult to be used for the test run. */ protected TestResult createTestResult() { return new TestResult(); } public TestResult doRun(final Test suite) { final TestResult result = createTestResult(); result.addListener(fPrinter); /* // uncomment this block to get a list of the single tests with time used final long[] longa = new long[1]; result.addListener(new TestListener() { public void addError(Test arg0, Throwable arg1) { // do nothing } public void addFailure(Test arg0, AssertionFailedError arg1) { // do nothing } public void endTest(Test arg0) { if (arg0 instanceof TestCase) { long longb = System.currentTimeMillis() - longa[0]; System.out.println( "endTest " + ((TestCase)arg0).getName() + " " + longb + " ms"); } } public void startTest(Test arg0) { if (arg0 instanceof TestCase) { longa[0] = System.currentTimeMillis(); System.out.println("startTest " + ((TestCase)arg0).getName()); } } } ); */ final long startTime = System.currentTimeMillis(); // Set up a timit limit if specified if (getTimeLimit() > 0) { Timer timer = new Timer(); timer.schedule( new TimerTask() { public void run() { setStopReason( "Test stopped because the time limit expired."); result.stop(); } }, 1000L * (long)getTimeLimit()); } // Start a new thread for each virtual user Thread threads[] = new Thread[getVUsers()]; for (int i = 0; i < getVUsers(); i++) { threads[i] = new Thread( new Runnable() { public void run() { for (int j = 0; getIterations() == 0 || j < getIterations(); j++) { suite.run(result); if (!result.wasSuccessful()) { setStopReason( "Test stopped due to errors."); result.stop(); } if (result.shouldStop()) { break; } } } }, "Test thread " + i); threads[i].start(); } System.out.println("All " + getVUsers() + " thread(s) started."); // wait for all threads to finish for (int i = 0; i < getVUsers(); i++) { try { threads[i].join(); } catch (InterruptedException e) { System.out.println( "Thread: " + threads[i].getName() + " interrupted: " + e.getMessage()); } } // print timer results and any exceptions long runTime = System.currentTimeMillis() - startTime; fPrinter.print(result, runTime); fPrinter.getWriter().println(getStopReason()); return result; } protected void runFailed(String message) { System.err.println(message); System.exit(FAILURE_EXIT); } public void setPrinter(MondrianResultPrinter printer) { fPrinter = printer; } public void setIterations(int fIterations) { this.fIterations = fIterations; } public int getIterations() { return fIterations; } public void setVUsers(int fVUsers) { this.fVUsers = fVUsers; } public int getVUsers() { return fVUsers; } public void setTimeLimit(int fTimeLimit) { this.fTimeLimit = fTimeLimit; } public int getTimeLimit() { return fTimeLimit; } private void setStopReason(String stopReason) { this.stopReason = stopReason; } private String getStopReason() { return stopReason; } } // End MondrianTestRunner.java mondrian-3.4.1/testsrc/main/mondrian/test/AccessControlTest.java0000644000175000017500000033110311742752424024701 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. // // jhyde, Feb 21, 2003 */ package mondrian.test; import mondrian.olap.*; import junit.framework.Assert; import java.util.List; import org.olap4j.mdx.IdentifierNode; /** * AccessControlTest is a set of unit-tests for access-control. * For these tests, all of the roles are of type RoleImpl. * * @see Role * * @author jhyde * @since Feb 21, 2003 */ public class AccessControlTest extends FoodMartTestCase { private static final String BiServer1574Role1 = "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""; public AccessControlTest(String name) { super(name); } public void testSchemaReader() { final TestContext testContext = getTestContext(); final Connection connection = testContext.getConnection(); Schema schema = connection.getSchema(); final boolean fail = true; Cube cube = schema.lookupCube("Sales", fail); final SchemaReader schemaReader = cube.getSchemaReader(connection.getRole()); final SchemaReader schemaReader1 = schemaReader.withoutAccessControl(); assertNotNull(schemaReader1); final SchemaReader schemaReader2 = schemaReader1.withoutAccessControl(); assertNotNull(schemaReader2); } public void testGrantDimensionNone() { final TestContext context = getTestContext().withFreshConnection(); final Connection connection = context.getConnection(); RoleImpl role = ((RoleImpl) connection.getRole()).makeMutableClone(); Schema schema = connection.getSchema(); Cube salesCube = schema.lookupCube("Sales", true); // todo: add Schema.lookupDimension final SchemaReader schemaReader = salesCube.getSchemaReader(role); Dimension genderDimension = (Dimension) schemaReader.lookupCompound( salesCube, Id.Segment.toList("Gender"), true, Category.Dimension); role.grant(genderDimension, Access.NONE); role.makeImmutable(); connection.setRole(role); context.assertAxisThrows( "[Gender].children", "MDX object '[Gender]' not found in cube 'Sales'"); } public void testRestrictMeasures() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""); final TestContext role1 = testContext.withRole("Role1"); final TestContext role2 = testContext.withRole("Role2"); role1.assertQueryReturns( "SELECT {[Measures].Members} ON COLUMNS FROM [SALES]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Sales Count]}\n" + "{[Measures].[Customer Count]}\n" + "{[Measures].[Promotion Sales]}\n" + "Row #0: 266,773\n" + "Row #0: 225,627.23\n" + "Row #0: 565,238.13\n" + "Row #0: 86,837\n" + "Row #0: 5,581\n" + "Row #0: 151,211.21\n"); role2.assertQueryReturns( "SELECT {[Measures].Members} ON COLUMNS FROM [SALES]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 266,773\n"); } public void testRoleMemberAccessNonExistentMemberFails() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "") .withRole("Role1"); testContext.assertQueryThrows( "select {[Store].Children} on 0 from [Sales]", "Member '[Store].[USA].[Non Existent]' not found"); } public void testRoleMemberAccess() { final Connection connection = getRestrictedConnection(); // because CA has access assertMemberAccess(connection, Access.CUSTOM, "[Store].[USA]"); assertMemberAccess(connection, Access.ALL, "[Store].[Mexico]"); assertMemberAccess(connection, Access.NONE, "[Store].[Mexico].[DF]"); assertMemberAccess( connection, Access.NONE, "[Store].[Mexico].[DF].[Mexico City]"); assertMemberAccess(connection, Access.NONE, "[Store].[Canada]"); assertMemberAccess( connection, Access.NONE, "[Store].[Canada].[BC].[Vancouver]"); assertMemberAccess( connection, Access.ALL, "[Store].[USA].[CA].[Los Angeles]"); assertMemberAccess( connection, Access.NONE, "[Store].[USA].[CA].[San Diego]"); // USA deny supercedes OR grant assertMemberAccess( connection, Access.NONE, "[Store].[USA].[OR].[Portland]"); assertMemberAccess( connection, Access.NONE, "[Store].[USA].[WA].[Seattle]"); assertMemberAccess(connection, Access.NONE, "[Store].[USA].[WA]"); // above top level assertMemberAccess(connection, Access.NONE, "[Store].[All Stores]"); } private void assertMemberAccess( final Connection connection, Access expectedAccess, String memberName) { final Role role = connection.getRole(); // restricted Schema schema = connection.getSchema(); final boolean fail = true; Cube salesCube = schema.lookupCube("Sales", fail); final SchemaReader schemaReader = salesCube.getSchemaReader(null).withLocus(); final Member member = schemaReader.getMemberByUniqueName( Util.parseIdentifier(memberName), true); final Access actualAccess = role.getAccess(member); Assert.assertEquals(memberName, expectedAccess, actualAccess); } private void assertCubeAccess( final Connection connection, Access expectedAccess, String cubeName) { final Role role = connection.getRole(); Schema schema = connection.getSchema(); final boolean fail = true; Cube cube = schema.lookupCube(cubeName, fail); final Access actualAccess = role.getAccess(cube); Assert.assertEquals(cubeName, expectedAccess, actualAccess); } private void assertHierarchyAccess( final Connection connection, Access expectedAccess, String cubeName, String hierarchyName) { final Role role = connection.getRole(); Schema schema = connection.getSchema(); final boolean fail = true; Cube cube = schema.lookupCube(cubeName, fail); final SchemaReader schemaReader = cube.getSchemaReader(null); // unrestricted final Hierarchy hierarchy = (Hierarchy) schemaReader.lookupCompound( cube, Util.parseIdentifier(hierarchyName), fail, Category.Hierarchy); final Access actualAccess = role.getAccess(hierarchy); Assert.assertEquals(cubeName, expectedAccess, actualAccess); } private Role.HierarchyAccess getHierarchyAccess( final Connection connection, String cubeName, String hierarchyName) { final Role role = connection.getRole(); Schema schema = connection.getSchema(); final boolean fail = true; Cube cube = schema.lookupCube(cubeName, fail); final SchemaReader schemaReader = cube.getSchemaReader(null); // unrestricted final Hierarchy hierarchy = (Hierarchy) schemaReader.lookupCompound( cube, Util.parseIdentifier(hierarchyName), fail, Category.Hierarchy); return role.getAccessDetails(hierarchy); } public void testGrantHierarchy1a() { // assert: can access Mexico (explicitly granted) // assert: can not access Canada (explicitly denied) // assert: can access USA (rule 3 - parent of allowed member San // Francisco) getRestrictedTestContext().assertAxisReturns( "[Store].level.members", "[Store].[Mexico]\n" + "[Store].[USA]"); } public void testGrantHierarchy1aAllMembers() { // assert: can access Mexico (explicitly granted) // assert: can not access Canada (explicitly denied) // assert: can access USA (rule 3 - parent of allowed member San // Francisco) getRestrictedTestContext().assertAxisReturns( "[Store].level.allmembers", "[Store].[Mexico]\n" + "[Store].[USA]"); } public void testGrantHierarchy1b() { // can access Mexico (explicitly granted) which is the first accessible // one getRestrictedTestContext().assertAxisReturns( "[Store].defaultMember", "[Store].[Mexico]"); } public void testGrantHierarchy1c() { // the root element is All Customers getRestrictedTestContext().assertAxisReturns( "[Customers].defaultMember", "[Customers].[Canada].[BC]"); } public void testGrantHierarchy2() { // assert: can access California (parent of allowed member) final TestContext testContext = getRestrictedTestContext(); testContext.assertAxisReturns( "[Store].[USA].children", "[Store].[USA].[CA]"); testContext.assertAxisReturns( "[Store].[USA].children", "[Store].[USA].[CA]"); testContext.assertAxisReturns( "[Store].[USA].[CA].children", "[Store].[USA].[CA].[Los Angeles]\n" + "[Store].[USA].[CA].[San Francisco]"); } public void testGrantHierarchy3() { // assert: can not access Washington (child of denied member) final TestContext testContext = getRestrictedTestContext(); testContext.assertAxisThrows("[Store].[USA].[WA]", "not found"); } private TestContext getRestrictedTestContext() { return new DelegatingTestContext(getTestContext()) { public Connection getConnection() { return getRestrictedConnection(); } }; } public void testGrantHierarchy4() { // assert: can not access Oregon (rule 1 - order matters) final TestContext testContext = getRestrictedTestContext(); testContext.assertAxisThrows( "[Store].[USA].[OR].children", "not found"); } public void testGrantHierarchy5() { // assert: can not access All (above top level) final TestContext testContext = getRestrictedTestContext(); testContext.assertAxisThrows("[Store].[All Stores]", "not found"); testContext.assertAxisReturns( "[Store].members", // note: // no: [All Stores] -- above top level // no: [Canada] -- not explicitly allowed // yes: [Mexico] -- explicitly allowed -- and all its children // except [DF] // no: [Mexico].[DF] // yes: [USA] -- implicitly allowed // yes: [CA] -- implicitly allowed // no: [OR], [WA] // yes: [San Francisco] -- explicitly allowed // no: [San Diego] "[Store].[Mexico]\n" + "[Store].[Mexico].[Guerrero]\n" + "[Store].[Mexico].[Guerrero].[Acapulco]\n" + "[Store].[Mexico].[Guerrero].[Acapulco].[Store 1]\n" + "[Store].[Mexico].[Jalisco]\n" + "[Store].[Mexico].[Jalisco].[Guadalajara]\n" + "[Store].[Mexico].[Jalisco].[Guadalajara].[Store 5]\n" + "[Store].[Mexico].[Veracruz]\n" + "[Store].[Mexico].[Veracruz].[Orizaba]\n" + "[Store].[Mexico].[Veracruz].[Orizaba].[Store 10]\n" + "[Store].[Mexico].[Yucatan]\n" + "[Store].[Mexico].[Yucatan].[Merida]\n" + "[Store].[Mexico].[Yucatan].[Merida].[Store 8]\n" + "[Store].[Mexico].[Zacatecas]\n" + "[Store].[Mexico].[Zacatecas].[Camacho]\n" + "[Store].[Mexico].[Zacatecas].[Camacho].[Store 4]\n" + "[Store].[Mexico].[Zacatecas].[Hidalgo]\n" + "[Store].[Mexico].[Zacatecas].[Hidalgo].[Store 12]\n" + "[Store].[Mexico].[Zacatecas].[Hidalgo].[Store 18]\n" + "[Store].[USA]\n" + "[Store].[USA].[CA]\n" + "[Store].[USA].[CA].[Los Angeles]\n" + "[Store].[USA].[CA].[Los Angeles].[Store 7]\n" + "[Store].[USA].[CA].[San Francisco]\n" + "[Store].[USA].[CA].[San Francisco].[Store 14]"); } public void testGrantHierarchy6() { // assert: parent if at top level is null getRestrictedTestContext().assertAxisReturns( "[Customers].[USA].[CA].parent", ""); } public void testGrantHierarchy7() { // assert: members above top level do not exist final TestContext testContext = getRestrictedTestContext(); testContext.assertAxisThrows( "[Customers].[Canada].children", "MDX object '[Customers].[Canada]' not found in cube 'Sales'"); } public void testGrantHierarchy8() { // assert: can not access Catherine Abel in San Francisco (below bottom // level) final TestContext testContext = getRestrictedTestContext(); testContext.assertAxisThrows( "[Customers].[USA].[CA].[San Francisco].[Catherine Abel]", "not found"); testContext.assertAxisReturns( "[Customers].[USA].[CA].[San Francisco].children", ""); Axis axis = testContext.executeAxis("[Customers].members"); // 13 states, 109 cities Assert.assertEquals(122, axis.getPositions().size()); } public void testGrantHierarchy8AllMembers() { // assert: can not access Catherine Abel in San Francisco (below bottom // level) final TestContext testContext = getRestrictedTestContext(); testContext.assertAxisThrows( "[Customers].[USA].[CA].[San Francisco].[Catherine Abel]", "not found"); testContext.assertAxisReturns( "[Customers].[USA].[CA].[San Francisco].children", ""); Axis axis = testContext.executeAxis("[Customers].allmembers"); // 13 states, 109 cities Assert.assertEquals(122, axis.getPositions().size()); } /** * Tests that we only aggregate over SF, LA, even when called from * functions. */ public void testGrantHierarchy9() { // Analysis services doesn't allow aggregation within calculated // measures, so use the following query to generate the results: // // with member [Store].[SF LA] as // 'Aggregate({[USA].[CA].[San Francisco], [Store].[USA].[CA].[Los // Angeles]})' // select {[Measures].[Unit Sales]} on columns, // {[Gender].children} on rows // from Sales // where ([Marital Status].[S], [Store].[SF LA]) final TestContext tc = new RestrictedTestContext(); tc.assertQueryReturns( "with member [Measures].[California Unit Sales] as " + " 'Aggregate({[Store].[USA].[CA].children}, [Measures].[Unit Sales])'\n" + "select {[Measures].[California Unit Sales]} on columns,\n" + " {[Gender].children} on rows\n" + "from Sales\n" + "where ([Marital Status].[S])", "Axis #0:\n" + "{[Marital Status].[S]}\n" + "Axis #1:\n" + "{[Measures].[California Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 6,636\n" + "Row #1: 7,329\n"); } public void testGrantHierarchyA() { final TestContext tc = new RestrictedTestContext(); // assert: totals for USA include missing cells tc.assertQueryReturns( "select {[Unit Sales]} on columns,\n" + "{[Store].[USA], [Store].[USA].children} on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA]}\n" + "{[Store].[USA].[CA]}\n" + "Row #0: 266,773\n" + "Row #1: 74,748\n"); } public void _testSharedObjectsInGrantMappingsBug() { final TestContext testContext = new TestContext() { public Connection getConnection() { boolean mustGet = true; Connection connection = super.getConnection(); Schema schema = connection.getSchema(); Cube salesCube = schema.lookupCube("Sales", mustGet); Cube warehouseCube = schema.lookupCube("Warehouse", mustGet); Hierarchy measuresInSales = salesCube.lookupHierarchy( new Id.Segment("Measures", Id.Quoting.UNQUOTED), false); Hierarchy storeInWarehouse = warehouseCube.lookupHierarchy( new Id.Segment("Store", Id.Quoting.UNQUOTED), false); RoleImpl role = new RoleImpl(); role.grant(schema, Access.NONE); role.grant(salesCube, Access.NONE); // For using hierarchy Measures in #assertExprThrows Role.RollupPolicy rollupPolicy = Role.RollupPolicy.FULL; role.grant( measuresInSales, Access.ALL, null, null, rollupPolicy); role.grant(warehouseCube, Access.NONE); role.grant(storeInWarehouse.getDimension(), Access.ALL); role.makeImmutable(); connection.setRole(role); return connection; } }; // Looking up default member on dimension Store in cube Sales should // fail. testContext.assertExprThrows( "[Store].DefaultMember", "'[Store]' not found in cube 'Sales'"); } public void testNoAccessToCube() { final TestContext tc = new RestrictedTestContext(); tc.assertQueryThrows("select from [HR]", "MDX cube 'HR' not found"); } private Connection getRestrictedConnection() { return getRestrictedConnection(true); } /** * Returns a connection with limited access to the schema. * * @param restrictCustomers true to restrict access to the customers * dimension. This will change the defaultMember of the dimension, * all cell values will be null because there are no sales data * for Canada * * @return restricted connection */ private Connection getRestrictedConnection(boolean restrictCustomers) { Connection connection = getTestContext().withSchemaPool(false).getConnection(); RoleImpl role = new RoleImpl(); Schema schema = connection.getSchema(); final boolean fail = true; Cube salesCube = schema.lookupCube("Sales", fail); final SchemaReader schemaReader = salesCube.getSchemaReader(null).withLocus(); Hierarchy storeHierarchy = salesCube.lookupHierarchy( new Id.Segment("Store", Id.Quoting.UNQUOTED), false); role.grant(schema, Access.ALL_DIMENSIONS); role.grant(salesCube, Access.ALL); Level nationLevel = Util.lookupHierarchyLevel(storeHierarchy, "Store Country"); Role.RollupPolicy rollupPolicy = Role.RollupPolicy.FULL; role.grant( storeHierarchy, Access.CUSTOM, nationLevel, null, rollupPolicy); role.grant( schemaReader.getMemberByUniqueName( Util.parseIdentifier("[Store].[All Stores].[USA].[OR]"), fail), Access.ALL); role.grant( schemaReader.getMemberByUniqueName( Util.parseIdentifier("[Store].[All Stores].[USA]"), fail), Access.CUSTOM); role.grant( schemaReader.getMemberByUniqueName( Util.parseIdentifier( "[Store].[All Stores].[USA].[CA].[San Francisco]"), fail), Access.ALL); role.grant( schemaReader.getMemberByUniqueName( Util.parseIdentifier( "[Store].[All Stores].[USA].[CA].[Los Angeles]"), fail), Access.ALL); role.grant( schemaReader.getMemberByUniqueName( Util.parseIdentifier( "[Store].[All Stores].[Mexico]"), fail), Access.ALL); role.grant( schemaReader.getMemberByUniqueName( Util.parseIdentifier( "[Store].[All Stores].[Mexico].[DF]"), fail), Access.NONE); role.grant( schemaReader.getMemberByUniqueName( Util.parseIdentifier( "[Store].[All Stores].[Canada]"), fail), Access.NONE); if (restrictCustomers) { Hierarchy customersHierarchy = salesCube.lookupHierarchy( new Id.Segment("Customers", Id.Quoting.UNQUOTED), false); Level stateProvinceLevel = Util.lookupHierarchyLevel(customersHierarchy, "State Province"); Level customersCityLevel = Util.lookupHierarchyLevel(customersHierarchy, "City"); role.grant( customersHierarchy, Access.CUSTOM, stateProvinceLevel, customersCityLevel, rollupPolicy); } // No access to HR cube. Cube hrCube = schema.lookupCube("HR", fail); role.grant(hrCube, Access.NONE); role.makeImmutable(); connection.setRole(role); return connection; } /* todo: test that access to restricted measure fails (will not work -- have not fixed Cube.getMeasures) */ private class RestrictedTestContext extends TestContext { public synchronized Connection getConnection() { return getRestrictedConnection(false); } } /** * Test context where the [Store] hierarchy has restricted access * and cell values are rolled up with 'partial' policy. */ private final TestContext rollupTestContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "") .withRole("Role1"); /** * Basic test of partial rollup policy. [USA] = [OR] + [WA], not * the usual [CA] + [OR] + [WA]. */ public void testRollupPolicyBasic() { rollupTestContext.assertQueryReturns( "select {[Store].[USA], [Store].[USA].Children} on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 192,025\n" + "Row #0: 67,659\n" + "Row #0: 124,366\n"); } /** * The total for [Store].[All Stores] is similarly reduced. All * children of [All Stores] are visible, but one grandchild is not. * Normally the total is 266,773. */ public void testRollupPolicyAll() { rollupTestContext.assertExprReturns( "([Store].[All Stores])", "192,025"); } /** * Access [Store].[All Stores] implicitly as it is the default member * of the [Stores] hierarchy. */ public void testRollupPolicyAllAsDefault() { rollupTestContext.assertExprReturns( "([Store])", "192,025"); } /** * Access [Store].[All Stores] via the Parent relationship (to check * that this doesn't circumvent access control). */ public void testRollupPolicyAllAsParent() { rollupTestContext.assertExprReturns( "([Store].[USA].Parent)", "192,025"); } /** * Tests that members below bottom level are regarded as visible. */ public void testRollupBottomLevel() { rollupPolicyBottom( Role.RollupPolicy.FULL, "74,748", "36,759", "266,773"); rollupPolicyBottom( Role.RollupPolicy.PARTIAL, "72,739", "35,775", "264,764"); rollupPolicyBottom(Role.RollupPolicy.HIDDEN, "", "", ""); } private void rollupPolicyBottom( Role.RollupPolicy rollupPolicy, String v1, String v2, String v3) { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "") .withRole("Role1"); // All of the children of [San Francisco] are invisible, because [City] // is the bottom level, but that shouldn't affect the total. testContext.assertExprReturns( "([Customers].[USA].[CA].[San Francisco])", "88"); testContext.assertExprThrows( "([Customers].[USA].[CA].[Los Angeles])", "MDX object '[Customers].[USA].[CA].[Los Angeles]' not found in cube 'Sales'"); testContext.assertExprReturns("([Customers].[USA].[CA])", v1); testContext.assertExprReturns( "([Customers].[USA].[CA], [Gender].[F])", v2); testContext.assertExprReturns("([Customers].[USA])", v3); checkQuery( testContext, "select [Customers].Children on 0, " + "[Gender].Members on 1 from [Sales]"); } /** * Calls various {@link SchemaReader} methods on the members returned in * a result set. * * @param testContext Test context * @param mdx MDX query */ private void checkQuery(TestContext testContext, String mdx) { Result result = testContext.executeQuery(mdx); final SchemaReader schemaReader = testContext.getConnection().getSchemaReader().withLocus(); for (Axis axis : result.getAxes()) { for (Position position : axis.getPositions()) { for (Member member : position) { final Member accessControlledParent = schemaReader.getMemberParent(member); if (member.getParentMember() == null) { assertNull(accessControlledParent); } final List accessControlledChildren = schemaReader.getMemberChildren(member); assertNotNull(accessControlledChildren); } } } } /** * Tests that a bad value for the rollupPolicy attribute gives the * appropriate error. */ public void testRollupPolicyNegative() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "") .withRole("Role1"); testContext.assertQueryThrows( "select from [Sales]", "Illegal rollupPolicy value 'bad'"); } /** * Tests where all children are visible but a grandchild is not. */ public void testRollupPolicyGreatGrandchildInvisible() { rollupPolicyGreatGrandchildInvisible( Role.RollupPolicy.FULL, "266,773", "74,748"); rollupPolicyGreatGrandchildInvisible( Role.RollupPolicy.PARTIAL, "266,767", "74,742"); rollupPolicyGreatGrandchildInvisible( Role.RollupPolicy.HIDDEN, "", ""); } private void rollupPolicyGreatGrandchildInvisible( Role.RollupPolicy policy, String v1, String v2) { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "") .withRole("Role1"); testContext.assertExprReturns("[Measures].[Unit Sales]", v1); testContext.assertExprReturns( "([Measures].[Unit Sales], [Customers].[USA])", v1); testContext.assertExprReturns( "([Measures].[Unit Sales], [Customers].[USA].[CA])", v2); } /** * Tests where two hierarchies are simultaneously access-controlled. */ public void testRollupPolicySimultaneous() { // note that v2 is different for full vs partial, v3 is the same rollupPolicySimultaneous( Role.RollupPolicy.FULL, "266,773", "74,748", "25,635"); rollupPolicySimultaneous( Role.RollupPolicy.PARTIAL, "72,631", "72,631", "25,635"); rollupPolicySimultaneous( Role.RollupPolicy.HIDDEN, "", "", ""); } private void rollupPolicySimultaneous( Role.RollupPolicy policy, String v1, String v2, String v3) { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "") .withRole("Role1"); testContext.assertExprReturns("[Measures].[Unit Sales]", v1); testContext.assertExprReturns( "([Measures].[Unit Sales], [Customers].[USA])", v1); testContext.assertExprReturns( "([Measures].[Unit Sales], [Customers].[USA].[CA])", v2); testContext.assertExprReturns( "([Measures].[Unit Sales], " + "[Customers].[USA].[CA], [Store].[USA].[CA])", v2); testContext.assertExprReturns( "([Measures].[Unit Sales], " + "[Customers].[USA].[CA], " + "[Store].[USA].[CA].[San Diego])", v3); } // todo: performance test where 1 of 1000 children is not visible public void testUnionRole() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); Connection connection; try { connection = testContext.withRole("Role3,Role2").getConnection(); fail("expected exception, got " + connection); } catch (RuntimeException e) { final String message = e.getMessage(); assertTrue(message, message.indexOf("Role 'Role3' not found") >= 0); } try { connection = testContext.withRole("Role1,Role3").getConnection(); fail("expected exception, got " + connection); } catch (RuntimeException e) { final String message = e.getMessage(); assertTrue(message, message.indexOf("Role 'Role3' not found") >= 0); } connection = testContext.withRole("Role1,Role2").getConnection(); // Cube access: // Both can see [Sales] // Role1 only see [Warehouse] // Neither can see [Warehouse and Sales] assertCubeAccess(connection, Access.ALL, "Sales"); assertCubeAccess(connection, Access.ALL, "Warehouse"); assertCubeAccess(connection, Access.NONE, "Warehouse and Sales"); // Hierarchy access: // Both can see [Customers] with Custom access // Both can see [Store], Role1 with Custom access, Role2 with All access // Role1 can see [Promotion Media], Role2 cannot // Neither can see [Marital Status] assertHierarchyAccess( connection, Access.CUSTOM, "Sales", "[Customers]"); assertHierarchyAccess( connection, Access.ALL, "Sales", "[Store]"); assertHierarchyAccess( connection, Access.ALL, "Sales", "[Promotion Media]"); assertHierarchyAccess( connection, Access.NONE, "Sales", "[Marital Status]"); // Rollup policy is the greater of Role1's partian and Role2's hidden final Role.HierarchyAccess hierarchyAccess = getHierarchyAccess(connection, "Sales", "[Store]"); assertEquals( Role.RollupPolicy.PARTIAL, hierarchyAccess.getRollupPolicy()); assertEquals(0, hierarchyAccess.getTopLevelDepth()); assertEquals(4, hierarchyAccess.getBottomLevelDepth()); // Member access: // both can see [USA] assertMemberAccess(connection, Access.ALL, "[Customers].[USA]"); // Role1 can see [CA], Role2 cannot assertMemberAccess(connection, Access.ALL, "[Customers].[USA].[CA]"); // Role1 cannoy see [USA].[OR].[Portland], Role2 can assertMemberAccess( connection, Access.ALL, "[Customers].[USA].[OR].[Portland]"); // Role1 cannot see [USA].[OR], Role2 can see it by virtue of [Portland] assertMemberAccess( connection, Access.CUSTOM, "[Customers].[USA].[OR]"); // Neither can see Beaverton assertMemberAccess( connection, Access.NONE, "[Customers].[USA].[OR].[Beaverton]"); // Rollup policy String mdx = "select Hierarchize(\n" + "{[Customers].[USA].Children,\n" + " [Customers].[USA].[OR].Children}) on 0\n" + "from [Sales]"; testContext.assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[CA]}\n" + "{[Customers].[USA].[OR]}\n" + "{[Customers].[USA].[OR].[Albany]}\n" + "{[Customers].[USA].[OR].[Beaverton]}\n" + "{[Customers].[USA].[OR].[Corvallis]}\n" + "{[Customers].[USA].[OR].[Lake Oswego]}\n" + "{[Customers].[USA].[OR].[Lebanon]}\n" + "{[Customers].[USA].[OR].[Milwaukie]}\n" + "{[Customers].[USA].[OR].[Oregon City]}\n" + "{[Customers].[USA].[OR].[Portland]}\n" + "{[Customers].[USA].[OR].[Salem]}\n" + "{[Customers].[USA].[OR].[W. Linn]}\n" + "{[Customers].[USA].[OR].[Woodburn]}\n" + "{[Customers].[USA].[WA]}\n" + "Row #0: 74,748\n" + "Row #0: 67,659\n" + "Row #0: 6,806\n" + "Row #0: 4,558\n" + "Row #0: 9,539\n" + "Row #0: 4,910\n" + "Row #0: 9,596\n" + "Row #0: 5,145\n" + "Row #0: 3,708\n" + "Row #0: 3,583\n" + "Row #0: 7,678\n" + "Row #0: 4,175\n" + "Row #0: 7,961\n" + "Row #0: 124,366\n"); testContext.withRole("Role1").assertQueryThrows( mdx, "MDX object '[Customers].[USA].[OR]' not found in cube 'Sales'"); testContext.withRole("Role2").assertQueryThrows( mdx, "MDX cube 'Sales' not found"); // Compared to above: // a. cities in Oregon are missing besides Portland // b. total for Oregon = total for Portland testContext.withRole("Role1,Role2").assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[CA]}\n" + "{[Customers].[USA].[OR]}\n" + "{[Customers].[USA].[OR].[Portland]}\n" + "{[Customers].[USA].[WA]}\n" + "Row #0: 74,742\n" + "Row #0: 3,583\n" + "Row #0: 3,583\n" + "Row #0: 124,366\n"); checkQuery(testContext.withRole("Role1,Role2"), mdx); } /** * Test to verify that non empty crossjoins enforce role access. * Testcase for bug * MONDRIAN-369, "Non Empty Crossjoin fails to enforce role access". */ public void testNonEmptyAccess() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "") .withRole("Role1"); // regular crossjoin returns the correct list of product children final String expected = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender], [Product].[Drink]}\n" + "Row #0: 24,597\n"; final String mdx = "select {[Measures].[Unit Sales]} ON COLUMNS, " + " Crossjoin({[Gender].[All Gender]}, " + "[Product].Children) ON ROWS " + "from [Sales]"; testContext.assertQueryReturns(mdx, expected); checkQuery(testContext, mdx); // with bug MONDRIAN-397, non empty crossjoin did not return the correct // list final String mdx2 = "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NON EMPTY Crossjoin({[Gender].[All Gender]}, " + "[Product].[All Products].Children) ON ROWS " + "from [Sales]"; testContext.assertQueryReturns(mdx2, expected); checkQuery(testContext, mdx2); } public void testNonEmptyAccessLevelMembers() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "").withRole("Role1"); // .members inside regular crossjoin returns the correct list of // product members final String expected = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender], [Product].[Drink]}\n" + "Row #0: 24,597\n"; final String mdx = "select {[Measures].[Unit Sales]} ON COLUMNS, " + " Crossjoin({[Gender].[All Gender]}, " + "[Product].[Product Family].Members) ON ROWS " + "from [Sales]"; testContext.assertQueryReturns(mdx, expected); checkQuery(testContext, mdx); // with bug MONDRIAN-397, .members inside non empty crossjoin did // not return the correct list final String mdx2 = "select {[Measures].[Unit Sales]} ON COLUMNS, " + "NON EMPTY Crossjoin({[Gender].[All Gender]}, " + "[Product].[Product Family].Members) ON ROWS " + "from [Sales]"; testContext.assertQueryReturns(mdx2, expected); checkQuery(testContext, mdx2); } /** * Testcase for bug * MONDRIAN-406, "Rollup policy doesn't work for members * that are implicitly visible". */ public void testGoodman() { final String query = "select {[Measures].[Unit Sales]} ON COLUMNS,\n" + "Hierarchize(Union(Union(Union({[Store].[All Stores]}," + " [Store].[All Stores].Children)," + " [Store].[All Stores].[USA].Children)," + " [Store].[All Stores].[USA].[CA].Children)) ON ROWS\n" + "from [Sales]\n" + "where [Time].[1997]"; // Note that total for [Store].[All Stores] and [Store].[USA] is sum // of visible children [Store].[CA] and [Store].[OR].[Portland]. final TestContext testContext = goodmanContext(Role.RollupPolicy.PARTIAL); testContext.assertQueryReturns( query, "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[All Stores]}\n" + "{[Store].[USA]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[CA].[Alameda]}\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "{[Store].[USA].[OR]}\n" + "Row #0: 74,748\n" + "Row #1: 74,748\n" + "Row #2: 74,748\n" + "Row #3: \n" + "Row #4: 21,333\n" + "Row #5: 25,663\n" + "Row #6: 25,635\n" + "Row #7: 2,117\n" + "Row #8: 26,079\n"); goodmanContext(Role.RollupPolicy.FULL).assertQueryReturns( query, "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[All Stores]}\n" + "{[Store].[USA]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[CA].[Alameda]}\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "{[Store].[USA].[OR]}\n" + "Row #0: 266,773\n" + "Row #1: 266,773\n" + "Row #2: 74,748\n" + "Row #3: \n" + "Row #4: 21,333\n" + "Row #5: 25,663\n" + "Row #6: 25,635\n" + "Row #7: 2,117\n" + "Row #8: 67,659\n"); goodmanContext(Role.RollupPolicy.HIDDEN).assertQueryReturns( query, "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[All Stores]}\n" + "{[Store].[USA]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[CA].[Alameda]}\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "{[Store].[USA].[OR]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: 74,748\n" + "Row #3: \n" + "Row #4: 21,333\n" + "Row #5: 25,663\n" + "Row #6: 25,635\n" + "Row #7: 2,117\n" + "Row #8: \n"); checkQuery(testContext, query); } private static TestContext goodmanContext(final Role.RollupPolicy policy) { return TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " " + " \n" + " \n" + "") .withRole("California manager"); } /** * Test case for bug * MONDRIAN-402, "Bug in RolapCubeHierarchy.hashCode() ?". * Access-control elements for hierarchies with * same name in different cubes could not be distinguished. */ public void testBugMondrian402() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "") .withRole("California manager"); assertHierarchyAccess( testContext.getConnection(), Access.NONE, "Sales", "Store"); assertHierarchyAccess( testContext.getConnection(), Access.CUSTOM, "Sales Ragged", "Store"); } public void testPartialRollupParentChildHierarchy() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "") .withRole("Buggy Role"); final String mdx = "select\n" + " {[Measures].[Number of Employees]} on columns,\n" + " {[Store]} on rows\n" + "from HR"; testContext.assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Number of Employees]}\n" + "Axis #2:\n" + "{[Store].[All Stores]}\n" + "Row #0: 1\n"); checkQuery(testContext, mdx); final String mdx2 = "select\n" + " {[Measures].[Number of Employees]} on columns,\n" + " {[Employees]} on rows\n" + "from HR"; testContext.assertQueryReturns( mdx2, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Number of Employees]}\n" + "Axis #2:\n" + "{[Employees].[All Employees]}\n" + "Row #0: 1\n"); checkQuery(testContext, mdx2); } public void testParentChildUserDefinedRole() { TestContext testContext = getTestContext().withCube("HR"); final Connection connection = testContext.getConnection(); final Role savedRole = connection.getRole(); try { // Run queries as top-level employee. connection.setRole( new PeopleRole( savedRole, connection.getSchema(), "Sheri Nowmer")); testContext.assertExprReturns( "[Employees].Members.Count", "1,156"); // Level 2 employee connection.setRole( new PeopleRole( savedRole, connection.getSchema(), "Derrick Whelply")); testContext.assertExprReturns( "[Employees].Members.Count", "605"); testContext.assertAxisReturns( "Head([Employees].Members, 4)," + "Tail([Employees].Members, 2)", "[Employees].[All Employees]\n" + "[Employees].[Sheri Nowmer]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Laurie Borges].[Ed Young].[Gregory Whiting].[Merrill Steel]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Laurie Borges].[Ed Young].[Gregory Whiting].[Melissa Marple]"); // Leaf employee connection.setRole( new PeopleRole( savedRole, connection.getSchema(), "Ann Weyerhaeuser")); testContext.assertExprReturns( "[Employees].Members.Count", "7"); testContext.assertAxisReturns( "[Employees].Members", "[Employees].[All Employees]\n" + "[Employees].[Sheri Nowmer]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Laurie Borges]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Laurie Borges].[Cody Goldey]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Laurie Borges].[Cody Goldey].[Shanay Steelman]\n" + "[Employees].[Sheri Nowmer].[Derrick Whelply].[Laurie Borges].[Cody Goldey].[Shanay Steelman].[Ann Weyerhaeuser]"); } finally { connection.setRole(savedRole); } } /** * Test case for * BISERVER-1574, * "Cube role rollupPolicy='partial' failure". The problem was a * NullPointerException in * {@link SchemaReader#getMemberParent(mondrian.olap.Member)} when called * on a members returned in a result set. JPivot calls that method but * Mondrian normally does not. */ public void testBugBiserver1574() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, BiServer1574Role1) .withRole("role1"); final String mdx = "select {([Measures].[Store Invoice], [Store Size in SQFT].[All Store Size in SQFTs])} ON COLUMNS,\n" + " {[Warehouse].[All Warehouses]} ON ROWS\n" + "from [Warehouse]"; checkQuery(testContext, mdx); testContext.assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Invoice], [Store Size in SQFT].[All Store Size in SQFTs]}\n" + "Axis #2:\n" + "{[Warehouse].[All Warehouses]}\n" + "Row #0: 4,042.96\n"); } /** * Testcase for bug * MONDRIAN-435, "Internal error in HierarchizeArrayComparator". Occurs * when apply Hierarchize function to tuples on a hierarchy with * partial-rollup. */ public void testBugMondrian435() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, BiServer1574Role1) .withRole("role1"); // minimal testcase testContext.assertQueryReturns( "select hierarchize(" + " crossjoin({[Store Size in SQFT], [Store Size in SQFT].Children}, {[Product]})" + ") on 0," + "[Store Type].Members on 1 from [Warehouse]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store Size in SQFT].[All Store Size in SQFTs], [Product].[All Products]}\n" + "{[Store Size in SQFT].[20319], [Product].[All Products]}\n" + "Axis #2:\n" + "{[Store Type].[All Store Types]}\n" + "{[Store Type].[Supermarket]}\n" + "Row #0: 4,042.96\n" + "Row #0: 4,042.96\n" + "Row #1: 4,042.96\n" + "Row #1: 4,042.96\n"); // explicit tuples, not crossjoin testContext.assertQueryReturns( "select hierarchize(" + " { ([Store Size in SQFT], [Product]),\n" + " ([Store Size in SQFT].[20319], [Product].[Food]),\n" + " ([Store Size in SQFT], [Product].[Drink].[Dairy]),\n" + " ([Store Size in SQFT].[20319], [Product]) }\n" + ") on 0," + "[Store Type].Members on 1 from [Warehouse]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store Size in SQFT].[All Store Size in SQFTs], [Product].[All Products]}\n" + "{[Store Size in SQFT].[All Store Size in SQFTs], [Product].[Drink].[Dairy]}\n" + "{[Store Size in SQFT].[20319], [Product].[All Products]}\n" + "{[Store Size in SQFT].[20319], [Product].[Food]}\n" + "Axis #2:\n" + "{[Store Type].[All Store Types]}\n" + "{[Store Type].[Supermarket]}\n" + "Row #0: 4,042.96\n" + "Row #0: 82.454\n" + "Row #0: 4,042.96\n" + "Row #0: 2,696.758\n" + "Row #1: 4,042.96\n" + "Row #1: 82.454\n" + "Row #1: 4,042.96\n" + "Row #1: 2,696.758\n"); // extended testcase; note that [Store Size in SQFT].Parent is null, // so disappears testContext.assertQueryReturns( "select non empty hierarchize(" + "union(" + " union(" + " crossjoin({[Store Size in SQFT]}, {[Product]})," + " crossjoin({[Store Size in SQFT], [Store Size in SQFT].Children}, {[Product]})," + " all)," + " union(" + " crossjoin({[Store Size in SQFT].Parent}, {[Product].[Drink]})," + " crossjoin({[Store Size in SQFT].Children}, {[Product].[Food]})," + " all)," + " all)) on 0," + "[Store Type].Members on 1 from [Warehouse]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store Size in SQFT].[All Store Size in SQFTs], [Product].[All Products]}\n" + "{[Store Size in SQFT].[All Store Size in SQFTs], [Product].[All Products]}\n" + "{[Store Size in SQFT].[20319], [Product].[All Products]}\n" + "{[Store Size in SQFT].[20319], [Product].[Food]}\n" + "Axis #2:\n" + "{[Store Type].[All Store Types]}\n" + "{[Store Type].[Supermarket]}\n" + "Row #0: 4,042.96\n" + "Row #0: 4,042.96\n" + "Row #0: 4,042.96\n" + "Row #0: 2,696.758\n" + "Row #1: 4,042.96\n" + "Row #1: 4,042.96\n" + "Row #1: 4,042.96\n" + "Row #1: 2,696.758\n"); testContext.assertQueryReturns( "select Hierarchize(\n" + " CrossJoin\n(" + " CrossJoin(\n" + " {[Product].[All Products], " + " [Product].[Food],\n" + " [Product].[Food].[Eggs],\n" + " [Product].[Drink].[Dairy]},\n" + " [Store Type].MEMBERS),\n" + " [Store Size in SQFT].MEMBERS),\n" + " PRE) on 0\n" + "from [Warehouse]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[All Products], [Store Type].[All Store Types], [Store Size in SQFT].[All Store Size in SQFTs]}\n" + "{[Product].[All Products], [Store Type].[All Store Types], [Store Size in SQFT].[20319]}\n" + "{[Product].[All Products], [Store Type].[Supermarket], [Store Size in SQFT].[All Store Size in SQFTs]}\n" + "{[Product].[All Products], [Store Type].[Supermarket], [Store Size in SQFT].[20319]}\n" + "{[Product].[Drink].[Dairy], [Store Type].[All Store Types], [Store Size in SQFT].[All Store Size in SQFTs]}\n" + "{[Product].[Drink].[Dairy], [Store Type].[All Store Types], [Store Size in SQFT].[20319]}\n" + "{[Product].[Drink].[Dairy], [Store Type].[Supermarket], [Store Size in SQFT].[All Store Size in SQFTs]}\n" + "{[Product].[Drink].[Dairy], [Store Type].[Supermarket], [Store Size in SQFT].[20319]}\n" + "{[Product].[Food], [Store Type].[All Store Types], [Store Size in SQFT].[All Store Size in SQFTs]}\n" + "{[Product].[Food], [Store Type].[All Store Types], [Store Size in SQFT].[20319]}\n" + "{[Product].[Food], [Store Type].[Supermarket], [Store Size in SQFT].[All Store Size in SQFTs]}\n" + "{[Product].[Food], [Store Type].[Supermarket], [Store Size in SQFT].[20319]}\n" + "{[Product].[Food].[Eggs], [Store Type].[All Store Types], [Store Size in SQFT].[All Store Size in SQFTs]}\n" + "{[Product].[Food].[Eggs], [Store Type].[All Store Types], [Store Size in SQFT].[20319]}\n" + "{[Product].[Food].[Eggs], [Store Type].[Supermarket], [Store Size in SQFT].[All Store Size in SQFTs]}\n" + "{[Product].[Food].[Eggs], [Store Type].[Supermarket], [Store Size in SQFT].[20319]}\n" + "Row #0: 4,042.96\n" + "Row #0: 4,042.96\n" + "Row #0: 4,042.96\n" + "Row #0: 4,042.96\n" + "Row #0: 82.454\n" + "Row #0: 82.454\n" + "Row #0: 82.454\n" + "Row #0: 82.454\n" + "Row #0: 2,696.758\n" + "Row #0: 2,696.758\n" + "Row #0: 2,696.758\n" + "Row #0: 2,696.758\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n"); } /** * Testcase for bug * MONDRIAN-436, "SubstitutingMemberReader.getMemberBuilder gives * UnsupportedOperationException". */ public void testBugMondrian436() { propSaver.set(propSaver.properties.EnableNativeCrossJoin, true); propSaver.set(propSaver.properties.EnableNativeFilter, true); propSaver.set(propSaver.properties.EnableNativeNonEmpty, true); propSaver.set(propSaver.properties.EnableNativeTopCount, true); propSaver.set(propSaver.properties.ExpandNonNative, true); // Run with native enabled, then with whatever properties are set for // this test run. checkBugMondrian436(); propSaver.reset(); checkBugMondrian436(); } private void checkBugMondrian436() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, BiServer1574Role1) .withRole("role1"); testContext.assertQueryReturns( "select non empty {[Measures].[Units Ordered],\n" + " [Measures].[Units Shipped]} on 0,\n" + "non empty hierarchize(\n" + " union(\n" + " crossjoin(\n" + " {[Store Size in SQFT]},\n" + " {[Product].[Drink],\n" + " [Product].[Food],\n" + " [Product].[Drink].[Dairy]}),\n" + " crossjoin(\n" + " {[Store Size in SQFT].[20319]},\n" + " {[Product].Children}))) on 1\n" + "from [Warehouse]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Units Ordered]}\n" + "{[Measures].[Units Shipped]}\n" + "Axis #2:\n" + "{[Store Size in SQFT].[All Store Size in SQFTs], [Product].[Drink]}\n" + "{[Store Size in SQFT].[All Store Size in SQFTs], [Product].[Drink].[Dairy]}\n" + "{[Store Size in SQFT].[All Store Size in SQFTs], [Product].[Food]}\n" + "{[Store Size in SQFT].[20319], [Product].[Drink]}\n" + "{[Store Size in SQFT].[20319], [Product].[Food]}\n" + "{[Store Size in SQFT].[20319], [Product].[Non-Consumable]}\n" + "Row #0: 865.0\n" + "Row #0: 767.0\n" + "Row #1: 195.0\n" + "Row #1: 182.0\n" + "Row #2: 6065.0\n" + "Row #2: 5723.0\n" + "Row #3: 865.0\n" + "Row #3: 767.0\n" + "Row #4: 6065.0\n" + "Row #4: 5723.0\n" + "Row #5: 2179.0\n" + "Row #5: 2025.0\n"); } /** * Tests that hierarchy-level access control works on a virtual cube. * See bug * * MONDRIAN-456, "Roles and virtual cubes". */ public void testVirtualCube() { TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "").withRole("VCRole"); testContext.assertQueryReturns( "select [Store].Members on 0 from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[All Stores]}\n" + "{[Store].[USA]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[CA].[Alameda]}\n" + "{[Store].[USA].[CA].[Alameda].[HQ]}\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "Row #0: 159,167.84\n" + "Row #0: 159,167.84\n" + "Row #0: 159,167.84\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 45,750.24\n" + "Row #0: 45,750.24\n" + "Row #0: 54,431.14\n" + "Row #0: 54,431.14\n" + "Row #0: 4,441.18\n" + "Row #0: 4,441.18\n"); } /** * this tests the fix for * http://jira.pentaho.com/browse/BISERVER-2491 * rollupPolicy=partial and queries to upper members don't work */ public void testBugBiserver2491() { final String BiServer2491Role2 = "" + " " + " " + " " + " " + " " + " " + " " + " " + ""; final TestContext testContext = TestContext.instance().create( null, null, null, null, null, BiServer2491Role2) .withRole("role2"); final String firstBrokenMdx = "select [Measures].[Unit Sales] ON COLUMNS, {[Store].[Store Country].Members} ON ROWS from [Sales]"; checkQuery(testContext, firstBrokenMdx); testContext.assertQueryReturns( firstBrokenMdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA]}\n" + "Row #0: 49,085\n"); final String secondBrokenMdx = "select [Measures].[Unit Sales] ON COLUMNS, " + "Descendants([Store],[Store].[Store Name]) ON ROWS from [Sales]"; checkQuery(testContext, secondBrokenMdx); testContext.assertQueryReturns( secondBrokenMdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[Alameda].[HQ]}\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "Row #0: \n" + "Row #1: 21,333\n" + "Row #2: 25,635\n" + "Row #3: 2,117\n"); } /** * Test case for bug * MONDRIAN-622, * "Poor performance with large union role". */ public void testBugMondrian622() { StringBuilder buf = new StringBuilder(); StringBuilder buf2 = new StringBuilder(); final String cubeName = "Sales with multiple customers"; final Result result = TestContext.instance().executeQuery( "select [Customers].[City].Members on 0 from [Sales]"); for (Position position : result.getAxes()[0].getPositions()) { Member member = position.get(0); String name = member.getParentMember().getName() + "." + member.getName(); // e.g. "BC.Burnaby" // e.g. "[Customers].[State Province].[BC].[Burnaby]" String uniqueName = Util.replace(member.getUniqueName(), ".[All Customers]", ""); // e.g. "[Customers2].[State Province].[BC].[Burnaby]" String uniqueName2 = Util.replace(uniqueName, "Customers", "Customers2"); // e.g. "[Customers3].[State Province].[BC].[Burnaby]" String uniqueName3 = Util.replace(uniqueName, "Customers", "Customers3"); buf.append( " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n"); buf2.append(" \n"); } final TestContext testContext = TestContext.instance().create( " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " ", " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n", null, null, null, buf.toString() + " \n" + " \n" + buf2.toString() + " \n" + " \n"); final long t0 = System.currentTimeMillis(); final TestContext testContext1 = testContext.withRole("Test"); testContext1.executeQuery("select from [" + cubeName + "]"); final long t1 = System.currentTimeMillis(); // System.out.println("Elapsed=" + (t1 - t0) + " millis"); // System.out.println( // "RoleImpl.accessCount=" + RoleImpl.accessCallCount); // testContext1.executeQuery( // "select from [Sales with multiple customers]"); // final long t2 = System.currentTimeMillis(); // System.out.println("Elapsed=" + (t2 - t1) + " millis"); // System.out.println("RoleImpl.accessCount=" + RoleImpl.accessCallCount); } /** * Test case for bug * MONDRIAN-694, * "Incorrect handling of child/parent relationship with hierarchy * grants". */ public void testBugMondrian694() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "") .withRole("REG1"); // With bug MONDRIAN-694 returns 874.80, should return 79.20. // Test case is minimal: doesn't happen without the Crossjoin, or // without the NON EMPTY, or with [Employees] as opposed to // [Employees].[All Employees], or with [Department].[All Departments]. testContext.assertQueryReturns( "select NON EMPTY {[Measures].[Org Salary]} ON COLUMNS,\n" + "NON EMPTY Crossjoin({[Department].[14]}, {[Employees].[All Employees]}) ON ROWS\n" + "from [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Org Salary]}\n" + "Axis #2:\n" + "{[Department].[14], [Employees].[All Employees]}\n" + "Row #0: $97.20\n"); // This query gave the right answer, even with MONDRIAN-694. testContext.assertQueryReturns( "select NON EMPTY {[Measures].[Org Salary]} ON COLUMNS, \n" + "NON EMPTY Hierarchize(Crossjoin({[Department].[14]}, {[Employees].[All Employees], [Employees].Children})) ON ROWS \n" + "from [HR] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Org Salary]}\n" + "Axis #2:\n" + "{[Department].[14], [Employees].[All Employees]}\n" + "{[Department].[14], [Employees].[Sheri Nowmer]}\n" + "Row #0: $97.20\n" + "Row #1: $97.20\n"); // Original test case, not quite minimal. With MONDRIAN-694, returns // $874.80 for [All Employees]. testContext.assertQueryReturns( "select NON EMPTY {[Measures].[Org Salary]} ON COLUMNS, \n" + "NON EMPTY Hierarchize(Union(Crossjoin({[Department].[All Departments].[14]}, {[Employees].[All Employees]}), Crossjoin({[Department].[All Departments].[14]}, [Employees].[All Employees].Children))) ON ROWS \n" + "from [HR] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Org Salary]}\n" + "Axis #2:\n" + "{[Department].[14], [Employees].[All Employees]}\n" + "{[Department].[14], [Employees].[Sheri Nowmer]}\n" + "Row #0: $97.20\n" + "Row #1: $97.20\n"); testContext.assertQueryReturns( "select NON EMPTY {[Measures].[Org Salary]} ON COLUMNS, \n" + "NON EMPTY Crossjoin(Hierarchize(Union({[Employees].[All Employees]}, [Employees].[All Employees].Children)), {[Department].[14]}) ON ROWS \n" + "from [HR] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Org Salary]}\n" + "Axis #2:\n" + "{[Employees].[All Employees], [Department].[14]}\n" + "{[Employees].[Sheri Nowmer], [Department].[14]}\n" + "Row #0: $97.20\n" + "Row #1: $97.20\n"); } /** * Test case for bug * MONDRIAN-722, "If * ignoreInvalidMembers=true, should ignore grants with invalid * members". */ public void testBugMondrian722() { propSaver.set( MondrianProperties.instance().IgnoreInvalidMembers, true); TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "") .withRole("CTO") .assertQueryReturns( "select [Measures] on 0,\n" + " Hierarchize(\n" + " {[Customers].[USA].Children,\n" + " [Customers].[USA].[CA].Children}) on 1\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Customers].[USA].[CA]}\n" + "{[Customers].[USA].[CA].[Los Angeles]}\n" + "{[Customers].[USA].[CA].[San Francisco]}\n" + "Row #0: 74,748\n" + "Row #1: 2,009\n" + "Row #2: 88\n"); } /** * Test case for bug * MONDRIAN-746, * "Report returns stack trace when turning on subtotals on a hierarchy with * top level hidden". */ public void testCalcMemberLevel() { checkCalcMemberLevel(getTestContext()); checkCalcMemberLevel( TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n") .withRole("Role1")); } /** * Test for bug MONDRIAN-568. Grants on OLAP elements are validated * by name, thus granting implicit access to all cubes which have * a dimension with the same name. */ public void testBugMondrian568() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " " + " \n" + " \n" + "\n" + "\n" + " \n" + " \n" + " \n" + ""); final TestContext testContextRole1 = testContext .withRole("Role1") .withCube("Sales"); final TestContext testContextRole12 = testContext .withRole("Role1,Role2") .withCube("Sales"); assertMemberAccess( testContextRole1.getConnection(), Access.NONE, "[Measures].[Store Cost]"); assertMemberAccess( testContextRole12.getConnection(), Access.NONE, "[Measures].[Store Cost]"); } private void checkCalcMemberLevel(TestContext testContext) { Result result = testContext.executeQuery( "with member [Store].[USA].[CA].[Foo] as\n" + " 1\n" + "select {[Measures].[Unit Sales]} on columns,\n" + "{[Store].[USA].[CA],\n" + " [Store].[USA].[CA].[Los Angeles],\n" + " [Store].[USA].[CA].[Foo]} on rows\n" + "from [Sales]"); final List rowPos = result.getAxes()[1].getPositions(); final Member member0 = rowPos.get(0).get(0); assertEquals("CA", member0.getName()); assertEquals("Store State", member0.getLevel().getName()); final Member member1 = rowPos.get(1).get(0); assertEquals("Los Angeles", member1.getName()); assertEquals("Store City", member1.getLevel().getName()); final Member member2 = rowPos.get(2).get(0); assertEquals("Foo", member2.getName()); assertEquals("Store City", member2.getLevel().getName()); } /** * Testcase for bug * MONDRIAN-935, * "no results when some level members in a member grant have no data". */ public void testBugMondrian935() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); testContext.withRole("Role1").assertQueryReturns( "select [Measures] on 0,\n" + "[Customers].[USA].Children * [Store Type].Children on 1\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Customers].[USA].[CA], [Store Type].[Supermarket]}\n" + "{[Customers].[USA].[WA], [Store Type].[Supermarket]}\n" + "Row #0: 1,118\n" + "Row #1: 73,178\n"); } public void testDimensionGrant() throws Exception { final TestContext context = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); context.withRole("Role1").assertAxisReturns( "[Education Level].Members", "[Education Level].[All Education Levels]\n" + "[Education Level].[Bachelors Degree]\n" + "[Education Level].[Graduate Degree]\n" + "[Education Level].[High School Degree]\n" + "[Education Level].[Partial College]\n" + "[Education Level].[Partial High School]"); context.withRole("Role1").assertAxisThrows( "[Customers].Members", "Mondrian Error:Failed to parse query 'select {[Customers].Members} on columns from Sales'"); context.withRole("Role2").assertAxisThrows( "[Customers].Members", "Mondrian Error:Failed to parse query 'select {[Customers].Members} on columns from Sales'"); context.withRole("Role1").assertQueryReturns( "select {[Education Level].Members} on columns, {[Measures].[Unit Sales]} on rows from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Education Level].[All Education Levels]}\n" + "{[Education Level].[Bachelors Degree]}\n" + "{[Education Level].[Graduate Degree]}\n" + "{[Education Level].[High School Degree]}\n" + "{[Education Level].[Partial College]}\n" + "{[Education Level].[Partial High School]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 266,773\n" + "Row #0: 68,839\n" + "Row #0: 15,570\n" + "Row #0: 78,664\n" + "Row #0: 24,545\n" + "Row #0: 79,155\n"); context.withRole("Role3").assertQueryThrows( "select {[Education Level].Members} on columns, {[Measures].[Unit Sales]} on rows from Sales", "Mondrian Error:Failed to parse query 'select {[Education Level].Members} on columns, {[Measures].[Unit Sales]} on rows from Sales'"); } // ~ Inner classes ========================================================= public static class PeopleRole extends DelegatingRole { private final String repName; public PeopleRole(Role role, Schema schema, String repName) { super(((RoleImpl)role).makeMutableClone()); this.repName = repName; defineGrantsForUser(schema); } private void defineGrantsForUser(Schema schema) { RoleImpl role = (RoleImpl)this.role; role.grant(schema, Access.NONE); Cube cube = schema.lookupCube("HR", true); role.grant(cube, Access.ALL); Hierarchy hierarchy = cube.lookupHierarchy( new Id.Segment("Employees", Id.Quoting.QUOTED), false); Level[] levels = hierarchy.getLevels(); Level topLevel = levels[1]; role.grant(hierarchy, Access.CUSTOM, null, null, RollupPolicy.FULL); role.grant(hierarchy.getAllMember(), Access.NONE); boolean foundMember = false; List members = schema.getSchemaReader().withLocus() .getLevelMembers(topLevel, true); for (Member member : members) { if (member.getUniqueName().contains("[" + repName + "]")) { foundMember = true; role.grant(member, Access.ALL); } } assertTrue(foundMember); } } /** * This is a test for MONDRIAN-1030. When the top level of a hierarchy * is not accessible and a partial rollup policy is used, the results would * be returned as those of the first member of those accessible only. * *

ie: If a union of roles give access to two two sibling root members * and the level to which they belong is not included in a query, the * returned cell data would be that of the first sibling and would exclude * those of the second. * *

This is because the RolapEvaluator cannot represent default members * as multiple members (only a single member is the default member) and * because the default member is not the 'all member', it adds a constrain * to the SQL for the first member only. * *

Currently, Mondrian disguises the root member in the evaluator as a * RestrictedMemberReader.MultiCardinalityDefaultMember. Later, * RolapHierarchy.LimitedRollupSubstitutingMemberReader will recognize it * and use the correct rollup policy on the parent member to generate * correct SQL. */ public void testMondrian1030() throws Exception { final String mdx1 = "With\n" + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Customers],[*BASE_MEMBERS_Product])'\n" + "Set [*SORTED_ROW_AXIS] as 'Order([*CJ_ROW_AXIS],[Customers].CurrentMember.OrderKey,BASC,[Education Level].CurrentMember.OrderKey,BASC)'\n" + "Set [*BASE_MEMBERS_Customers] as '[Customers].[City].Members'\n" + "Set [*BASE_MEMBERS_Product] as '[Education Level].Members'\n" + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}'\n" + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Customers].currentMember,[Education Level].currentMember)})'\n" + "Set [*CJ_COL_AXIS] as '[*NATIVE_CJ_SET]'\n" + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Unit Sales]', FORMAT_STRING = '#,###', SOLVE_ORDER=400\n" + "Select\n" + "[*BASE_MEMBERS_Measures] on columns,\n" + "Non Empty [*SORTED_ROW_AXIS] on rows\n" + "From [Sales] \n"; final String mdx2 = "With\n" + "Set [*BASE_MEMBERS_Product] as '[Education Level].Members'\n" + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}'\n" + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Unit Sales]', FORMAT_STRING = '#,###', SOLVE_ORDER=400\n" + "Select\n" + "[*BASE_MEMBERS_Measures] on columns,\n" + "Non Empty [*BASE_MEMBERS_Product] on rows\n" + "From [Sales] \n"; final TestContext context = getTestContext().create( null, null, null, null, null, " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n"); // Control tests context.withRole("Role1").assertQueryReturns( mdx1, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*FORMATTED_MEASURE_0]}\n" + "Axis #2:\n" + "{[Customers].[USA].[CA].[Coronado], [Education Level].[All Education Levels]}\n" + "{[Customers].[USA].[CA].[Coronado], [Education Level].[Bachelors Degree]}\n" + "{[Customers].[USA].[CA].[Coronado], [Education Level].[Graduate Degree]}\n" + "{[Customers].[USA].[CA].[Coronado], [Education Level].[High School Degree]}\n" + "{[Customers].[USA].[CA].[Coronado], [Education Level].[Partial College]}\n" + "{[Customers].[USA].[CA].[Coronado], [Education Level].[Partial High School]}\n" + "Row #0: 2,391\n" + "Row #1: 559\n" + "Row #2: 205\n" + "Row #3: 551\n" + "Row #4: 253\n" + "Row #5: 823\n"); context.withRole("Role2").assertQueryReturns( mdx1, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*FORMATTED_MEASURE_0]}\n" + "Axis #2:\n" + "{[Customers].[USA].[CA].[Burbank], [Education Level].[All Education Levels]}\n" + "{[Customers].[USA].[CA].[Burbank], [Education Level].[Bachelors Degree]}\n" + "{[Customers].[USA].[CA].[Burbank], [Education Level].[Graduate Degree]}\n" + "{[Customers].[USA].[CA].[Burbank], [Education Level].[High School Degree]}\n" + "{[Customers].[USA].[CA].[Burbank], [Education Level].[Partial College]}\n" + "{[Customers].[USA].[CA].[Burbank], [Education Level].[Partial High School]}\n" + "Row #0: 3,086\n" + "Row #1: 914\n" + "Row #2: 126\n" + "Row #3: 1,029\n" + "Row #4: 286\n" + "Row #5: 731\n"); context.withRole("Role1,Role2").assertQueryReturns( mdx1, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*FORMATTED_MEASURE_0]}\n" + "Axis #2:\n" + "{[Customers].[USA].[CA].[Burbank], [Education Level].[All Education Levels]}\n" + "{[Customers].[USA].[CA].[Burbank], [Education Level].[Bachelors Degree]}\n" + "{[Customers].[USA].[CA].[Burbank], [Education Level].[Graduate Degree]}\n" + "{[Customers].[USA].[CA].[Burbank], [Education Level].[High School Degree]}\n" + "{[Customers].[USA].[CA].[Burbank], [Education Level].[Partial College]}\n" + "{[Customers].[USA].[CA].[Burbank], [Education Level].[Partial High School]}\n" + "{[Customers].[USA].[CA].[Coronado], [Education Level].[All Education Levels]}\n" + "{[Customers].[USA].[CA].[Coronado], [Education Level].[Bachelors Degree]}\n" + "{[Customers].[USA].[CA].[Coronado], [Education Level].[Graduate Degree]}\n" + "{[Customers].[USA].[CA].[Coronado], [Education Level].[High School Degree]}\n" + "{[Customers].[USA].[CA].[Coronado], [Education Level].[Partial College]}\n" + "{[Customers].[USA].[CA].[Coronado], [Education Level].[Partial High School]}\n" + "Row #0: 3,086\n" + "Row #1: 914\n" + "Row #2: 126\n" + "Row #3: 1,029\n" + "Row #4: 286\n" + "Row #5: 731\n" + "Row #6: 2,391\n" + "Row #7: 559\n" + "Row #8: 205\n" + "Row #9: 551\n" + "Row #10: 253\n" + "Row #11: 823\n"); // Actual tests context.withRole("Role1").assertQueryReturns( mdx2, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*FORMATTED_MEASURE_0]}\n" + "Axis #2:\n" + "{[Education Level].[All Education Levels]}\n" + "{[Education Level].[Bachelors Degree]}\n" + "{[Education Level].[Graduate Degree]}\n" + "{[Education Level].[High School Degree]}\n" + "{[Education Level].[Partial College]}\n" + "{[Education Level].[Partial High School]}\n" + "Row #0: 2,391\n" + "Row #1: 559\n" + "Row #2: 205\n" + "Row #3: 551\n" + "Row #4: 253\n" + "Row #5: 823\n"); context.withRole("Role2").assertQueryReturns( mdx2, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*FORMATTED_MEASURE_0]}\n" + "Axis #2:\n" + "{[Education Level].[All Education Levels]}\n" + "{[Education Level].[Bachelors Degree]}\n" + "{[Education Level].[Graduate Degree]}\n" + "{[Education Level].[High School Degree]}\n" + "{[Education Level].[Partial College]}\n" + "{[Education Level].[Partial High School]}\n" + "Row #0: 3,086\n" + "Row #1: 914\n" + "Row #2: 126\n" + "Row #3: 1,029\n" + "Row #4: 286\n" + "Row #5: 731\n"); context.withRole("Role1,Role2").assertQueryReturns( mdx2, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*FORMATTED_MEASURE_0]}\n" + "Axis #2:\n" + "{[Education Level].[All Education Levels]}\n" + "{[Education Level].[Bachelors Degree]}\n" + "{[Education Level].[Graduate Degree]}\n" + "{[Education Level].[High School Degree]}\n" + "{[Education Level].[Partial College]}\n" + "{[Education Level].[Partial High School]}\n" + "Row #0: 5,477\n" + "Row #1: 1,473\n" + "Row #2: 331\n" + "Row #3: 1,580\n" + "Row #4: 539\n" + "Row #5: 1,554\n"); } /** * This is a test for * MONDRIAN-1030 * When a query is based on a level higher than one in the same hierarchy * which has access controls, it would only constrain at the current level * if the rollup policy of partial is used. * *

Example. A query on USA where only Los-Angeles is accessible would * return the values for California instead of only LA. */ public void testBugMondrian1030_2() { TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "") .withRole("Bacon") .assertQueryReturns( "select {[Measures].[Unit Sales]} on 0,\n" + " {[Customers].[USA]} on 1\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Customers].[USA]}\n" + "Row #0: 2,009\n"); } /** * Test for * MONDRIAN-1091 * The RoleImpl would try to search for member grants by object identity * rather than unique name. When using the partial rollup policy, the * members are wrapped, so identity checks would fail. */ public void testMondrian1091() throws Exception { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "") .withRole("Role1"); testContext.assertQueryReturns( "select {[Store].Members} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[All Stores]}\n" + "{[Store].[USA]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[CA].[Alameda]}\n" + "{[Store].[USA].[CA].[Alameda].[HQ]}\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "Row #0: 74,748\n" + "Row #0: 74,748\n" + "Row #0: 74,748\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 21,333\n" + "Row #0: 21,333\n" + "Row #0: 25,663\n" + "Row #0: 25,663\n" + "Row #0: 25,635\n" + "Row #0: 25,635\n" + "Row #0: 2,117\n" + "Row #0: 2,117\n"); org.olap4j.metadata.Cube cube = testContext.getOlap4jConnection() .getOlapSchema().getCubes().get("Sales"); org.olap4j.metadata.Member allMember = cube.lookupMember( IdentifierNode.parseIdentifier("[Store].[All Stores]") .getSegmentList()); assertNotNull(allMember); assertEquals(1, allMember.getHierarchy().getRootMembers().size()); assertEquals( "[Store].[All Stores]", allMember.getHierarchy().getRootMembers().get(0).getUniqueName()); } } // End AccessControlTest.java mondrian-3.4.1/testsrc/main/mondrian/test/PropertiesTest.java0000644000175000017500000003223011735330606024266 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; /** * Tests intrinsic member and cell properties as specified in OLE DB for OLAP * specification. * * @author anikitin * @since 5 July, 2005 */ public class PropertiesTest extends FoodMartTestCase { public PropertiesTest(String name) { super(name); } /** * Tests existence and values of mandatory member properties. */ public void testMandatoryMemberProperties() { Cube salesCube = getConnection().getSchema().lookupCube("Sales", true); SchemaReader scr = salesCube.getSchemaReader(null).withLocus(); Member member = scr.getMemberByUniqueName( Id.Segment.toList("Customers", "All Customers", "USA", "CA"), true); final boolean caseSensitive = MondrianProperties.instance().CaseSensitive.get(); String stringPropValue; Integer intPropValue; // I'm not sure this property has to store the same value // getConnection().getCatalogName() returns. // todo: // stringPropValue = (String)member.getPropertyValue("CATALOG_NAME"); // assertEquals(getConnection().getCatalogName(), stringPropValue); stringPropValue = (String)member.getPropertyValue("SCHEMA_NAME"); assertEquals(getConnection().getSchema().getName(), stringPropValue); // todo: // stringPropValue = (String)member.getPropertyValue("CUBE_NAME"); // assertEquals(salesCube.getName(), stringPropValue); stringPropValue = (String)member.getPropertyValue("DIMENSION_UNIQUE_NAME"); assertEquals(member.getDimension().getUniqueName(), stringPropValue); // Case sensitivity. stringPropValue = (String)member.getPropertyValue( "dimension_unique_name", caseSensitive); if (caseSensitive) { assertNull(stringPropValue); } else { assertEquals( member.getDimension().getUniqueName(), stringPropValue); } // Non-existent property. stringPropValue = (String)member.getPropertyValue("DIMENSION_UNIQUE_NAME_XXXX"); assertNull(stringPropValue); // Leading spaces. stringPropValue = (String)member.getPropertyValue(" DIMENSION_UNIQUE_NAME"); assertNull(stringPropValue); // Trailing spaces. stringPropValue = (String)member.getPropertyValue("DIMENSION_UNIQUE_NAME "); assertNull(stringPropValue); stringPropValue = (String)member.getPropertyValue("HIERARCHY_UNIQUE_NAME"); assertEquals(member.getHierarchy().getUniqueName(), stringPropValue); // This property works in Mondrian 1.1.5 (due to XMLA support) stringPropValue = (String)member.getPropertyValue("LEVEL_UNIQUE_NAME"); assertEquals(member.getLevel().getUniqueName(), stringPropValue); // This property works in Mondrian 1.1.5 (due to XMLA support) intPropValue = (Integer)member.getPropertyValue("LEVEL_NUMBER"); assertEquals( Integer.valueOf(member.getLevel().getDepth()), intPropValue); // This property works in Mondrian 1.1.5 (due to XMLA support) stringPropValue = (String)member.getPropertyValue("MEMBER_UNIQUE_NAME"); assertEquals(member.getUniqueName(), stringPropValue); stringPropValue = (String)member.getPropertyValue("MEMBER_NAME"); assertEquals(member.getName(), stringPropValue); intPropValue = (Integer)member.getPropertyValue("MEMBER_TYPE"); assertEquals( Integer.valueOf(member.getMemberType().ordinal()), intPropValue); stringPropValue = (String)member.getPropertyValue("MEMBER_GUID"); assertNull(stringPropValue); // This property works in Mondrian 1.1.5 (due to XMLA support) stringPropValue = (String)member.getPropertyValue("MEMBER_CAPTION"); assertEquals(member.getCaption(), stringPropValue); stringPropValue = (String)member.getPropertyValue("CAPTION"); assertEquals(member.getCaption(), stringPropValue); // It's worth checking case-sensitivity for CAPTION because it is a // synonym, not a true property. stringPropValue = (String) member.getPropertyValue( "caption", caseSensitive); if (caseSensitive) { assertNull(stringPropValue); } else { assertEquals(member.getCaption(), stringPropValue); } intPropValue = (Integer)member.getPropertyValue("MEMBER_ORDINAL"); assertEquals(Integer.valueOf(member.getOrdinal()), intPropValue); if (false) { intPropValue = (Integer)member.getPropertyValue("CHILDREN_CARDINALITY"); assertEquals( Integer.valueOf(scr.getMemberChildren(member).size()), intPropValue); } intPropValue = (Integer)member.getPropertyValue("PARENT_LEVEL"); assertEquals( Integer.valueOf(member.getParentMember().getLevel().getDepth()), intPropValue); stringPropValue = (String)member.getPropertyValue("PARENT_UNIQUE_NAME"); assertEquals(member.getParentUniqueName(), stringPropValue); intPropValue = (Integer)member.getPropertyValue("PARENT_COUNT"); assertEquals(Integer.valueOf(1), intPropValue); stringPropValue = (String)member.getPropertyValue("DESCRIPTION"); assertEquals(member.getDescription(), stringPropValue); // Case sensitivity. stringPropValue = (String)member.getPropertyValue("desCription", caseSensitive); if (caseSensitive) { assertNull(stringPropValue); } else { assertEquals(member.getDescription(), stringPropValue); } } public void testGetChildCardinalityPropertyValue() { Cube salesCube = getConnection().getSchema().lookupCube("Sales", true); SchemaReader scr = salesCube.getSchemaReader(null); Member memberForCardinalityTest = scr.getMemberByUniqueName( Id.Segment.toList("Marital Status", "All Marital Status"), true); Integer intPropValue = (Integer) memberForCardinalityTest.getPropertyValue( "CHILDREN_CARDINALITY"); assertEquals(Integer.valueOf(111), intPropValue); } /** * Tests the ability of MDX parser to pass requested member properties * to Result object. */ public void testPropertiesMDX() { Result result = executeQuery( "SELECT {[Customers].[All Customers].[USA].[CA]} DIMENSION PROPERTIES \n" + " CATALOG_NAME, SCHEMA_NAME, CUBE_NAME, DIMENSION_UNIQUE_NAME, \n" + " HIERARCHY_UNIQUE_NAME, LEVEL_UNIQUE_NAME, LEVEL_NUMBER, MEMBER_UNIQUE_NAME, \n" + " MEMBER_NAME, MEMBER_TYPE, MEMBER_GUID, MEMBER_CAPTION, MEMBER_ORDINAL, CHILDREN_CARDINALITY,\n" + " PARENT_LEVEL, PARENT_UNIQUE_NAME, PARENT_COUNT, DESCRIPTION ON COLUMNS\n" + "FROM [Sales]"); QueryAxis[] axes = result.getQuery().getAxes(); Id[] axesProperties = axes[0].getDimensionProperties(); String[] props = { "CATALOG_NAME", "SCHEMA_NAME", "CUBE_NAME", "DIMENSION_UNIQUE_NAME", "HIERARCHY_UNIQUE_NAME", "LEVEL_UNIQUE_NAME", "LEVEL_NUMBER", "MEMBER_UNIQUE_NAME", "MEMBER_NAME", "MEMBER_TYPE", "MEMBER_GUID", "MEMBER_CAPTION", "MEMBER_ORDINAL", "CHILDREN_CARDINALITY", "PARENT_LEVEL", "PARENT_UNIQUE_NAME", "PARENT_COUNT", "DESCRIPTION" }; assertEquals(axesProperties.length, props.length); int i = 0; for (String prop : props) { assertEquals("[" + prop + "]", axesProperties[i++].toString()); } } /** * Tests the ability to project non-standard member properties. */ public void testMemberProperties() { Result result = executeQuery( "SELECT {[Store].Children} DIMENSION PROPERTIES\n" + " CATALOG_NAME, PARENT_UNIQUE_NAME, [Store Type], FORMAT_EXP\n" + " ON COLUMNS\n" + "FROM [Sales]"); QueryAxis[] axes = result.getQuery().getAxes(); Id[] axesProperties = axes[0].getDimensionProperties(); assertEquals(4, axesProperties.length); } /** * Tests the ability to project non-standard member properties. */ public void testMemberPropertiesBad() { Result result = executeQuery( "SELECT {[Store].Children} DIMENSION PROPERTIES\n" + " CATALOG_NAME, PARENT_UNIQUE_NAME, [Store Type], BAD\n" + " ON COLUMNS\n" + "FROM [Sales]"); QueryAxis[] axes = result.getQuery().getAxes(); Id[] axesProperties = axes[0].getDimensionProperties(); assertEquals(4, axesProperties.length); } public void testMandatoryCellProperties() { Connection connection = getConnection(); Query salesCube = connection.parseQuery( "select \n" + " {[Measures].[Store Sales], [Measures].[Unit Sales]} on columns, \n" + " {[Gender].members} on rows \n" + "from [Sales]"); Result result = connection.execute(salesCube); int x = 1; int y = 2; Cell cell = result.getCell(new int[] {x, y}); assertNull(cell.getPropertyValue("BACK_COLOR")); assertNull(cell.getPropertyValue("CELL_EVALUATION_LIST")); assertEquals(y * 2 + x, cell.getPropertyValue("CELL_ORDINAL")); assertNull(cell.getPropertyValue("FORE_COLOR")); assertNull(cell.getPropertyValue("FONT_NAME")); assertNull(cell.getPropertyValue("FONT_SIZE")); assertEquals(0, cell.getPropertyValue("FONT_FLAGS")); assertEquals("Standard", cell.getPropertyValue("FORMAT_STRING")); // FORMAT is a synonym for FORMAT_STRING assertEquals("Standard", cell.getPropertyValue("FORMAT")); assertEquals("135,215", cell.getPropertyValue("FORMATTED_VALUE")); assertNull(cell.getPropertyValue("NON_EMPTY_BEHAVIOR")); assertEquals(0, cell.getPropertyValue("SOLVE_ORDER")); assertEquals( 135215.0, ((Number) cell.getPropertyValue("VALUE")).doubleValue(), 0.1); // Case sensitivity. if (MondrianProperties.instance().CaseSensitive.get()) { assertNull(cell.getPropertyValue("cell_ordinal")); assertNull(cell.getPropertyValue("font_flags")); assertNull(cell.getPropertyValue("format_string")); assertNull(cell.getPropertyValue("format")); assertNull(cell.getPropertyValue("formatted_value")); assertNull(cell.getPropertyValue("solve_order")); assertNull(cell.getPropertyValue("value")); } else { assertEquals(y * 2 + x, cell.getPropertyValue("cell_ordinal")); assertEquals(0, cell.getPropertyValue("font_flags")); assertEquals("Standard", cell.getPropertyValue("format_string")); assertEquals("Standard", cell.getPropertyValue("format")); assertEquals("135,215", cell.getPropertyValue("formatted_value")); assertEquals(0, cell.getPropertyValue("solve_order")); assertEquals( 135215.0, ((Number) cell.getPropertyValue("value")).doubleValue(), 0.1); } } public void testPropertyDescription() throws Exception { TestContext context = getTestContext().create( null, "\n" + "

\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n", null, null, null, null); assertEquals( "BaconDesc", context.getOlap4jConnection().getOlapSchema() .getCubes().get("Foo") .getDimensions().get("Promotions") .getHierarchies().get(0) .getLevels().get(1) .getProperties().get("BarProp") .getDescription()); } } // End PropertiesTest.java mondrian-3.4.1/testsrc/main/mondrian/test/SteelWheelsSchemaTest.java0000644000175000017500000006732611735330606025515 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; import mondrian.rolap.RolapConnectionProperties; import mondrian.spi.impl.FilterDynamicSchemaProcessor; import java.io.InputStream; public class SteelWheelsSchemaTest extends SteelWheelsTestCase { /** * Sanity check, that enumerates the Measures dimension. */ public void testMeasures() { TestContext testContext = getTestContext(); if (!testContext.databaseIsValid()) { return; } testContext.assertAxisReturns( "Measures.Members", "[Measures].[Quantity]\n" + "[Measures].[Sales]\n" + "[Measures].[Fact Count]"); } /** * Test case for Infobright issue where [Markets].[All Markets].[Japan] * was not found but [Markets].[All Markets].[JAPAN] was OK. * (We've since dropped 'All Xxx' from member unique names.) */ public void testMarkets() { TestContext testContext = getTestContext(); if (!testContext.databaseIsValid()) { return; } testContext.assertQueryReturns( "select [Markets].[All Markets].[Japan] on 0 from [SteelWheelsSales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Markets].[Japan]}\n" + "Row #0: 4,923\n"); testContext.assertQueryReturns( "select [Markets].Children on 0 from [SteelWheelsSales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Markets].[#null]}\n" + "{[Markets].[APAC]}\n" + "{[Markets].[EMEA]}\n" + "{[Markets].[Japan]}\n" + "{[Markets].[NA]}\n" + "Row #0: \n" + "Row #0: 12,878\n" + "Row #0: 49,578\n" + "Row #0: 4,923\n" + "Row #0: 37,952\n"); testContext.assertQueryReturns( "select Subset([Markets].Members, 130, 8) on 0 from [SteelWheelsSales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Markets].[EMEA].[UK].[Isle of Wight].[Cowes]}\n" + "{[Markets].[Japan]}\n" + "{[Markets].[Japan].[Hong Kong]}\n" + "{[Markets].[Japan].[Hong Kong].[#null]}\n" + "{[Markets].[Japan].[Hong Kong].[#null].[Central Hong Kong]}\n" + "{[Markets].[Japan].[Japan]}\n" + "{[Markets].[Japan].[Japan].[Osaka]}\n" + "{[Markets].[Japan].[Japan].[Osaka].[Osaka]}\n" + "Row #0: 895\n" + "Row #0: 4,923\n" + "Row #0: 596\n" + "Row #0: 58,396\n" + "Row #0: 596\n" + "Row #0: 1,842\n" + "Row #0: 692\n" + "Row #0: 692\n"); testContext.assertQueryReturns( "select [Markets].[Territory].Members on 0 from " + "[SteelWheelsSales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Markets].[#null]}\n" + "{[Markets].[APAC]}\n" + "{[Markets].[EMEA]}\n" + "{[Markets].[Japan]}\n" + "{[Markets].[NA]}\n" + "Row #0: \n" + "Row #0: 12,878\n" + "Row #0: 49,578\n" + "Row #0: 4,923\n" + "Row #0: 37,952\n"); } /** * Test case for bug * MONDRIAN-755, "Getting drillthrough count results in exception". */ public void testBugMondrian755() { TestContext testContext = getTestContext(); if (!testContext.databaseIsValid()) { return; } // One-dimensional query, using set and trivial calc member. checkCellZero( testContext, "With \n" + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}'\n" + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Sales]', FORMAT_STRING = '#,###', SOLVE_ORDER=400\n" + "Select\n" + "[*BASE_MEMBERS_Measures] on columns\n" + "From [SteelWheelsSales]"); // One-dimensional query, using trivial calc member. checkCellZero( testContext, "With \n" + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Sales]', FORMAT_STRING = '#,###', SOLVE_ORDER=400\n" + "Select\n" + " {[Measures].[*FORMATTED_MEASURE_0]} on columns\n" + "From [SteelWheelsSales]"); // One-dimensional query, using simple calc member. checkCellZero( testContext, "With \n" + "Member [Measures].[Avg Price] as '[Measures].[Sales] / [Measures].[Quantity]', FORMAT_STRING = '#.##'\n" + "Select\n" + " {[Measures].[Avg Price]} on columns\n" + "From [SteelWheelsSales]"); // Zero dim query checkCellZero( testContext, "Select\n" + "From [SteelWheelsSales]"); // Zero dim query on calc member checkCellZero( testContext, "With \n" + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Sales]', FORMAT_STRING = '#,###', SOLVE_ORDER=400\n" + "Select\n" + "From [SteelWheelsSales]\n" + "Where [Measures].[*FORMATTED_MEASURE_0]"); // Two-dimensional query, using trivial calc member. checkCellZero( testContext, "With \n" + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures]" + ".[Sales]', FORMAT_STRING = '#,###', SOLVE_ORDER=400\n" + "Select\n" + " {[Measures].[*FORMATTED_MEASURE_0]} on columns," + " [Product].[All Products] * [Customers].[All Customers] on " + "rows\n" + "From [SteelWheelsSales]"); } private void checkCellZero(TestContext testContext, String mdx) { final Result result = testContext.executeQuery(mdx); final Cell cell = result.getCell(new int[result.getAxes().length]); assertTrue(cell.canDrillThrough()); assertEquals(2996, cell.getDrillThroughCount()); } /** * Test case for bug * MONDRIAN-756, "Error in RolapResult.replaceNonAllMembers leads to * NPE". * * @see #testBugMondrian805() duplicate bug MONDRIAN-805 */ public void testBugMondrian756() { TestContext testContext0 = getTestContext(); if (!testContext0.databaseIsValid()) { return; } final Util.PropertyList propertyList = testContext0.getConnectionProperties().clone(); propertyList.put( RolapConnectionProperties.DynamicSchemaProcessor.name(), Mondrian756SchemaProcessor.class.getName()); TestContext testContext = testContext0.withProperties(propertyList); testContext.assertQueryReturns( "select NON EMPTY {[Measures].[Quantity]} ON COLUMNS,\n" + "NON EMPTY {[Markets].[APAC]} ON ROWS\n" + "from [SteelWheelsSales]\n" + "where [Time].[2004]", "Axis #0:\n" + "{[Time].[2004]}\n" + "Axis #1:\n" + "Axis #2:\n"); } public static class Mondrian756SchemaProcessor extends FilterDynamicSchemaProcessor { @Override public String filter( String schemaUrl, Util.PropertyList connectInfo, InputStream stream) throws Exception { String schema = super.filter(schemaUrl, connectInfo, stream); return Util.replace( schema, " hasAll=\"true\"", " hasAll=\"false\""); } } /** * Test case for bug * MONDRIAN-756, "Error in RolapResult.replaceNonAllMembers leads to * NPE". * * @see #testBugMondrian805() duplicate bug MONDRIAN-805 */ public void testBugMondrian756b() { final TestContext testContext0 = getTestContext(); if (!testContext0.databaseIsValid()) { return; } String schema = testContext0.getRawSchema() .replaceAll( " hasAll=\"true\"", " hasAll=\"false\""); final TestContext testContext = testContext0.withSchema(schema); testContext.assertQueryReturns( "select NON EMPTY {[Measures].[Quantity]} ON COLUMNS,\n" + "NON EMPTY {[Markets].[APAC]} ON ROWS\n" + "from [SteelWheelsSales]\n" + "where [Time].[2004]", "Axis #0:\n" + "{[Time].[2004]}\n" + "Axis #1:\n" + "Axis #2:\n"); } /** * Test case for * bug MONDRIAN-805, * "Two dimensions with hasAll=false fail". */ public void testBugMondrian805() { final TestContext testContext0 = getTestContext(); if (!testContext0.databaseIsValid()) { return; } String schema = testContext0.getRawSchema() .replaceAll( "" + "" + "" + "
\n" + "
" + "\n" + "" + "\n" + "" + "\n" + "" + "\n" + "" + "
" + "" + "" + "" + "\n" + "
" + "" + "\n" + "" + "\n" + "" + "\n" + "" + "\n" + "" + "\n" + "" + "\n" + "" + "" + "
" + "
" + "" + "" + "\n" + "
" + "\n" + "" + "\n" + "" + "" + "\n" + "" + "\n" + "" + "\n" + "" + "" + "
" + "
" + "" + "" + "\n" + "
" + "\n" + "" + "\n" + "" + "\n" + "" + "
" + "
" + "" + "" + "\n" + "" + "" + "" + "" + "\n" + "
" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "
" + "" + "\n" + "
" + "\n" + "" + "\n" + "" + "\n" + "" + "\n" + "" + "\n" + "" + "\n" + "" + "
" + "" + "\n" + "
" + "\n" + "" + "\n" + "" + "\n" + "" + "\n" + "" + "\n" + "" + "\n" + "" + "
" + "\n") .withCube("SteelWheelsSales1"); final String mdxQuery = "with set [*NATIVE_CJ_SET] as 'Filter([*BASE_MEMBERS_Time], (NOT IsEmpty([Measures].[Price Each])))'\n" + " set [*SORTED_ROW_AXIS] as 'Order([*CJ_ROW_AXIS], Ancestor([Time].CurrentMember, [Time].[Years]).OrderKey, BASC, [Time].CurrentMember.OrderKey, BASC)'\n" + " set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}'\n" + " set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {[Time].CurrentMember})'\n" + " set [*BASE_MEMBERS_Time] as '[Time].[Quarters].Members'\n" + " set [*CJ_COL_AXIS] as '[*NATIVE_CJ_SET]'\n" + " member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Price Each]', FORMAT_STRING = \"#,###.0\", SOLVE_ORDER = 400.0\n" + "select [*BASE_MEMBERS_Measures] ON COLUMNS,\n" + " [*SORTED_ROW_AXIS] ON ROWS\n" + "from [SteelWheelsSales2]\n"; if (!context.databaseIsValid()) { return; } context.assertQueryReturns( mdxQuery, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*FORMATTED_MEASURE_0]}\n" + "Axis #2:\n" + "{[Time].[2003].[QTR1]}\n" + "{[Time].[2003].[QTR2]}\n" + "{[Time].[2003].[QTR3]}\n" + "{[Time].[2003].[QTR4]}\n" + "{[Time].[2004].[QTR1]}\n" + "{[Time].[2004].[QTR2]}\n" + "{[Time].[2004].[QTR3]}\n" + "{[Time].[2004].[QTR4]}\n" + "{[Time].[2005].[QTR1]}\n" + "{[Time].[2005].[QTR2]}\n" + "Row #0: 3,373.8\n" + "Row #1: 2,384.9\n" + "Row #2: 4,480.1\n" + "Row #3: 19,829.8\n" + "Row #4: 6,167.2\n" + "Row #5: 5,493.5\n" + "Row #6: 6,433.7\n" + "Row #7: 25,362.9\n" + "Row #8: 12,406.3\n" + "Row #9: 6,107.0\n"); } public void testBugMondrian935() { final TestContext testContext = getTestContext(); if (!testContext.databaseIsValid()) { return; } testContext.assertQueryReturns( "with set [*NATIVE_CJ_SET] as '[*BASE_MEMBERS_Product]' \n" + " set [*SORTED_ROW_AXIS] as 'Order([*CJ_ROW_AXIS], " + "[Product].CurrentMember.OrderKey, BASC)' \n" + " set [*BASE_MEMBERS_Product] as '[Product].[Line].Members' \n" + " set [*BASE_MEMBERS_Measures] as '{[Measures].[*ZERO]}' \n" + " set [*CJ_COL_AXIS] as '[*NATIVE_CJ_SET]' \n" + " set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], " + "{[Product].CurrentMember})' \n" + " member [Measures].[*ZERO] as '0.0', SOLVE_ORDER = 0.0 \n" + "select [*BASE_MEMBERS_Measures] ON COLUMNS, \n" + " [*SORTED_ROW_AXIS] ON ROWS \n" + "from [SteelWheelsSales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*ZERO]}\n" + "Axis #2:\n" + "{[Product].[Classic Cars]}\n" + "{[Product].[Motorcycles]}\n" + "{[Product].[Planes]}\n" + "{[Product].[Ships]}\n" + "{[Product].[Trains]}\n" + "{[Product].[Trucks and Buses]}\n" + "{[Product].[Vintage Cars]}\n" + "Row #0: 0\n" + "Row #1: 0\n" + "Row #2: 0\n" + "Row #3: 0\n" + "Row #4: 0\n" + "Row #5: 0\n" + "Row #6: 0\n"); } /** * Test case for bug * MONDRIAN-626, "DATE type Levels can cause errors with certain JDBC * drivers (e.g. Oracle 5/6)". * *

A Parameter type of date or timestamp * was causing an exception because those types were not implemented * correctly.

* * @throws Exception on error */ public void testPropertyWithParameterOfTimestampType() throws Exception { final TestContext testContext = getTestContext(); if (!testContext.databaseIsValid()) { return; } TestContext context = createContext( getTestContext(), "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n"); context.assertQueryReturns( "with member [Measures].[Date] as 'Format([Orders].CurrentMember.Properties(\"OrderDate\"), \"yyyy-mm-dd\")'\n" + "select {[Orders].[Order].[10421]} on rows,\n" + "{[Measures].[Date]} on columns from [Foo]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Date]}\n" + "Axis #2:\n" + "{[Orders].[10421]}\n" + "Row #0: 2005-05-29\n"); } /** * Tests a query that is generated by Analyzer to query members. It should * only execute one SQL query, basically, "select year_id from time group by * year_id order by year_id". It should definitely not join to fact table. */ public void testEsr1587() { final TestContext testContext = getTestContext(); if (!testContext.databaseIsValid()) { return; } testContext.assertQueryReturns( "with set [*NATIVE_CJ_SET] as '[*BASE_MEMBERS_Time]'\n" + " set [*SORTED_COL_AXIS] as 'Order([*CJ_COL_AXIS], [Time].CurrentMember.OrderKey, BASC)'\n" + " set [*BASE_MEMBERS_Measures] as '{[Measures].[*ZERO]}'\n" + " set [*BASE_MEMBERS_Time] as 'TopCount([Time].[Years].Members, 200)'\n" + " set [*CJ_COL_AXIS] as 'Generate([*NATIVE_CJ_SET], {[Time].CurrentMember})'\n" + " member [Measures].[*ZERO] as '0', SOLVE_ORDER = 0\n" + "select Crossjoin([*SORTED_COL_AXIS], [*BASE_MEMBERS_Measures]) ON COLUMNS\n" + "from [SteelWheelsSales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[2003], [Measures].[*ZERO]}\n" + "{[Time].[2004], [Measures].[*ZERO]}\n" + "{[Time].[2005], [Measures].[*ZERO]}\n" + "Row #0: 0\n" + "Row #0: 0\n" + "Row #0: 0\n"); } } // End SteelWheelsSchemaTest.java mondrian-3.4.1/testsrc/main/mondrian/test/DelegatingTestContext.java0000644000175000017500000000213311735330606025541 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.olap.Util; import java.io.PrintWriter; /** * Extension of {@link TestContext} which delegates all behavior to * a parent test context. * *

Derived classes can selectively override methods. * * @author jhyde * @since 7 September, 2005 */ public class DelegatingTestContext extends TestContext { protected final TestContext context; protected DelegatingTestContext(TestContext context) { this.context = context; } public Util.PropertyList getConnectionProperties() { return context.getConnectionProperties(); } public String getDefaultCubeName() { return context.getDefaultCubeName(); } public PrintWriter getWriter() { return context.getWriter(); } } // End DelegatingTestContext.java mondrian-3.4.1/testsrc/main/mondrian/test/CompoundSlicerTest.java0000644000175000017500000010410411735330606025060 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2011 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.util.Bug; /** * Tests the expressions used for calculated members. Please keep in sync * with the actual code used by the wizard. * * @author jhyde * @since 15 May, 2009 */ public class CompoundSlicerTest extends FoodMartTestCase { /** * Creates a CompoundSlicerTest. */ public CompoundSlicerTest() { super(); } /** * Creates a CompoundSlicerTest with a given name. * * @param name Test name */ public CompoundSlicerTest(String name) { super(name); } /** * Query that simulates a compound slicer by creating a calculated member * that aggregates over a set and places it in the WHERE clause. */ public void testSimulatedCompoundSlicer() { assertQueryReturns( "with\n" + " member [Measures].[Price per Unit] as\n" + " [Measures].[Store Sales] / [Measures].[Unit Sales]\n" + " set [Top Products] as\n" + " TopCount(\n" + " [Product].[Brand Name].Members,\n" + " 3,\n" + " ([Measures].[Unit Sales], [Time].[1997].[Q3]))\n" + " member [Product].[Top] as\n" + " Aggregate([Top Products])\n" + "select {\n" + " [Measures].[Unit Sales],\n" + " [Measures].[Price per Unit]} on 0,\n" + " [Gender].Children * [Marital Status].Children on 1\n" + "from [Sales]\n" + "where ([Product].[Top], [Time].[1997].[Q3])", "Axis #0:\n" + "{[Product].[Top], [Time].[1997].[Q3]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Price per Unit]}\n" + "Axis #2:\n" + "{[Gender].[F], [Marital Status].[M]}\n" + "{[Gender].[F], [Marital Status].[S]}\n" + "{[Gender].[M], [Marital Status].[M]}\n" + "{[Gender].[M], [Marital Status].[S]}\n" + "Row #0: 779\n" + "Row #0: 2.40\n" + "Row #1: 811\n" + "Row #1: 2.24\n" + "Row #2: 829\n" + "Row #2: 2.23\n" + "Row #3: 886\n" + "Row #3: 2.25\n"); // Now the equivalent query, using a set in the slicer. assertQueryReturns( "with\n" + " member [Measures].[Price per Unit] as\n" + " [Measures].[Store Sales] / [Measures].[Unit Sales]\n" + " set [Top Products] as\n" + " TopCount(\n" + " [Product].[Brand Name].Members,\n" + " 3,\n" + " ([Measures].[Unit Sales], [Time].[1997].[Q3]))\n" + "select {\n" + " [Measures].[Unit Sales],\n" + " [Measures].[Price per Unit]} on 0,\n" + " [Gender].Children * [Marital Status].Children on 1\n" + "from [Sales]\n" + "where [Top Products] * [Time].[1997].[Q3]", "Axis #0:\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos], [Time].[1997].[Q3]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale], [Time].[1997].[Q3]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony], [Time].[1997].[Q3]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Price per Unit]}\n" + "Axis #2:\n" + "{[Gender].[F], [Marital Status].[M]}\n" + "{[Gender].[F], [Marital Status].[S]}\n" + "{[Gender].[M], [Marital Status].[M]}\n" + "{[Gender].[M], [Marital Status].[S]}\n" + "Row #0: 779\n" + "Row #0: 2.40\n" + "Row #1: 811\n" + "Row #1: 2.24\n" + "Row #2: 829\n" + "Row #2: 2.23\n" + "Row #3: 886\n" + "Row #3: 2.25\n"); } /** * Tests compound slicer with EXCEPT. * *

Test case for * Bug MONDRIAN-637, "Using Except in the slicer makes no sense". */ public void testCompoundSlicerExcept() { final String expected = "Axis #0:\n" + "{[Promotion Media].[Bulk Mail]}\n" + "{[Promotion Media].[Cash Register Handout]}\n" + "{[Promotion Media].[Daily Paper, Radio]}\n" + "{[Promotion Media].[Daily Paper, Radio, TV]}\n" + "{[Promotion Media].[In-Store Coupon]}\n" + "{[Promotion Media].[No Media]}\n" + "{[Promotion Media].[Product Attachment]}\n" + "{[Promotion Media].[Radio]}\n" + "{[Promotion Media].[Street Handout]}\n" + "{[Promotion Media].[Sunday Paper]}\n" + "{[Promotion Media].[Sunday Paper, Radio]}\n" + "{[Promotion Media].[Sunday Paper, Radio, TV]}\n" + "{[Promotion Media].[TV]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 259,035\n" + "Row #1: 127,871\n" + "Row #2: 131,164\n"; // slicer expression that inherits [Promotion Media] member from context assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + " [Gender].Members on 1\n" + "from [Sales]\n" + "where Except(\n" + " [Promotion Media].Children,\n" + " {[Promotion Media].[Daily Paper]})", expected); // similar query, but don't assume that [Promotion Media].CurrentMember // = [Promotion Media].[All Media] assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + " [Gender].Members on 1\n" + "from [Sales]\n" + "where Except(\n" + " [Promotion Media].[All Media].Children,\n" + " {[Promotion Media].[Daily Paper]})", expected); // reference query, computing the same numbers a different way assertQueryReturns( "with member [Promotion Media].[Except Daily Paper] as\n" + " Aggregate(\n" + " Except(\n" + " [Promotion Media].Children,\n" + " {[Promotion Media].[Daily Paper]}))\n" + "select [Measures].[Unit Sales]\n" + " * {[Promotion Media],\n" + " [Promotion Media].[Daily Paper],\n" + " [Promotion Media].[Except Daily Paper]} on 0,\n" + " [Gender].Members on 1\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales], [Promotion Media].[All Media]}\n" + "{[Measures].[Unit Sales], [Promotion Media].[Daily Paper]}\n" + "{[Measures].[Unit Sales], [Promotion Media].[Except Daily Paper]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 266,773\n" + "Row #0: 7,738\n" + "Row #0: 259,035\n" + "Row #1: 131,558\n" + "Row #1: 3,687\n" + "Row #1: 127,871\n" + "Row #2: 135,215\n" + "Row #2: 4,051\n" + "Row #2: 131,164\n"); } /** * Tests a query with a compond slicer over tuples. (Multiple rows, each * of which has multiple members.) */ public void testCompoundSlicerOverTuples() { // reference query assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + " TopCount(\n" + " [Product].[Product Category].Members\n" + " * [Customers].[City].Members,\n" + " 10) on 1\n" + "from [Sales]\n" + "where [Time].[1997].[Q3]", "Axis #0:\n" + "{[Time].[1997].[Q3]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Burnaby]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Cliffside]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Haney]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Ladner]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Langford]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Langley]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Metchosin]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[N. Vancouver]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Newton]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Oak Bay]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: \n" + "Row #3: \n" + "Row #4: \n" + "Row #5: \n" + "Row #6: \n" + "Row #7: \n" + "Row #8: \n" + "Row #9: \n"); // The actual query. Note that the set in the slicer has two dimensions. // This could not be expressed using calculated members and the // Aggregate function. assertQueryReturns( "with\n" + " member [Measures].[Price per Unit] as\n" + " [Measures].[Store Sales] / [Measures].[Unit Sales]\n" + " set [Top Product Cities] as\n" + " TopCount(\n" + " [Product].[Product Category].Members\n" + " * [Customers].[City].Members,\n" + " 3,\n" + " ([Measures].[Unit Sales], [Time].[1997].[Q3]))\n" + "select {\n" + " [Measures].[Unit Sales],\n" + " [Measures].[Price per Unit]} on 0,\n" + " [Gender].Children * [Marital Status].Children on 1\n" + "from [Sales]\n" + "where [Top Product Cities] * [Time].[1997].[Q3]", "Axis #0:\n" + "{[Product].[Food].[Snack Foods].[Snack Foods], [Customers].[USA].[WA].[Spokane], [Time].[1997].[Q3]}\n" + "{[Product].[Food].[Produce].[Vegetables], [Customers].[USA].[WA].[Spokane], [Time].[1997].[Q3]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods], [Customers].[USA].[WA].[Puyallup], [Time].[1997].[Q3]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Price per Unit]}\n" + "Axis #2:\n" + "{[Gender].[F], [Marital Status].[M]}\n" + "{[Gender].[F], [Marital Status].[S]}\n" + "{[Gender].[M], [Marital Status].[M]}\n" + "{[Gender].[M], [Marital Status].[S]}\n" + "Row #0: 483\n" + "Row #0: 2.21\n" + "Row #1: 419\n" + "Row #1: 2.21\n" + "Row #2: 422\n" + "Row #2: 2.22\n" + "Row #3: 332\n" + "Row #3: 2.20\n"); } /** * Tests that if the slicer contains zero members, all cells are null. */ public void testEmptySetSlicerReturnsNull() { assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + "[Product].Children on 1\n" + "from [Sales]\n" + "where {}", "Axis #0:\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: \n"); } /** * Tests that if the slicer is calculated using an expression and contains * zero members, all cells are null. */ public void testEmptySetSlicerViaExpressionReturnsNull() { assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + "[Product].Children on 1\n" + "from [Sales]\n" + "where filter([Gender].members * [Marital Status].members, 1 = 0)", "Axis #0:\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: \n"); } /** * Test case for a basic query with more than one member of the same * hierarchy in the WHERE clause. */ public void testCompoundSlicer() { // Reference query. assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + "[Gender].Members on 1\n" + "from [Sales]\n" + "where {[Product].[Drink]}", "Axis #0:\n" + "{[Product].[Drink]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 24,597\n" + "Row #1: 12,202\n" + "Row #2: 12,395\n"); // Reference query. assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + "[Gender].Members on 1\n" + "from [Sales]\n" + "where {[Product].[Food]}", "Axis #0:\n" + "{[Product].[Food]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 191,940\n" + "Row #1: 94,814\n" + "Row #2: 97,126\n"); // Sum members at same level. // Note that 216,537 = 24,597 (drink) + 191,940 (food). assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + "[Gender].Members on 1\n" + "from [Sales]\n" + "where {[Product].[Drink], [Product].[Food]}", "Axis #0:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 216,537\n" + "Row #1: 107,016\n" + "Row #2: 109,521\n"); // sum list that contains duplicates // duplicates are ignored (checked SSAS 2005) assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + "[Gender].Members on 1\n" + "from [Sales]\n" + "where {[Product].[Drink], [Product].[Food], [Product].[Drink]}", Bug.BugMondrian555Fixed ? "Axis #0:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Drink]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 241,134, 241,134, 241,134\n" + "Row #1: 119,218, 119,218, 119,218\n" + "Row #2: 121,916, 121,916, 121,916\n" : "Axis #0:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Drink]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 241,134\n" + "Row #1: 119,218\n" + "Row #2: 121,916\n"); // sum list that contains a null member - // null member is ignored; // confirmed behavior with ssas 2005 assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + "[Gender].Members on 1\n" + "from [Sales]\n" + "where {[Product].[All Products].Parent, [Product].[Food], [Product].[Drink]}", "Axis #0:\n" + "{[Product].[Food]}\n" + "{[Product].[Drink]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 216,537\n" + "Row #1: 107,016\n" + "Row #2: 109,521\n"); // Reference query. assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + "[Gender].Members on 1\n" + "from [Sales]\n" + "where {\n" + " [Product].[Drink],\n" + " [Product].[Food].[Dairy]}", "Axis #0:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food].[Dairy]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 37,482\n" + "Row #1: 18,715\n" + "Row #2: 18,767\n"); // Sum list that contains a member and one of its children; // SSAS 2005 doesn't simply sum them: it behaves behavior as if // predicates are pushed down to the fact table. Mondrian double-counts, // and that is a bug. assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + "[Gender].Members on 1\n" + "from [Sales]\n" + "where {\n" + " [Product].[Drink],\n" + " [Product].[Food].[Dairy],\n" + " [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer]}", Bug.BugMondrian555Fixed ? "Axis #0:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food].[Dairy]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 37,482\n" + "Row #1: 18,715\n" + "Row #2: 18.767\n" : "Axis #0:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food].[Dairy]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 39,165\n" + "Row #1: 19,532\n" + "Row #2: 19,633\n"); // The correct behavior of the aggregate function is to double-count. // SSAS 2005 and Mondrian give the same behavior. assertQueryReturns( "with member [Product].[Foo] as\n" + " Aggregate({\n" + " [Product].[Drink],\n" + " [Product].[Food].[Dairy],\n" + " [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer]})\n" + "select [Measures].[Unit Sales] on 0,\n" + "[Gender].Members on 1\n" + "from [Sales]\n" + "where [Product].[Foo]\n", "Axis #0:\n" + "{[Product].[Foo]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 39,165\n" + "Row #1: 19,532\n" + "Row #2: 19,633\n"); } /** * Slicer that is a member expression that evaluates to null. * SSAS 2005 allows this, and returns null cells. */ public void testSlicerContainsNullMember() { assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + "[Gender].Members on 1\n" + "from [Sales]\n" + "where [Product].Parent", "Axis #0:\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: \n"); } /** * Slicer that is literal null. * SSAS 2005 allows this, and returns null cells; Mondrian currently gives * an error. */ public void testSlicerContainsLiteralNull() { final String mdx = "select [Measures].[Unit Sales] on 0,\n" + "[Gender].Members on 1\n" + "from [Sales]\n" + "where null"; if (Bug.Ssas2005Compatible) { // SSAS returns a cell set containing null cells. assertQueryReturns( mdx, "xxx"); } else { // Mondrian gives an error. This is not unreasonable. It is very // low priority to make Mondrian consistent with SSAS 2005 in this // behavior. assertQueryThrows( mdx, "Function does not support NULL member parameter"); } } /** * Slicer that is a tuple and one of the members evaluates to null; * that makes it a null tuple, and it is eliminated from the list. * SSAS 2005 allows this, and returns null cells. */ public void testSlicerContainsPartiallyNullMember() { assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + "[Gender].Members on 1\n" + "from [Sales]\n" + "where ([Product].Parent, [Store].[USA].[CA])", "Axis #0:\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: \n"); } /** * Compound slicer with distinct-count measure. */ public void testCompoundSlicerWithDistinctCount() { // Reference query. assertQueryReturns( "select [Measures].[Customer Count] on 0,\n" + " {[Store].[USA].[CA], [Store].[USA].[OR].[Portland]}\n" + " * {([Product].[Food], [Time].[1997].[Q1]),\n" + " ([Product].[Drink], [Time].[1997].[Q2].[4])} on 1\n" + "from [Sales]\n", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA], [Product].[Food], [Time].[1997].[Q1]}\n" + "{[Store].[USA].[CA], [Product].[Drink], [Time].[1997].[Q2].[4]}\n" + "{[Store].[USA].[OR].[Portland], [Product].[Food], [Time].[1997].[Q1]}\n" + "{[Store].[USA].[OR].[Portland], [Product].[Drink], [Time].[1997].[Q2].[4]}\n" + "Row #0: 1,069\n" + "Row #1: 155\n" + "Row #2: 332\n" + "Row #3: 48\n"); // The figures look reasonable, because: // 332 + 48 = 380 > 352 // 1069 + 155 = 1224 > 1175 assertQueryReturns( "select [Measures].[Customer Count] on 0,\n" + "{[Store].[USA].[CA], [Store].[USA].[OR].[Portland]} on 1\n" + "from [Sales]\n" + "where {\n" + " ([Product].[Food], [Time].[1997].[Q1]),\n" + " ([Product].[Drink], [Time].[1997].[Q2].[4])}", "Axis #0:\n" + "{[Product].[Food], [Time].[1997].[Q1]}\n" + "{[Product].[Drink], [Time].[1997].[Q2].[4]}\n" + "Axis #1:\n" + "{[Measures].[Customer Count]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR].[Portland]}\n" + "Row #0: 1,175\n" + "Row #1: 352\n"); } /** * Tests compound slicer, and other rollups, with AVG function. * *

Test case for * Bug MONDRIAN-675, * "Allow rollup of measures based on AVG aggregate function". */ public void testRollupAvg() { final TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", null, "\n" + "\n" + "\n", null, null); // basic query with avg testContext.assertQueryReturns( "select from [Sales]\n" + "where [Measures].[Avg Unit Sales]", "Axis #0:\n" + "{[Measures].[Avg Unit Sales]}\n" + "3.072"); // roll up using compound slicer // (should give a real value, not an error) testContext.assertQueryReturns( "select from [Sales]\n" + "where [Measures].[Avg Unit Sales]\n" + " * {[Customers].[USA].[OR], [Customers].[USA].[CA]}", "Axis #0:\n" + "{[Measures].[Avg Unit Sales], [Customers].[USA].[OR]}\n" + "{[Measures].[Avg Unit Sales], [Customers].[USA].[CA]}\n" + "6.189"); // roll up using a named set testContext.assertQueryReturns( "with member [Customers].[OR and CA] as Aggregate(\n" + " {[Customers].[USA].[OR], [Customers].[USA].[CA]})\n" + "select from [Sales]\n" + "where ([Measures].[Avg Unit Sales], [Customers].[OR and CA])", "Axis #0:\n" + "{[Measures].[Avg Unit Sales], [Customers].[OR and CA]}\n" + "3.094"); } /** * Test case for * Bug MONDRIAN-899, * "Order() function does not work properly together with WHERE clause". */ public void testBugMondrian899() { final String expected = "Axis #0:\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[5]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q3].[7]}\n" + "{[Time].[1997].[Q3].[8]}\n" + "{[Time].[1997].[Q3].[9]}\n" + "{[Time].[1997].[Q4].[10]}\n" + "{[Time].[1997].[Q4].[11]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Customers].[USA].[WA].[Spokane].[Wildon Cameron]}\n" + "{[Customers].[USA].[WA].[Spokane].[Emily Barela]}\n" + "{[Customers].[USA].[WA].[Spokane].[Dauna Barton]}\n" + "{[Customers].[USA].[WA].[Spokane].[Mona Vigil]}\n" + "{[Customers].[USA].[WA].[Spokane].[Linda Combs]}\n" + "{[Customers].[USA].[WA].[Spokane].[Eric Winters]}\n" + "{[Customers].[USA].[WA].[Spokane].[Jack Zucconi]}\n" + "{[Customers].[USA].[WA].[Spokane].[Luann Crawford]}\n" + "{[Customers].[USA].[WA].[Spokane].[Suzanne Davis]}\n" + "{[Customers].[USA].[WA].[Spokane].[Lucy Flowers]}\n" + "{[Customers].[USA].[WA].[Spokane].[Donna Weisinger]}\n" + "{[Customers].[USA].[WA].[Spokane].[Stanley Marks]}\n" + "{[Customers].[USA].[WA].[Spokane].[James Short]}\n" + "{[Customers].[USA].[WA].[Spokane].[Curtis Pollard]}\n" + "{[Customers].[USA].[WA].[Spokane].[Dawn Laner]}\n" + "{[Customers].[USA].[WA].[Spokane].[Patricia Towns]}\n" + "{[Customers].[USA].[WA].[Puyallup].[William Wade]}\n" + "{[Customers].[USA].[WA].[Spokane].[Lorriene Weathers]}\n" + "{[Customers].[USA].[WA].[Spokane].[Grace McLaughlin]}\n" + "{[Customers].[USA].[WA].[Spokane].[Edna Woodson]}\n" + "{[Customers].[USA].[WA].[Spokane].[Harry Torphy]}\n" + "{[Customers].[USA].[WA].[Spokane].[Anne Allard]}\n" + "{[Customers].[USA].[WA].[Spokane].[Bonnie Staley]}\n" + "{[Customers].[USA].[WA].[Olympia].[Patricia Gervasi]}\n" + "{[Customers].[USA].[WA].[Spokane].[Shirley Gottbehuet]}\n" + "{[Customers].[USA].[WA].[Puyallup].[Jeremy Styers]}\n" + "{[Customers].[USA].[WA].[Spokane].[Beth Ohnheiser]}\n" + "{[Customers].[USA].[WA].[Bremerton].[Harold Powers]}\n" + "{[Customers].[USA].[WA].[Spokane].[Daniel Thompson]}\n" + "{[Customers].[USA].[WA].[Spokane].[Fran McEvilly]}\n" + "Row #0: 327\n" + "Row #1: 323\n" + "Row #2: 319\n" + "Row #3: 308\n" + "Row #4: 305\n" + "Row #5: 296\n" + "Row #6: 296\n" + "Row #7: 295\n" + "Row #8: 291\n" + "Row #9: 289\n" + "Row #10: 285\n" + "Row #11: 284\n" + "Row #12: 281\n" + "Row #13: 279\n" + "Row #14: 279\n" + "Row #15: 278\n" + "Row #16: 277\n" + "Row #17: 271\n" + "Row #18: 268\n" + "Row #19: 266\n" + "Row #20: 265\n" + "Row #21: 264\n" + "Row #22: 260\n" + "Row #23: 251\n" + "Row #24: 250\n" + "Row #25: 249\n" + "Row #26: 249\n" + "Row #27: 248\n" + "Row #28: 247\n" + "Row #29: 247\n"; assertQueryReturns( "select NON EMPTY {[Measures].[Unit Sales]} ON COLUMNS, \n" + " Subset(Order([Customers].[Name].Members, [Measures].[Unit Sales], BDESC), 10.0, 30.0) ON ROWS \n" + "from [Sales] \n" + "where ([Time].[1997].[Q1].[2] : [Time].[1997].[Q4].[11])", expected); // Equivalent query. assertQueryReturns( "select NON EMPTY {[Measures].[Unit Sales]} ON COLUMNS, \n" + " Tail(\n" + " TopCount([Customers].[Name].Members, 40, [Measures].[Unit Sales]),\n" + " 30) ON ROWS \n" + "from [Sales] \n" + "where ([Time].[1997].[Q1].[2] : [Time].[1997].[Q4].[11])", expected); } // similar to MONDRIAN-899 testcase public void testTopCount() { assertQueryReturns( "select NON EMPTY {[Measures].[Unit Sales]} ON COLUMNS, \n" + " TopCount([Customers].[USA].[WA].[Spokane].Children, 10, [Measures].[Unit Sales]) ON ROWS \n" + "from [Sales] \n" + "where ([Time].[1997].[Q1].[2] : [Time].[1997].[Q1].[3])", "Axis #0:\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Customers].[USA].[WA].[Spokane].[Grace McLaughlin]}\n" + "{[Customers].[USA].[WA].[Spokane].[George Todero]}\n" + "{[Customers].[USA].[WA].[Spokane].[Matt Bellah]}\n" + "{[Customers].[USA].[WA].[Spokane].[Mary Francis Benigar]}\n" + "{[Customers].[USA].[WA].[Spokane].[Lucy Flowers]}\n" + "{[Customers].[USA].[WA].[Spokane].[David Hassard]}\n" + "{[Customers].[USA].[WA].[Spokane].[Dauna Barton]}\n" + "{[Customers].[USA].[WA].[Spokane].[Dora Sims]}\n" + "{[Customers].[USA].[WA].[Spokane].[Joann Mramor]}\n" + "{[Customers].[USA].[WA].[Spokane].[Mike Madrid]}\n" + "Row #0: 131\n" + "Row #1: 129\n" + "Row #2: 113\n" + "Row #3: 103\n" + "Row #4: 95\n" + "Row #5: 94\n" + "Row #6: 92\n" + "Row #7: 85\n" + "Row #8: 79\n" + "Row #9: 79\n"); } /** * Test case for * Bug MONDRIAN-900, * "Filter() function works incorrectly together with WHERE clause". */ public void testBugMondrian900() { assertQueryReturns( "select NON EMPTY {[Measures].[Unit Sales]} ON COLUMNS,\n" + " Tail(Filter([Customers].[Name].Members, ([Measures].[Unit Sales] IS EMPTY)), 3) ON ROWS \n" + "from [Sales]\n" + "where ([Time].[1997].[Q1].[2] : [Time].[1997].[Q4].[10])", "Axis #0:\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[5]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q3].[7]}\n" + "{[Time].[1997].[Q3].[8]}\n" + "{[Time].[1997].[Q3].[9]}\n" + "{[Time].[1997].[Q4].[10]}\n" + "Axis #1:\n" + "Axis #2:\n" + "{[Customers].[USA].[WA].[Walla Walla].[Melanie Snow]}\n" + "{[Customers].[USA].[WA].[Walla Walla].[Ramon Williams]}\n" + "{[Customers].[USA].[WA].[Yakima].[Louis Gomez]}\n"); } } // End CompoundSlicerTest.java mondrian-3.4.1/testsrc/main/mondrian/test/I18nTest.java0000644000175000017500000000677111735330606022664 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; import mondrian.rolap.RolapConnectionProperties; import mondrian.util.Format; import java.util.Calendar; import java.util.Locale; /** * Test suite for internalization and localization. * * @see mondrian.util.FormatTest * * @author jhyde * @since September 22, 2005 */ public class I18nTest extends FoodMartTestCase { public static final char Euro = '\u20AC'; public static final char Nbsp = '\u00A0'; public static final char EA = '\u00e9'; // e acute public static final char UC = '\u00FB'; // u circumflex public void testFormat() { // Make sure Util is loaded, so that the LocaleFormatFactory gets // registered. Util.discard(Util.nl); Locale spanish = new Locale("es", "ES"); Locale german = new Locale("de", "DE"); // Thousands and decimal separators are different in Spain Format numFormat = new Format("#,000.00", spanish); assertEquals("123.456,79", numFormat.format(new Double(123456.789))); // Currency too Format currencyFormat = new Format("Currency", spanish); assertEquals( "1.234.567,79 " + Euro, currencyFormat.format(new Double(1234567.789))); // Dates Format dateFormat = new Format("Medium Date", spanish); Calendar calendar = Calendar.getInstance(); calendar.set(Calendar.YEAR, 2005); calendar.set(Calendar.MONTH, 0); // January, 0-based calendar.set(Calendar.DATE, 22); java.util.Date date = calendar.getTime(); assertEquals("22-ene-05", dateFormat.format(date)); // Dates in German dateFormat = new Format("Long Date", german); assertEquals("Samstag, Januar 22, 2005", dateFormat.format(date)); } public void testAutoFrench() { // Create a connection in French. String localeName = "fr_FR"; String resultString = "12" + Nbsp + "345,67"; assertFormatNumber(localeName, resultString); } public void testAutoSpanish() { // Format a number in (Peninsular) spanish. assertFormatNumber("es", "12.345,67"); } public void testAutoMexican() { // Format a number in Mexican spanish. assertFormatNumber("es_MX", "12,345.67"); } private void assertFormatNumber(String localeName, String resultString) { final Util.PropertyList properties = TestContext.instance().getConnectionProperties().clone(); properties.put(RolapConnectionProperties.Locale.name(), localeName); Connection connection = DriverManager.getConnection(properties, null); Query query = connection.parseQuery( "WITH MEMBER [Measures].[Foo] AS ' 12345.67 ',\n" + " FORMAT_STRING='#,###.00'\n" + "SELECT {[Measures].[Foo]} ON COLUMNS\n" + "FROM [Sales]"); Result result = connection.execute(query); String actual = TestContext.toString(result); TestContext.assertEqualsVerbose( "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: " + resultString + "\n", actual); } } // End I18nTest.java mondrian-3.4.1/testsrc/main/mondrian/test/DialectTest.java0000644000175000017500000011666511735330606023516 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2011 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.olap.Util; import mondrian.spi.Dialect; import mondrian.spi.DialectManager; import mondrian.spi.impl.*; import junit.framework.AssertionFailedError; import junit.framework.TestCase; import java.sql.*; import java.util.*; import javax.sql.DataSource; /** * Unit test which checks that {@link mondrian.spi.Dialect} * accurately represents the capabilities of the underlying database. * *

The existing mondrian tests, when run on various databases and drivers, * make sure that Dialect never over-states the capabilities of a particular * database. But sometimes they under-state a database's capabilities: for * example, MySQL version 3 did not allow subqueries in the FROM clause, but * version 4 does. This test helps ensure that mondrian is using the full * capabilities of each database. * *

NOTE: If you see failures in this test, let the mondrian * developers know! * You may be running a version of a database which no one has * tried before, and which has more capabilities than we expect. If you tell us * about them, we can change mondrian to use those features.

* * @author jhyde * @since May 18, 2007 */ public class DialectTest extends TestCase { private Connection connection; private Dialect dialect; private static final String INFOBRIGHT_UNSUPPORTED = "The query includes syntax that is not supported by the Infobright" + " Optimizer. Either restructure the query with supported syntax, or" + " enable the MySQL Query Path in the brighthouse.ini file to execute" + " the query with reduced performance."; private static final String NEOVIEW_SYNTAX_ERROR = "(?s).* ERROR\\[15001\\] A syntax error occurred at or before: .*"; /** * Creates a DialectTest. * * @param name Test case name */ public DialectTest(String name) { super(name); } protected DataSource getDataSource() { return TestContext.instance().getConnection().getDataSource(); } protected void tearDown() throws Exception { if (connection != null) { try { connection.close(); } catch (SQLException e) { // ignore } finally { connection = null; } } super.tearDown(); } protected Dialect getDialect() { if (dialect == null) { dialect = DialectManager.createDialect(getDataSource(), null); } return dialect; } protected Connection getConnection() { if (connection == null) { try { connection = getDataSource().getConnection(); } catch (SQLException e) { throw Util.newInternal(e, "while creating connection"); } } return connection; } public void testDialectVsDatabaseProduct() throws SQLException { final Dialect dialect = getDialect(); final Dialect.DatabaseProduct databaseProduct = dialect.getDatabaseProduct(); final DatabaseMetaData databaseMetaData = getConnection().getMetaData(); switch (databaseProduct) { case MYSQL: // Dialect has identified that it is MySQL. assertTrue(dialect instanceof MySqlDialect); assertFalse(dialect instanceof InfobrightDialect); assertFalse(MySqlDialect.isInfobright(databaseMetaData)); assertEquals("MySQL", databaseMetaData.getDatabaseProductName()); break; case HIVE: // Dialect has identified that it is Hive. assertTrue(dialect instanceof HiveDialect); break; case INFOBRIGHT: // Dialect has identified that it is MySQL. assertTrue(dialect instanceof MySqlDialect); assertTrue(dialect instanceof InfobrightDialect); assertTrue(MySqlDialect.isInfobright(databaseMetaData)); assertEquals("MySQL", databaseMetaData.getDatabaseProductName()); break; case POSTGRESQL: // Dialect has identified that it is PostgreSQL. assertTrue(dialect instanceof PostgreSqlDialect); assertFalse(dialect instanceof NetezzaDialect); assertTrue( databaseMetaData.getDatabaseProductName() .indexOf("PostgreSQL") >= 0); break; case NETEZZA: // Dialect has identified that it is Netezza and a sub class of // PostgreSql. assertTrue(dialect instanceof PostgreSqlDialect); assertTrue(dialect instanceof NetezzaDialect); assertTrue( databaseMetaData.getDatabaseProductName() .indexOf("Netezza") >= 0); break; default: // Neither MySQL nor Infobright. assertFalse(dialect instanceof MySqlDialect); assertFalse(dialect instanceof InfobrightDialect); assertNotSame("MySQL", databaseMetaData.getDatabaseProductName()); break; } } public void testAllowsCompoundCountDistinct() { String sql = dialectize( "select count(distinct [customer_id], [product_id])\n" + "from [sales_fact_1997]"); if (getDialect().allowsCompoundCountDistinct()) { assertQuerySucceeds(sql); } else { String[] errs = { // oracle "(?s)ORA-00909: invalid number of arguments.*", // derby "Syntax error: Encountered \",\" at line 1, column 36.", // access "\\[Microsoft\\]\\[ODBC Microsoft Access Driver\\] Syntax error \\(missing operator\\) in query expression '.*'.", // hsqldb "Unexpected token in statement \\[select count\\(distinct \"customer_id\",\\]", // infobright INFOBRIGHT_UNSUPPORTED, // neoview ".* ERROR\\[3129\\] Function COUNT DISTINCT accepts exactly one operand\\. .*", // postgres "ERROR: function count\\(integer, integer\\) does not exist.*", // LucidDb ".*Invalid number of arguments to function 'COUNT'. Was expecting 1 arguments", // teradata ".*Syntax error: expected something between the word 'customer_id' and ','\\..*", // netezza "(?s).*ERROR: Function 'COUNT', number of parameters greater than the maximum \\(1\\).*", // Vertica "ERROR: function count\\(int, int\\) does not exist, or permission is denied for count\\(int, int\\)", }; assertQueryFails(sql, errs); } } public void testAllowsCountDistinct() { String sql1 = dialectize( "select count(distinct [customer_id]) from [sales_fact_1997]"); // one distinct-count and one nondistinct-agg String sql2 = dialectize( "select count(distinct [customer_id]),\n" + " sum([time_id])\n" + "from [sales_fact_1997]"); if (getDialect().allowsCountDistinct()) { assertQuerySucceeds(sql1); assertQuerySucceeds(sql2); } else { String[] errs = { // access "\\[Microsoft\\]\\[ODBC Microsoft Access Driver\\] Syntax error \\(missing operator\\) in query expression '.*'." }; assertQueryFails(sql1, errs); assertQueryFails(sql2, errs); } } public void testAllowsMultipleCountDistinct() { // multiple distinct-counts String sql1 = dialectize( "select count(distinct [customer_id]),\n" + " count(distinct [time_id])\n" + "from [sales_fact_1997]"); // multiple distinct-counts with group by and other aggs String sql3 = dialectize( "select [unit_sales],\n" + " count(distinct [customer_id]),\n" + " count(distinct [product_id])\n" + "from [sales_fact_1997]\n" + "where [time_id] in (371, 372)\n" + "group by [unit_sales]"); if (getDialect().allowsMultipleCountDistinct()) { assertQuerySucceeds(sql1); assertQuerySucceeds(sql3); assertTrue(getDialect().allowsCountDistinct()); } else { String[] errs = { // derby "Multiple DISTINCT aggregates are not supported at this time.", // access "\\[Microsoft\\]\\[ODBC Microsoft Access Driver\\] Syntax error \\(missing operator\\) in query expression '.*'." }; assertQueryFails(sql1, errs); assertQueryFails(sql3, errs); } } public void testAllowsDdl() { int phase = 0; SQLException e = null; Statement stmt = null; try { String dropSql = dialectize("drop table [foo]"); String createSql = dialectize("create table [foo] ([i] integer)"); stmt = getConnection().createStatement(); // drop previously existing table, and ignore any errors try { stmt.execute(dropSql); } catch (SQLException e3) { // ignore } // now create and drop a dummy table phase = 1; assertFalse(stmt.execute(createSql)); phase = 2; assertFalse(stmt.execute(dropSql)); phase = 3; } catch (SQLException e2) { e = e2; } finally { if (stmt != null) { try { stmt.close(); } catch (SQLException e1) { // ignore } } } if (getDialect().allowsDdl()) { assertNull(e == null ? null : e.getMessage(), e); assertEquals(3, phase); } else { assertEquals(1, phase); assertNotNull(e); } } public void testAllowsFromQuery() { String sql = dialectize( "select * from (select * from [sales_fact_1997]) as [x]"); if (getDialect().allowsFromQuery()) { assertQuerySucceeds(sql); } else { assertQueryFails(sql, new String[] {}); } } public void testRequiresFromQueryAlias() { if (getDialect().requiresAliasForFromQuery()) { assertTrue(getDialect().allowsFromQuery()); } if (!getDialect().allowsFromQuery()) { return; } String sql = dialectize("select * from (select * from [sales_fact_1997])"); if (getDialect().requiresAliasForFromQuery()) { String[] errs = { // mysql "Every derived table must have its own alias", // derby "Syntax error: Encountered \"\" at line 1, column 47.", // hive "(?s).*mismatched input \'\' expecting Identifier in subquery source.*", // postgres "ERROR\\: subquery in FROM must have an alias.*", // teradata ".*Syntax error, expected something like a name or a Unicode " + "delimited identifier or an 'UDFCALLNAME' keyword between " + "'\\)' and ';'\\.", // neoview NEOVIEW_SYNTAX_ERROR, // netezza "(?s).*ERROR: sub-SELECT in FROM must have an alias.*", }; assertQueryFails(sql, errs); } else { assertQuerySucceeds(sql); } } public void testRequiresOrderByAlias() { String sql = dialectize( "SELECT [unit_sales]\n" + "FROM [sales_fact_1997]\n" + "ORDER BY [unit_sales] + [store_id]"); if (getDialect().requiresOrderByAlias()) { final String[] errs = { // infobright INFOBRIGHT_UNSUPPORTED, // hive "(?s).*Invalid Table Alias or Column Reference.*", // neoview NEOVIEW_SYNTAX_ERROR, }; assertQueryFails(sql, errs); } else { assertQuerySucceeds(sql); } } public void testAllowsOrderByAlias() { String sql = dialectize( "SELECT [unit_sales] as [x],\n" + " [unit_sales] + [store_id] as [y]\n" + "FROM [sales_fact_1997]\n" + "ORDER BY [y]"); if (getDialect().allowsOrderByAlias()) { assertQuerySucceeds(sql); } else { String[] errs = { // oracle "(?s)ORA-03001: unimplemented feature.*", // access "\\[Microsoft\\]\\[ODBC Microsoft Access Driver\\] Too few parameters. Expected 1.", // infobright INFOBRIGHT_UNSUPPORTED, }; assertQueryFails(sql, errs); } } public void testRequiresUnionOrderByExprToBeInSelectClause() { String sql = dialectize( "SELECT [unit_sales], [store_sales]\n" + "FROM [sales_fact_1997]\n" + "UNION ALL\n" + "SELECT [unit_sales], [store_sales]\n" + "FROM [sales_fact_1997]\n" + "ORDER BY [unit_sales] + [store_sales]"); if (!getDialect().requiresUnionOrderByExprToBeInSelectClause()) { assertQuerySucceeds(sql); } else { String[] errs = { // access "\\[Microsoft\\]\\[ODBC Microsoft Access Driver\\] The ORDER " + "BY expression \\(\\[unit_sales\\]\\+\\[store_sales\\]\\) " + "includes fields that are not selected by the query\\. " + "Only those fields requested in the first query can be " + "included in an ORDER BY expression\\.", // derby (yes, lame message) "Java exception: ': java.lang.NullPointerException'.", // hsqldb "(?s)Cannot be in ORDER BY clause in statement .*", // neoview NEOVIEW_SYNTAX_ERROR, // oracle "ORA-01785: ORDER BY item must be the number of a SELECT-list " + "expression\n", // teradata ".*The ORDER BY clause must contain only integer constants.", // Greenplum / Postgres "ERROR: ORDER BY on a UNION/INTERSECT/EXCEPT result must be on " + "one of the result columns.*", // Vectorwise "Parse error in StringBuffer at line 0, column 525\\: \\\\.", }; assertQueryFails(sql, errs); } } public void testSupportsGroupByExpressions() { String sql = dialectize( "SELECT sum([unit_sales] + 3) + 8\n" + "FROM [sales_fact_1997]\n" + "GROUP BY [unit_sales] + [store_id]"); if (getDialect().supportsGroupByExpressions()) { assertQuerySucceeds(sql); } else { final String[] errs = { // mysql "'sum\\(`unit_sales` \\+ 3\\) \\+ 8' isn't in GROUP BY", // neoview ".* ERROR\\[4197\\] This expression cannot be used in the GROUP BY clause\\. .*", }; assertQueryFails(sql, errs); } } /** * Tests that the * {@link mondrian.spi.Dialect#supportsGroupingSets()} * dialect property is accurate. */ public void testAllowsGroupingSets() { String sql = dialectize( "SELECT [customer_id],\n" + " SUM([store_sales]),\n" + " GROUPING([unit_sales]),\n" + " GROUPING([customer_id])\n" + "FROM [sales_fact_1997]\n" + "GROUP BY GROUPING SETS(\n" + " ([customer_id], [unit_sales]),\n" + " [customer_id],\n" + " ())"); if (getDialect().supportsGroupingSets()) { assertQuerySucceeds(sql); } else { String[] errs = { // derby "Syntax error: Encountered \"SETS\" at line 6, column 19.", // hive "(?s).*line 6:18 mismatched input 'SETS' expecting EOF.*", // hsqldb "(?s)Unexpected token: GROUPING in statement .*", // mysql "(?s)You have an error in your SQL syntax; check .*", // access "(?s)\\[Microsoft\\]\\[ODBC Microsoft Access Driver\\] Syntax error \\(missing operator\\) in query expression 'GROUPING SETS.*", // luciddb "(?s).*Encountered \"GROUPING\" at line 3, column 2\\..*", // postgres "ERROR: syntax error at or near \"SETS\".*", // neoview NEOVIEW_SYNTAX_ERROR, // netezza "(?s).*found \"SETS\" \\(at char 135\\) expecting `EXCEPT' or `FOR' or `INTERSECT' or `ORDER' or `UNION'.*", // Vertica "line 3, There is no such function as \\'grouping\\'\\.", }; assertQueryFails(sql, errs); } } public void testSupportsMultiValueInExpr() { String sql = dialectize( "SELECT [unit_sales]\n" + "FROM [sales_fact_1997]\n" + "WHERE ([unit_sales], [time_id]) IN ((1, 371), (2, 394))"); if (getDialect().supportsMultiValueInExpr()) { assertQuerySucceeds(sql); } else { String[] errs = { // derby "Syntax error: Encountered \",\" at line 3, column 20.", // access "\\[Microsoft\\]\\[ODBC Microsoft Access Driver\\] Syntax error \\(comma\\) in query expression '.*'.", // hive "(?s).*line 3:19 mismatched input ','.*", // hsqldb "(?s)Unexpected token: , in statement .*", // infobright INFOBRIGHT_UNSUPPORTED, // neoview NEOVIEW_SYNTAX_ERROR, // teradata ".*Syntax error, expected something like a 'SELECT' keyword or '\\(' between '\\(' and the integer '1'\\.", // netezza "(?s).*found \"1\" \\(at char 81\\) expecting `SELECT' or `'\\(''.*", }; assertQueryFails(sql, errs); } } public void testResultSetConcurrency() { int[] Types = { ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.TYPE_SCROLL_SENSITIVE }; int[] Concurs = { ResultSet.CONCUR_READ_ONLY, ResultSet.CONCUR_UPDATABLE }; String sql = dialectize("SELECT [unit_sales] FROM [sales_fact_1997]"); for (int type : Types) { for (int concur : Concurs) { boolean b = getDialect().supportsResultSetConcurrency(type, concur); Statement stmt = null; try { stmt = getConnection().createStatement(type, concur); ResultSet resultSet = stmt.executeQuery(sql); assertTrue(resultSet.next()); Object col1 = resultSet.getObject(1); Util.discard(col1); if (!b) { // It's a little surprising that the driver said it // didn't support this type/concurrency combination, // but allowed the statement to be executed anyway. // But don't fail. Util.discard( "expected to fail for type=" + type + ", concur=" + concur); } } catch (SQLException e) { if (b) { fail( "expected to succeed for type=" + type + ", concur=" + concur); throw Util.newInternal(e, "query [" + sql + "] failed"); } } finally { if (stmt != null) { try { stmt.close(); } catch (SQLException e) { // ignore } } } } } } public void testGenerateInline() throws SQLException { final List typeList = Arrays.asList("String", "Numeric"); final List nameList = Arrays.asList("x", "y"); assertInline( nameList, typeList, new String[]{"a", "1"}); assertInline( nameList, typeList, new String[]{"a", "1"}, new String[]{"bb", "2"}); // string containing single quote (problem for all database) and a // backslash (problem for mysql; appears as a double backslash for // java's benefit, but is a single backslash by the time it gets to SQL) assertInline( nameList, typeList, new String[]{"can't stop", "1"}, new String[]{"back\\slash", "2"}); // date value final List typeList2 = Arrays.asList("String", "Date"); assertInline( nameList, typeList2, new String[]{"a", "2008-04-29"}, new String[]{"b", "2007-01-02"}); } /** * Tests that the dialect can generate a valid query to sort ascending and * descending, with NULL values appearing last in both cases. */ public void testForceNullCollation() throws SQLException { checkForceNullCollation(true, true); checkForceNullCollation(false, true); checkForceNullCollation(true, false); checkForceNullCollation(false, false); } /** * Checks that the dialect can generate a valid query to sort in a given * direction, with NULL values appearing last. * * @param ascending Whether ascending * @param nullsLast Force nulls last or not. */ private void checkForceNullCollation( boolean ascending, boolean nullsLast) throws SQLException { Dialect dialect = getDialect(); String query = "select " + dialect.quoteIdentifier("store_manager") + " from " + dialect.quoteIdentifier("store") + " order by " + dialect.generateOrderItem( dialect.quoteIdentifier("store_manager"), true, ascending, nullsLast); if (ascending) { if (nullsLast) { assertFirstLast(query, "Brown", null); } else { assertFirstLast(query, null, "Williams"); } } else { // Largest value comes first, null comes last. switch (dialect.getDatabaseProduct()) { case GREENPLUM: // Current version cannot force null order, introduced in // Postgres 8.3 return; case HIVE: // Hive cannot force nulls to appear last return; case NEOVIEW: // Neoview cannot force nulls to appear last return; } if (nullsLast) { assertFirstLast(query, "Williams", null); } else { assertFirstLast(query, null, "Brown"); } } } private void assertFirstLast( String query, String expectedFirst, String expectedLast) throws SQLException { ResultSet resultSet = getConnection().createStatement().executeQuery(query); List values = new ArrayList(); while (resultSet.next()) { values.add(resultSet.getString(1)); if (resultSet.wasNull()) { values.set(values.size() - 1, null); } } resultSet.close(); String actualFirst = values.get(0); String actualLast = values.get(values.size() - 1); assertEquals(query, expectedFirst, actualFirst); assertEquals(query, expectedLast, actualLast); } private void assertInline( List nameList, List typeList, String[]... valueList) throws SQLException { String sql = getDialect().generateInline( nameList, typeList, Arrays.asList(valueList)); Statement stmt = null; try { stmt = getConnection().createStatement(); ResultSet resultSet = stmt.executeQuery(sql); Set> actualValues = new HashSet>(); while (resultSet.next()) { final List row = new ArrayList(); for (int i = 0; i < typeList.size(); i++) { final String s; final String type = typeList.get(i); if (type.equals("String")) { s = resultSet.getString(i + 1); } else if (type.equals("Date")) { s = String.valueOf(resultSet.getDate(i + 1)); } else if (type.equals("Numeric")) { s = String.valueOf(resultSet.getInt(i + 1)); } else { throw new RuntimeException("unknown type " + type); } row.add(s); } actualValues.add(row); } Set> expectedRows = new HashSet>(); for (String[] strings : valueList) { expectedRows.add(Arrays.asList(strings)); } assertEquals(expectedRows, actualValues); stmt.close(); stmt = null; } finally { if (stmt != null) { try { stmt.close(); } catch (SQLException e) { // ignore } } } } /** * Converts query or DDL statement into this dialect. * * @param s SQL query or DDL statement * @return Query or DDL statement translated into this dialect */ private String dialectize(String s) { if (dialect == null) { dialect = getDialect(); } final Dialect.DatabaseProduct databaseProduct = dialect.getDatabaseProduct(); switch (databaseProduct) { case ACCESS: break; case HIVE: if (s.contains("UNION ALL")) { s = "SELECT * FROM (" + s + ") x"; } s = s.replace('[', '`'); s = s.replace(']', '`'); s = s.replaceAll(" as ", ""); break; case MYSQL: case INFOBRIGHT: s = s.replace('[', '`'); s = s.replace(']', '`'); break; case ORACLE: s = s.replace('[', '"'); s = s.replace(']', '"'); s = s.replaceAll(" as ", ""); break; default: s = s.replace('[', '"'); s = s.replace(']', '"'); break; } return s; } /** * Asserts that a query succeeds and produces at least one row. * * @param sql SQL query in current dialect */ protected void assertQuerySucceeds(String sql) { Statement stmt = null; try { stmt = getConnection().createStatement(); ResultSet resultSet = stmt.executeQuery(sql); assertTrue(resultSet.next()); Object col1 = resultSet.getObject(1); Util.discard(col1); } catch (SQLException e) { throw Util.newInternal(e, "query [" + sql + "] failed"); } finally { if (stmt != null) { try { stmt.close(); } catch (SQLException e) { // ignore } } } } /** * Asserts that a query fails. * * @param sql SQL query * @param patterns Array of expected patterns, generally one for each * SQL dialect for which the test is expected to fail */ protected void assertQueryFails(String sql, String[] patterns) { Statement stmt = null; try { stmt = getConnection().createStatement(); ResultSet resultSet; try { resultSet = stmt.executeQuery(sql); } catch (SQLException e2) { // execution failed - good String message = e2.getMessage(); for (String pattern : patterns) { if (message.matches(pattern)) { return; } } throw new AssertionFailedError( "error [" + message + "] did not match any of the supplied patterns"); } assertTrue(resultSet.next()); Object col1 = resultSet.getObject(1); Util.discard(col1); } catch (SQLException e) { throw Util.newInternal(e, "failed in wrong place"); } finally { if (stmt != null) { try { stmt.close(); } catch (SQLException e) { // ignore } } } } /** * Unit test for {@link Dialect#allowsSelectNotInGroupBy}. */ public void testAllowsSelectNotInGroupBy() throws SQLException { Dialect dialect = getDialect(); String sql = "select " + dialect.quoteIdentifier("time_id") + ", " + dialect.quoteIdentifier("the_month") + " from " + dialect.quoteIdentifier("time_by_day") + " group by " + dialect.quoteIdentifier("time_id"); if (dialect.allowsSelectNotInGroupBy()) { final ResultSet resultSet = getConnection().createStatement().executeQuery(sql); assertTrue(resultSet.next()); resultSet.close(); } else { String[] errs = { // oracle "ORA-00979: not a GROUP BY expression\n", // derby "The SELECT list of a grouped query contains at least one " + "invalid expression. If a SELECT list has a GROUP BY, the " + "list may only contain valid grouping expressions and valid " + "aggregate expressions. ", // hive "(?s).*line 1:18 Expression Not In Group By Key `the_month`.*", // hsqldb "(?s)Not in aggregate function or group by clause: .*", // mysql (if sql_mode contains ONLY_FULL_GROUP_BY) "ERROR 1055 (42000): 'foodmart.time_by_day.the_month' isn't in " + "GROUP BY", // access "\\[Microsoft\\]\\[ODBC Microsoft Access Driver\\] You tried " + "to execute a query that does not include the specified " + "expression 'the_month' as part of an aggregate function.", // luciddb "From line 1, column 19 to line 1, column 29: Expression " + "'the_month' is not being grouped", // neoview ".* ERROR\\[4005\\] Column reference \"the_month\" must be a " + "grouping column or be specified within an aggregate. .*", // teradata ".*Selected non-aggregate values must be part of the " + "associated group.", // Greenplum "ERROR: column \"time_by_day.the_month\" must appear in the " + "GROUP BY clause or be used in an aggregate function", // Vectorwise "line 1, The columns in the SELECT clause must be contained in the GROUP BY clause\\." }; assertQueryFails(sql, errs); } } public void testHavingRequiresAlias() throws Exception { Dialect dialect = getDialect(); StringBuilder sb = new StringBuilder( "select upper(" + dialect.quoteIdentifier("customer", "fname") + ") as c from " + dialect.quoteIdentifier("customer") + " group by " + dialect.quoteIdentifier("customer", "fname") + " having " + dialect.quoteIdentifier("customer", "fname") + " LIKE "); dialect.quoteStringLiteral(sb, "%"); if (!dialect.requiresHavingAlias()) { final ResultSet resultSet = getConnection().createStatement().executeQuery(sb.toString()); assertTrue(resultSet.next()); resultSet.close(); } else { String[] errs = { // mysql "Unknown column 'customer\\.fname' in 'having clause'", // vectorwise "No conversion defined for result data type\\.", }; assertQueryFails(sb.toString(), errs); } } public void testAllowsRegularExpressionInWhereClause() throws Exception { Dialect dialect = getDialect(); if (dialect.allowsRegularExpressionInWhereClause()) { assertNotNull( dialect.generateRegularExpression( dialect.quoteIdentifier("customer", "fname"), "(?i).*\\QJeanne\\E.*")); StringBuilder sb = new StringBuilder( "select " + dialect.quoteIdentifier("customer", "fname") + " from " + dialect.quoteIdentifier("customer") + " group by " + dialect.quoteIdentifier("customer", "fname") + " having " + dialect.generateRegularExpression( dialect.quoteIdentifier("customer", "fname"), "(?i).*\\QJeanne\\E.*")); final ResultSet resultSet = getConnection().createStatement().executeQuery(sb.toString()); assertTrue(resultSet.next()); resultSet.close(); } else { assertNull( dialect.generateRegularExpression( "Foo", "(?i).*\\QBar\\E.*")); } } /** * This is a test for * * http://jira.pentaho.com/browse/MONDRIAN-1057 * Some dialects are not removing the \Q and \E markers if they * are in the middle of the regexp. */ public void testComplexRegularExpression() throws Exception { final String regexp = "(?i).*\\QJeanne\\E.*|.*\\QSheri\\E.*|.*\\QJonathan\\E.*|.*\\QJewel\\E.*"; Dialect dialect = getDialect(); if (dialect.allowsRegularExpressionInWhereClause()) { assertNotNull( dialect.generateRegularExpression( dialect.quoteIdentifier("customer", "fname"), regexp)); StringBuilder sb = new StringBuilder( "select " + dialect.quoteIdentifier("customer", "fname") + " from " + dialect.quoteIdentifier("customer") + " group by " + dialect.quoteIdentifier("customer", "fname") + " having " + dialect.generateRegularExpression( dialect.quoteIdentifier("customer", "fname"), regexp)); final ResultSet resultSet = getConnection().createStatement().executeQuery(sb.toString()); int i = 0; while (resultSet.next()) { i++; } assertEquals(7, i); resultSet.close(); } else { assertNull( dialect.generateRegularExpression( "Foo", "(?i).*\\QBar\\E.*")); } } public void testRegularExpressionSqlInjection() throws SQLException { // bug mondrian-983 // We know that mysql's dialect can handle this regex boolean couldTranslate = checkRegex("(?i).*\\Qa\"\"\\); window.alert(\"\"woot');\\E.*"); switch (getDialect().getDatabaseProduct()) { case MYSQL: assertTrue(couldTranslate); break; } // On mysql, this gives error: // Got error 'repetition-operator operand invalid' from regexp // // Ideally, we would detect that the regex cannot be translated to // SQL (perhaps because it's not a valid java regex). Currently the // database gives an error, and that's better than nothing. Throwable throwable = null; try { couldTranslate = checkRegex( "\"(?i).*\\Qa\"\"\\); window.alert(\"\"woot');\\E.*\""); } catch (SQLException e) { throwable = e; } switch (getDialect().getDatabaseProduct()) { case MYSQL: assertNotNull(throwable); assertTrue(couldTranslate); assertTrue( throwable.getMessage().contains( "Got error 'repetition-operator operand invalid' from " + "regexp")); break; default: // As far as we know, all other databases either handle this regex // just fine or our dialect for that database refuses to translate // the regex to SQL. assertNull(throwable); } // Every dialect should refuse to translate an invalid regex. couldTranslate = checkRegex("ab[cd"); assertFalse(couldTranslate); } /** * Translates a regular expression into SQL and executes the query. * Returns whether the dialect was able to translate the regex. * * @param regex Java regular expression string * @return Whether dialect could translate regex to SQL. * @throws SQLException on error */ private boolean checkRegex(String regex) throws SQLException { Dialect dialect = getDialect(); final String sqlRegex = dialect.generateRegularExpression( dialect.quoteIdentifier("customer", "fname"), regex); if (sqlRegex != null) { String sql = "select * from " + dialect.quoteIdentifier("customer") + " where " + sqlRegex; final ResultSet resultSet = getConnection().createStatement().executeQuery(sql); assertFalse(resultSet.next()); resultSet.close(); return true; } else { return false; } } } // End DialectTest.java mondrian-3.4.1/testsrc/main/mondrian/test/loader/0000755000175000017500000000000011735330606021675 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/test/loader/CsvLoader.java0000644000175000017500000003222711735330606024430 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.test.loader; import org.apache.log4j.Logger; import java.io.*; import java.util.ArrayList; import java.util.List; /** * This is a basic Comma-separated-value (CSV, Csv) reader. As input it * ultimately takes a java.io.Reader but has helper support for * java.io.InputStream, file names and * java.io.File. * One can also specify a separator character other than the default * comma, ',', character and, also, that the input's first line contains the * names of the columns (by default this is not assumed). Lastly, this supports * only the comment character '#' and only at the start of a line. This comment * support could be generalized but that task is left to others. *

* To use this class one gives it a java.io.Reader and then calls * the hasNextLine and nextLine methods much like a * java.io.Iterator but in this case the nextLine * method returns a String[] holding the, possibly null, values of * the parsed next line. The size of the String[] is the size of * the first line parsed that contains the separator character (comment lines * are not used). If the number of separator characters in subsequent lines is * less than the initial numbers, the trailing entries in the * String[] returned by the nextLine method are null. * On the other hand, if there are more separator characters in a subsequent * line, the world ends with an IndexOutOfBoundsException (sorry, * making this more graceful is also a task for others). When one is through * using a CsvLoader instance one should call the close method * (which closes the Reader). *

* All well and good, but there are two additional methods that can be used to * extend the capabilities of this CSV parser, the nextSet and * putBack methods. With these methods one can, basically, reset * the CsvLoader to a state where it does not yet know how many * separator characters to expect per line (while stay at the current line in * the Reader). The nextSet (next set of CSV lines) * resets the loader while the putBack method can be used to place * the last line returned back into loader. These methods are used in * CsvDBLoader allowing one to have multiple sets of CSV rows with * differing number of values per sets. *

* There are six special start/end characters when seen prevent the * recognition of both the separator character and new lines: * *

 *    double quotes: "" ""
 *    single quotes: '  '
 *    bracket: i     [ ]
 *    parenthesis:   ()
 *    braces:        { }
 *    chevrons:      < >
 * 
* *

* Its certainly not the penultimate such parser but its hoped that its * adequate. * * @author Richard M. Emberson */ public class CsvLoader { protected static final Logger LOGGER = Logger.getLogger(CsvLoader.class); public static final char DEFAULT_SEPARATOR = ','; public static final char DOUBLE_QUOTE = '"'; public static final char SINGLE_QUOTE = '\''; // bracket [] public static final char BRACKET_START = '['; public static final char BRACKET_END = '['; // parenthesis () public static final char PAREN_START = '('; public static final char PAREN_END = ')'; // braces {} public static final char BRACES_START = '{'; public static final char BRACES_END = '}'; // chevrons <> public static final char CHEVRON_START = '<'; public static final char CHEVRON_END = '>'; private BufferedReader bufReader; private final char separator; private final boolean includesHeader; private int nextSet; private boolean inComment; private String[] columnNames; private String[] columns; public CsvLoader(InputStream in, String charset) throws UnsupportedEncodingException { this(new InputStreamReader(in, charset)); } public CsvLoader( InputStream in, char separator, boolean includesHeader, String charset) throws UnsupportedEncodingException { this(new InputStreamReader(in, charset), separator, includesHeader); } public CsvLoader(InputStream in) { this(new InputStreamReader(in)); } public CsvLoader(InputStream in, char separator, boolean includesHeader) { this(new InputStreamReader(in), separator, includesHeader); } public CsvLoader(String filename) throws FileNotFoundException { this(new FileReader(filename)); } public CsvLoader(String filename, char separator, boolean includesHeader) throws FileNotFoundException { this(new FileReader(filename), separator, includesHeader); } public CsvLoader(File file) throws FileNotFoundException { this(new FileReader(file)); } public CsvLoader(File file, char separator, boolean includesHeader) throws FileNotFoundException { this(new FileReader(file), separator, includesHeader); } public CsvLoader(Reader reader) { this(reader, DEFAULT_SEPARATOR, false); } public CsvLoader(Reader reader, char separator, boolean includesHeader) { this.bufReader = (reader instanceof BufferedReader) ? (BufferedReader) reader : new BufferedReader(reader); this.separator = separator; this.includesHeader = includesHeader; } protected void initialize() throws IOException { if (this.columnNames == null) { if (this.columns == null) { this.columns = nextColumns(); } if (this.columns == null) { if (this.nextSet > 0) { return; } throw new IOException("No columns can be read"); } if (! this.inComment) { if (this.includesHeader) { this.columnNames = this.columns; this.columns = null; } else { this.columnNames = new String[this.columns.length]; for (int i = 0; i < this.columns.length; i++) { this.columnNames[i] = "Column" + i; } } } } } public String[] getColumnNames() throws IOException { initialize(); return this.columnNames; } public boolean inComment() { return this.inComment; } public boolean hasNextLine() throws IOException { initialize(); if (this.bufReader == null) { return false; } else if (this.columns != null) { return true; } else { this.columns = nextColumns(); return (this.columns != null); } } public String[] nextLine() { if (this.bufReader == null) { return null; } else { try { return this.columns; } finally { this.columns = null; } } } public int getNextSetCount() { return this.nextSet; } public void nextSet() { this.nextSet++; this.columns = null; this.columnNames = null; } public void putBack(String[] columns) { this.columns = columns; } public void close() throws IOException { if (this.bufReader != null) { this.bufReader.close(); this.bufReader = null; } } protected String[] nextColumns() throws IOException { StringBuilder buf = new StringBuilder(); // the separator char seen in single or double quotes is not treated // as a separator boolean inDoubleQuote = false; boolean inSingleQuote = false; // bracket [] boolean inBracket = false; // parenthesis () boolean inParen = false; // braces {} boolean inBraces = false; // chevrons <> boolean inChevrons = false; String[] columns = null; // if we do not know how many columns there are List list = null; if (this.columnNames == null) { list = new ArrayList(); } else { columns = new String[columnNames.length]; } int columnCount = 0; char c; do { String line = this.bufReader.readLine(); if (line == null) { return null; } recordInCommentLine(line); int pos = 0; while (pos < line.length()) { c = line.charAt(pos++); if (inDoubleQuote) { if (c == DOUBLE_QUOTE) { inDoubleQuote = false; } } else if (inSingleQuote) { if (c == SINGLE_QUOTE) { inSingleQuote = false; } } else if (inParen) { if (c == PAREN_END) { inParen = false; } } else if (inBracket) { if (c == BRACKET_END) { inBracket = false; } } else if (inBraces) { if (c == BRACES_END) { inBraces = false; } } else if (inChevrons) { if (c == CHEVRON_END) { inChevrons = false; } } else if (c == DOUBLE_QUOTE) { inDoubleQuote = true; } else if (c == SINGLE_QUOTE) { inSingleQuote = true; } else if (c == BRACKET_START) { inBracket = true; } else if (c == PAREN_START) { inParen = true; } else if (c == BRACES_START) { inBraces = true; } else if (c == CHEVRON_START) { inChevrons = true; } if (inDoubleQuote || inSingleQuote || inParen || inBracket || inBraces || inChevrons) { buf.append(c); } else if (c == this.separator) { String data = buf.toString(); if (list != null) { list.add(data); } else { columns[columnCount++] = data; } buf.setLength(0); } else { buf.append(c); } } } while (inDoubleQuote || inSingleQuote); String data = buf.toString(); if (list != null) { list.add(data); } else { columns[columnCount++] = data; } if (list != null) { int size = list.size(); columns = (String[]) list.toArray(new String[size]); } if (LOGGER.isDebugEnabled()) { buf.setLength(0); buf.append("Columns: "); if (this.inComment) { buf.append("comment=true: "); } for (int i = 0; i < columns.length; i++) { String column = columns[i]; if (i > 0) { buf.append(", "); } buf.append(column); } LOGGER.debug(buf.toString()); } return columns; } protected void recordInCommentLine(String line) { this.inComment = (line.charAt(0) == '#'); } public static void main(String[] args) throws Exception { char separator = DEFAULT_SEPARATOR; for (int cnt = 0; cnt < args.length; cnt++) { String filename = args[cnt]; System.out.println("FileName:" + filename); CsvLoader csvLoader = new CsvLoader(filename); String[] columnNames = csvLoader.getColumnNames(); System.out.println("Column Names:"); System.out.print(" "); for (int i = 0; i < columnNames.length; i++) { System.out.print(columnNames[i]); if (i + 1 < columnNames.length) { System.out.print(separator); } } System.out.println(); System.out.println("Data:"); while (csvLoader.hasNextLine()) { System.out.print(" "); String[] columns = csvLoader.nextLine(); for (int i = 0; i < columns.length; i++) { System.out.print(columns[i]); if (i + 1 < columns.length) { System.out.print(separator); } } System.out.println(); } } } } // End CsvLoader.java mondrian-3.4.1/testsrc/main/mondrian/test/loader/package.html0000644000175000017500000000007411735330606024157 0ustar drazzibdrazzib Utilities to load test data. mondrian-3.4.1/testsrc/main/mondrian/test/loader/MondrianFoodMartLoader.java0000644000175000017500000035543011735330606027104 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.test.loader; import mondrian.olap.Util; import mondrian.resource.MondrianResource; import mondrian.rolap.RolapUtil; import mondrian.spi.Dialect; import mondrian.spi.DialectManager; import org.apache.log4j.*; import java.io.*; import java.math.BigDecimal; import java.sql.*; import java.text.*; import java.util.*; import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; /** * Utility to load the FoodMart dataset into an arbitrary JDBC database. * *

This is known to create test data for the following databases:

*
    * *
  • LucidDB-0.9.2
  • * *
  • MySQL 3.23 using MySQL-connector/J 3.0.16
  • * *
  • MySQL 4.15 using MySQL-connector/J 3.0.16
  • * *
  • Oracle 10g using ojdbc14.jar
  • * *
  • Postgres 8.0 beta using postgresql-driver-jdbc3-74-214.jar
  • * *
  • FirebirdSQL 1.0.2 and JayBird 1.5 (JDBC)
  • * *
* *

Output can be to a set of files with create table, insert and create * index statements, or directly to a JDBC connection with JDBC batches * (lots faster!)

* *

Command line examples for specific databases

* *

MySQL

* *
* $ mysqladmin create foodmart
* $ java -cp 'classes;testclasses' mondrian.test.loader.MondrianFoodMartLoader * --aggregates -tables -data -indexes -jdbcDrivers=com.mysql.jdbc.Driver * -inputJdbcURL=jdbc:odbc:MondrianFoodMart * -outputJdbcURL=jdbc:mysql://localhost/foodmart *
* *

FirebirdSQL

* *
* $ /firebird/bin/isql -u SYSDBA -p masterkey
* Use CONNECT or CREATE DATABASE to specify a database
* SQL> CREATE DATABASE '/mondrian/foodmart.gdb';
* SQL> QUIT;
* $ java -cp "/mondrian/lib/mondrian.jar:/mondrian/lib/log4j.jar: * /mondrian/lib/eigenbase-xom.jar:/mondrian/lib/eigenbase-resgen.jar: * /mondrian/lib/eigenbase-properties.jar:/jdbc/fb/firebirdsql-full.jar" * mondrian.test.loader.MondrianFoodMartLoader * -tables -data -indexes * -jdbcDrivers="org.firebirdsql.jdbc.FBDriver" * -inputFile="/mondrian/demo/FoodMartCreateData.sql" * -outputJdbcURL="jdbc:firebirdsql:localhost/3050:/mondrian/foodmart.gdb" * -inputJdbcUser=SYSDBA * -inputJdbcPassword=masterkey *
* *

See {@code bin/loadFoodMart.sh} for examples of command lines for other * databases. * * @author jhyde * @since 23 December, 2004 */ public class MondrianFoodMartLoader { // Constants private static final Logger LOGGER = Logger.getLogger(MondrianFoodMartLoader.class); private static final String nl = Util.nl; // Fields private static final Pattern decimalDataTypeRegex = Pattern.compile("DECIMAL\\((.*),(.*)\\)"); private static final DecimalFormat integerFormatter = new DecimalFormat(decimalFormat(15, 0)); private static final String dateFormatString = "yyyy-MM-dd"; private static final DateFormat dateFormatter = new SimpleDateFormat(dateFormatString); private String jdbcDrivers; private String jdbcURL; private String userName; private String password; private String schema = null; private String inputJdbcURL; private String inputUserName; private String inputPassword; private String inputSchema = null; private String inputFile; private String outputDirectory; private boolean aggregates = false; private boolean tables = false; private boolean indexes = false; private boolean data = false; private long pauseMillis = 0; private boolean jdbcInput = false; private boolean jdbcOutput = false; private boolean populationQueries = false; private boolean analyze = false; private Pattern include = null; private Pattern exclude = null; private boolean generateUniqueConstraints = false; private int outputBatchSize = -1; private Connection connection; private Connection inputConnection; private FileWriter fileOutput = null; private File file; private final Map tableMetadataToLoad = new HashMap(); private final Map aggregateTableMetadataToLoad = new HashMap(); private final Map> tableConstraints = new HashMap>(); private Dialect dialect; private boolean infobrightLoad; private long lastUpdate = 0; /** * Creates an instance of the loader and parses the command-line options. * * @param args Command-line options */ public MondrianFoodMartLoader(String[] args) { if (args.length == 0) { usage(); return; } StringBuilder errorMessage = new StringBuilder(); StringBuilder parametersMessage = new StringBuilder(); // Add a console appender for error messages. final ConsoleAppender consoleAppender = new ConsoleAppender( // Formats the message on its own line, // omits timestamp, priority etc. new PatternLayout("%m%n"), "System.out"); consoleAppender.setThreshold(Level.ERROR); LOGGER.addAppender(consoleAppender); for (String arg : args) { if (arg.equals("-verbose")) { // Make sure the logger is passing at least debug events. consoleAppender.setThreshold(Level.DEBUG); if (!LOGGER.isDebugEnabled()) { LOGGER.setLevel(Level.DEBUG); } } else if (arg.equals("-aggregates")) { aggregates = true; } else if (arg.equals("-tables")) { tables = true; } else if (arg.equals("-data")) { data = true; } else if (arg.startsWith("-pauseMillis=")) { pauseMillis = Long.parseLong( arg.substring("-pauseMillis=".length())); } else if (arg.equals("-indexes")) { indexes = true; } else if (arg.equals("-populationQueries")) { populationQueries = true; } else if (arg.equals("-analyze")) { analyze = true; } else if (arg.startsWith("-include=")) { include = Pattern.compile(arg.substring("-include=".length())); } else if (arg.startsWith("-exclude=")) { exclude = Pattern.compile(arg.substring("-exclude=".length())); } else if (arg.startsWith("-jdbcDrivers=")) { jdbcDrivers = arg.substring("-jdbcDrivers=".length()); } else if (arg.startsWith("-outputJdbcURL=")) { jdbcURL = arg.substring("-outputJdbcURL=".length()); } else if (arg.startsWith("-outputJdbcUser=")) { userName = arg.substring("-outputJdbcUser=".length()); } else if (arg.startsWith("-outputJdbcPassword=")) { password = arg.substring("-outputJdbcPassword=".length()); } else if (arg.startsWith("-outputJdbcSchema=")) { schema = arg.substring("-outputJdbcSchema=".length()); if (schema.trim().length() == 0) { schema = null; } } else if (arg.startsWith("-inputJdbcURL=")) { inputJdbcURL = arg.substring("-inputJdbcURL=".length()); } else if (arg.startsWith("-inputJdbcUser=")) { inputUserName = arg.substring("-inputJdbcUser=".length()); } else if (arg.startsWith("-inputJdbcPassword=")) { inputPassword = arg.substring("-inputJdbcPassword=".length()); } else if (arg.startsWith("-inputJdbcSchema=")) { inputSchema = arg.substring("-inputJdbcSchema=".length()); if (inputSchema.trim().length() == 0) { inputSchema = null; } } else if (arg.startsWith("-inputFile=")) { inputFile = arg.substring("-inputFile=".length()); } else if (arg.startsWith("-outputDirectory=")) { outputDirectory = arg.substring("-outputDirectory=".length()); } else if (arg.startsWith("-outputJdbcBatchSize=")) { outputBatchSize = Integer.parseInt( arg.substring("-outputJdbcBatchSize=".length())); } else { errorMessage.append("unknown arg: ").append(arg).append(nl); } if (LOGGER.isInfoEnabled()) { parametersMessage.append("\t").append(arg).append(nl); } } if (inputJdbcURL != null) { jdbcInput = true; if (inputFile != null) { errorMessage.append( "Specified both an input JDBC connection and an input file"); } } if (jdbcURL != null && outputDirectory == null) { jdbcOutput = true; } if (errorMessage.length() > 0) { usage(); throw MondrianResource.instance().MissingArg.ex( errorMessage.toString()); } if (LOGGER.isInfoEnabled()) { LOGGER.info("Parameters: " + nl + parametersMessage.toString()); } } /** * Prints help. */ public void usage() { String[] lines = { "Usage: MondrianFoodMartLoader " + "[-verbose] [-tables] [-data] [-indexes] [-populationQueries] " + "[-pauseMillis=] " + "[-include=] " + "[-exclude=] " + "[-pauseMillis=] " + "-jdbcDrivers= " + "-outputJdbcURL= " + "[-outputJdbcUser=user] " + "[-outputJdbcPassword=password] " + "[-outputJdbcSchema=schema] " + "[-outputJdbcBatchSize=] " + "| " + "[-outputDirectory=] " + "[" + " [-inputJdbcURL= [-inputJdbcUser=user] " + " [-inputJdbcPassword=password] [-inputJdbcSchema=schema]]" + " | " + " [-inputfile=]" + "]" + "\n" + " JDBC connect string for DB.\n" + " [user] JDBC user name for DB.\n" + " [password] JDBC password for user for DB.\n" + " If no source DB parameters are given, assumes data\n" + " comes from file.\n" + " [schema] schema overriding connection defaults\n" + " [file name] File containing test data - INSERT statements in MySQL\n" + " format. If no input file name or input JDBC parameters\n" + " are given, assume insert statements come from\n" + " demo/FoodMartCreateData.zip file\n" + " [outputDirectory] Where FoodMartCreateTables.sql, FoodMartCreateData.sql\n" + " and FoodMartCreateIndexes.sql will be created.\n" + " -outputJdbcBatchSize=\n" + " Size of JDBC batch updates (default 50 records).\n" + " -jdbcDrivers=\n" + " Comma-separated list of JDBC drivers;\n" + " they must be on the classpath.\n" + " -verbose Verbose mode.\n" + " -aggregates If specified, create aggregate tables and indexes for them.\n" + " -tables If specified, drop and create the tables.\n" + " -data If specified, load the data.\n" + " -indexes If specified, drop and create the indexes.\n" + " -populationQueries If specified, run the data loading queries. Runs by\n" + " default if -data is specified.\n" + " -analyze If specified, analyze tables after populating and indexing\n" + " them.\n" + " -pauseMillis= Pause n milliseconds between batches;\n" + " if not specified, or 0, do not pause.\n" + " -include= Create, load, and index only tables whose name\n" + " matches regular expression\n" + " -exclude= Create, load, and index only tables whose name\n" + " does not match regular expression\n" + " if not specified, or 0, do not pause.\n" + "\n" + "To load data in trickle mode, first run with '-exclude=sales_fact_1997'\n" + "then run with '-include=sales_fact_1997 -pauseMillis=1 -outputJdbcBatchSize=1\n" }; for (String s : lines) { System.out.println(s); } } /** * Command-line entry point. * * @param args Command-line arguments */ public static void main(String[] args) { // Set locale to English, so that '.' and ',' in numbers are parsed // correctly. Locale.setDefault(Locale.ENGLISH); LOGGER.warn("Starting load at: " + (new Date())); try { new MondrianFoodMartLoader(args).load(); } catch (Throwable e) { LOGGER.error("Main error", e); } LOGGER.warn("Finished load at: " + (new Date())); } /** * Load output from the input, optionally creating tables, * populating tables and creating indexes */ private void load() throws Exception { RolapUtil.loadDrivers(jdbcDrivers); if (userName == null) { connection = DriverManager.getConnection(jdbcURL); } else { connection = DriverManager.getConnection(jdbcURL, userName, password); } if (jdbcInput) { if (inputUserName == null) { inputConnection = DriverManager.getConnection(inputJdbcURL); } else { inputConnection = DriverManager.getConnection( inputJdbcURL, inputUserName, inputPassword); } } final DatabaseMetaData metaData = connection.getMetaData(); String productName = metaData.getDatabaseProductName(); String version = metaData.getDatabaseProductVersion(); LOGGER.info( "Output connection is " + productName + ", version: " + version); dialect = DialectManager.createDialect(null, connection); LOGGER.info( "Mondrian Dialect is " + dialect + ", detected database product: " + dialect.getDatabaseProduct()); if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.INFOBRIGHT && indexes) { System.out.println("Infobright engine detected: ignoring indexes"); indexes = false; } if (outputBatchSize == -1) { // No explicit batch size was set by user, so assign a good // default now if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.LUCIDDB) { // LucidDB column-store writes perform better with large batches outputBatchSize = 1000; } else { outputBatchSize = 50; } } if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.LUCIDDB) { // LucidDB doesn't support CREATE UNIQUE INDEX, but it // does support standard UNIQUE constraints generateUniqueConstraints = true; } try { final Condition tableFilter; if (include != null || exclude != null) { tableFilter = new Condition() { public boolean test(String tableName) { if (include != null) { if (!include.matcher(tableName).matches()) { return false; } } if (exclude != null) { if (!exclude.matcher(tableName).matches()) { return true; } } // Table name matched the inclusion criterion // (or everything was included) // and did not match the exclusion criterion // (or nothing was excluded), // therefore is included. return true; } }; } else { tableFilter = new Condition() { public boolean test(String s) { return true; } }; } if (generateUniqueConstraints) { // Initialize tableConstraints createIndexes(false, false, tableFilter); } // This also initializes tableMetadataToLoad createTables(tableFilter); if (data) { if (!populationQueries) { if (jdbcInput) { loadDataFromJdbcInput( tableFilter, pauseMillis, outputBatchSize); } else { loadDataFromFile( tableFilter, pauseMillis, outputBatchSize); } } // Index the base tables before running queries to populate // the summary tables. if (indexes) { createIndexes(true, false, tableFilter); } loadFromSqlInserts(); } else { // Create indexes without loading data. if (indexes) { createIndexes(true, false, tableFilter); } } if (indexes && aggregates) { createIndexes(false, true, tableFilter); } if (analyze) { analyzeTables(); } } finally { if (connection != null) { connection.close(); connection = null; } if (inputConnection != null) { inputConnection.close(); inputConnection = null; } if (fileOutput != null) { fileOutput.close(); fileOutput = null; } } } /** * Parses a file of INSERT statements and output to the configured JDBC * connection or another file in the dialect of the target data source. * *

Assumes that the input INSERT statements are generated * by this loader by something like:

* *
     * MondrianFoodLoader
     * -verbose -tables -data -indexes -analyze
     * -jdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver,com.mysql.jdbc.Driver
     * -inputJdbcURL=jdbc:odbc:MondrianFoodMart
     * -outputJdbcURL=jdbc:mysql://localhost/textload?user=root&password=myAdmin
     * -outputDirectory=C:\Temp\wip\Loader-Output
* * @param tableFilter Condition whether to load rows from a given table * @param pauseMillis How many milliseconds to pause between rows * @param batchSize How often to write/commit to the database */ private void loadDataFromFile( Condition tableFilter, long pauseMillis, int batchSize) throws Exception { InputStream is = openInputStream(); if (is == null) { throw new Exception("No data file to process"); } if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.INFOBRIGHT) { infobrightLoad = true; file = File.createTempFile("tmpfile", ".csv"); fileOutput = new FileWriter(file); } else { infobrightLoad = false; if (outputDirectory != null) { file = new File(outputDirectory, "createData.sql"); fileOutput = new FileWriter(file); } } try { final InputStreamReader reader = new InputStreamReader(is); final BufferedReader bufferedReader = new BufferedReader(reader); final Pattern mySQLRegex = Pattern.compile( "INSERT INTO `([^ ]+)` \\((.*)\\) VALUES\\((.*)\\);"); final Pattern doubleQuoteRegex = Pattern.compile( "INSERT INTO \"([^ ]+)\" \\((.*)\\) VALUES\\((.*)\\);"); String line; int lineNumber = 0; int tableRowCount = 0; String prevTable = null; String quotedTableName = null; String quotedColumnNames = null; Column[] orderedColumns = null; StringBuilder massagedLine = new StringBuilder(); final List batch = new ArrayList(batchSize); Pattern regex = null; String quoteChar = null; while ((line = bufferedReader.readLine()) != null) { ++lineNumber; if (line.startsWith("#")) { continue; } if (regex == null) { Matcher m1 = mySQLRegex.matcher(line); if (m1.matches()) { regex = mySQLRegex; quoteChar = "`"; } else { regex = doubleQuoteRegex; quoteChar = "\""; } } // Split the up the line. For example, // INSERT INTO `foo` (`column1`,`column2`) VALUES (1, 'bar'); // would yield // tableName = "foo" // columnNames = " `column1`,`column2` " // values = "1, 'bar'" final Matcher matcher = regex.matcher(line); if (!matcher.matches()) { throw MondrianResource.instance().InvalidInsertLine.ex( lineNumber, line); } String tableName = matcher.group(1); // e.g. "foo" String columnNames = matcher.group(2); String values = matcher.group(3); // This is a table we're not interested in. Ignore the line. if (!tableFilter.test(tableName)) { continue; } // If table just changed, flush the previous batch. if (!tableName.equals(prevTable)) { writeBatch(batch, pauseMillis); batch.clear(); afterTable(prevTable, tableRowCount); tableRowCount = 0; prevTable = tableName; quotedTableName = quoteId(schema, tableName); quotedColumnNames = columnNames.replaceAll( quoteChar, dialect.getQuoteIdentifierString()); String[] splitColumnNames = columnNames.replaceAll(quoteChar, "") .replaceAll(" ", "").split(","); Column[] columns = tableMetadataToLoad.get(tableName); orderedColumns = new Column[columns.length]; for (int i = 0; i < splitColumnNames.length; i++) { Column thisColumn = null; for (int j = 0; j < columns.length && thisColumn == null; j++) { if (columns[j].name.equalsIgnoreCase( splitColumnNames[i])) { thisColumn = columns[j]; } } if (thisColumn == null) { throw new Exception( "Unknown column in INSERT statement from file: " + splitColumnNames[i]); } else { orderedColumns[i] = thisColumn; } } } ++tableRowCount; if (pauseMillis > 0) { Thread.sleep(pauseMillis); } if (infobrightLoad) { massagedLine.setLength(0); getMassagedValues(massagedLine, orderedColumns, values); fileOutput.write( massagedLine.toString() .replaceAll("\"", "\\\"") .replace('\'', '"') .trim()); fileOutput.write(nl); } else { massagedLine.setLength(0); massagedLine .append("INSERT INTO ") .append(quotedTableName) .append(" (") .append(quotedColumnNames) .append(") VALUES("); getMassagedValues(massagedLine, orderedColumns, values); massagedLine.append(")"); line = massagedLine.toString(); batch.add(line); if (batch.size() >= batchSize) { writeBatch(batch, pauseMillis); batch.clear(); if (pauseMillis > 0) { // Every ten seconds print an update. final long t = System.currentTimeMillis(); if (t - lastUpdate > 10000) { lastUpdate = t; LOGGER.debug( tableName + ": wrote row #" + tableRowCount + "."); } Thread.sleep(pauseMillis); } } } } // Print summary of the final table. writeBatch(batch, pauseMillis); afterTable(prevTable, tableRowCount); tableRowCount = 0; } finally { if (is != null) { is.close(); } } } /** * Called after the last row of a table has been read into the batch. * * @param table Table name, or null if there is no previous table * @param tableRowCount Number of rows in the table * @throws IOException */ private void afterTable( String table, int tableRowCount) throws IOException { if (table == null) { return; } if (!infobrightLoad) { LOGGER.info( "Table " + table + ": loaded " + tableRowCount + " rows."); return; } fileOutput.close(); LOGGER.info( "Infobright bulk load: Table " + table + ": loaded " + tableRowCount + " rows."); final String sql = "LOAD DATA INFILE '" + file.getAbsolutePath().replaceAll("\\\\", "\\\\\\\\") + "' INTO TABLE " + (schema != null ? schema + "." : "") + table + " FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"'" + " ESCAPED BY '\\\\'"; Statement statement = null; try { statement = connection.createStatement(); statement.executeUpdate( sql); } catch (SQLException e) { throw new RuntimeException( "Error while executing statement: " + sql, e); } finally { if (statement != null) { try { statement.close(); } catch (SQLException e) { // ignore } } } // Re-open for the next table. fileOutput = new FileWriter(file); } /** * Converts column values for a destination dialect. * * @param columns column metadata for the table * @param values the contents of the INSERT VALUES clause, * for example "34,67.89,'GHt''ab'". * These are in MySQL form. * @param buf Buffer in which to write values */ private void getMassagedValues( StringBuilder buf, Column[] columns, String values) throws Exception { // Get the values out as individual elements // Split the string at commas, and cope with embedded commas String[] individualValues = new String[columns.length]; String[] splitValues = values.split(","); // If these 2 are the same length, then there are no embedded commas if (splitValues.length == columns.length) { individualValues = splitValues; } else { // "34,67.89,'GH,t''a,b'" => { "34", "67.89", "'GH", "t''a", "b'" int valuesPos = 0; boolean inQuote = false; for (int i = 0; i < splitValues.length; i++) { if (i == 0) { individualValues[valuesPos] = splitValues[i]; inQuote = inQuote(splitValues[i], inQuote); } else { // at end if (inQuote) { individualValues[valuesPos] = individualValues[valuesPos] + "," + splitValues[i]; inQuote = inQuote(splitValues[i], inQuote); } else { valuesPos++; individualValues[valuesPos] = splitValues[i]; inQuote = inQuote(splitValues[i], inQuote); } } } assert valuesPos + 1 == columns.length; } for (int i = 0; i < columns.length; i++) { if (i > 0) { buf.append(","); } String value = individualValues[i]; if (value != null && value.trim().equals("NULL")) { value = null; } buf.append(columnValue(value, columns[i])); } } /** * Returns whether we are inside a quoted string after reading a string. * * @param str String * @param nowInQuote Whether we are inside a quoted string initially * @return whether we are inside a quoted string after reading the string */ private boolean inQuote(String str, boolean nowInQuote) { if (str.indexOf('\'') == -1) { // No quote, so stay the same return nowInQuote; } int lastPos = 0; while (lastPos <= str.length() && str.indexOf('\'', lastPos) != -1) { int pos = str.indexOf('\'', lastPos); nowInQuote = !nowInQuote; lastPos = pos + 1; } return nowInQuote; } /** * Loads data from a JDBC source. * * @param tableFilter Condition whether to load rows from a given table * @param pauseMillis How many milliseconds to pause between batches * @param batchSize How often to write/commit to the database * @throws Exception */ private void loadDataFromJdbcInput( Condition tableFilter, long pauseMillis, int batchSize) throws Exception { if (outputDirectory != null) { file = new File(outputDirectory, "createData.sql"); fileOutput = new FileWriter(file); } /* * For each input table, * read specified columns for all rows in the input connection * * For each row, insert a row */ for (Map.Entry tableEntry : tableMetadataToLoad.entrySet()) { final String tableName = tableEntry.getKey(); if (!tableFilter.test(tableName)) { continue; } final Column[] tableColumns = tableEntry.getValue(); int rowsAdded = loadTable( tableName, tableColumns, pauseMillis, batchSize); LOGGER.info( "Table " + tableName + ": loaded " + rowsAdded + " rows."); } if (outputDirectory != null) { fileOutput.close(); } } /** * After data has been loaded from a file or via JDBC, creates any derived * data. */ private void loadFromSqlInserts() throws Exception { InputStream is = getClass().getResourceAsStream("insert.sql"); if (is == null) { is = new FileInputStream( new File("testsrc/main/mondrian/test/loader/insert.sql")); } if (is == null) { throw new Exception("Cannot find insert.sql in class path"); } try { final InputStreamReader reader = new InputStreamReader(is); final BufferedReader bufferedReader = new BufferedReader(reader); String line; int lineNumber = 0; Util.discard(lineNumber); StringBuilder buf = new StringBuilder(); String fromQuoteChar = null; String toQuoteChar = dialect.getQuoteIdentifierString(); while ((line = bufferedReader.readLine()) != null) { ++lineNumber; line = line.trim(); if (line.startsWith("#") || line.length() == 0) { continue; } if (fromQuoteChar == null) { if (line.indexOf('`') >= 0) { fromQuoteChar = "`"; } else if (line.indexOf('"') >= 0) { fromQuoteChar = "\""; } } if (fromQuoteChar != null && !fromQuoteChar.equals(toQuoteChar)) { line = line.replaceAll(fromQuoteChar, toQuoteChar); } // End of buf if (line.charAt(line.length() - 1) == ';') { buf.append(" ") .append(line.substring(0, line.length() - 1)); buf = updateSQLLineForSchema(buf); executeDDL(buf.toString()); buf.setLength(0); } else { buf.append(" ") .append(line.substring(0, line.length())); } } if (buf.length() > 0) { executeDDL(buf.toString()); } } finally { if (is != null) { is.close(); } } } private StringBuilder updateSQLLineForSchema(StringBuilder buf) { if (schema == null) { return buf; } final String INSERT_INTO_CLAUSE = "INSERT INTO "; // Replace INSERT INTO "table" with // INSERT INTO "schema"."table" // Case has to match! StringBuilder insertSb = insertSchema( buf, INSERT_INTO_CLAUSE, true, true); // Prepend schema to all known table names. // These will be in the FROM clause // Case has to match! for (String tableName : tableMetadataToLoad.keySet()) { insertSb = insertSchema( insertSb, quoteId(tableName), false, false); } LOGGER.debug(insertSb.toString()); return insertSb; } private StringBuilder insertSchema( StringBuilder sb, String toFind, boolean mandatory, boolean insertBefore) { int pos = sb.indexOf(toFind); if (pos < 0) { if (mandatory) { throw new RuntimeException( "insert.sql error: No insert clause in " + sb.toString()); } else { return sb; } } StringBuilder insertSb = new StringBuilder(); if (insertBefore) { insertSb.append(sb.substring(0, pos)) .append(toFind) .append(quoteId(schema)) .append(".") .append(sb.substring(pos + toFind.length())); } else { insertSb.append(sb.substring(0, pos)) .append(quoteId(schema)) .append(".") .append(toFind) .append(sb.substring(pos + toFind.length())); } return insertSb; } /** * Read the given table from the input RDBMS and output to destination * RDBMS or file * * @param name Name of table * @param columns Columns to be read/output * @param pauseMillis How many milliseconds to pause between rows * @param batchSize How often to write/commit to the database * @return #rows inserted */ private int loadTable( String name, Column[] columns, long pauseMillis, int batchSize) throws Exception { int rowsAdded = 0; StringBuilder buf = new StringBuilder(); buf.append("select "); for (int i = 0; i < columns.length; i++) { Column column = columns[i]; if (i > 0) { buf.append(","); } buf.append(quoteId(dialect, column.name)); } buf.append(" from ") .append(quoteId(dialect, inputSchema, name)); String ddl = buf.toString(); Statement statement = null; ResultSet rs = null; try { statement = inputConnection.createStatement(); LOGGER.debug("Input table SQL: " + ddl); rs = statement.executeQuery(ddl); List batch = new ArrayList(batchSize); boolean displayedInsert = false; while (rs.next()) { // Get a batch of insert statements, then save a batch String insertStatement = createInsertStatement(rs, name, columns); if (!displayedInsert && LOGGER.isDebugEnabled()) { LOGGER.debug( "Example Insert statement: " + insertStatement); displayedInsert = true; } batch.add(insertStatement); if (batch.size() >= batchSize) { final int rowCount = writeBatch(batch, pauseMillis); rowsAdded += rowCount; batch.clear(); if (pauseMillis > 0) { final long t = System.currentTimeMillis(); if (t - lastUpdate > 10000) { lastUpdate = t; LOGGER.debug( name + ": wrote row #" + rowsAdded + "."); } Thread.sleep(pauseMillis); } } } if (batch.size() > 0) { rowsAdded += writeBatch(batch, pauseMillis); } } finally { if (rs != null) { try { rs.close(); } catch (SQLException e) { // ignore } } if (statement != null) { try { statement.close(); } catch (SQLException e) { // ignore } } } return rowsAdded; } /** * Creates a SQL INSERT statement in the dialect of the output RDBMS. * * @param rs ResultSet of input RDBMS * @param name name of table * @param columns column definitions for INSERT statement * @return String the INSERT statement */ private String createInsertStatement( ResultSet rs, String name, Column[] columns) throws Exception { StringBuilder buf = new StringBuilder(); buf.append("INSERT INTO ") .append(quoteId(schema, name)) .append(" ("); for (int i = 0; i < columns.length; i++) { Column column = columns[i]; if (i > 0) { buf.append(","); } buf.append(quoteId(column.name)); } buf.append(") VALUES("); for (int i = 0; i < columns.length; i++) { Column column = columns[i]; if (i > 0) { buf.append(","); } buf.append(columnValue(rs, column)); } buf.append(")"); return buf.toString(); } /** * If we are outputting to JDBC, * Execute the given set of SQL statements * * Otherwise, * output the statements to a file. * * @param sqls SQL statements to execute * @param pauseMillis How many milliseconds to pause between batches * @return # SQL statements executed */ private int writeBatch( List sqls, long pauseMillis) throws IOException, SQLException { if (sqls.size() == 0) { // nothing to do return sqls.size(); } if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.INFOBRIGHT) { for (String sql : sqls) { fileOutput.write(sql); fileOutput.write(nl); } } else if (outputDirectory != null) { for (String sql : sqls) { fileOutput.write(sql); fileOutput.write(";" + nl); } } else { final boolean useTxn; if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.NEOVIEW) { // setAutoCommit can not changed to true again, throws // "com.hp.t4jdbc.HPT4Exception: SetAutoCommit not possible", // since a transaction is active useTxn = false; } else if (pauseMillis > 0) { // No point trickling in data if we don't commit it as we write. useTxn = false; connection.setAutoCommit(true); } else { useTxn = connection.getMetaData().supportsTransactions(); } if (useTxn) { connection.setAutoCommit(false); } switch (dialect.getDatabaseProduct()) { case LUCIDDB: case NEOVIEW: // LucidDB doesn't perform well with single-row inserts, // and its JDBC driver doesn't support batch writes, // so collapse the batch into one big multi-row insert. // Similarly Neoview. String VALUES_TOKEN = "VALUES"; StringBuilder sb = new StringBuilder(sqls.get(0)); for (int i = 1; i < sqls.size(); i++) { sb.append(",\n"); int valuesPos = sqls.get(i).indexOf(VALUES_TOKEN); if (valuesPos < 0) { throw new RuntimeException( "Malformed INSERT: " + sqls.get(i)); } valuesPos += VALUES_TOKEN.length(); sb.append(sqls.get(i).substring(valuesPos)); } sqls.clear(); sqls.add(sb.toString()); } Statement stmt = connection.createStatement(); if (sqls.size() == 1) { // Don't use batching if there's only one item. This allows // us to work around bugs in the JDBC driver by setting // outputJdbcBatchSize=1. stmt.execute(sqls.get(0)); } else { for (String sql : sqls) { stmt.addBatch(sql); } int[] updateCounts; try { updateCounts = stmt.executeBatch(); } catch (SQLException e) { for (String sql : sqls) { LOGGER.error("Error in SQL batch: " + sql); } throw e; } int updates = 0; for (int i = 0; i < updateCounts.length; updates += updateCounts[i], i++) { if (updateCounts[i] == 0) { LOGGER.error("Error in SQL: " + sqls.get(i)); } } if (updates < sqls.size()) { throw new RuntimeException( "Failed to execute batch: " + sqls.size() + " versus " + updates); } } stmt.close(); if (useTxn) { connection.setAutoCommit(true); } } return sqls.size(); } /** * Open the file of INSERT statements to load the data. Default * file name is ./demo/FoodMartCreateData.zip * * @return FileInputStream */ private InputStream openInputStream() throws Exception { final String defaultZipFileName = "FoodMartCreateData.zip"; final String defaultDataFileName = "FoodMartCreateData.sql"; final File file = (inputFile != null) ? new File(inputFile) : new File("demo", defaultZipFileName); if (!file.exists()) { LOGGER.error("No input file: " + file); return null; } if (file.getName().toLowerCase().endsWith(".zip")) { ZipFile zippedData = new ZipFile(file); ZipEntry entry = zippedData.getEntry(defaultDataFileName); return zippedData.getInputStream(entry); } else { return new FileInputStream(file); } } /** * Create all indexes for the FoodMart database. * * @param baseTables Whether to create indexes on base tables * @param summaryTables Whether to create indexes on agg tables */ private void createIndexes( boolean baseTables, boolean summaryTables, Condition tableFilter) throws Exception { if (outputDirectory != null) { file = new File(outputDirectory, "createIndexes.sql"); fileOutput = new FileWriter(file); } createIndex( true, "account", "i_account_id", new String[] {"account_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "account", "i_account_parent", new String[] {"account_parent"}, baseTables, summaryTables, tableFilter); createIndex( true, "category", "i_category_id", new String[] {"category_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "category", "i_category_parent", new String[] {"category_parent"}, baseTables, summaryTables, tableFilter); createIndex( true, "currency", "i_currency", new String[] {"currency_id", "date"}, baseTables, summaryTables, tableFilter); createIndex( false, "customer", "i_cust_acct_num", new String[] {"account_num"}, baseTables, summaryTables, tableFilter); createIndex( false, "customer", "i_customer_fname", new String[] {"fname"}, baseTables, summaryTables, tableFilter); createIndex( false, "customer", "i_customer_lname", new String[] {"lname"}, baseTables, summaryTables, tableFilter); createIndex( false, "customer", "i_cust_child_home", new String[] {"num_children_at_home"}, baseTables, summaryTables, tableFilter); createIndex( true, "customer", "i_customer_id", new String[] {"customer_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "customer", "i_cust_postal_code", new String[] {"postal_code"}, baseTables, summaryTables, tableFilter); createIndex( false, "customer", "i_cust_region_id", new String[] {"customer_region_id"}, baseTables, summaryTables, tableFilter); createIndex( true, "department", "i_department_id", new String[] {"department_id"}, baseTables, summaryTables, tableFilter); createIndex( true, "employee", "i_employee_id", new String[] {"employee_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "employee", "i_empl_dept_id", new String[] {"department_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "employee", "i_empl_store_id", new String[] {"store_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "employee", "i_empl_super_id", new String[] {"supervisor_id"}, baseTables, summaryTables, tableFilter); createIndex( true, "employee_closure", "i_empl_closure", new String[] {"supervisor_id", "employee_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "employee_closure", "i_empl_closure_emp", new String[] {"employee_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "expense_fact", "i_expense_store_id", new String[] {"store_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "expense_fact", "i_expense_acct_id", new String[] {"account_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "expense_fact", "i_expense_time_id", new String[] {"time_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "inventory_fact_1997", "i_inv_97_prod_id", new String[] {"product_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "inventory_fact_1997", "i_inv_97_store_id", new String[] {"store_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "inventory_fact_1997", "i_inv_97_time_id", new String[] {"time_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "inventory_fact_1997", "i_inv_97_wrhse_id", new String[] {"warehouse_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "inventory_fact_1998", "i_inv_98_prod_id", new String[] {"product_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "inventory_fact_1998", "i_inv_98_store_id", new String[] {"store_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "inventory_fact_1998", "i_inv_98_time_id", new String[] {"time_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "inventory_fact_1998", "i_inv_98_wrhse_id", new String[] {"warehouse_id"}, baseTables, summaryTables, tableFilter); createIndex( true, "position", "i_position_id", new String[] {"position_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "product", "i_prod_brand_name", new String[] {"brand_name"}, baseTables, summaryTables, tableFilter); createIndex( true, "product", "i_product_id", new String[] {"product_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "product", "i_prod_class_id", new String[] {"product_class_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "product", "i_product_name", new String[] {"product_name"}, baseTables, summaryTables, tableFilter); createIndex( false, "product", "i_product_SKU", new String[] {"SKU"}, baseTables, summaryTables, tableFilter); createIndex( true, "promotion", "i_promotion_id", new String[] {"promotion_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "promotion", "i_promo_dist_id", new String[] {"promotion_district_id"}, baseTables, summaryTables, tableFilter); createIndex( true, "reserve_employee", "i_rsrv_empl_id", new String[] {"employee_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "reserve_employee", "i_rsrv_empl_dept", new String[] {"department_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "reserve_employee", "i_rsrv_empl_store", new String[] {"store_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "reserve_employee", "i_rsrv_empl_sup", new String[] {"supervisor_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "salary", "i_salary_pay_date", new String[] {"pay_date"}, baseTables, summaryTables, tableFilter); createIndex( false, "salary", "i_salary_employee", new String[] {"employee_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "sales_fact_1997", "i_sls_97_cust_id", new String[] {"customer_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "sales_fact_1997", "i_sls_97_prod_id", new String[] {"product_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "sales_fact_1997", "i_sls_97_promo_id", new String[] {"promotion_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "sales_fact_1997", "i_sls_97_store_id", new String[] {"store_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "sales_fact_1997", "i_sls_97_time_id", new String[] {"time_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "sales_fact_dec_1998", "i_sls_dec98_cust", new String[] {"customer_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "sales_fact_dec_1998", "i_sls_dec98_prod", new String[] {"product_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "sales_fact_dec_1998", "i_sls_dec98_promo", new String[] {"promotion_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "sales_fact_dec_1998", "i_sls_dec98_store", new String[] {"store_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "sales_fact_dec_1998", "i_sls_dec98_time", new String[] {"time_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "sales_fact_1998", "i_sls_98_cust_id", new String[] {"customer_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "sales_fact_1998", "i_sls_1998_prod_id", new String[] {"product_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "sales_fact_1998", "i_sls_1998_promo", new String[] {"promotion_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "sales_fact_1998", "i_sls_1998_store", new String[] {"store_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "sales_fact_1998", "i_sls_1998_time_id", new String[] {"time_id"}, baseTables, summaryTables, tableFilter); createIndex( true, "store", "i_store_id", new String[] {"store_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "store", "i_store_region_id", new String[] {"region_id"}, baseTables, summaryTables, tableFilter); createIndex( true, "store_ragged", "i_store_raggd_id", new String[] {"store_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "store_ragged", "i_store_rggd_reg", new String[] {"region_id"}, baseTables, summaryTables, tableFilter); createIndex( true, "time_by_day", "i_time_id", new String[] {"time_id"}, baseTables, summaryTables, tableFilter); createIndex( true, "time_by_day", "i_time_day", new String[] {"the_date"}, baseTables, summaryTables, tableFilter); createIndex( false, "time_by_day", "i_time_year", new String[] {"the_year"}, baseTables, summaryTables, tableFilter); createIndex( false, "time_by_day", "i_time_quarter", new String[] {"quarter"}, baseTables, summaryTables, tableFilter); createIndex( false, "time_by_day", "i_time_month", new String[] {"month_of_year"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_pl_01_sales_fact_1997", "i_sls97pl01cust", new String[] {"customer_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_pl_01_sales_fact_1997", "i_sls97pl01prod", new String[] {"product_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_pl_01_sales_fact_1997", "i_sls97pl01time", new String[] {"time_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_ll_01_sales_fact_1997", "i_sls97ll01cust", new String[] {"customer_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_ll_01_sales_fact_1997", "i_sls97ll01prod", new String[] {"product_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_ll_01_sales_fact_1997", "i_sls97ll01time", new String[] {"time_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_l_05_sales_fact_1997", "i_sls97l05cust", new String[] {"customer_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_l_05_sales_fact_1997", "i_sls97l05prod", new String[] {"product_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_l_05_sales_fact_1997", "i_sls97l05promo", new String[] {"promotion_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_l_05_sales_fact_1997", "i_sls97l05store", new String[] {"store_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_c_14_sales_fact_1997", "i_sls97c14cust", new String[] {"customer_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_c_14_sales_fact_1997", "i_sls97c14prod", new String[] {"product_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_c_14_sales_fact_1997", "i_sls97c14promo", new String[] {"promotion_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_c_14_sales_fact_1997", "i_sls97c14store", new String[] {"store_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_lc_100_sales_fact_1997", "i_sls97lc100cust", new String[] {"customer_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_lc_100_sales_fact_1997", "i_sls97lc100prod", new String[] {"product_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_c_special_sales_fact_1997", "i_sls97speccust", new String[] {"customer_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_c_special_sales_fact_1997", "i_sls97specprod", new String[] {"product_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_c_special_sales_fact_1997", "i_sls97specpromo", new String[] {"promotion_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_c_special_sales_fact_1997", "i_sls97specstore", new String[] {"store_id"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_g_ms_pcat_sales_fact_1997", "i_sls97gmp_gender", new String[] {"gender"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_g_ms_pcat_sales_fact_1997", "i_sls97gmp_ms", new String[] {"marital_status"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_g_ms_pcat_sales_fact_1997", "i_sls97gmp_pfam", new String[] {"product_family"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_g_ms_pcat_sales_fact_1997", "i_sls97gmp_pdept", new String[] {"product_department"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_g_ms_pcat_sales_fact_1997", "i_sls97gmp_pcat", new String[] {"product_category"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_g_ms_pcat_sales_fact_1997", "i_sls97gmp_tmonth", new String[] {"month_of_year"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_g_ms_pcat_sales_fact_1997", "i_sls97gmp_tquarter", new String[] {"quarter"}, baseTables, summaryTables, tableFilter); createIndex( false, "agg_g_ms_pcat_sales_fact_1997", "i_sls97gmp_tyear", new String[] {"the_year"}, baseTables, summaryTables, tableFilter); if (outputDirectory != null) { fileOutput.close(); } } /** * Creates an index. * *

If we are outputting to JDBC, executes the CREATE INDEX statement; * otherwise, outputs the statement to a file. */ private void createIndex( boolean isUnique, String tableName, String indexName, String[] columnNames, boolean baseTables, boolean aggregateTables, Condition tableFilter) { if (!tableFilter.test(tableName)) { return; } if (!baseTables && !aggregateTables) { // This is just a dry run to record the unique indexes // so that we can implement them as standard // UNIQUE constraints if desired. if (!isUnique || !generateUniqueConstraints) { return; } List constraintList = tableConstraints.get(tableName); if (constraintList == null) { constraintList = new ArrayList(); tableConstraints.put(tableName, constraintList); } constraintList.add( new UniqueConstraint( indexName, columnNames)); return; } else { if (isUnique && generateUniqueConstraints) { // We'll implement this via a UNIQUE constraint instead return; } } try { // Is it an aggregate table or a base table? boolean isBase = !aggregateTableMetadataToLoad.containsKey(tableName); // Only do aggregate tables if (populationQueries && !isBase) { return; } if (isBase && !baseTables) { // This is a base table, but we're not to index base tables. return; } if (!isBase && !aggregateTables) { // This is an aggregate table, but we're not to index agg // tables. return; } StringBuilder buf = new StringBuilder(); // If we're [re]creating tables, no need to drop indexes. if (jdbcOutput && !tables) { try { buf.append("DROP INDEX ") .append(quoteId(schema, indexName)); switch (dialect.getDatabaseProduct()) { case MYSQL: case INFOBRIGHT: case TERADATA: buf.append(" ON ") .append(quoteId(schema, tableName)); break; } final String deleteDDL = buf.toString(); executeDDL(deleteDDL); } catch (Exception e1) { LOGGER.info( "Index Drop failed for " + tableName + ", " + indexName + " : but continue"); } } buf.setLength(0); buf.append(isUnique ? "CREATE UNIQUE INDEX " : "CREATE INDEX ") .append(quoteId(indexName)); if (dialect.getDatabaseProduct() != Dialect.DatabaseProduct.TERADATA) { buf.append(" ON ").append(quoteId(schema, tableName)); } buf.append(" ("); for (int i = 0; i < columnNames.length; i++) { String columnName = columnNames[i]; if (i > 0) { buf.append(", "); } buf.append(quoteId(columnName)); } buf.append(")"); if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.TERADATA) { buf.append(" ON ").append(quoteId(schema, tableName)); } final String createDDL = buf.toString(); executeDDL(createDDL); } catch (Exception e) { throw MondrianResource.instance().CreateIndexFailed.ex( indexName, tableName, e); } } /** * Defines all tables for the FoodMart database.

* *

Also initializes {@link #tableMetadataToLoad} and * {@link #aggregateTableMetadataToLoad}. * * @param tableFilter Condition whether to load a particular table */ private void createTables(Condition tableFilter) throws Exception { if (outputDirectory != null) { file = new File(outputDirectory, "createTables.sql"); fileOutput = new FileWriter(file); } createTable( "sales_fact_1997", tableFilter, new Column("product_id", Type.Integer, false), new Column("time_id", Type.Integer, false), new Column("customer_id", Type.Integer, false), new Column("promotion_id", Type.Integer, false), new Column("store_id", Type.Integer, false), new Column("store_sales", Type.Currency, false), new Column("store_cost", Type.Currency, false), new Column("unit_sales", Type.Currency, false)); createTable( "sales_fact_1998", tableFilter, new Column("product_id", Type.Integer, false), new Column("time_id", Type.Integer, false), new Column("customer_id", Type.Integer, false), new Column("promotion_id", Type.Integer, false), new Column("store_id", Type.Integer, false), new Column("store_sales", Type.Currency, false), new Column("store_cost", Type.Currency, false), new Column("unit_sales", Type.Currency, false)); createTable( "sales_fact_dec_1998", tableFilter, new Column("product_id", Type.Integer, false), new Column("time_id", Type.Integer, false), new Column("customer_id", Type.Integer, false), new Column("promotion_id", Type.Integer, false), new Column("store_id", Type.Integer, false), new Column("store_sales", Type.Currency, false), new Column("store_cost", Type.Currency, false), new Column("unit_sales", Type.Currency, false)); createTable( "inventory_fact_1997", tableFilter, new Column("product_id", Type.Integer, false), new Column("time_id", Type.Integer, true), new Column("warehouse_id", Type.Integer, true), new Column("store_id", Type.Integer, true), new Column("units_ordered", Type.Integer, true), new Column("units_shipped", Type.Integer, true), new Column("warehouse_sales", Type.Currency, true), new Column("warehouse_cost", Type.Currency, true), new Column("supply_time", Type.Smallint, true), new Column("store_invoice", Type.Currency, true)); createTable( "inventory_fact_1998", tableFilter, new Column("product_id", Type.Integer, false), new Column("time_id", Type.Integer, true), new Column("warehouse_id", Type.Integer, true), new Column("store_id", Type.Integer, true), new Column("units_ordered", Type.Integer, true), new Column("units_shipped", Type.Integer, true), new Column("warehouse_sales", Type.Currency, true), new Column("warehouse_cost", Type.Currency, true), new Column("supply_time", Type.Smallint, true), new Column("store_invoice", Type.Currency, true)); // Aggregate tables createTable( "agg_pl_01_sales_fact_1997", tableFilter, false, true, new Column("product_id", Type.Integer, false), new Column("time_id", Type.Integer, false), new Column("customer_id", Type.Integer, false), new Column("store_sales_sum", Type.Currency, false), new Column("store_cost_sum", Type.Currency, false), new Column("unit_sales_sum", Type.Currency, false), new Column("fact_count", Type.Integer, false)); createTable( "agg_ll_01_sales_fact_1997", tableFilter, false, true, new Column("product_id", Type.Integer, false), new Column("time_id", Type.Integer, false), new Column("customer_id", Type.Integer, false), new Column("store_sales", Type.Currency, false), new Column("store_cost", Type.Currency, false), new Column("unit_sales", Type.Currency, false), new Column("fact_count", Type.Integer, false)); createTable( "agg_l_03_sales_fact_1997", tableFilter, false, true, new Column("time_id", Type.Integer, false), new Column("customer_id", Type.Integer, false), new Column("store_sales", Type.Currency, false), new Column("store_cost", Type.Currency, false), new Column("unit_sales", Type.Currency, false), new Column("fact_count", Type.Integer, false)); createTable( "agg_l_04_sales_fact_1997", tableFilter, false, true, new Column("time_id", Type.Integer, false), new Column("store_sales", Type.Currency, false), new Column("store_cost", Type.Currency, false), new Column("unit_sales", Type.Currency, false), new Column("customer_count", Type.Integer, false), new Column("fact_count", Type.Integer, false)); createTable( "agg_l_05_sales_fact_1997", tableFilter, false, true, new Column("product_id", Type.Integer, false), new Column("customer_id", Type.Integer, false), new Column("promotion_id", Type.Integer, false), new Column("store_id", Type.Integer, false), new Column("store_sales", Type.Currency, false), new Column("store_cost", Type.Currency, false), new Column("unit_sales", Type.Currency, false), new Column("fact_count", Type.Integer, false)); createTable( "agg_c_10_sales_fact_1997", tableFilter, false, true, new Column("month_of_year", Type.Smallint, false), new Column("quarter", Type.Varchar30, false), new Column("the_year", Type.Smallint, false), new Column("store_sales", Type.Currency, false), new Column("store_cost", Type.Currency, false), new Column("unit_sales", Type.Currency, false), new Column("customer_count", Type.Integer, false), new Column("fact_count", Type.Integer, false)); createTable( "agg_c_14_sales_fact_1997", tableFilter, false, true, new Column("product_id", Type.Integer, false), new Column("customer_id", Type.Integer, false), new Column("store_id", Type.Integer, false), new Column("promotion_id", Type.Integer, false), new Column("month_of_year", Type.Smallint, false), new Column("quarter", Type.Varchar30, false), new Column("the_year", Type.Smallint, false), new Column("store_sales", Type.Currency, false), new Column("store_cost", Type.Currency, false), new Column("unit_sales", Type.Currency, false), new Column("fact_count", Type.Integer, false)); createTable( "agg_lc_100_sales_fact_1997", tableFilter, false, true, new Column("product_id", Type.Integer, false), new Column("customer_id", Type.Integer, false), new Column("quarter", Type.Varchar30, false), new Column("the_year", Type.Smallint, false), new Column("store_sales", Type.Currency, false), new Column("store_cost", Type.Currency, false), new Column("unit_sales", Type.Currency, false), new Column("fact_count", Type.Integer, false)); createTable( "agg_c_special_sales_fact_1997", tableFilter, false, true, new Column("product_id", Type.Integer, false), new Column("promotion_id", Type.Integer, false), new Column("customer_id", Type.Integer, false), new Column("store_id", Type.Integer, false), new Column("time_month", Type.Smallint, false), new Column("time_quarter", Type.Varchar30, false), new Column("time_year", Type.Smallint, false), new Column("store_sales_sum", Type.Currency, false), new Column("store_cost_sum", Type.Currency, false), new Column("unit_sales_sum", Type.Currency, false), new Column("fact_count", Type.Integer, false)); createTable( "agg_g_ms_pcat_sales_fact_1997", tableFilter, false, true, new Column("gender", Type.Varchar30, false), new Column("marital_status", Type.Varchar30, false), new Column("product_family", Type.Varchar30, true), new Column("product_department", Type.Varchar30, true), new Column("product_category", Type.Varchar30, true), new Column("month_of_year", Type.Smallint, false), new Column("quarter", Type.Varchar30, false), new Column("the_year", Type.Smallint, false), new Column("store_sales", Type.Currency, false), new Column("store_cost", Type.Currency, false), new Column("unit_sales", Type.Currency, false), new Column("customer_count", Type.Integer, false), new Column("fact_count", Type.Integer, false)); createTable( "agg_lc_06_sales_fact_1997", tableFilter, false, true, new Column("time_id", Type.Integer, false), new Column("city", Type.Varchar30, false), new Column("state_province", Type.Varchar30, false), new Column("country", Type.Varchar30, false), new Column("store_sales", Type.Currency, false), new Column("store_cost", Type.Currency, false), new Column("unit_sales", Type.Currency, false), new Column("fact_count", Type.Integer, false)); createTable( "currency", tableFilter, new Column("currency_id", Type.Integer, false), new Column("date", Type.Date, false), new Column("currency", Type.Varchar30, false), new Column("conversion_ratio", Type.Currency, false)); createTable( "account", tableFilter, new Column("account_id", Type.Integer, false), new Column("account_parent", Type.Integer, true), new Column("account_description", Type.Varchar30, true), new Column("account_type", Type.Varchar30, false), new Column("account_rollup", Type.Varchar30, false), new Column("Custom_Members", Type.Varchar255, true)); createTable( "category", tableFilter, new Column("category_id", Type.Varchar30, false), new Column("category_parent", Type.Varchar30, true), new Column("category_description", Type.Varchar30, false), new Column("category_rollup", Type.Varchar30, true)); createTable( "customer", tableFilter, new Column("customer_id", Type.Integer, false), new Column("account_num", Type.Bigint, false), new Column("lname", Type.Varchar30, false), new Column("fname", Type.Varchar30, false), new Column("mi", Type.Varchar30, true), new Column("address1", Type.Varchar30, true), new Column("address2", Type.Varchar30, true), new Column("address3", Type.Varchar30, true), new Column("address4", Type.Varchar30, true), new Column("city", Type.Varchar30, true), new Column("state_province", Type.Varchar30, true), new Column("postal_code", Type.Varchar30, false), new Column("country", Type.Varchar30, false), new Column("customer_region_id", Type.Integer, false), new Column("phone1", Type.Varchar30, false), new Column("phone2", Type.Varchar30, false), new Column("birthdate", Type.Date, false), new Column("marital_status", Type.Varchar30, false), new Column("yearly_income", Type.Varchar30, false), new Column("gender", Type.Varchar30, false), new Column("total_children", Type.Smallint, false), new Column("num_children_at_home", Type.Smallint, false), new Column("education", Type.Varchar30, false), new Column("date_accnt_opened", Type.Date, false), new Column("member_card", Type.Varchar30, true), new Column("occupation", Type.Varchar30, true), new Column("houseowner", Type.Varchar30, true), new Column("num_cars_owned", Type.Integer, true), new Column("fullname", Type.Varchar60, false)); createTable( "days", tableFilter, new Column("day", Type.Integer, false), new Column("week_day", Type.Varchar30, false)); createTable( "department", tableFilter, new Column("department_id", Type.Integer, false), new Column("department_description", Type.Varchar30, false)); createTable( "employee", tableFilter, new Column("employee_id", Type.Integer, false), new Column("full_name", Type.Varchar30, false), new Column("first_name", Type.Varchar30, false), new Column("last_name", Type.Varchar30, false), new Column("position_id", Type.Integer, true), new Column("position_title", Type.Varchar30, true), new Column("store_id", Type.Integer, false), new Column("department_id", Type.Integer, false), new Column("birth_date", Type.Date, false), new Column("hire_date", Type.Timestamp, true), new Column("end_date", Type.Timestamp, true), new Column("salary", Type.Currency, false), new Column("supervisor_id", Type.Integer, true), new Column("education_level", Type.Varchar30, false), new Column("marital_status", Type.Varchar30, false), new Column("gender", Type.Varchar30, false), new Column("management_role", Type.Varchar30, true)); createTable( "employee_closure", tableFilter, new Column("employee_id", Type.Integer, false), new Column("supervisor_id", Type.Integer, false), new Column("distance", Type.Integer, true)); createTable( "expense_fact", tableFilter, new Column("store_id", Type.Integer, false), new Column("account_id", Type.Integer, false), new Column("exp_date", Type.Timestamp, false), new Column("time_id", Type.Integer, false), new Column("category_id", Type.Varchar30, false), new Column("currency_id", Type.Integer, false), new Column("amount", Type.Currency, false)); createTable( "position", tableFilter, new Column("position_id", Type.Integer, false), new Column("position_title", Type.Varchar30, false), new Column("pay_type", Type.Varchar30, false), new Column("min_scale", Type.Currency, false), new Column("max_scale", Type.Currency, false), new Column("management_role", Type.Varchar30, false)); createTable( "product", tableFilter, new Column("product_class_id", Type.Integer, false), new Column("product_id", Type.Integer, false), new Column("brand_name", Type.Varchar60, true), new Column("product_name", Type.Varchar60, false), new Column("SKU", Type.Bigint, false), new Column("SRP", Type.Currency, true), new Column("gross_weight", Type.Real, true), new Column("net_weight", Type.Real, true), new Column("recyclable_package", Type.Boolean, true), new Column("low_fat", Type.Boolean, true), new Column("units_per_case", Type.Smallint, true), new Column("cases_per_pallet", Type.Smallint, true), new Column("shelf_width", Type.Real, true), new Column("shelf_height", Type.Real, true), new Column("shelf_depth", Type.Real, true)); createTable( "product_class", tableFilter, new Column("product_class_id", Type.Integer, false), new Column("product_subcategory", Type.Varchar30, true), new Column("product_category", Type.Varchar30, true), new Column("product_department", Type.Varchar30, true), new Column("product_family", Type.Varchar30, true)); createTable( "promotion", tableFilter, new Column("promotion_id", Type.Integer, false), new Column("promotion_district_id", Type.Integer, true), new Column("promotion_name", Type.Varchar30, true), new Column("media_type", Type.Varchar30, true), new Column("cost", Type.Currency, true), new Column("start_date", Type.Timestamp, true), new Column("end_date", Type.Timestamp, true)); createTable( "region", tableFilter, new Column("region_id", Type.Integer, false), new Column("sales_city", Type.Varchar30, true), new Column("sales_state_province", Type.Varchar30, true), new Column("sales_district", Type.Varchar30, true), new Column("sales_region", Type.Varchar30, true), new Column("sales_country", Type.Varchar30, true), new Column("sales_district_id", Type.Integer, true)); createTable( "reserve_employee", tableFilter, new Column("employee_id", Type.Integer, false), new Column("full_name", Type.Varchar30, false), new Column("first_name", Type.Varchar30, false), new Column("last_name", Type.Varchar30, false), new Column("position_id", Type.Integer, true), new Column("position_title", Type.Varchar30, true), new Column("store_id", Type.Integer, false), new Column("department_id", Type.Integer, false), new Column("birth_date", Type.Timestamp, false), new Column("hire_date", Type.Timestamp, true), new Column("end_date", Type.Timestamp, true), new Column("salary", Type.Currency, false), new Column("supervisor_id", Type.Integer, true), new Column("education_level", Type.Varchar30, false), new Column("marital_status", Type.Varchar30, false), new Column("gender", Type.Varchar30, false)); createTable( "salary", tableFilter, new Column("pay_date", Type.Timestamp, false), new Column("employee_id", Type.Integer, false), new Column("department_id", Type.Integer, false), new Column("currency_id", Type.Integer, false), new Column("salary_paid", Type.Currency, false), new Column("overtime_paid", Type.Currency, false), new Column("vacation_accrued", Type.Real, false), new Column("vacation_used", Type.Real, false)); createTable( "store", tableFilter, new Column("store_id", Type.Integer, false), new Column("store_type", Type.Varchar30, true), new Column("region_id", Type.Integer, true), new Column("store_name", Type.Varchar30, true), new Column("store_number", Type.Integer, true), new Column("store_street_address", Type.Varchar30, true), new Column("store_city", Type.Varchar30, true), new Column("store_state", Type.Varchar30, true), new Column("store_postal_code", Type.Varchar30, true), new Column("store_country", Type.Varchar30, true), new Column("store_manager", Type.Varchar30, true), new Column("store_phone", Type.Varchar30, true), new Column("store_fax", Type.Varchar30, true), new Column("first_opened_date", Type.Timestamp, true), new Column("last_remodel_date", Type.Timestamp, true), new Column("store_sqft", Type.Integer, true), new Column("grocery_sqft", Type.Integer, true), new Column("frozen_sqft", Type.Integer, true), new Column("meat_sqft", Type.Integer, true), new Column("coffee_bar", Type.Boolean, true), new Column("video_store", Type.Boolean, true), new Column("salad_bar", Type.Boolean, true), new Column("prepared_food", Type.Boolean, true), new Column("florist", Type.Boolean, true)); createTable( "store_ragged", tableFilter, new Column("store_id", Type.Integer, false), new Column("store_type", Type.Varchar30, true), new Column("region_id", Type.Integer, true), new Column("store_name", Type.Varchar30, true), new Column("store_number", Type.Integer, true), new Column("store_street_address", Type.Varchar30, true), new Column("store_city", Type.Varchar30, true), new Column("store_state", Type.Varchar30, true), new Column("store_postal_code", Type.Varchar30, true), new Column("store_country", Type.Varchar30, true), new Column("store_manager", Type.Varchar30, true), new Column("store_phone", Type.Varchar30, true), new Column("store_fax", Type.Varchar30, true), new Column("first_opened_date", Type.Timestamp, true), new Column("last_remodel_date", Type.Timestamp, true), new Column("store_sqft", Type.Integer, true), new Column("grocery_sqft", Type.Integer, true), new Column("frozen_sqft", Type.Integer, true), new Column("meat_sqft", Type.Integer, true), new Column("coffee_bar", Type.Boolean, true), new Column("video_store", Type.Boolean, true), new Column("salad_bar", Type.Boolean, true), new Column("prepared_food", Type.Boolean, true), new Column("florist", Type.Boolean, true)); createTable( "time_by_day", tableFilter, new Column("time_id", Type.Integer, false), new Column("the_date", Type.Timestamp, true), new Column("the_day", Type.Varchar30, true), new Column("the_month", Type.Varchar30, true), new Column("the_year", Type.Smallint, true), new Column("day_of_month", Type.Smallint, true), new Column("week_of_year", Type.Integer, true), new Column("month_of_year", Type.Smallint, true), new Column("quarter", Type.Varchar30, true), new Column("fiscal_period", Type.Varchar30, true)); createTable( "warehouse", tableFilter, new Column("warehouse_id", Type.Integer, false), new Column("warehouse_class_id", Type.Integer, true), new Column("stores_id", Type.Integer, true), new Column("warehouse_name", Type.Varchar60, true), new Column("wa_address1", Type.Varchar30, true), new Column("wa_address2", Type.Varchar30, true), new Column("wa_address3", Type.Varchar30, true), new Column("wa_address4", Type.Varchar30, true), new Column("warehouse_city", Type.Varchar30, true), new Column("warehouse_state_province", Type.Varchar30, true), new Column("warehouse_postal_code", Type.Varchar30, true), new Column("warehouse_country", Type.Varchar30, true), new Column("warehouse_owner_name", Type.Varchar30, true), new Column("warehouse_phone", Type.Varchar30, true), new Column("warehouse_fax", Type.Varchar30, true)); createTable( "warehouse_class", tableFilter, new Column("warehouse_class_id", Type.Integer, false), new Column("description", Type.Varchar30, true)); if (outputDirectory != null) { fileOutput.close(); } } /** * If we are outputting to JDBC, and not creating tables, delete all rows. * *

Otherwise: * *

Generate the SQL CREATE TABLE statement. * *

If we are outputting to JDBC, * Execute a DROP TABLE statement * Execute the CREATE TABLE statement * *

Otherwise, * output the statement to a file. */ private void createTable( String name, Condition tableFilter, Column... columns) { createTable(name, tableFilter, true, false, columns); } /** * Creates a table definition. * * @param name Table name * @param tableFilter Table filter * @param loadData Whether to load data * @param aggregate Whether it is an aggregate table * @param columns Column definitions */ private void createTable( String name, Condition tableFilter, boolean loadData, boolean aggregate, Column... columns) { try { // Initialize columns for (Column column1 : columns) { column1.init(dialect); } if (!tableFilter.test(name)) { return; } // Store this metadata if we are going to load the table // from JDBC or a file if (loadData) { tableMetadataToLoad.put(name, columns); } if (aggregate && aggregates) { aggregateTableMetadataToLoad.put(name, columns); } if (!tables) { if (data && jdbcOutput) { if (populationQueries && !aggregate) { return; } // We're going to load the data without [re]creating // the table, so let's remove the data. try { executeDDL("DELETE FROM " + quoteId(schema, name)); } catch (SQLException e) { throw MondrianResource.instance().CreateTableFailed.ex( name, e); } } return; } else if (populationQueries && !aggregate) { // only create the aggregate tables if we are running // -tables -populationQueries return; } // If table does not exist, that is OK try { executeDDL("DROP TABLE " + quoteId(schema, name)); } catch (Exception e) { LOGGER.debug("Drop of " + name + " failed. Ignored"); } // Define the table. StringBuilder buf = new StringBuilder(); buf.append("CREATE TABLE ") .append(quoteId(schema, name)) .append("("); for (int i = 0; i < columns.length; i++) { Column column = columns[i]; if (i > 0) { buf.append(","); } buf.append(nl); buf.append(" ").append(quoteId(column.name)).append(" ") .append(column.typeName); if (!column.constraint.equals("")) { buf.append(" ").append(column.constraint); } } List uniqueConstraints = tableConstraints.get(name); if (uniqueConstraints != null) { for (UniqueConstraint uniqueConstraint : uniqueConstraints) { buf.append(","); buf.append(nl); buf.append(" "); buf.append("CONSTRAINT "); buf.append(quoteId(uniqueConstraint.name)); buf.append(" UNIQUE("); String [] columnNames = uniqueConstraint.columnNames; for (int i = 0; i < columnNames.length; i++) { if (i > 0) { buf.append(","); } buf.append(quoteId(columnNames[i])); } buf.append(")"); } } buf.append(")"); switch (dialect.getDatabaseProduct()) { case NEOVIEW: // no unique keys defined buf.append(" NO PARTITION"); } final String ddl = buf.toString(); executeDDL(ddl); } catch (Exception e) { throw MondrianResource.instance().CreateTableFailed.ex(name, e); } } private void analyzeTables() throws SQLException { switch (dialect.getDatabaseProduct()) { case LUCIDDB: Statement statement = null; try { LOGGER.info("Analyzing schema..."); statement = connection.createStatement(); statement.execute( "call " + "applib.estimate_statistics_for_schema(current_schema)"); LOGGER.info("Analyze complete."); } finally { if (statement != null) { try { statement.close(); } catch (SQLException e) { // ignore } } } break; default: LOGGER.warn("Analyze is not supported for current database."); break; } } /** * Executes a DDL statement. * * @param ddl DDL statement * @throws Exception on error */ private void executeDDL(String ddl) throws Exception { LOGGER.info(ddl); if (jdbcOutput) { Statement statement = null; try { statement = connection.createStatement(); statement.execute(ddl); } finally { if (statement != null) { try { statement.close(); } catch (SQLException e) { // ignore } } } } else { fileOutput.write(ddl); fileOutput.write(";" + nl); } } /** * Quote the given SQL identifier suitable for the output DBMS. */ private String quoteId(String name) { return quoteId(dialect, name); } /** * Quote the given SQL identifier suitable for the given DBMS type. */ private String quoteId(Dialect dialect, String name) { return dialect.quoteIdentifier(name); } /** * Quote the given SQL identifier suitable for the output DBMS, * with schema. */ private String quoteId(String schemaName, String name) { return quoteId(dialect, schemaName, name); } /** * Quote the given SQL identifier suitable for the given DBMS type, * with schema. */ private String quoteId(Dialect dialect, String schemaName, String name) { return dialect.quoteIdentifier(schemaName, name); } /** * String representation of the column in the result set, suitable for * inclusion in a SQL insert statement.

* * The column in the result set is transformed according to the type in * the column parameter.

* * Different DBMSs (and drivers) return different Java types for a given * column; {@link ClassCastException}s may occur. * * @param rs ResultSet row to process * @param column Column to process * @return String representation of column value */ private String columnValue(ResultSet rs, Column column) throws Exception { Object obj = rs.getObject(column.name); String columnType = column.typeName; if (obj == null) { return "NULL"; } /* * Output for an INTEGER column, handling Doubles and Integers * in the result set */ if (columnType.startsWith(Type.Integer.name)) { if (obj.getClass() == Double.class) { try { Double result = (Double) obj; return integerFormatter.format(result.doubleValue()); } catch (ClassCastException cce) { LOGGER.error( "CCE: " + column.name + " to Long from: " + obj.getClass().getName() + " - " + obj.toString()); throw cce; } } else { try { int result = ((Number) obj).intValue(); return Integer.toString(result); } catch (ClassCastException cce) { LOGGER.error( "CCE: " + column.name + " to Integer from: " + obj.getClass().getName() + " - " + obj.toString()); throw cce; } } /* * Output for an SMALLINT column, handling Integers * in the result set */ } else if (columnType.startsWith(Type.Smallint.name)) { if (obj instanceof Boolean) { return (Boolean) obj ? "1" : "0"; } else { try { Integer result = (Integer) obj; return result.toString(); } catch (ClassCastException cce) { LOGGER.error( "CCE: " + column.name + " to Integer from: " + obj.getClass().getName() + " - " + obj.toString()); throw cce; } } /* * Output for an BIGINT column, handling Doubles and Longs * in the result set */ } else if (columnType.startsWith("BIGINT")) { if (obj.getClass() == Double.class) { try { Double result = (Double) obj; return integerFormatter.format(result.doubleValue()); } catch (ClassCastException cce) { LOGGER.error( "CCE: " + column.name + " to Double from: " + obj.getClass().getName() + " - " + obj.toString()); throw cce; } } else { try { Long result = (Long) obj; return result.toString(); } catch (ClassCastException cce) { LOGGER.error( "CCE: " + column.name + " to Long from: " + obj.getClass().getName() + " - " + obj.toString()); throw cce; } } /* * Output for a String, managing embedded quotes */ } else if (columnType.startsWith("VARCHAR")) { return embedQuotes((String) obj); /* * Output for a TIMESTAMP */ } else { if (columnType.startsWith("TIMESTAMP")) { Timestamp ts = (Timestamp) obj; // REVIEW jvs 26-Nov-2006: Is it safe to replace // these with dialect.quoteTimestampLiteral, etc? switch (dialect.getDatabaseProduct()) { case ORACLE: case LUCIDDB: case NEOVIEW: return "TIMESTAMP '" + ts + "'"; default: return "'" + ts + "'"; } //return "'" + ts + "'" ; /* * Output for a DATE */ } else if (columnType.startsWith("DATE")) { Date dt = (Date) obj; switch (dialect.getDatabaseProduct()) { case ORACLE: case LUCIDDB: case NEOVIEW: return "DATE '" + dateFormatter.format(dt) + "'"; default: return "'" + dateFormatter.format(dt) + "'"; } /* * Output for a FLOAT */ } else if (columnType.startsWith(Type.Real.name)) { Float result = (Float) obj; return result.toString(); /* * Output for a DECIMAL(length, places) */ } else if (columnType.startsWith("DECIMAL")) { final Matcher matcher = decimalDataTypeRegex.matcher(columnType); if (!matcher.matches()) { throw new Exception( "Bad DECIMAL column type for " + columnType); } DecimalFormat formatter = new DecimalFormat( decimalFormat(matcher.group(1), matcher.group(2))); if (obj.getClass() == Double.class) { try { Double result = (Double) obj; return formatter.format(result.doubleValue()); } catch (ClassCastException cce) { LOGGER.error( "CCE: " + column.name + " to Double from: " + obj.getClass().getName() + " - " + obj.toString()); throw cce; } } else { // should be (obj.getClass() == BigDecimal.class) try { BigDecimal result = (BigDecimal) obj; return formatter.format(result); } catch (ClassCastException cce) { LOGGER.error( "CCE: " + column.name + " to BigDecimal from: " + obj.getClass().getName() + " - " + obj.toString()); throw cce; } } /* * Output for a BOOLEAN (Postgres) or BIT (other DBMSs) */ } else if (columnType.startsWith("BOOLEAN") || columnType.startsWith("BIT")) { Boolean result = (Boolean) obj; return result.toString(); /* * Output for a BOOLEAN - TINYINT(1) (MySQL) */ } else if (columnType.startsWith("TINYINT(1)")) { return (Boolean) obj ? "1" : "0"; } } throw new Exception( "Unknown column type: " + columnType + " for column: " + column.name); } private String columnValue( String columnValue, Column column) throws Exception { String columnType = column.typeName; if (columnValue == null) { return "NULL"; } /* * Output for a TIMESTAMP */ final Dialect.DatabaseProduct product = dialect.getDatabaseProduct(); if (columnType.startsWith("TIMESTAMP")) { switch (product) { case ORACLE: case LUCIDDB: case NEOVIEW: return "TIMESTAMP " + columnValue; } /* * Output for a DATE */ } else if (columnType.startsWith("DATE")) { switch (product) { case ORACLE: case LUCIDDB: case NEOVIEW: return "DATE " + columnValue; } /* * Output for a BOOLEAN (Postgres) or BIT (other DBMSs) */ } else if (column.type == Type.Boolean) { String trimmedValue = columnValue.trim(); switch (product) { case MYSQL: case INFOBRIGHT: case ORACLE: case DB2: case DB2_AS400: case DB2_OLD_AS400: case FIREBIRD: case MSSQL: case DERBY: case TERADATA: case INGRES: case NEOVIEW: case VECTORWISE: case VERTICA: if (trimmedValue.equals("true")) { return "1"; } else if (trimmedValue.equals("false")) { return "0"; } break; default: if (trimmedValue.equals("1")) { return "true"; } else if (trimmedValue.equals("0")) { return "false"; } break; } } return columnValue; } /** * Generate an appropriate string to use in an SQL insert statement for * a VARCHAR colummn, taking into account NULL strings and strings with * embedded quotes. * * @param original String to transform * @return NULL if null string, otherwise massaged string with doubled * quotes for SQL */ private String embedQuotes(String original) { if (original == null) { return "NULL"; } StringBuilder buf = new StringBuilder(); buf.append("'"); for (int i = 0; i < original.length(); i++) { char ch = original.charAt(i); buf.append(ch); if (ch == '\'') { buf.append('\''); } } buf.append("'"); return buf.toString(); } /** * Generates an appropriate number format string for doubles etc * to be used to include a number in an SQL insert statement. * *

For example, {@code decimalFormat("6", "2")} returns "###0.00". * * @param lengthStr String representing integer: number of digits to format * @param placesStr String representing integer: number of decimal places * @return numeric format string */ private static String decimalFormat(String lengthStr, String placesStr) { int length = Integer.parseInt(lengthStr); int places = Integer.parseInt(placesStr); return decimalFormat(length, places); } /** * Generates an appropriate number format string for doubles etc. * to be used to include a number in an SQL insert statement. * *

For example, {@code decimalFormat(6, 2)} returns "###0.00". * * @param length int: number of digits to format * @param places int: number of decimal places * @return numeric format string */ private static String decimalFormat(int length, int places) { StringBuilder buf = new StringBuilder(); for (int i = 0; i < length; i++) { if ((length - i) == places) { buf.append('.'); } if ((length - i) <= (places + 1)) { buf.append("0"); } else { buf.append("#"); } } return buf.toString(); } private static class Column { private final String name; private final Type type; private String typeName; private final String constraint; public Column(String name, Type type, boolean nullsAllowed) { this.name = name; this.type = type; this.constraint = nullsAllowed ? "" : "NOT NULL"; } public void init(Dialect dialect) { this.typeName = type.toPhysical(dialect); } } private static class UniqueConstraint { final String name; final String [] columnNames; public UniqueConstraint(String name, String [] columnNames) { this.name = name; this.columnNames = columnNames; } } /** * Represents a logical type, such as "BOOLEAN".

* * Specific databases will represent this with their own particular physical * type, for example "TINYINT(1)", "BOOLEAN" or "BIT"; * see {@link #toPhysical(mondrian.spi.Dialect)}. */ private static class Type { /** * The name of this type. Immutable, and independent of the RDBMS. */ private final String name; private static final Type Integer = new Type("INTEGER"); private static final Type Currency = new Type("DECIMAL(10,4)"); private static final Type Smallint = new Type("SMALLINT"); private static final Type Varchar30 = new Type("VARCHAR(30)"); private static final Type Varchar255 = new Type("VARCHAR(255)"); private static final Type Varchar60 = new Type("VARCHAR(60)"); private static final Type Real = new Type("REAL"); private static final Type Boolean = new Type("BOOLEAN"); private static final Type Bigint = new Type("BIGINT"); private static final Type Date = new Type("DATE"); private static final Type Timestamp = new Type("TIMESTAMP"); private Type(String name) { this.name = name; } /** * Returns the physical type which a given RDBMS (dialect) uses to * represent this logical type. */ String toPhysical(Dialect dialect) { if (this == Integer || this == Currency || this == Smallint || this == Varchar30 || this == Varchar60 || this == Varchar255 || this == Real) { return name; } if (this == Boolean) { switch (dialect.getDatabaseProduct()) { case POSTGRESQL: case GREENPLUM: case LUCIDDB: case NETEZZA: return name; case MYSQL: case INFOBRIGHT: return "TINYINT(1)"; case MSSQL: return "BIT"; default: return Smallint.name; } } if (this == Bigint) { switch (dialect.getDatabaseProduct()) { case ORACLE: case FIREBIRD: return "DECIMAL(15,0)"; default: return name; } } if (this == Date) { switch (dialect.getDatabaseProduct()) { case MSSQL: return "DATETIME"; case INGRES: return "INGRESDATE"; default: return name; } } if (this == Timestamp) { switch (dialect.getDatabaseProduct()) { case MSSQL: case MYSQL: case INFOBRIGHT: return "DATETIME"; case INGRES: return "INGRESDATE"; default: return name; } } throw new AssertionError("unexpected type: " + name); } } /** * Functor that evaluates a condition. * * @param Argument type */ private interface Condition { /** * Evaluates the condition. * @param t Argument * @return Whether condition holds */ boolean test(T t); } } // End MondrianFoodMartLoader.java mondrian-3.4.1/testsrc/main/mondrian/test/loader/CsvDBLoader.java0000644000175000017500000011127011735330606024632 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.test.loader; import java.io.*; import java.util.*; import java.util.regex.Pattern; /** * Implementation of {@link DBLoader} which gets its Tables by reading CSV files * using the {@link CsvLoader} class and is the loader use for CSV junit tests. * *

* CsvDBLoader requires that the CSV files have a specific format * as defined: * *

 * list_of_csv_files : (csv_file)+
 * csv_file: table_definitions
 * table_definitions: (table_definition)+
 * table_definition: actions table_name column_names column_types
 *          (file_name or nos_of_rows or rows)
 * actions: (action)*
 * action: '##' (ActionBefore: | ActionAfter:) action_type
 * action_type: DropIndex index_name | CreateIndex index_name column_name
 * table_name: '##' TableName: table_name
 * column_names: '##' ColumnNames: column_name (',' column_name)*
 * column_types: '##' ColumnTypes: column_types ('.' column_types)*
 * file_name:'##' FileName: relative_filename ?
 * nos_of_rows:'##' NosOfRows: number
 * column_types: type (':' null)
 * type: "INTEGER" "DECIMAL(*,*)" "SMALLINT"
 *       "VARCHAR(*)" "REAL" "BOOLEAN"
 *       "BIGINT" "DATE" "TIMESTAMP"
 *  rows: (row)*
 *  row: value (',' value)*
 *
 *  if FileName is given, then
 *      there is no NosOfRows
 *      the file can only contains rows
 *  else if NosOfRows is given, then
 *      there is no FileName
 *      the number of rows in current file are rows for table
 *  else
 *      the all remaining rows in current file are rows for table
 *  fi
 *
 *  comment lines start with '#'
 *
 * 
*

* See the testsrc/main/mondrian/rolap/aggmatcher/BUG_1541077.csv file * for an example. * * * @author Richard M. Emberson */ public class CsvDBLoader extends DBLoader { public static final String ACTION_BEFORE_TAG = "ActionBefore:"; public static final String ACTION_AFTER_TAG = "ActionAfter:"; public static final String DROP_INDEX_TAG = "DropIndex"; public static final String CREATE_INDEX_TAG = "CreateIndex"; public static final String TABLE_NAME_TAG = "TableName:"; public static final String COLUMN_NAMES_TAG = "ColumnNames:"; public static final String COLUMN_TYPES_TAG = "ColumnTypes:"; public static final String FILE_NAME_TAG = "FileName:"; public static final String NOS_OF_ROWS_TAG = "NosOfRows:"; private File inputDirectory; private String inputDirectoryRegex; private File[] inputFiles; private File inputFile; public CsvDBLoader() { super(); } public void setInputDirectory(File inputDirectory) { this.inputDirectory = inputDirectory; } public File getInputDirectory() { return this.inputDirectory; } public void setInputDirectoryRegex(String inputDirectoryRegex) { this.inputDirectoryRegex = inputDirectoryRegex; } public String getInputDirectoryRegex() { return this.inputDirectoryRegex; } public void setInputFiles(File[] inputFiles) { this.inputFiles = inputFiles; } public File[] getInputFiles() { return this.inputFiles; } public void setInputFile(File inputFile) { this.inputFile = inputFile; } public File getInputFile() { return this.inputFile; } public Table[] getTables() throws Exception { initialize(); if (this.inputDirectory != null) { return getTablesFromDirectory(); } else if (this.inputFiles != null) { return getTablesFromFiles(); } else if (this.inputFile != null) { return getTablesFromFile(); } else { return new Table[0]; } } public Table[] getTablesFromDirectory() throws Exception { File[] files; if (this.inputDirectoryRegex == null) { files = this.inputDirectory.listFiles(); } else { final Pattern pat = Pattern.compile(this.inputDirectoryRegex); files = this.inputDirectory.listFiles( new FilenameFilter() { public boolean accept(File dir, String name) { return pat.matcher(name).matches(); } } ); if (files == null) { files = new File[0]; } } return getTables(files); } public Table[] getTablesFromFiles() throws Exception { return getTables(this.inputFiles); } public Table[] getTablesFromFile() throws Exception { List list = new ArrayList
(); loadTables(this.inputFile, list); return list.toArray(new Table[list.size()]); } public Table[] getTables(File[] files) throws Exception { List
list = new ArrayList
(); for (File file : files) { loadTables(file, list); } return list.toArray(new Table[list.size()]); } public Table[] getTables(Reader reader) throws Exception { List
list = new ArrayList
(); loadTables(reader, list); return list.toArray(new Table[list.size()]); } public static class ListRowStream implements RowStream { private List list; ListRowStream() { this(new ArrayList()); } ListRowStream(List list) { this.list = list; } void add(Row row) { this.list.add(row); } public Iterator iterator() { return this.list.iterator(); } } public static class CsvLoaderRowStream implements RowStream { private final CsvLoader csvloader; CsvLoaderRowStream(CsvLoader csvloader) { this.csvloader = csvloader; } public Iterator iterator() { return new Iterator() { String[] line; public boolean hasNext() { try { boolean hasNext = CsvLoaderRowStream.this.csvloader.hasNextLine(); if (! hasNext) { CsvLoaderRowStream.this.csvloader.close(); } else { line = CsvLoaderRowStream.this.csvloader.nextLine(); // remove comment lines if (line.length > 0 && line[0].length() > 0 && line[0].startsWith("#")) { return hasNext(); } } return hasNext; } catch (IOException ex) { // } return false; } public Row next() { return new RowDefault(line); /* return new RowDefault( CsvLoaderRowStream.this.csvloader.nextLine()); */ } public void remove() { } }; } } /** * Looks for a file relative to the current directory and all of its * parent directories. This gives a little flexibility if you invoke a * test in a sub-directory. * * @param file File * @return File within current directory or a parent directory. */ private static File resolveFile(File file) { if (file.isAbsolute()) { return file; } else { File base = new File(System.getProperty("user.dir")); while (base != null) { File file2 = new File(base, file.getPath()); if (file2.exists()) { return file2; } base = base.getParentFile(); } } return file; } public void loadTables(File file, List
tableList) throws Exception { Reader reader = new FileReader(resolveFile(file)); loadTables(reader, tableList); } public void loadTables(Reader reader, List
tableList) throws Exception { CsvLoader csvloader = null; try { Table table = null; List beforeActionList = new ArrayList(); List afterActionList = new ArrayList(); String tableName = null; String[] columnNames = null; String[] columnTypes = null; String fileName = null; int nosOfRowsStr = -1; boolean ok = false; Column[] columns; csvloader = new CsvLoader(reader); int lineNos = 0; while (csvloader.hasNextLine()) { String[] values = csvloader.nextLine(); lineNos++; //System.out.println("CsvLoader.loadTables: lineNos=" +lineNos); if (values.length == 0) { continue; } String value0 = values[0]; //System.out.println("CsvLoader.loadTables: value0=" +value0); if (value0.startsWith("##") && (fileName == null)) { if (table != null) { table = null; beforeActionList.clear(); afterActionList.clear(); tableName = null; columnNames = null; columnTypes = null; fileName = null; nosOfRowsStr = -1; } // meta info int index = value0.indexOf(ACTION_BEFORE_TAG); if (index != -1) { String s = value0.substring( index + ACTION_BEFORE_TAG.length()); if (s.length() == 0) { throw new IOException( "CSV File parse Error: " + " no action before sql" + ", linenos " + lineNos); } s = s.trim(); if (! s.startsWith(DROP_INDEX_TAG)) { // only support dropping indexes currently throw new IOException( "CSV File parse Error: " + " unknown before action" + s + ", linenos " + lineNos); } // get index name index = s.indexOf(' '); if (index < 0) { // only support dropping indexes currently throw new IOException( "CSV File parse Error: " + " no index name in before action" + s + ", linenos " + lineNos); } s = s.substring(index + 1); s = s.trim(); beforeActionList.add(s); continue; } index = value0.indexOf(ACTION_AFTER_TAG); if (index != -1) { String s = value0.substring( index + ACTION_AFTER_TAG.length()); if (s.length() == 0) { throw new IOException( "CSV File parse Error: " + " no action after sql" + ", linenos " + lineNos); } s = s.trim(); if (! s.startsWith(CREATE_INDEX_TAG)) { // only support creating indexes currently throw new IOException( "CSV File parse Error: " + " unknown before action" + s + ", linenos " + lineNos); } // get index name index = s.indexOf(' '); if (index < 0) { // only support creating indexes currently throw new IOException( "CSV File parse Error: " + " no index_name/column_name in after action" + s + ", linenos " + lineNos); } // CreateIndex index_name column_name s = s.substring(index + 1); s = s.trim(); index = s.indexOf(' '); // just check that there is a space and if (index < 0) { // only support creating indexes currently throw new IOException( "CSV File parse Error: " + " no column_name after index_name " + "in after action" + s + ", linenos " + lineNos); } afterActionList.add(s); continue; } index = value0.indexOf(TABLE_NAME_TAG); if (index != -1) { String s = value0.substring( index + TABLE_NAME_TAG.length()); if (s.length() == 0) { throw new IOException( "CSV File parse Error: " + " no table name" + ", linenos " + lineNos); } s = s.trim(); if (tableName != null) { throw new IOException( "CSV File parse Error: " + " new table name \"" + s + "\" while processing table name \"" + tableName + "\", linenos " + lineNos); } tableName = s; continue; } index = value0.indexOf(COLUMN_NAMES_TAG); if (index != -1) { String s = value0.substring( index + COLUMN_NAMES_TAG.length()); if (s.length() == 0) { throw new IOException( "CSV File parse Error: " + " no column names" + ", linenos " + lineNos); } if (tableName == null) { throw new IOException( "CSV File parse Error: " + " no table name for columns " + ", linenos " + lineNos); } values[0] = s.trim(); columnNames = values; continue; } index = value0.indexOf(COLUMN_TYPES_TAG); if (index != -1) { String s = value0.substring( index + COLUMN_TYPES_TAG.length()); if (s.length() == 0) { throw new IOException( "CSV File parse Error: " + " no column types" + ", linenos " + lineNos); } if (columnNames == null) { throw new IOException( "CSV File parse Error: " + " no column names for columns types" + ", linenos " + lineNos); } values[0] = s.trim(); columnTypes = values; if (columnNames.length != columnTypes.length) { throw new IOException( "CSV File parse Error: " + " number of column names \"" + columnNames.length + "\" does not equal " + " number of column types \"" + columnTypes.length + "\", linenos " + lineNos); } continue; } ok = true; index = value0.indexOf(FILE_NAME_TAG); if (index != -1) { String s = value0.substring( index + FILE_NAME_TAG.length()); if (s.length() == 0) { throw new IOException( "CSV File parse Error: " + " no file name " + ", linenos " + lineNos); } if (columnTypes == null) { throw new IOException( "CSV File parse Error: " + " no column types for file name" + ", linenos " + lineNos); } fileName = s.trim(); continue; } index = value0.indexOf(NOS_OF_ROWS_TAG); if (index != -1) { String s = value0.substring( index + NOS_OF_ROWS_TAG.length()); if (s.length() == 0) { throw new IOException( "CSV File parse Error: " + " no number of rows" + ", linenos " + lineNos); } if (columnTypes == null) { throw new IOException( "CSV File parse Error: " + " no column types for file name" + ", linenos " + lineNos); } nosOfRowsStr = Integer.parseInt(s.trim()); continue; } } else if (value0.startsWith("# ")) { // comment, do nothing } else { //System.out.println("CsvLoader.loadTables: ELSE"); // rows if (! ok) { if (tableName == null) { throw new IOException( "CSV File parse Error: " + " no table name before rows" + ", linenos " + lineNos); } if (columnNames == null) { throw new IOException( "CSV File parse Error: " + " no column names before rows" + ", linenos " + lineNos); } if (columnTypes == null) { throw new IOException( "CSV File parse Error: " + " no column types before rows" + ", linenos " + lineNos); } } columns = loadColumns(columnNames, columnTypes, lineNos); table = new Table(tableName, columns); table.setBeforeActions(beforeActionList); table.setAfterActions(afterActionList); tableList.add(table); Table.Controller controller = table.getController(); if (fileName != null) { //System.out.println("CsvLoader.loadTables: fileName="+fileName); RowStream rowStream = new CsvLoaderRowStream( new CsvLoader(fileName)); controller.setRowStream(rowStream); csvloader.nextSet(); csvloader.putBack(values); //System.out.println("CsvLoader.loadTables: fileName putback0=" +values[0]); lineNos--; fileName = null; //System.out.println("CsvLoader.loadTables: fileName OK"); } else if (nosOfRowsStr != -1) { //System.out.println("CsvLoader.loadTables: nosOfRowsStr="+nosOfRowsStr); List list = new ArrayList(); list.add(new RowDefault(values)); nosOfRowsStr--; while (nosOfRowsStr-- > 0) { if (! csvloader.hasNextLine()) { throw new Exception( "CSV File parse Error: " + " not enough lines in file " + lineNos); } values = csvloader.nextLine(); value0 = values[0]; if (value0.startsWith("# ")) { nosOfRowsStr++; continue; } //System.out.println("CsvLoader.loadTables: v0="+values[0]); list.add(new RowDefault(values)); lineNos++; } RowStream rowStream = new ListRowStream(list); controller.setRowStream(rowStream); csvloader.nextSet(); //System.out.println("CsvLoader.loadTables: nosOfRowsStr OK"); } else { //System.out.println("CsvLoader.loadTables: else"); csvloader.putBack(values); RowStream rowStream = new CsvLoaderRowStream(csvloader); controller.setRowStream(rowStream); csvloader = null; break; } } } } finally { if (csvloader != null) { csvloader.close(); } } //System.out.println("CsvLoader.loadTables: BOTTOM:"); } protected Column[] loadColumns( String[] columnNames, String[] columnTypes, int lineNos) throws Exception { List list = new ArrayList(); for (int i = 0; i < columnNames.length; i++) { String columnName = columnNames[i]; //System.out.println("columnName="+columnName); String columnType = columnTypes[i]; //System.out.println("columnType="+columnType); int index = columnType.indexOf(':'); //System.out.println("index="+index); Type type; boolean nullsAllowed = false; if (index != -1) { String nullString = columnType.substring(index + 1).trim(); if (nullString.equalsIgnoreCase("NULL")) { nullsAllowed = true; } else { throw new IOException( "CSV File parse Error: " + " for type name \"" + columnType + "\" expecting \"null\" not \"" + nullString + "\", linenos " + lineNos); } columnType = columnType.substring(0, index).trim(); //System.out.println("columnType="+columnType); type = Type.getType(columnType); //System.out.println("nullsAllowed="+nullsAllowed); } else { type = Type.getType(columnType); } if (type == null) { type = Type.makeType(columnType); } if (type == null) { throw new IOException( "CSV File parse Error: " + " no type found for type name \"" + columnType + "\", linenos " + lineNos); } Column column = new Column(columnName, type, nullsAllowed); list.add(column); } return list.toArray(new Column[list.size()]); } protected void check() throws Exception { super.check(); if (this.inputDirectory != null) { if (this.inputFiles != null) { throw new Exception( "Both input Directory and input files can not be set"); } if (this.inputFile != null) { throw new Exception( "Both input Directory and input file can not be set"); } } if (this.inputFiles != null) { if (this.inputFile != null) { throw new Exception( "Both input input files and input file can not be set"); } } } protected static File checkDirectory(String dirName) throws Exception { File dir = new File(dirName); if (! dir.exists()) { throw new Exception( "The directory \"" + dirName + "\" does not exist"); } if (! dir.isDirectory()) { throw new Exception( "The file \"" + dirName + "\" is not a directory"); } return dir; } protected static void usage(String msg) { StringBuilder buf = new StringBuilder(500); if (msg != null) { buf.append(msg); buf.append(nl); } buf.append("Usage: CsvDBLoader "); buf.append(" [-h or -help]"); buf.append(" (-p "); buf.append(" or -p=)"); buf.append(" (-jdbcDrivers "); buf.append(" or -jdbcDrivers=)"); buf.append(" (-jdbcURL "); buf.append(" or -jdbcURL=)"); buf.append(" [-user username"); buf.append(" or -user=username]"); buf.append(" [-password password"); buf.append(" or -password=password]"); buf.append(" [-BatchSize "); buf.append(" or -BatchSize=]"); buf.append(" [-outputDirectory "); buf.append(" or -outputDirectory ]"); buf.append(" [-f or -force]"); buf.append(" [-inputDirectory "); buf.append(" or -inputDirectory ]"); buf.append(" [-regex "); buf.append(" or -regex ]"); buf.append(" (inputFiles)+"); buf.append(nl); buf.append("Options:"); buf.append(nl); buf.append( " A property file which can be used to"); buf.append(nl); buf.append(" to set some of the options."); buf.append(nl); buf.append( " Comma-separated list of JDBC drivers;"); buf.append(nl); buf.append(" JDBC connect string for DB."); buf.append(nl); buf.append(" [username] JDBC user name for DB."); buf.append(nl); buf.append(" [password] JDBC password for user for DB."); buf.append(nl); buf.append( " " + "Size of JDBC batch updates - default to 50 inserts."); buf.append(nl); buf.append( " [outputDirectory] " + "Directory where per-table sql should be put"); buf.append(nl); buf.append(" rather than loading the database."); buf.append(nl); buf.append( " [force] " + "If output files already exist, delete them"); buf.append(nl); buf.append(" [inputDirectory] Directory containing input files"); buf.append(nl); buf.append( " [regular expression] A regular expression used to determine"); buf.append(nl); buf.append( " " + "which files in the input directory to use."); buf.append(nl); buf.append("The values in the property file are overridden by"); buf.append(nl); buf.append(" the explicit command line options"); System.out.println(buf.toString()); System.exit((msg == null) ? 0 : 1); } public static void main(String[] args) throws Exception { String propFile = null; String jdbcDrivers = null; String jdbcURL = null; String user = null; String password = null; String batchSizeStr = null; String outputDirectory = null; boolean force = false; List files = new ArrayList(); String inputDirectory = null; String regex = null; for (int i = 0; i < args.length; i++) { String arg = args[i]; if (arg.equals("-h") || arg.equals("-help")) { usage(null); } else if (arg.equals("-f") || arg.equals("-force")) { force = true; } else if (arg.startsWith("-p=")) { propFile = args[i].substring("-p=".length()); } else if (arg.equals("-p")) { if (++i == args.length) { usage("Missing argument for -p"); } arg = args[i]; if (arg.startsWith("-")) { usage("Bad argument for -p: " + arg); } propFile = arg; } else if (arg.startsWith("-jdbcDrivers=")) { jdbcDrivers = args[i].substring("-jdbcDrivers=".length()); } else if (arg.equals("-jdbcDrivers")) { if (++i == args.length) { usage("Missing argument for -jdbcDrivers"); } arg = args[i]; if (arg.startsWith("-")) { usage("Bad argument for -jdbcDrivers: " + arg); } jdbcDrivers = arg; } else if (arg.startsWith("-jdbcURL=")) { jdbcURL = args[i].substring("-jdbcURL=".length()); } else if (arg.equals("-jdbcURL")) { if (++i == args.length) { usage("Missing argument for -jdbcURL"); } arg = args[i]; if (arg.startsWith("-")) { usage("Bad argument for -jdbcURL: " + arg); } jdbcURL = arg; } else if (arg.startsWith("-user=")) { user = args[i].substring("-user=".length()); } else if (arg.equals("-user")) { if (++i == args.length) { usage("Missing argument for -user"); } arg = args[i]; if (arg.startsWith("-")) { usage("Bad argument for -user: " + arg); } user = arg; } else if (arg.startsWith("-password=")) { password = args[i].substring("-password=".length()); } else if (arg.equals("-password")) { if (++i == args.length) { usage("Missing argument for -password"); } arg = args[i]; if (arg.startsWith("-")) { usage("Bad argument for -password: " + arg); } password = arg; } else if (arg.startsWith("-batchSize=")) { batchSizeStr = args[i].substring("-batchSize=".length()); } else if (arg.equals("-batchSize")) { if (++i == args.length) { usage("Missing argument for -batchSize"); } arg = args[i]; if (arg.startsWith("-")) { usage("Bad argument for -batchSize: " + arg); } batchSizeStr = arg; } else if (arg.startsWith("-outputDirectory=")) { outputDirectory = args[i].substring("-outputDirectory=".length()); } else if (arg.equals("-outputDirectory")) { if (++i == args.length) { usage("Missing argument for -outputDirectory"); } arg = args[i]; if (arg.startsWith("-")) { usage("Bad argument for -outputDirectory: " + arg); } outputDirectory = arg; } else if (arg.startsWith("-inputDirectory=")) { inputDirectory = args[i].substring("-inputDirectory=".length()); } else if (arg.equals("-inputDirectory")) { if (++i == args.length) { usage("Missing argument for -inputDirectory"); } arg = args[i]; if (arg.startsWith("-")) { usage("Bad argument for -inputDirectory: " + arg); } inputDirectory = arg; } else if (arg.startsWith("-regex=")) { regex = args[i].substring("-regex=".length()); } else if (arg.equals("-regex")) { if (++i == args.length) { usage("Missing argument for -regex"); } arg = args[i]; if (arg.startsWith("-")) { usage("Bad argument for -regex: " + arg); } regex = arg; } else if (arg.startsWith("-")) { usage("Bad option : " + arg); } else { File file = new File(arg); if (! file.exists()) { String msg = "The file \"" + arg + "\" does not exist"; throw new Exception(msg); } files.add(file); } } CsvDBLoader loader = new CsvDBLoader(); if (propFile != null) { Properties props = new Properties(); props.load(new FileInputStream(propFile)); String v = props.getProperty(BATCH_SIZE_PROP); if (v != null) { loader.setBatchSize(Integer.parseInt(v)); } v = props.getProperty(JDBC_DRIVER_PROP); if (v != null) { loader.setJdbcDriver(v); } v = props.getProperty(JDBC_URL_PROP); if (v != null) { loader.setJdbcURL(v); } v = props.getProperty(JDBC_USER_PROP); if (v != null) { loader.setUserName(v); } v = props.getProperty(JDBC_PASSWORD_PROP); if (v != null) { loader.setPassword(v); } v = props.getProperty(OUTPUT_DIRECTORY_PROP); if (v != null) { File dir = checkDirectory(v); loader.setOutputDirectory(dir); } v = props.getProperty(FORCE_PROP); if (v != null) { force = Boolean.valueOf(v); } } if (batchSizeStr != null) { loader.setBatchSize(Integer.parseInt(batchSizeStr)); } if (jdbcDrivers != null) { loader.setJdbcDriver(jdbcDrivers); } if (jdbcURL != null) { loader.setJdbcURL(jdbcURL); } if (user != null) { loader.setUserName(user); } if (password != null) { loader.setPassword(password); } if (outputDirectory != null) { File dir = checkDirectory(outputDirectory); loader.setOutputDirectory(dir); } loader.setForce(force); if (files.size() == 1) { loader.setInputFile(files.get(0)); } else if (files.size() > 1) { loader.setInputFiles(files.toArray(new File[files.size()])); } if (inputDirectory != null) { File dir = checkDirectory(inputDirectory); loader.setInputDirectory(dir); } if (regex != null) { loader.setInputDirectoryRegex(regex); } loader.initialize(); Table[] tables = loader.getTables(); loader.generateStatements(tables); loader.executeStatements(tables); } } // End CsvDBLoader.java mondrian-3.4.1/testsrc/main/mondrian/test/loader/README.txt0000644000175000017500000000316111735330606023374 0ustar drazzibdrazzib MondrianDataLoader README ========================= java mondrian.test.loader.MondrianDataLoader [-verbose] [-tables] [-data] [-indexes] -jdbcDrivers= -outputJdbcURL= [-outputJdbcUser=user] [-outputJdbcPassword=password] [-outputJdbcBatchSize=100] [-outputDirectory=] [ [-inputJdbcURL= [-inputJdbcUser=user] [-inputJdbcPassword=password]] | [-inputfile=]] Examples ======== # load from Access DB to create SQL scripts in the output directory. This directory will contain files: createTables.sql, createData.sql, createIndexes.sql in the dialect of SQL indicated by the outputJdbcURL -verbose -tables -data -indexes -jdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver,org.postgresql.Driver -inputJdbcURL=jdbc:odbc:MondrianFoodMart -outputJdbcURL=jdbc:postgresql://localhost/FM2 -outputJdbcUser=postgres -outputJdbcPassword=pgAdmin -outputDirectory=C:\Temp\wip # load from Access DB to MySQL -verbose -tables -data -indexes -jdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver,com.mysql.jdbc.Driver -inputJdbcURL=jdbc:odbc:MondrianFoodMart -outputJdbcURL=jdbc:mysql://localhost/fm2?user=root&password=myAdmin # load from named file containing insert statements to output JDBC connection (Postgres) -verbose -data -jdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver,org.postgresql.Driver -inputFile=C:\Temp\wip\createData.sql -outputJdbcURL=jdbc:postgresql://localhost/FM3 -outputJdbcUser=postgres -outputJdbcPassword=pgAdmin -verbose -data -indexes -jdbcDrivers=com.mysql.jdbc.Driver -inputFile=C:\Temp\wip\Loader-Output\createData.sql -outputJdbcURL=jdbc:mysql://localhost/textload?user=root&password=myAdmin mondrian-3.4.1/testsrc/main/mondrian/test/loader/DBLoader.java0000644000175000017500000014363611735330606024171 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.test.loader; import mondrian.olap.Util; import mondrian.resource.MondrianResource; import mondrian.rolap.RolapUtil; import mondrian.spi.Dialect; import mondrian.spi.DialectManager; import org.apache.log4j.Logger; import java.io.*; import java.math.BigDecimal; import java.sql.*; import java.sql.Date; import java.text.*; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * This is an abstract base class for the creation and load of one or more * database tables with data. Optionally, if a table already exists it can be * dropped or its rows deleted. *

* Within this class is the Table sub-class. There is an abstract method the * returns an array of Table instances. A Table instance is used to define * a database table and its content (its rows or data). A concrete subclass * of this class can define how the array of Tables are created (CSV files, * XML files, by reading another database, etc.) and then using this * class load thoses Tables into a database. In addition, rather than loading * a database, this class can be used to generate files containing the * sql that can be used to load the database. *

* To use this class the following must be specified: *

    *
  • * JDBC Driver: This is used both for the generation of the SQL (using the * SqlQuery.Dialect) and loading the database itself. *
  • *
  • * JDBC URL: How to connect to the database. *
  • *
  • * User Name: The database user name (optional). *
  • *
  • * Password: The database password (optional). *
  • *
  • * Output Directory: If specified, then rather than creating and loading the * tables in the database, files containing database specific SQL statements are * generated. The names of the files are based upon the table name plus * a suffix. * Each table has four files for the different SQL operations: drop the table, * drop the rows in the table, create the table and load the rows in the table. * The suffixes have default values which can be overriden the the value * of System properties: *
      *
    • * The property "mondrian.test.loader.drop.table.suffix" can be used to * define the drop table suffix. The default value is "drop.sql". *
    • *
    • * The property "mondrian.test.loader.drop.table.rows.suffix" can be used to * define the drop rows table suffix. The default value is "droprows.sql". *
    • *
    • * The property "mondrian.test.loader.create.table.suffix" can be used to * define the create table suffix. The default value is "create.sql". *
    • *
    • * The property "mondrian.test.loader.table.rows.suffix" can be used to * define the create table suffix. The default value is "loadrows.sql". *
    • *
    *
  • *
  • * Force: If files are being generated and if they already exist, setting the * force flag to true instructs this class to over-write the existing file. * If the force flag is false and a file already exists and exception is thrown. *
  • *
*

* Each Table object created has a Controller object with four boolean instance * variables that control what actions are taken when the Table's * executeStatements method is called. Those instance variables are: *

    *
  • * DropTable: If true, the table is dropped from the database * (Default value is true). *
  • *
  • * DropRows: If true, the rows in the table are dropped * (Default value is true). *
  • *
  • * CreateTable: If true, the table is created in the database * (Default value is true). *
  • *
  • * LoadRows: If true, the rows are loaded into the table * (Default value is true). *
  • *
*

* The Table.Controller must also have its RowStream object defined. * A RowStream produces a set of Row objects (see the Row interface * below) which in turn has an array of Objects that represent the * values of a row in the Table. * The default RowStream is an emtpy RowStrean, no rows. The user * must implement the RowStrean interface. One such implementation might * be a RowStrean containing a list of rows. In this case, all of the * rows would be in memory. Another implementation might read each row's * data from a file or another database. In this case, the row data is * not in memory allowing one to load much larger tables. *

* Each column must have one of the following SQL data type definitions: *

    *
  • * INTEGER *
  • *
  • * DECIMAL(*,*) *
  • *
  • * SMALLINT *
  • *
  • * VARCHAR(*) *
  • *
  • * REAL *
  • *
  • * BOOLEAN *
  • *
  • * BIGINT *
  • *
  • * DATE *
  • *
  • * TIMESTAMP *
  • *
*

* NOTE: Much of the code appearing in this class came from the * MondrianFoodMartLoader class. * * @author Richard M. Emberson */ public abstract class DBLoader { protected static final Logger LOGGER = Logger.getLogger(DBLoader.class); public static final String nl = System.getProperty("line.separator"); private static final int DEFAULT_BATCH_SIZE = 50; public static final String BATCH_SIZE_PROP = "mondrian.test.loader.batch.size"; public static final String JDBC_DRIVER_PROP = "mondrian.test.loader.jdbc.driver"; public static final String JDBC_URL_PROP = "mondrian.test.loader.jdbc.url"; public static final String JDBC_USER_PROP = "mondrian.test.loader.jdbc.user"; public static final String JDBC_PASSWORD_PROP = "mondrian.test.loader.jdbc.password"; public static final String OUTPUT_DIRECTORY_PROP = "mondrian.test.loader.output.directory"; public static final String FORCE_PROP = "mondrian.test.loader.force"; // suffixes of output files public static final String DROP_TABLE_INDEX_PROP = "mondrian.test.loader.drop.table.index.suffix"; public static final String DROP_TABLE_INDEX_SUFFIX_DEFAULT = "dropindex.sql"; public static final String CREATE_TABLE_INDEX_PROP = "mondrian.test.loader.create.table.index.suffix"; public static final String CREATE_TABLE_INDEX_SUFFIX_DEFAULT = "createindex.sql"; public static final String DROP_TABLE_PROP = "mondrian.test.loader.drop.table.suffix"; public static final String DROP_TABLE_SUFFIX_DEFAULT = "drop.sql"; public static final String DROP_TABLE_ROWS_PROP = "mondrian.test.loader.drop.table.rows.suffix"; public static final String DROP_TABLE_ROWS_SUFFIX_DEFAULT = "droprows.sql"; public static final String CREATE_TABLE_PROP = "mondrian.test.loader.create.table.suffix"; public static final String CREATE_TABLE_SUFFIX_DEFAULT = "create.sql"; public static final String LOAD_TABLE_ROWS_PROP = "mondrian.test.loader.load.table.rows.suffix"; public static final String LOAD_TABLE_ROWS_SUFFIX_DEFAULT = "loadrows.sql"; static final Pattern decimalDataTypeRegex = Pattern.compile("DECIMAL\\((.*),(.*)\\)"); static final Pattern varcharDataTypeRegex = Pattern.compile("VARCHAR\\((.*)\\)"); static final DecimalFormat integerFormatter = new DecimalFormat(decimalFormat(15, 0)); static final String dateFormatString = "yyyy-MM-dd"; static final String oracleDateFormatString = "YYYY-MM-DD"; static final DateFormat dateFormatter = new SimpleDateFormat(dateFormatString); /** * Generate an appropriate number format string for doubles etc * to be used to include a number in an SQL insert statement. * * Calls decimalFormat(int length, int places) to do the work. * * @param lengthStr String representing integer: number of digits to format * @param placesStr String representing integer: number of decimal places * @return number format, ie. length = 6, places = 2 => "####.##" */ public static String decimalFormat(String lengthStr, String placesStr) { int length = Integer.parseInt(lengthStr); int places = Integer.parseInt(placesStr); return decimalFormat(length, places); } /** * Generate an appropriate number format string for doubles etc * to be used to include a number in an SQL insert statement. * * @param length int: number of digits to format * @param places int: number of decimal places * @return number format, ie. length = 6, places = 2 => "###0.00" */ public static String decimalFormat(int length, int places) { StringBuilder buf = new StringBuilder(); for (int i = 0; i < length; i++) { if ((length - i) == places) { buf.append('.'); } if ((length - i) <= (places + 1)) { buf.append("0"); } else { buf.append("#"); } } return buf.toString(); } /** * The RowStream interface allows one to load large sets of * rows by streaming them in, one does not have to have all * of the row data in memory. */ public interface RowStream { Iterator iterator(); } static final RowStream EMPTY_ROW_STREAM = new RowStream() { public Iterator iterator() { List list = Collections.emptyList(); return list.iterator(); } }; public class Table { public class Controller { private boolean dropTable; private boolean dropRows; private boolean createTable; private boolean loadRows; private RowStream rowStream; private Controller() { this.dropTable = true; this.dropRows = true; this.createTable = true; this.loadRows = true; this.rowStream = EMPTY_ROW_STREAM; } public boolean shouldDropTable() { return this.dropTable; } public void setShouldDropTable(boolean dropTable) { this.dropTable = dropTable; } public boolean shouldDropTableRows() { return this.dropRows; } public void setShouldDropTableRows(boolean dropRows) { this.dropRows = dropRows; } public boolean createTable() { return this.createTable; } public void setCreateTable(boolean createTable) { this.createTable = createTable; } public boolean loadRows() { return this.loadRows; } public void setloadRows(boolean loadRows) { this.loadRows = loadRows; } public void setRowStream(RowStream rowStream) { this.rowStream = (rowStream == null) ? EMPTY_ROW_STREAM : rowStream; } public Iterator rows() { return this.rowStream.iterator(); } } private final String name; private final Column[] columns; private final Controller controller; private String dropTableStmt; private String dropTableRowsStmt; private String createTableStmt; private List beforeActionList; private List afterActionList; public Table(String name, Column[] columns) { this.name = name; this.columns = columns; this.controller = new Controller(); this.beforeActionList = Collections.emptyList(); this.afterActionList = Collections.emptyList(); } public String getName() { return this.name; } public String getDropTableStmt() { return this.dropTableStmt; } public void setDropTableStmt(String dropTableStmt) { this.dropTableStmt = dropTableStmt; } public String getDropTableRowsStmt() { return this.dropTableRowsStmt; } public void setDropTableRowsStmt(String dropTableRowsStmt) { this.dropTableRowsStmt = dropTableRowsStmt; } public String getCreateTableStmt() { return this.createTableStmt; } public void setCreateTableStmt(String createTableStmt) { this.createTableStmt = createTableStmt; } public void setBeforeActions(List beforeActionList) { if (! beforeActionList.isEmpty()) { if (this.beforeActionList == Collections.EMPTY_LIST) { this.beforeActionList = new ArrayList(); } this.beforeActionList.addAll(beforeActionList); } } public void setAfterActions(List afterActionList) { if (! afterActionList.isEmpty()) { if (this.afterActionList == Collections.EMPTY_LIST) { this.afterActionList = new ArrayList(); } this.afterActionList.addAll(afterActionList); } } public List getBeforeActions() { return this.beforeActionList; } public List getAfterActions() { return this.afterActionList; } public Column[] getColumns() { return this.columns; } public Controller getController() { return this.controller; } public void executeStatements() throws Exception { DBLoader.this.executeStatements(this); } } public interface Row { Object[] values(); } public static class RowDefault implements Row { private final Object[] values; public RowDefault(Object[] values) { this.values = values; } public Object[] values() { return this.values; } } public static class Column { private final String name; private final Type type; private String typeName; private final boolean canBeNull; public Column(String name, Type type, boolean canBeNull) { this.name = name; this.type = type; this.canBeNull = canBeNull; } public void init(Dialect dialect) { this.typeName = type.toPhysical(dialect); } public String getName() { return this.name; } public Type getType() { return this.type; } public String getTypeName() { return this.typeName; } public boolean canBeNull() { return this.canBeNull; } public String getConstraint() { return canBeNull ? "" : "NOT NULL"; } } /** * Represents a logical type, such as "BOOLEAN".

* * Specific databases will represent this their own particular physical * type, for example "TINYINT(1)", "BOOLEAN" or "BIT"; * see {@link #toPhysical(mondrian.spi.Dialect)}. */ public static class Type { public static final Type Integer = new Type("INTEGER"); public static final Type Decimal = new Type("DECIMAL(10,4)"); public static final Type Smallint = new Type("SMALLINT"); public static final Type Varchar30 = new Type("VARCHAR(30)"); public static final Type Varchar255 = new Type("VARCHAR(255)"); public static final Type Varchar60 = new Type("VARCHAR(60)"); public static final Type Real = new Type("REAL"); public static final Type Boolean = new Type("BOOLEAN"); public static final Type Bigint = new Type("BIGINT"); public static final Type Date = new Type("DATE"); // yyyy-mm-dd hh:mm:ss.fffffffff public static final Type Timestamp = new Type("TIMESTAMP"); public static final Map extraTypes = new HashMap(); public static Type getType(String typeName) { String upperCaseTypeName = typeName.toUpperCase(); if (upperCaseTypeName.equals("INTEGER")) { return Type.Integer; } else if (upperCaseTypeName.equals("INT")) { return Type.Integer; } else if (upperCaseTypeName.equals("DECIMAL(10,4)")) { return Type.Decimal; } else if (upperCaseTypeName.equals("SMALLINT")) { return Type.Smallint; } else if (upperCaseTypeName.equals("VARCHAR(30)")) { return Type.Varchar30; } else if (upperCaseTypeName.equals("VARCHAR(255)")) { return Type.Varchar255; } else if (upperCaseTypeName.equals("VARCHAR(60)")) { return Type.Varchar60; } else if (upperCaseTypeName.equals("REAL")) { return Type.Real; } else if (upperCaseTypeName.equals("BOOLEAN")) { return Type.Boolean; } else if (upperCaseTypeName.equals("BOOL")) { return Type.Boolean; } else if (upperCaseTypeName.equals("BIGINT")) { return Type.Bigint; } else if (upperCaseTypeName.equals("DATE")) { return Type.Date; } else if (upperCaseTypeName.equals("TIMESTAMP")) { return Type.Timestamp; } else { return extraTypes.get(upperCaseTypeName); } } public static Type makeType(String typeName) { // only call after calling getType above fails // it must either be a DECIMAL or VARCHAR type Type type = null; String upperCaseTypeName = typeName.toUpperCase(); Matcher matcher = decimalDataTypeRegex.matcher(upperCaseTypeName); if (matcher.matches()) { type = new Type(upperCaseTypeName); extraTypes.put(upperCaseTypeName, type); return type; } matcher = varcharDataTypeRegex.matcher(upperCaseTypeName); if (matcher.matches()) { type = new Type(upperCaseTypeName); extraTypes.put(upperCaseTypeName, type); return type; } // failed to create new type return type; } /** * The name of this type. Immutable, and independent of the RDBMS. */ private final String name; public Type(String name) { this.name = name; } public String getName() { return this.name; } /** * Returns the physical type which a given RDBMS (dialect) uses to * represent this logical type. * * @param dialect Dialect * @return Physical type the dialect uses to represent this type */ public String toPhysical(Dialect dialect) { if (this == Integer || this == Decimal || this == Smallint || this == Varchar30 || this == Varchar60 || this == Varchar255 || this == Real) { return name; } if (this == Boolean) { switch (dialect.getDatabaseProduct()) { case POSTGRESQL: return name; case MYSQL: return "TINYINT(1)"; case MSSQL: return "BIT"; default: return Smallint.name; } } if (this == Bigint) { switch (dialect.getDatabaseProduct()) { case ORACLE: case FIREBIRD: return "DECIMAL(15,0)"; default: return name; } } if (this == Date) { switch (dialect.getDatabaseProduct()) { case MSSQL: return "DATETIME"; default: return name; } } if (this == Timestamp) { switch (dialect.getDatabaseProduct()) { case MSSQL: case MYSQL: return "DATETIME"; case INGRES: return "DATE"; default: return name; } } // for extra types if (name.startsWith("DECIMAL(")) { switch (dialect.getDatabaseProduct()) { case ACCESS: return "CURRENCY"; } return name; } else if (name.startsWith("VARCHAR(")) { return name; } throw new AssertionError("unexpected type: " + name); } } private String jdbcDriver; private String jdbcURL; private String userName; private String password; private Connection connection; private File outputDirectory; private boolean force; private Writer fileWriter; private Dialect dialect; private int batchSize; private boolean initialize; protected DBLoader() { this.batchSize = DEFAULT_BATCH_SIZE; this.fileWriter = null; } public abstract Table[] getTables() throws Exception; public void dropTables(Table[] tables) throws Exception { Exception firstEx = null; for (Table table : tables) { try { dropTable(table); } catch (Exception ex) { if (firstEx == null) { firstEx = ex; } } } if (firstEx != null) { throw firstEx; } } public void dropTable(Table table) throws Exception { String dropTableStmt = table.getDropTableStmt(); if (dropTableStmt != null) { executeDDL(dropTableStmt); } } public void setOutputDirectory(File outputDirectory) { this.outputDirectory = outputDirectory; } public File getOutputDirectory() { return this.outputDirectory; } public void setForce(boolean force) { this.force = force; } public boolean getForce() { return this.force; } public void setBatchSize(int batchSize) { this.batchSize = batchSize; } public int getBatchSize() { return this.batchSize; } public void setJdbcDriver(String jdbcDriver) { this.jdbcDriver = jdbcDriver; } public String getJdbcDriver() { return this.jdbcDriver; } public void setJdbcURL(String jdbcURL) { this.jdbcURL = jdbcURL; } public String getJdbcURL() { return this.jdbcURL; } public void setUserName(String userName) { this.userName = userName; } public String getUserName() { return this.userName; } public void setPassword(String password) { this.password = password; } public String getPassword() { return this.password; } public Connection getConnection() { return this.connection; } public void initialize() throws Exception { if (this.initialize) { return; } check(); if (this.connection == null) { RolapUtil.loadDrivers(this.jdbcDriver); if (this.userName == null) { this.connection = DriverManager.getConnection(this.jdbcURL); } else { this.connection = DriverManager.getConnection( this.jdbcURL, this.userName, this.password); } } final DatabaseMetaData metaData = this.connection.getMetaData(); String productName = metaData.getDatabaseProductName(); String version = metaData.getDatabaseProductVersion(); LOGGER.info("Output connection is " + productName + ", " + version); if (!metaData.supportsBatchUpdates()) { this.batchSize = 1; } this.dialect = DialectManager.createDialect(null, this.connection); this.initialize = true; } public void setConnection(Connection connection) { this.connection = connection; } public void generateStatements(Table[] tables) throws Exception { initialize(); for (Table table : tables) { generateStatements(table); } } protected void generateStatements(Table table) throws Exception { Column[] columns = table.getColumns(); initializeColumns(columns); generateBeforeActions(table); generateDropTable(table); generateDropTableRows(table); generateCreateTable(table); generateAfterActions(table); } protected void generateBeforeActions(Table table) { List dropIndexList = table.getBeforeActions(); if (dropIndexList.isEmpty()) { return; } String tableName = table.getName(); String quotedTableName = quoteId(tableName); for (int i = 0; i < dropIndexList.size(); i++) { String indexName = dropIndexList.get(i); String quotedIndexName = quoteId(indexName); String dropIndexStmt = "DROP INDEX " + quotedIndexName + " ON " + quotedTableName; dropIndexList.set(i, dropIndexStmt); } } protected void generateDropTable(Table table) { String tableName = table.getName(); String dropTableStmt = "DROP TABLE " + quoteId(tableName); table.setDropTableStmt(dropTableStmt); } protected void generateDropTableRows(Table table) { String tableName = table.getName(); String dropTableRowsStmt = "DELETE FROM " + quoteId(tableName); table.setDropTableRowsStmt(dropTableRowsStmt); } protected void generateCreateTable(Table table) { String tableName = table.getName(); Column[] columns = table.getColumns(); // Define the table. StringBuilder buf = new StringBuilder(50); buf.append("CREATE TABLE "); buf.append(quoteId(tableName)); buf.append(" ("); for (int i = 0; i < columns.length; i++) { Column column = columns[i]; if (i > 0) { buf.append(","); } buf.append(nl); buf.append(" "); buf.append(quoteId(column.name)); buf.append(" "); buf.append(column.typeName); String constraint = column.getConstraint(); if (!constraint.equals("")) { buf.append(" "); buf.append(constraint); } } buf.append(nl); buf.append(")"); String ddl = buf.toString(); table.setCreateTableStmt(ddl); } protected void generateAfterActions(Table table) { List createIndexList = table.getAfterActions(); if (createIndexList.isEmpty()) { return; } String tableName = table.getName(); String quotedTableName = quoteId(tableName); for (int i = 0; i < createIndexList.size(); i++) { String indexAndColumnName = createIndexList.get(i); int index = indexAndColumnName.indexOf(' '); String indexName = indexAndColumnName.substring(0, index); String columnName = indexAndColumnName.substring(index + 1); String quotedIndexName = quoteId(indexName.trim()); String quotedColumnName = quoteId(columnName.trim()); String createIndexStmt = "CREATE INDEX " + quotedIndexName + " ON " + quotedTableName + " ( " + quotedColumnName + " )"; createIndexList.set(i, createIndexStmt); } } public void executeStatements(Table[] tables) throws Exception { for (Table table : tables) { table.executeStatements(); } } protected void executeStatements(Table table) throws Exception { executeBeforeActions(table); executeDropTable(table); executeDropTableRows(table); executeCreateTable(table); executeLoadTableRows(table); executeAfterActions(table); } protected boolean makeFileWriter(Table table, String suffix) throws Exception { if (this.outputDirectory != null) { String fileName = table.getName() + suffix; File file = new File(outputDirectory, fileName); if (file.exists()) { if (this.force) { if (! file.delete()) { throw new Exception( "Table file \"" + fileName + "\" could not be deleted"); } } else { throw new Exception( "Table file \"" + fileName + "\" already exists" + " - delete or use force flag"); } } this.fileWriter = new FileWriter(file); return true; } else { return false; } } protected void closeFileWriter() { try { if (this.fileWriter != null) { this.fileWriter.flush(); this.fileWriter.close(); this.fileWriter = null; } } catch (IOException ex) { LOGGER.debug("Could not close file writer: " + ex); } } /** * Undoes all of the database table creations performed * when the load method was called. */ public void clear() { } /** * Releases resources. * *

Call this method when the load process is finished and the connection * is no longer going to be used. */ public void close() { } protected void check() throws Exception { if (this.connection == null) { if (this.jdbcDriver == null) { throw new Exception("Not set: jdbcDriver"); } if (this.jdbcURL == null) { throw new Exception("Not set: jdbcURL"); } } } protected void initializeColumns(Column[] columns) { // Initialize columns for (Column column : columns) { column.init(this.dialect); } } protected void executeBeforeActions(Table table) throws Exception { List beforeActionList = table.getBeforeActions(); if (beforeActionList.isEmpty()) { return; } String suffix = System.getProperty( DROP_TABLE_INDEX_PROP, DROP_TABLE_INDEX_SUFFIX_DEFAULT); try { if (makeFileWriter(table, "." + suffix)) { for (String stmt : beforeActionList) { writeDDL(stmt); } } else { for (String stmt : beforeActionList) { executeDDL(stmt); } } } catch (SQLException e) { LOGGER.debug( "Before Table actions of " + table.getName() + " failed. Ignored"); } finally { closeFileWriter(); } } protected void executeAfterActions(Table table) throws Exception { List afterActionList = table.getAfterActions(); if (afterActionList.isEmpty()) { return; } String suffix = System.getProperty( CREATE_TABLE_INDEX_PROP, CREATE_TABLE_INDEX_SUFFIX_DEFAULT); try { if (makeFileWriter(table, "." + suffix)) { for (String stmt : afterActionList) { writeDDL(stmt); } } else { for (String stmt : afterActionList) { executeDDL(stmt); } } } catch (SQLException e) { LOGGER.debug( "After Table actions of " + table.getName() + " failed. Ignored"); } finally { closeFileWriter(); } } protected boolean executeDropTableRows(Table table) throws Exception { try { Table.Controller controller = table.getController(); if (controller.shouldDropTableRows()) { String suffix = System.getProperty( DROP_TABLE_ROWS_PROP, DROP_TABLE_ROWS_SUFFIX_DEFAULT); String dropTableRowsStmt = table.getDropTableRowsStmt(); if (makeFileWriter(table, "." + suffix)) { writeDDL(dropTableRowsStmt); } else { executeDDL(dropTableRowsStmt); } } return true; } catch (SQLException e) { LOGGER.debug( "Drop Table row of " + table.getName() + " failed. Ignored"); } finally { closeFileWriter(); } return false; } protected boolean executeDropTable(Table table) { // If table does not exist, that is OK try { Table.Controller controller = table.getController(); if (controller.shouldDropTable()) { String suffix = System.getProperty( DROP_TABLE_PROP, DROP_TABLE_SUFFIX_DEFAULT); String dropTableStmt = table.getDropTableStmt(); if (makeFileWriter(table, "." + suffix)) { writeDDL(dropTableStmt); } else { executeDDL(dropTableStmt); } } return true; } catch (Exception e) { LOGGER.debug("Drop of " + table.getName() + " failed. Ignored"); } finally { closeFileWriter(); } return false; } protected boolean executeCreateTable(Table table) { try { Table.Controller controller = table.getController(); if (controller.createTable()) { String suffix = System.getProperty( CREATE_TABLE_PROP, CREATE_TABLE_SUFFIX_DEFAULT); String ddl = table.getCreateTableStmt(); if (makeFileWriter(table, "." + suffix)) { writeDDL(ddl); } else { executeDDL(ddl); } } return true; } catch (Exception e) { throw MondrianResource.instance().CreateTableFailed.ex( table.getName(), e); } finally { closeFileWriter(); } } protected int executeLoadTableRows(Table table) { try { String suffix = System.getProperty( LOAD_TABLE_ROWS_PROP, LOAD_TABLE_ROWS_SUFFIX_DEFAULT); makeFileWriter(table, "." + suffix); Table.Controller controller = table.getController(); int rowsAdded = 0; if (controller.loadRows()) { String[] batch = new String[this.batchSize]; int nosInBatch = 0; Iterator it = controller.rows(); boolean displayedInsert = false; while (it.hasNext()) { Row row = it.next(); Object[] values = row.values(); String insertStatement = createInsertStatement(table, values); if (!displayedInsert && LOGGER.isDebugEnabled()) { LOGGER.debug( "Example Insert statement: " + insertStatement); displayedInsert = true; } batch[nosInBatch++] = insertStatement; if (nosInBatch >= this.batchSize) { rowsAdded += writeBatch(batch, nosInBatch); nosInBatch = 0; } } if (nosInBatch > 0) { rowsAdded += writeBatch(batch, nosInBatch); } } return rowsAdded; } catch (Exception e) { throw Util.newError(e, "Load of " + table.getName() + " failed."); } finally { closeFileWriter(); } } protected String createInsertStatement(Table table, Object[] values) throws Exception { Column[] columns = table.getColumns(); if (columns.length != values.length) { int numberOfNullColumns = 0; for (Column c : columns) { if (c.canBeNull()) { numberOfNullColumns++; } } if (numberOfNullColumns == 0) { StringBuilder buf = new StringBuilder(); buf.append("For table "); buf.append(table.getName()); buf.append(" the columns length "); buf.append(columns.length); buf.append(" does not equal the values length "); buf.append(values.length); throw new Exception(buf.toString()); } else if (columns.length != values.length + numberOfNullColumns) { StringBuilder buf = new StringBuilder(); buf.append("For table "); buf.append(table.getName()); buf.append(" the columns length "); buf.append(columns.length); buf.append(" and number allowed to be null "); buf.append(numberOfNullColumns); buf.append(" does not equal the values length "); buf.append(values.length); throw new Exception(buf.toString()); } Object[] vs = new Object[columns.length]; for (int i = 0, j = 0; i < columns.length; i++) { if (! columns[i].canBeNull()) { vs[i] = values[j++]; } } values = vs; } StringBuilder buf = new StringBuilder(); buf.append("INSERT INTO "); buf.append(quoteId(table.getName())); buf.append(" ( "); for (int i = 0; i < columns.length; i++) { Column column = columns[i]; if (i > 0) { buf.append(","); } buf.append(quoteId(column.getName())); } buf.append(" ) VALUES ( "); for (int i = 0; i < columns.length; i++) { Column column = columns[i]; if (i > 0) { buf.append(","); } buf.append(columnValue(column, values[i])); } buf.append(" )"); return buf.toString(); } /** * Quote the given SQL identifier suitable for the output DBMS. * * @param name Identifier * @return Quoted identifier */ protected String quoteId(String name) { return this.dialect.quoteIdentifier(name); } /** * Convert the columns value to a string based upon its column type. * * @param column Column * @param value Column value * @return Column value as a SQL string */ protected String columnValue(Column column, Object value) { Type type = column.getType(); String typeName = type.getName(); if (value == null) { return "NULL"; } else if ((value instanceof String) && (((String)value).length() == 0)) { return "NULL"; } /* * Output for an INTEGER column, handling Doubles and Integers * in the result set */ if (type == Type.Integer) { if (value instanceof String) { return (String) value; } else if (value instanceof Double) { Double result = (Double) value; return integerFormatter.format(result.doubleValue()); } else if (value instanceof Integer) { Integer result = (Integer) value; return result.toString(); } /* * Output for an SMALLINT column, handling Integers * in the result set */ } else if (type == Type.Smallint) { if (value instanceof String) { return (String) value; } else if (value instanceof Boolean) { return (Boolean) value ? "1" : "0"; } else if (value instanceof Integer) { Integer result = (Integer) value; return result.toString(); } /* * Output for an BIGINT column, handling Doubles and Longs * in the result set */ } else if (type == Type.Bigint) { if (value instanceof String) { return (String) value; } else if (value instanceof Double) { Double result = (Double) value; return integerFormatter.format(result.doubleValue()); } else if (value instanceof Long) { Long result = (Long) value; return result.toString(); } /* * Output for a String, managing embedded quotes */ } else if ((type == Type.Varchar30) || (type == Type.Varchar255) || (type == Type.Varchar60) || typeName.startsWith("VARCHAR(")) { if (value instanceof String) { return embedQuotes((String) value); } /* * Output for a TIMESTAMP */ } else if (type == Type.Timestamp) { if (value instanceof String) { Timestamp ts = Timestamp.valueOf((String) value); switch (dialect.getDatabaseProduct()) { case ORACLE: case LUCIDDB: return "TIMESTAMP '" + ts + "'"; default: return "'" + ts + "'"; } } else if (value instanceof Timestamp) { Timestamp ts = (Timestamp) value; switch (dialect.getDatabaseProduct()) { case ORACLE: case LUCIDDB: return "TIMESTAMP '" + ts + "'"; default: return "'" + ts + "'"; } } /* * Output for a DATE */ } else if (type == Type.Date) { if (value instanceof String) { Date dt = Date.valueOf((String) value); switch (dialect.getDatabaseProduct()) { case ORACLE: case LUCIDDB: return "DATE '" + dateFormatter.format(dt) + "'"; default: return "'" + dateFormatter.format(dt) + "'"; } } else if (value instanceof Date) { Date dt = (Date) value; switch (dialect.getDatabaseProduct()) { case ORACLE: case LUCIDDB: return "DATE '" + dateFormatter.format(dt) + "'"; default: return "'" + dateFormatter.format(dt) + "'"; } } /* * Output for a FLOAT */ } else if (type == Type.Real) { if (value instanceof String) { return (String) value; } else if (value instanceof Float) { Float result = (Float) value; return result.toString(); } /* * Output for a DECIMAL(length, places) */ } else if ((type == Type.Decimal) || typeName.startsWith("DECIMAL(")) { if (value instanceof String) { return (String) value; } else { Matcher matcher = decimalDataTypeRegex.matcher(typeName); if (!matcher.matches()) { throw new RuntimeException( "Bad DECIMAL column type for " + typeName); } DecimalFormat formatter = new DecimalFormat( decimalFormat(matcher.group(1), matcher.group(2))); if (value instanceof Double) { Double result = (Double) value; return formatter.format(result.doubleValue()); } else if (value instanceof BigDecimal) { BigDecimal result = (BigDecimal) value; return formatter.format(result); } } /* * Output for a BOOLEAN (Postgres) or BIT (other DBMSs) */ } else if (type == Type.Boolean) { if (value instanceof String) { String trimmedValue = ((String) value).trim(); switch (dialect.getDatabaseProduct()) { case MYSQL: case ORACLE: case DB2: case DB2_AS400: case DB2_OLD_AS400: case FIREBIRD: case MSSQL: case DERBY: case INGRES: if (trimmedValue.equals("true")) { return "1"; } else if (trimmedValue.equals("false")) { return "0"; } else if (trimmedValue.equals("1")) { return "1"; } else if (trimmedValue.equals("0")) { return "0"; } default: if (trimmedValue.equals("1")) { return "true"; } else if (trimmedValue.equals("0")) { return "false"; } } } else if (value instanceof Boolean) { Boolean result = (Boolean) value; return result.toString(); } /* * Output for a BOOLEAN - TINYINT(1) (MySQL) } else if (columnType.startsWith("TINYINT(1)")) { Boolean result = (Boolean) obj; if (result.booleanValue()) { return "1"; } else { return "0"; } */ } throw new RuntimeException( "Unknown column type: " + typeName + " for column: " + column.getName()); } /** * Generate an appropriate string to use in an SQL insert statement for a * VARCHAR colummn, taking into account NULL strings and strings with * embedded quotes * * @param original String to transform * @return NULL if null string, otherwise massaged string with doubled * quotes for SQL */ protected String embedQuotes(String original) { if (original == null) { return "NULL"; } StringBuilder buf = new StringBuilder(); buf.append("'"); for (int i = 0; i < original.length(); i++) { char ch = original.charAt(i); buf.append(ch); if (ch == '\'') { buf.append('\''); } } buf.append("'"); return buf.toString(); } /** * If we are outputting to JDBC, * Execute the given set of SQL statements * * Otherwise, * output the statements to a file. * * @param batch SQL statements to execute * @param batchSize # SQL statements to execute * @return # SQL statements executed */ protected int writeBatch(String[] batch, int batchSize) throws IOException, SQLException { if (this.fileWriter != null) { for (int i = 0; i < batchSize; i++) { this.fileWriter.write(batch[i]); this.fileWriter.write(';'); this.fileWriter.write(nl); } } else { if (connection.getMetaData().supportsTransactions()) { connection.setAutoCommit(false); } Statement stmt = connection.createStatement(); if (batchSize == 1) { // Don't use batching if there's only one item. This allows // us to work around bugs in the JDBC driver by setting // outputJdbcBatchSize=1. stmt.execute(batch[0]); } else { for (int i = 0; i < batchSize; i++) { stmt.addBatch(batch[i]); } int [] updateCounts; try { updateCounts = stmt.executeBatch(); } catch (SQLException e) { for (int i = 0; i < batchSize; i++) { LOGGER.error("Error in SQL batch: " + batch[i]); } throw e; } int updates = 0; for (int i = 0; i < updateCounts.length; updates += updateCounts[i], i++) { if (updateCounts[i] == 0) { LOGGER.error("Error in SQL: " + batch[i]); } } if (updates < batchSize) { throw new RuntimeException( "Failed to execute batch: " + batchSize + " versus " + updates); } } if (connection.getMetaData().supportsTransactions()) { connection.commit(); } stmt.close(); if (connection.getMetaData().supportsTransactions()) { connection.setAutoCommit(true); } } return batchSize; } protected void writeDDL(String ddl) throws Exception { LOGGER.debug(ddl); this.fileWriter.write(ddl); this.fileWriter.write(';'); this.fileWriter.write(nl); } protected void executeDDL(String ddl) throws Exception { LOGGER.debug(ddl); Statement statement = getConnection().createStatement(); statement.execute(ddl); } } // End DBLoader.java mondrian-3.4.1/testsrc/main/mondrian/test/loader/insert.sql0000644000175000017500000003065011735330606023726 0ustar drazzibdrazzib# This software is subject to the terms of the Eclipse Public License v1.0 # Agreement, available at the following URL: # http://www.eclipse.org/legal/epl-v10.html. # You must accept the terms of that agreement to use this software. # # Copyright (C) 2004-2005 Julian Hyde # Copyright (C) 2005-2007 Pentaho # All Rights Reserved. ################################################################## ## agg_pl_01_sales_fact_1997 done ################################################################## # physical # lost "promotion_id" "store_id" INSERT INTO "agg_pl_01_sales_fact_1997" ( "product_id", "time_id", "customer_id", "store_sales_sum", "store_cost_sum", "unit_sales_sum", "fact_count" ) SELECT "product_id" AS "product_id", "time_id" AS "time_id", "customer_id" AS "customer_id", SUM("store_sales") AS "store_sales", SUM("store_cost") AS "store_cost", SUM("unit_sales") AS "unit_sales", COUNT(*) AS fact_count FROM "sales_fact_1997" GROUP BY "product_id", "time_id", "customer_id"; INSERT INTO "agg_ll_01_sales_fact_1997" ( "product_id", "time_id", "customer_id", "store_sales", "store_cost", "unit_sales", "fact_count" ) SELECT "product_id" AS "product_id", "time_id" AS "time_id", "customer_id" AS "customer_id", SUM("store_sales") AS "store_sales", SUM("store_cost") AS "store_cost", SUM("unit_sales") AS "unit_sales", COUNT(*) AS "fact_count" FROM "sales_fact_1997" GROUP BY "product_id", "time_id", "customer_id"; ################################################################## ## agg_l_03_sales_fact_1997 done ################################################################## # logical # lost "product_id" "promotion_id" "store_id" INSERT INTO "agg_l_03_sales_fact_1997" ( "customer_id", "time_id", "store_sales", "store_cost", "unit_sales", "fact_count" ) SELECT "customer_id", "time_id", SUM("store_sales") AS "store_sales", SUM("store_cost") AS "store_cost", SUM("unit_sales") AS "unit_sales", COUNT(*) AS "fact_count" FROM "sales_fact_1997" GROUP BY "customer_id", "time_id"; ################################################################## ## agg_lc_06_sales_fact_1997 done ################################################################## # collapse "customer_id" # lost "product_id" "promotion_id" "store_id" INSERT INTO "agg_lc_06_sales_fact_1997" ( "time_id", "city", "state_province", "country", "store_sales", "store_cost", "unit_sales", "fact_count" ) SELECT "B"."time_id", "D"."city", "D"."state_province", "D"."country", SUM("B"."store_sales") AS "store_sales", SUM("B"."store_cost") AS "store_cost", SUM("B"."unit_sales") AS "unit_sales", COUNT(*) AS fact_count FROM "sales_fact_1997" "B", "customer" "D" WHERE "B"."customer_id" = "D"."customer_id" GROUP BY "B"."time_id", "D"."city", "D"."state_province", "D"."country"; ################################################################## ## agg_l_04_sales_fact_1997 done ################################################################## # logical # lost "customer_id" "product_id" "promotion_id" "store_id" INSERT INTO "agg_l_04_sales_fact_1997" ( "time_id", "store_sales", "store_cost", "unit_sales", "customer_count", "fact_count" ) SELECT "time_id", SUM("store_sales") AS "store_sales", SUM("store_cost") AS "store_cost", SUM("unit_sales") AS "unit_sales", COUNT(DISTINCT "customer_id") AS "customer_count", COUNT(*) AS "fact_count" FROM "sales_fact_1997" GROUP BY "time_id"; ################################################################## ## agg_c_10_sales_fact_1997 done ################################################################## # collapse "time_id" # lost "customer_id" "product_id" "promotion_id" "store_id" INSERT INTO "agg_c_10_sales_fact_1997" ( "month_of_year", "quarter", "the_year", "store_sales", "store_cost", "unit_sales", "customer_count", "fact_count" ) SELECT "D"."month_of_year", "D"."quarter", "D"."the_year", SUM("B"."store_sales") AS "store_sales", SUM("B"."store_cost") AS "store_cost", SUM("B"."unit_sales") AS "unit_sales", COUNT(DISTINCT "customer_id") AS "customer_count", COUNT(*) AS fact_count FROM "sales_fact_1997" "B", "time_by_day" "D" WHERE "B"."time_id" = "D"."time_id" GROUP BY "D"."month_of_year", "D"."quarter", "D"."the_year"; ################################################################## ## agg_l_05_sales_fact_1997 done ################################################################## # logical # lost "time_id" INSERT INTO "agg_l_05_sales_fact_1997" ( "product_id", "customer_id", "promotion_id", "store_id", "store_sales", "store_cost", "unit_sales", "fact_count" ) SELECT "product_id", "customer_id", "promotion_id", "store_id", SUM("store_sales") AS "store_sales", SUM("store_cost") AS "store_cost", SUM("unit_sales") AS "unit_sales", COUNT(*) AS fact_count FROM "sales_fact_1997" GROUP BY "product_id", "customer_id", "promotion_id", "store_id"; ################################################################## ## agg_c_14_sales_fact_1997 done ################################################################## # collapse "time_id" INSERT INTO "agg_c_14_sales_fact_1997" ( "product_id", "customer_id", "promotion_id", "store_id", "month_of_year", "quarter", "the_year", "store_sales", "store_cost", "unit_sales", "fact_count" ) SELECT "B"."product_id", "B"."customer_id", "B"."promotion_id", "B"."store_id", "D"."month_of_year", "D"."quarter", "D"."the_year", SUM("B"."store_sales") AS "store_sales", SUM("B"."store_cost") AS "store_cost", SUM("B"."unit_sales") AS "unit_sales", COUNT(*) AS fact_count FROM "sales_fact_1997" "B", "time_by_day" "D" WHERE "B"."time_id" = "D"."time_id" GROUP BY "B"."product_id", "B"."customer_id", "B"."promotion_id", "B"."store_id", "D"."month_of_year", "D"."quarter", "D"."the_year"; ################################################################## ## agg_lc_100_sales_fact_1997 done ################################################################## # drop "promotion_id" # drop "store_id" # collapse "time_id" INSERT INTO "agg_lc_100_sales_fact_1997" ( "product_id", "customer_id", "quarter", "the_year", "store_sales", "store_cost", "unit_sales", "fact_count" ) SELECT "B"."product_id", "B"."customer_id", "D"."quarter", "D"."the_year", SUM("B"."store_sales") AS "store_sales", SUM("B"."store_cost") AS "store_cost", SUM("B"."unit_sales") AS "unit_sales", COUNT(*) AS fact_count FROM "sales_fact_1997" "B", "time_by_day" "D" WHERE "B"."time_id" = "D"."time_id" GROUP BY "B"."product_id", "B"."customer_id", "D"."quarter", "D"."the_year"; ################################################################## ################################################################## ## SPECIAL ################################################################## ################################################################## ## agg_c_special_sales_fact_1997 done ## based upon agg_c_14_sales_fact_1997 ################################################################## # collapse "time_id" INSERT INTO "agg_c_special_sales_fact_1997" ( "product_id", "customer_id", "promotion_id", "store_id", "time_month", "time_quarter", "time_year", "store_sales_sum", "store_cost_sum", "unit_sales_sum", "fact_count" ) SELECT "B"."product_id", "B"."customer_id", "B"."promotion_id", "B"."store_id", "D"."month_of_year", "D"."quarter", "D"."the_year", SUM("B"."store_sales") AS "store_sales_sum", SUM("B"."store_cost") AS "store_cost_sum", SUM("B"."unit_sales") AS "unit_sales_sum", COUNT(*) AS "fact_count" FROM "sales_fact_1997" "B", "time_by_day" "D" WHERE "B"."time_id" = "D"."time_id" GROUP BY "B"."product_id", "B"."customer_id", "B"."promotion_id", "B"."store_id", "D"."month_of_year", "D"."quarter", "D"."the_year"; ################################################################## # agg_gender_ms_state_sales_fact_1997 ################################################################## INSERT INTO "agg_g_ms_pcat_sales_fact_1997" ( "gender", "marital_status", "product_family", "product_department", "product_category", "month_of_year", "quarter", "the_year", "store_sales", "store_cost", "unit_sales", "customer_count", "fact_count" ) SELECT "C"."gender", "C"."marital_status", "PC"."product_family", "PC"."product_department", "PC"."product_category", "T"."month_of_year", "T"."quarter", "T"."the_year", SUM("B"."store_sales") AS "store_sales", SUM("B"."store_cost") AS "store_cost", SUM("B"."unit_sales") AS "unit_sales", COUNT(DISTINCT "C"."customer_id") AS "customer_count", COUNT(*) AS "fact_count" FROM "sales_fact_1997" "B", "time_by_day" "T", "product" "P", "product_class" "PC", "customer" "C" WHERE "B"."time_id" = "T"."time_id" AND "B"."customer_id" = "C"."customer_id" AND "B"."product_id" = "P"."product_id" AND "P"."product_class_id" = "PC"."product_class_id" GROUP BY "C"."gender", "C"."marital_status", "PC"."product_family", "PC"."product_department", "PC"."product_category", "T"."month_of_year", "T"."quarter", "T"."the_year"; # Above query, rephrased for Access (which does not support # COUNT(DISTINCT ...) explicitly. # #INSERT INTO "agg_g_ms_pcat_sales_fact_1997" ( # "gender", # "marital_status", # "product_family", # "product_department", # "product_category", # "month_of_year", # "quarter", # "the_year", # "store_sales", # "store_cost", # "unit_sales", # "customer_count", # "fact_count" #) SELECT # "C"."gender", # "C"."marital_status", # "PC"."product_family", # "PC"."product_department", # "PC"."product_category", # "T"."month_of_year", # "T"."quarter", # "T"."the_year", # SUM("B"."store_sales") AS "store_sales", # SUM("B"."store_cost") AS "store_cost", # SUM("B"."unit_sales") AS "unit_sales", # ( # SELECT COUNT("customer_id") # FROM ( # SELECT DISTINCT # "DC"."gender", # "DC"."marital_status", # "DPC"."product_family", # "DPC"."product_department", # "DPC"."product_category", # "DT"."month_of_year", # "DT"."quarter", # "DT"."the_year", # "DB"."customer_id" # FROM # "sales_fact_1997" "DB", # "time_by_day" "DT", # "product" "DP", # "product_class" "DPC", # "customer" "DC" # WHERE # "DB"."time_id" = "DT"."time_id" # AND "DB"."customer_id" = "DC"."customer_id" # AND "DB"."product_id" = "DP"."product_id" # AND "DP"."product_class_id" = "DPC"."product_class_id") AS "CDC" # WHERE "CDC"."gender" = "C"."gender" # AND "CDC"."marital_status" = "C"."marital_status" # AND "CDC"."product_family" = "PC"."product_family" # AND "CDC"."product_department" = "PC"."product_department" # AND "CDC"."product_category" = "PC"."product_category" # AND "CDC"."month_of_year" = "T"."month_of_year" # AND "CDC"."quarter" = "T"."quarter" # AND "CDC"."the_year" = "T"."the_year" # GROUP BY # "gender", # "marital_status", # "product_family", # "product_department", # "product_category", # "month_of_year", # "quarter", # "the_year") AS "customer_count", # COUNT(*) AS "fact_count" #FROM "sales_fact_1997" "B", # "time_by_day" "T", # "product" "P", # "product_class" "PC", # "customer" "C" #WHERE # "B"."time_id" = "T"."time_id" #AND "B"."customer_id" = "C"."customer_id" #AND "B"."product_id" = "P"."product_id" #AND "P"."product_class_id" = "PC"."product_class_id" #GROUP BY # "C"."gender", # "C"."marital_status", # "PC"."product_family", # "PC"."product_department", # "PC"."product_category", # "T"."month_of_year", # "T"."quarter", # "T"."the_year"; # End insert.sql mondrian-3.4.1/testsrc/main/mondrian/test/loader/CsvDBTestCase.java0000644000175000017500000001014611735330606025137 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.test.loader; import mondrian.olap.Schema; import mondrian.spi.Dialect; import mondrian.test.FoodMartTestCase; import mondrian.test.TestContext; import java.io.File; import java.sql.Connection; import java.sql.SQLException; /** * Base class for tests that use * a CSV database defined in a single file. While the CsvDBLoader * supports being defined by a single file, list of files, or * directory with optional regular expression for matching files * in the directory to be loaded, this is simplest at this point. * *

* To use this file one must define both the directory and file * abstract methods. * * @author Richard M. Emberson */ public abstract class CsvDBTestCase extends FoodMartTestCase { private CsvDBLoader loader; private CsvDBLoader.Table[] tables; private TestContext testContext; public CsvDBTestCase() { super(); } public CsvDBTestCase(String name) { super(name); } protected final boolean isApplicable() { final Dialect dialect = getTestContext().getDialect(); return dialect.allowsDdl() && dialect.getDatabaseProduct() != Dialect.DatabaseProduct.INFOBRIGHT; } protected void setUp() throws Exception { // If this database does not allow DDL, the test won't run. Don't bother // setting up. if (!isApplicable()) { return; } super.setUp(); Connection connection = getSqlConnection(); String dirName = getDirectoryName(); String fileName = getFileName(); File inputFile = new File(dirName, fileName); this.loader = new CsvDBLoader(); this.loader.setConnection(connection); this.loader.initialize(); this.loader.setInputFile(inputFile); this.tables = this.loader.getTables(); this.loader.generateStatements(this.tables); // create database tables this.loader.executeStatements(this.tables); String parameterDefs = getParameterDescription(); String cubeDefs = getCubeDescription(); String virtualCubeDefs = getVirtualCubeDescription(); String namedSetDefs = getNamedSetDescription(); String udfDefs = getUdfDescription(); String roleDefs = getRoleDescription(); this.testContext = TestContext.instance().create( parameterDefs, cubeDefs, virtualCubeDefs, namedSetDefs, udfDefs, roleDefs); } protected void tearDown() throws Exception { // If this database does not allow DDL, we didn't run setUp; so, nothing // to tear down. if (!isApplicable()) { return; } try { // drop database tables this.loader.dropTables(this.tables); } catch (Exception ex) { // ignore } testContext = null; // allow gc super.tearDown(); } protected Connection getSqlConnection() throws SQLException { return getConnection().getDataSource().getConnection(); } protected Schema getSchema() { return getConnection().getSchema(); } protected TestContext getCubeTestContext() { return testContext; } protected abstract String getDirectoryName(); protected abstract String getFileName(); protected String getParameterDescription() { return null; } protected abstract String getCubeDescription(); protected String getVirtualCubeDescription() { return null; } protected String getNamedSetDescription() { return null; } protected String getUdfDescription() { return null; } protected String getRoleDescription() { return null; } } // End CsvDBTestCase.java mondrian-3.4.1/testsrc/main/mondrian/test/SolveOrderScopeIsolationTest.java0000644000175000017500000007413711735330606027106 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2008-2011 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; import static mondrian.olap.SolveOrderMode.ABSOLUTE; import static mondrian.olap.SolveOrderMode.SCOPED; /** * SolveOrderScopeIsolationTest Test conformance to SSAS2005 solve * order scope isolation behavior. * Scope Isolation: In SQL Server 2005 Analysis Services, when a cube * Multidimensional Expressions (MDX) script contains calculated members, * by default the calculated members are resolved before any session-scoped * calculations are resolved and before any query-defined calculations are * resolved. This is different from SQL Server 2000 Analysis Services behavior, * where solve order can explicitly be used to insert a session-scoped or * query-defined calculation in between two cube-level calculations. * Further details at: http://msdn2.microsoft.com/en-us/library/ms144787.aspx * * This initial set of tests are added to indicate the kind of behavior that is * expected to support this SSAS 2005 feature. All tests start with an * underscore so as to not to execute even if the test class is added to Main * * @author ajogleka * @since Apr 04, 2008 */ public class SolveOrderScopeIsolationTest extends FoodMartTestCase { SolveOrderMode defaultSolveOrderMode; public void setUp() throws Exception { super.setUp(); defaultSolveOrderMode = getSolveOrderMode(); } public void tearDown() throws Exception { setSolveOrderMode(defaultSolveOrderMode); super.tearDown(); } private static final String memberDefs = "\n" + " \n" + "" + "\n" + " [Measures].[Store Sales] - [Measures].[Store Cost]\n" + " \n" + " \n" + "" + "\n" + " \n" + " \n" + "" + "\n" + " \n" + ""; private SolveOrderMode getSolveOrderMode() { return Util.lookup( SolveOrderMode.class, MondrianProperties.instance().SolveOrderMode.get().toUpperCase()); } final void setSolveOrderMode(SolveOrderMode mode) { MondrianProperties.instance().SolveOrderMode.set(mode.toString()); } public TestContext getTestContext() { return TestContext.instance().createSubstitutingCube( "Sales", null, memberDefs); } public void testAllSolveOrderModesHandled() { for (SolveOrderMode mode : SolveOrderMode.values()) { switch (mode) { case ABSOLUTE: case SCOPED: break; default: fail( "Tests for solve order mode " + mode.toString() + " have not been implemented."); } } } public void testSetSolveOrderMode() { setSolveOrderMode(ABSOLUTE); assertEquals(ABSOLUTE, getSolveOrderMode()); setSolveOrderMode(SCOPED); assertEquals(SCOPED, getSolveOrderMode()); } public void testOverrideCubeMemberDoesNotHappenAbsolute() { final String mdx = "with\n" + "member gender.override as 'gender.maleMinusFemale', " + "SOLVE_ORDER=5\n" + "select {measures.[ratio], measures.[unit sales], " + "measures.[sales count]} on 0,\n" + "{gender.override,gender.maleMinusFemale} on 1\n" + "from sales"; setSolveOrderMode(SolveOrderMode.ABSOLUTE); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Gender].[override]}\n" + "{[Gender].[maleMinusFemale]}\n" + "Row #0: 3.11\n" + "Row #0: 3,657\n" + "Row #0: 1,175\n" + "Row #1: 0.0\n" + "Row #1: 3,657\n" + "Row #1: 1,175\n"); } public void testOverrideCubeMemberDoesNotHappenScoped() { final String mdx = "with\n" + "member gender.override as 'gender.maleMinusFemale', " + "SOLVE_ORDER=5\n" + "select {measures.[ratio], measures.[unit sales], " + "measures.[sales count]} on 0,\n" + "{gender.override,gender.maleMinusFemale} on 1\n" + "from sales"; setSolveOrderMode(SolveOrderMode.SCOPED); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Gender].[override]}\n" + "{[Gender].[maleMinusFemale]}\n" + "Row #0: 0.0\n" + "Row #0: 3,657\n" + "Row #0: 1,175\n" + "Row #1: 0.0\n" + "Row #1: 3,657\n" + "Row #1: 1,175\n"); } /** * Test for future capability: SCOPE_ISOLATION=CUBE which is implemented in * Analysis Services but not yet in Mondrian. */ public void _future_testOverrideCubeMemberHappensWithScopeIsolation() { setSolveOrderMode(SCOPED); assertQueryReturns( "with\n" + "member gender.maleMinusFemale as 'Gender.M - gender.f', " + "SOLVE_ORDER=3000, FORMAT_STRING='#.##'\n" + "member gender.override as 'gender.maleMinusFemale', " + "SOLVE_ORDER=5, FORMAT_STRING='#.##', SCOPE_ISOLATION=CUBE \n" + "member measures.[ratio] as " + "'measures.[unit sales] / measures.[sales count]', SOLVE_ORDER=10\n" + "select {measures.[ratio],\n" + "measures.[unit sales],\n" + "measures.[sales count]} on 0,\n" + "{gender.override, gender.maleMinusFemale} on 1\n" + "from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Gender].[override]}\n" + "{[Gender].[maleMinusFemale]}\n" + "Row #0: 3.11" + "Row #0: 3657\n" + "Row #0: 1175\n" + "Row #1: \n" + "Row #1: 3657\n" + "Row #1: 1175\n"); } public void testCubeMemberEvalBeforeQueryMemberAbsolute() { final String mdx = "WITH MEMBER [Customers].USAByWA AS\n" + "'[Customers].[Country].[USA] / [Customers].[State Province].[WA]', " + "SOLVE_ORDER=5\n" + "SELECT {[Country].[USA],[State Province].[WA], [Customers].USAByWA} ON 0, " + " {[Measures].[Store Sales], [Measures].[Store Cost], [Measures].[ProfitSolveOrder3000]} ON 1 " + "FROM SALES\n"; setSolveOrderMode(ABSOLUTE); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA]}\n" + "{[Customers].[USA].[WA]}\n" + "{[Customers].[USAByWA]}\n" + "Axis #2:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[ProfitSolveOrder3000]}\n" + "Row #0: 565,238.13\n" + "Row #0: 263,793.22\n" + "Row #0: 2.14\n" + "Row #1: 225,627.23\n" + "Row #1: 105,324.31\n" + "Row #1: 2.14\n" + "Row #2: $339,610.896400\n" + "Row #2: $158,468.912100\n" + "Row #2: $0.000518\n"); } public void testCubeMemberEvalBeforeQueryMemberScoped() { final String mdx = "WITH MEMBER [Customers].USAByWA AS\n" + "'[Customers].[Country].[USA] / [Customers].[State Province].[WA]', " + "SOLVE_ORDER=5\n" + "SELECT {[Country].[USA],[State Province].[WA], [Customers].USAByWA} ON 0 " + "FROM SALES\n" + "WHERE ProfitSolveOrder3000"; setSolveOrderMode(SCOPED); assertQueryReturns( mdx, "Axis #0:\n" + "{[Measures].[ProfitSolveOrder3000]}\n" + "Axis #1:\n" + "{[Customers].[USA]}\n" + "{[Customers].[USA].[WA]}\n" + "{[Customers].[USAByWA]}\n" + "Row #0: $339,610.896400\n" + "Row #0: $158,468.912100\n" + "Row #0: $2.143076\n"); } public void testOverrideCubeMemberInTupleDoesNotHappenAbsolute() { final String mdx = "with\n" + "member gender.override as " + "'([Gender].[maleMinusFemale], [Product].[Food])', SOLVE_ORDER=5\n" + "select {measures.[ratio],\n" + "measures.[unit sales],\n" + "measures.[sales count]} on 0,\n" + "{gender.override, gender.maleMinusFemale} on 1\n" + "from sales"; setSolveOrderMode(ABSOLUTE); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Gender].[override]}\n" + "{[Gender].[maleMinusFemale]}\n" + "Row #0: 3.09\n" + "Row #0: 2,312\n" + "Row #0: 749\n" + "Row #1: 0.0\n" + "Row #1: 3,657\n" + "Row #1: 1,175\n"); } public void testOverrideCubeMemberInTupleDoesNotHappenScoped() { final String mdx = "with\n" + "member gender.override as " + "'([Gender].[maleMinusFemale], [Product].[Food])', SOLVE_ORDER=5\n" + "select {measures.[ratio],\n" + "measures.[unit sales],\n" + "measures.[sales count]} on 0,\n" + "{gender.override, gender.maleMinusFemale} on 1\n" + "from sales"; setSolveOrderMode(SCOPED); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Gender].[override]}\n" + "{[Gender].[maleMinusFemale]}\n" + "Row #0: 0.0\n" + "Row #0: 2,312\n" + "Row #0: 749\n" + "Row #1: 0.0\n" + "Row #1: 3,657\n" + "Row #1: 1,175\n"); } public void testConditionalCubeMemberEvalBeforeOtherMembersAbsolute() { final String mdx = "with\n" + "member gender.override as 'iif(1=0," + "[gender].[all gender].[m], [Gender].[maleMinusFemale])', " + "SOLVE_ORDER=5\n" + "select {measures.[ratio],\n" + "measures.[unit sales],\n" + "measures.[sales count]} on 0,\n" + "{[Gender].[override], gender.maleMinusFemale} on 1\n" + "from sales"; setSolveOrderMode(ABSOLUTE); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Gender].[override]}\n" + "{[Gender].[maleMinusFemale]}\n" + "Row #0: 3.11\n" + "Row #0: 3,657\n" + "Row #0: 1,175\n" + "Row #1: 0.0\n" + "Row #1: 3,657\n" + "Row #1: 1,175\n"); } public void testConditionalCubeMemberEvalBeforeOtherMembersScoped() { final String mdx = "with\n" + "member gender.override as 'iif(1=0," + "[gender].[all gender].[m], [Gender].[maleMinusFemale])', " + "SOLVE_ORDER=5\n" + "select {measures.[ratio],\n" + "measures.[unit sales],\n" + "measures.[sales count]} on 0,\n" + "{[Gender].[override], gender.maleMinusFemale} on 1\n" + "from sales"; setSolveOrderMode(SCOPED); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Gender].[override]}\n" + "{[Gender].[maleMinusFemale]}\n" + "Row #0: 0.0\n" + "Row #0: 3,657\n" + "Row #0: 1,175\n" + "Row #1: 0.0\n" + "Row #1: 3,657\n" + "Row #1: 1,175\n"); } public void testOverrideCubeMemberUsingStrToMemberDoesNotHappenAbsolute() { final String mdx = "with\n" + "member gender.override as 'iif(1=0,[gender].[all gender].[m], " + "StrToMember(\"[Gender].[maleMinusFemale]\"))', " + "SOLVE_ORDER=5\n" + "select {measures.[ratio],\n" + "measures.[unit sales],\n" + "measures.[sales count]} on 0,\n" + "{[Gender].[override], gender.maleMinusFemale} on 1\n" + "from sales"; setSolveOrderMode(ABSOLUTE); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Gender].[override]}\n" + "{[Gender].[maleMinusFemale]}\n" + "Row #0: 3.11\n" + "Row #0: 3,657\n" + "Row #0: 1,175\n" + "Row #1: 0.0\n" + "Row #1: 3,657\n" + "Row #1: 1,175\n"); } public void testOverrideCubeMemberUsingStrToMemberDoesNotHappenScoped() { final String mdx = "with\n" + "member gender.override as 'iif(1=0,[gender].[all gender].[m], " + "StrToMember(\"[Gender].[maleMinusFemale]\"))', " + "SOLVE_ORDER=5\n" + "select {measures.[ratio],\n" + "measures.[unit sales],\n" + "measures.[sales count]} on 0,\n" + "{[Gender].[override], gender.maleMinusFemale} on 1\n" + "from sales"; setSolveOrderMode(SCOPED); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Gender].[override]}\n" + "{[Gender].[maleMinusFemale]}\n" + "Row #0: 0.0\n" + "Row #0: 3,657\n" + "Row #0: 1,175\n" + "Row #1: 0.0\n" + "Row #1: 3,657\n" + "Row #1: 1,175\n"); } /** * This test validates that behavior is consistent with Analysis Services * 2000 when Solve order scope is ABSOLUTE. AS2K will throw an error * whenever attempting to aggregate over calculated members (i.e. when the * solve order of the Aggregate member is higher than the calculations it * intersects with). */ public void testAggregateMemberEvalAfterOtherMembersAbsolute() { final String mdx = "With\n" + "member Time.Time.Total1 as " + "'AGGREGATE({[Time].[1997].[Q1],[Time].[1997].[Q2]})' , SOLVE_ORDER=20 \n" + ", FORMAT_STRING='#,###'\n" + "member measures.[ratio1] as " + "'measures.[unit sales] / measures.[sales count]', " + "SOLVE_ORDER=10 , FORMAT_STRING='#.##'\n" + "select {measures.[ratio1],\n" + "measures.[unit sales],\n" + "measures.[sales count]} on 0,\n" + "{Time.Total, Time.Total1, [Time].[1997].[Q1], [Time].[1997].[Q2]} on 1\n" + "from sales"; setSolveOrderMode(ABSOLUTE); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio1]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Time].[Total]}\n" + "{[Time].[Total1]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "Row #0: #ERR: mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context\n" + "Row #0: 128,901\n" + "Row #0: 41,956\n" + "Row #1: #ERR: mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context\n" + "Row #1: 128,901\n" + "Row #1: 41,956\n" + "Row #2: 3.07\n" + "Row #2: 66,291\n" + "Row #2: 21,588\n" + "Row #3: 3.07\n" + "Row #3: 62,610\n" + "Row #3: 20,368\n"); } public void testAggregateMemberEvalAfterOtherMembersScoped() { final String mdx = "With\n" + "member Time.Time.Total1 as " + "'AGGREGATE({[Time].[1997].[Q1],[Time].[1997].[Q2]})' , SOLVE_ORDER=20 \n" + ", FORMAT_STRING='#,###'\n" + "member measures.[ratio1] as " + "'measures.[unit sales] / measures.[sales count]', " + "SOLVE_ORDER=10 , FORMAT_STRING='#.##'\n" + "select {measures.[ratio1],\n" + "measures.[unit sales],\n" + "measures.[sales count]} on 0,\n" + "{Time.Total, Time.Total1, [Time].[1997].[Q1], [Time].[1997].[Q2]} on 1\n" + "from sales"; setSolveOrderMode(SCOPED); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio1]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Time].[Total]}\n" + "{[Time].[Total1]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "Row #0: 3.07\n" + "Row #0: 128,901\n" + "Row #0: 41,956\n" + "Row #1: 3\n" + "Row #1: 128,901\n" + "Row #1: 41,956\n" + "Row #2: 3.07\n" + "Row #2: 66,291\n" + "Row #2: 21,588\n" + "Row #3: 3.07\n" + "Row #3: 62,610\n" + "Row #3: 20,368\n"); } /** * This test validates that behavior is consistent with Analysis Services * 2000 when Solve order scope is ABSOLUTE. AS2K will throw an error * whenever attempting to aggregate over calculated members (i.e. when the * solve order of the Aggregate member is higher than the calculations it * intersects with). */ public void testConditionalAggregateMemberEvalAfterOtherMembersAbsolute() { final String mdx = "With\n" + "member Time.Time.Total1 as 'IIF(Measures.CURRENTMEMBER IS Measures.Profit, 1, " + "AGGREGATE({[Time].[1997].[Q1],[Time].[1997].[Q2]}))' , SOLVE_ORDER=20 \n" + "\n" + "member measures.[ratio1] as 'measures.[unit sales] / measures.[sales count]', " + "SOLVE_ORDER=10\n" + "select {measures.[ratio1],\n" + "measures.[unit sales],\n" + "measures.[sales count]} on 0,\n" + "{Time.Total, Time.Total1, [Time].[1997].[Q1], [Time].[1997].[Q2]} on 1\n" + "from sales"; setSolveOrderMode(ABSOLUTE); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio1]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Time].[Total]}\n" + "{[Time].[Total1]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "Row #0: #ERR: mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context\n" + "Row #0: 128,901\n" + "Row #0: 41,956\n" + "Row #1: #ERR: mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context\n" + "Row #1: 128,901\n" + "Row #1: 41,956\n" + "Row #2: 3\n" + "Row #2: 66,291\n" + "Row #2: 21,588\n" + "Row #3: 3\n" + "Row #3: 62,610\n" + "Row #3: 20,368\n"); } public void testConditionalAggregateMemberEvalAfterOtherMembersScoped() { final String mdx = "With\n" + "member Time.Time.Total1 as 'IIF(Measures.CURRENTMEMBER IS Measures.Profit, 1, " + "AGGREGATE({[Time].[1997].[Q1],[Time].[1997].[Q2]}))' , SOLVE_ORDER=20 \n" + "\n" + "member measures.[ratio1] as 'measures.[unit sales] / measures.[sales count]', " + "SOLVE_ORDER=10, FORMAT_STRING='#.##'\n" + "select {measures.[ratio1],\n" + "measures.[unit sales],\n" + "measures.[sales count]} on 0,\n" + "{Time.Total, Time.Total1, [Time].[1997].[Q1], [Time].[1997].[Q2]} on 1\n" + "from sales"; setSolveOrderMode(SCOPED); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio1]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Time].[Total]}\n" + "{[Time].[Total1]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "Row #0: 3.07\n" + "Row #0: 128,901\n" + "Row #0: 41,956\n" + "Row #1: 3.07\n" + "Row #1: 128,901\n" + "Row #1: 41,956\n" + "Row #2: 3.07\n" + "Row #2: 66,291\n" + "Row #2: 21,588\n" + "Row #3: 3.07\n" + "Row #3: 62,610\n" + "Row #3: 20,368\n"); } /** * This test validates that behavior is consistent with Analysis Services * 2000 when Solve order scope is ABSOLUTE. AS2K will throw an error * whenever attempting to aggregate over calculated members (i.e. when the * solve order of the Aggregate member is higher than the calculations it * intersects with). */ public void testStrToMemberReturningAggEvalAfterOtherMembersAbsolute() { final String mdx = "With\n" + "member Time.Time.StrTotal as 'AGGREGATE({[Time].[1997].[Q1],[Time].[1997].[Q2]})', " + "SOLVE_ORDER=100\n" + "member Time.Time.Total as 'IIF(Measures.CURRENTMEMBER IS Measures.Profit, 1, \n" + "StrToMember(\"[Time].[StrTotal]\"))' , SOLVE_ORDER=20 \n" + "member measures.[ratio1] as 'measures.[unit sales] / measures.[sales count]', " + "SOLVE_ORDER=10, FORMAT_STRING='#.##'\n" + "select {measures.[ratio1],\n" + "measures.[unit sales],\n" + "measures.[sales count]} on 0,\n" + "{Time.Total, [Time].[1997].[Q1], [Time].[1997].[Q2]} on 1\n" + "from sales"; setSolveOrderMode(ABSOLUTE); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio1]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Time].[Total]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "Row #0: #ERR: mondrian.olap.fun.MondrianEvaluationException: Could not find an aggregator in the current evaluation context\n" + "Row #0: 128,901\n" + "Row #0: 41,956\n" + "Row #1: 3.07\n" + "Row #1: 66,291\n" + "Row #1: 21,588\n" + "Row #2: 3.07\n" + "Row #2: 62,610\n" + "Row #2: 20,368\n"); } public void testStrToMemberReturningAggEvalAfterOtherMembersScoped() { final String mdx = "With\n" + "member Time.Time.StrTotal as 'AGGREGATE({[Time].[1997].[Q1],[Time].[1997].[Q2]})', " + "SOLVE_ORDER=100\n" + "member Time.Time.Total as 'IIF(Measures.CURRENTMEMBER IS Measures.Profit, 1, \n" + "StrToMember(\"[Time].[StrTotal]\"))' , SOLVE_ORDER=20 \n" + "member measures.[ratio1] as 'measures.[unit sales] / measures.[sales count]', " + "SOLVE_ORDER=10, FORMAT_STRING='#.##'\n" + "select {measures.[ratio1],\n" + "measures.[unit sales],\n" + "measures.[sales count]} on 0,\n" + "{Time.Total, [Time].[1997].[Q1], [Time].[1997].[Q2]} on 1\n" + "from sales"; setSolveOrderMode(SCOPED); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio1]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Time].[Total]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "Row #0: 3.07\n" + "Row #0: 128,901\n" + "Row #0: 41,956\n" + "Row #1: 3.07\n" + "Row #1: 66,291\n" + "Row #1: 21,588\n" + "Row #2: 3.07\n" + "Row #2: 62,610\n" + "Row #2: 20,368\n"); } public void test2LevelOfOverrideCubeMemberDoesNotHappenAbsolute() { final String mdx = "With member gender.override1 as 'gender.maleMinusFemale',\n" + "SOLVE_ORDER=20\n" + "member gender.override2 as 'gender.override1', SOLVE_ORDER=2\n" + "member measures.[ratio1] as 'measures.[unit sales] / measures.[sales count]', " + "SOLVE_ORDER=50, FORMAT_STRING='0.0#'\n" + "select {measures.[ratio], measures.[ratio1],\n" + "measures.[unit sales],\n" + "measures.[sales count]} on 0,\n" + "{gender.override1, gender.override2, gender.maleMinusFemale} on 1\n" + "from sales"; setSolveOrderMode(ABSOLUTE); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio]}\n" + "{[Measures].[ratio1]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Gender].[override1]}\n" + "{[Gender].[override2]}\n" + "{[Gender].[maleMinusFemale]}\n" + "Row #0: 0.0\n" + "Row #0: 3.11\n" + "Row #0: 3,657\n" + "Row #0: 1,175\n" + "Row #1: 3.11\n" + "Row #1: 3.11\n" + "Row #1: 3,657\n" + "Row #1: 1,175\n" + "Row #2: 0.0\n" + "Row #2: 0.0\n" + "Row #2: 3,657\n" + "Row #2: 1,175\n"); } public void test2LevelOfOverrideCubeMemberDoesNotHappenScoped() { final String mdx = "With member gender.override1 as 'gender.maleMinusFemale',\n" + "SOLVE_ORDER=20\n" + "member gender.override2 as 'gender.override1', SOLVE_ORDER=2\n" + "member measures.[ratio1] as 'measures.[unit sales] / measures.[sales count]', " + "SOLVE_ORDER=50, FORMAT_STRING='0.0#'\n" + "select {measures.[ratio], measures.[ratio1],\n" + "measures.[unit sales],\n" + "measures.[sales count]} on 0,\n" + "{gender.override1, gender.override2, gender.maleMinusFemale} on 1\n" + "from sales"; setSolveOrderMode(SCOPED); assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ratio]}\n" + "{[Measures].[ratio1]}\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Sales Count]}\n" + "Axis #2:\n" + "{[Gender].[override1]}\n" + "{[Gender].[override2]}\n" + "{[Gender].[maleMinusFemale]}\n" + "Row #0: 0.0\n" + "Row #0: 3.11\n" + "Row #0: 3,657\n" + "Row #0: 1,175\n" + "Row #1: 0.0\n" + "Row #1: 3.11\n" + "Row #1: 3,657\n" + "Row #1: 1,175\n" + "Row #2: 0.0\n" + "Row #2: 3.11\n" + "Row #2: 3,657\n" + "Row #2: 1,175\n"); } } // End SolveOrderScopeIsolationTest.java mondrian-3.4.1/testsrc/main/mondrian/test/TestContext.java0000644000175000017500000021532711735330606023570 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.calc.*; import mondrian.olap.Axis; import mondrian.olap.Cell; import mondrian.olap.Connection; import mondrian.olap.DriverManager; import mondrian.olap.*; import mondrian.olap.Position; import mondrian.olap.fun.FunUtil; import mondrian.resource.MondrianResource; import mondrian.rolap.*; import mondrian.spi.*; import mondrian.spi.impl.FilterDynamicSchemaProcessor; import mondrian.util.DelegatingInvocationHandler; import junit.framework.*; import junit.framework.Test; import org.olap4j.*; import org.olap4j.impl.CoordinateIterator; import org.olap4j.layout.TraditionalCellSetFormatter; import java.io.*; import java.lang.ref.SoftReference; import java.lang.reflect.Proxy; import java.net.MalformedURLException; import java.net.URL; import java.sql.*; import java.util.*; import java.util.regex.Pattern; import javax.sql.DataSource; /** * TestContext is a singleton class which contains the information * necessary to run mondrian tests (otherwise we'd have to pass this * information into the constructor of TestCases). * *

The singleton instance (retrieved via the {@link #instance()} method) * contains a connection to the FoodMart database, and runs expressions in the * context of the Sales cube. * *

Using the {@link DelegatingTestContext} subclass, you can create derived * classes which use a different connection or a different cube. * * @author jhyde * @since 29 March, 2002 */ public class TestContext { private static TestContext instance; // the singleton private PrintWriter pw; private SoftReference connectionRef; private Dialect dialect; protected static final String nl = Util.nl; private static final String indent = " "; private static final String lineBreak = "\"," + nl + "\""; private static final String lineBreak2 = "\\\\n\"" + nl + indent + "+ \""; private static final Pattern LineBreakPattern = Pattern.compile("\r\n|\r|\n"); private static final Pattern TabPattern = Pattern.compile("\t"); private static final String[] AllHiers = { "[Measures]", "[Store]", "[Store Size in SQFT]", "[Store Type]", "[Time]", MondrianProperties.instance().SsasCompatibleNaming.get() ? "[Time].[Weekly]" : "[Time.Weekly]", "[Product]", "[Promotion Media]", "[Promotions]", "[Customers]", "[Education Level]", "[Gender]", "[Marital Status]", "[Yearly Income]" }; private static String unadulteratedFoodMartSchema; /** * Retrieves the singleton (instantiating if necessary). */ public static synchronized TestContext instance() { if (instance == null) { instance = new TestContext(); } return instance; } /** * Creates a TestContext. */ protected TestContext() { // Run all tests in the US locale, not the system default locale, // because the results all assume the US locale. MondrianResource.setThreadLocale(Locale.US); this.pw = new PrintWriter(System.out, true); } /** * Returns the connect string by which the unit tests can talk to the * FoodMart database. * *

In the base class, the result is the same as the static method * {@link #getDefaultConnectString}. If a derived class overrides * {@link #getConnectionProperties()}, the result of this method * will change also. */ public final String getConnectString() { return getConnectionProperties().toString(); } /** * Constructs a connect string by which the unit tests can talk to the * FoodMart database. * * The algorithm is as follows:

    *
  • Starts with {@link MondrianProperties#TestConnectString}, if it is * set.
  • *
  • If {@link MondrianProperties#FoodmartJdbcURL} is set, this * overrides the Jdbc property.
  • *
  • If the catalog URL is unset or invalid, it assumes that * we are at the root of the source tree, and references * demo/FoodMart.xml
  • . *
*/ public static String getDefaultConnectString() { String connectString = MondrianProperties.instance().TestConnectString.get(); final Util.PropertyList connectProperties; if (connectString == null || connectString.equals("")) { connectProperties = new Util.PropertyList(); connectProperties.put("Provider", "mondrian"); } else { connectProperties = Util.parseConnectString(connectString); } String jdbcURL = MondrianProperties.instance().FoodmartJdbcURL.get(); if (jdbcURL != null) { connectProperties.put("Jdbc", jdbcURL); } String jdbcUser = MondrianProperties.instance().TestJdbcUser.get(); if (jdbcUser != null) { connectProperties.put("JdbcUser", jdbcUser); } String jdbcPassword = MondrianProperties.instance().TestJdbcPassword.get(); if (jdbcPassword != null) { connectProperties.put("JdbcPassword", jdbcPassword); } // Find the catalog. Use the URL specified in the connect string, if // it is specified and is valid. Otherwise, reference FoodMart.xml // assuming we are at the root of the source tree. URL catalogURL = null; String catalog = connectProperties.get("catalog"); if (catalog != null) { try { catalogURL = new URL(catalog); } catch (MalformedURLException e) { // ignore } } if (catalogURL == null) { // Works if we are running in root directory of source tree File file = new File("demo/FoodMart.xml"); if (!file.exists()) { // Works if we are running in bin directory of runtime env file = new File("../demo/FoodMart.xml"); } try { catalogURL = Util.toURL(file); } catch (MalformedURLException e) { throw new Error(e.getMessage()); } } connectProperties.put("catalog", catalogURL.toString()); return connectProperties.toString(); } public synchronized void flushSchemaCache() { // it's pointless to flush the schema cache if we // have a handle on the connection object already getConnection().getCacheControl(null).flushSchemaCache(); } /** * Returns the connection to run queries. * *

When invoked on the default TestContext instance, returns a connection * to the FoodMart database. */ public synchronized Connection getConnection() { if (connectionRef != null) { Connection connection = connectionRef.get(); if (connection != null) { return connection; } } final Connection connection = DriverManager.getConnection( getConnectionProperties(), null, null); connectionRef = new SoftReference(connection); return connection; } /** * Returns a connection to the FoodMart database * with a dynamic schema processor and disables use of RolapSchema Pool. */ public TestContext withSchemaProcessor( Class dynProcClass) { final Util.PropertyList properties = getConnectionProperties().clone(); properties.put( RolapConnectionProperties.DynamicSchemaProcessor.name(), dynProcClass.getName()); properties.put( RolapConnectionProperties.UseSchemaPool.name(), "false"); return withProperties(properties); } /** * Returns a {@link TestContext} similar to this one, but which uses a fresh * connection. * * @return Test context which uses the a fresh connection * * @see #withSchemaPool(boolean) */ public final TestContext withFreshConnection() { final Connection connection = withSchemaPool(false).getConnection(); return withConnection(connection); } public TestContext withSchemaPool(boolean usePool) { final Util.PropertyList properties = getConnectionProperties().clone(); properties.put( RolapConnectionProperties.UseSchemaPool.name(), Boolean.toString(usePool)); return withProperties(properties); } public Util.PropertyList getConnectionProperties() { final Util.PropertyList propertyList = Util.parseConnectString(getDefaultConnectString()); if (MondrianProperties.instance().TestHighCardinalityDimensionList .get() != null && propertyList.get( RolapConnectionProperties.DynamicSchemaProcessor.name()) == null) { propertyList.put( RolapConnectionProperties.DynamicSchemaProcessor.name(), HighCardDynamicSchemaProcessor.class.getName()); } return propertyList; } /** * Returns a the XML of the current schema with added parameters and cube * definitions. */ public String getSchema( String parameterDefs, String cubeDefs, String virtualCubeDefs, String namedSetDefs, String udfDefs, String roleDefs) { // First, get the unadulterated schema. String s = getRawFoodMartSchema(); // Add parameter definitions, if specified. if (parameterDefs != null) { int i = s.indexOf(""); s = s.substring(0, i) + parameterDefs + s.substring(i); } // Add cube definitions, if specified. if (cubeDefs != null) { int i = s.indexOf( ""); s = s.substring(0, i) + cubeDefs + s.substring(i); } // Add virtual cube definitions, if specified. if (virtualCubeDefs != null) { int i = s.indexOf( ""); s = s.substring(0, i) + virtualCubeDefs + s.substring(i); } // Add named set definitions, if specified. Schema-level named sets // occur after and and before elements. if (namedSetDefs != null) { int i = s.indexOf(""); } s = s.substring(0, i) + namedSetDefs + s.substring(i); } // Add definitions of roles, if specified. if (roleDefs != null) { int i = s.indexOf(""); } s = s.substring(0, i) + roleDefs + s.substring(i); } // Add definitions of user-defined functions, if specified. if (udfDefs != null) { int i = s.indexOf(""); s = s.substring(0, i) + udfDefs + s.substring(i); } return s; } /** * Returns the definition of the "FoodMart" schema as stored in * {@code FoodMart.xml}. * * @return XML definition of the FoodMart schema */ public static String getRawFoodMartSchema() { synchronized (SnoopingSchemaProcessor.class) { if (unadulteratedFoodMartSchema == null) { unadulteratedFoodMartSchema = instance().getRawSchema(); } } return unadulteratedFoodMartSchema; } /** * Returns the definition of the schema. * * @return XML definition of the FoodMart schema */ public String getRawSchema() { final Connection connection = withSchemaProcessor(SnoopingSchemaProcessor.class) .getConnection(); connection.close(); String schema = SnoopingSchemaProcessor.THREAD_RESULT.get(); Util.threadLocalRemove(SnoopingSchemaProcessor.THREAD_RESULT); return schema; } /** * Returns a the XML of the foodmart schema, adding dimension definitions * to the definition of a given cube. */ private String substituteSchema( String rawSchema, String cubeName, String dimensionDefs, String measureDefs, String memberDefs, String namedSetDefs) { String s = rawSchema; // Search for the or element. int h = s.indexOf("", h); } // Add dimension definitions, if specified. if (dimensionDefs != null) { int i = s.indexOf(" end) { i = end; } s = s.substring(0, i) + measureDefs + s.substring(i); } // Add calculated member definitions, if specified. if (memberDefs != null) { int i = s.indexOf(" end) { i = end; } s = s.substring(0, i) + memberDefs + s.substring(i); } if (namedSetDefs != null) { int i = s.indexOf(" end) { i = end; } s = s.substring(0, i) + namedSetDefs + s.substring(i); } return s; } /** * Executes a query. * * @param queryString Query string */ public Result executeQuery(String queryString) { Connection connection = getConnection(); queryString = upgradeQuery(queryString); Query query = connection.parseQuery(queryString); final Result result = connection.execute(query); // If we're deep testing, check that we never return the dummy null // value when cells are null. TestExpDependencies isn't the perfect // switch to enable this, but it will do for now. if (MondrianProperties.instance().TestExpDependencies.booleanValue()) { assertResultValid(result); } return result; } public ResultSet executeStatement(String queryString) throws SQLException { OlapConnection connection = getOlap4jConnection(); queryString = upgradeQuery(queryString); OlapStatement stmt = connection.createStatement(); return stmt.executeQuery(queryString); } /** * Executes a query using olap4j. */ public CellSet executeOlap4jQuery(String queryString) throws SQLException { OlapConnection connection = getOlap4jConnection(); queryString = upgradeQuery(queryString); OlapStatement stmt = connection.createStatement(); final CellSet cellSet = stmt.executeOlapQuery(queryString); // If we're deep testing, check that we never return the dummy null // value when cells are null. TestExpDependencies isn't the perfect // switch to enable this, but it will do for now. if (MondrianProperties.instance().TestExpDependencies.booleanValue()) { assertCellSetValid(cellSet); } return cellSet; } /** * Checks that a {@link Result} is valid. * * @param result Query result */ private void assertResultValid(Result result) { for (Cell cell : cellIter(result)) { final Object value = cell.getValue(); // Check that the dummy value used to represent null cells never // leaks into the outside world. Assert.assertNotSame(value, Util.nullValue); Assert.assertFalse( value instanceof Number && ((Number) value).doubleValue() == FunUtil.DoubleNull); // Similarly empty values. Assert.assertNotSame(value, Util.EmptyValue); Assert.assertFalse( value instanceof Number && ((Number) value).doubleValue() == FunUtil.DoubleEmpty); // Cells should be null if and only if they are null or empty. if (cell.getValue() == null) { Assert.assertTrue(cell.isNull()); } else { Assert.assertFalse(cell.isNull()); } } // There should be no null members. for (Axis axis : result.getAxes()) { for (Position position : axis.getPositions()) { for (Member member : position) { Assert.assertNotNull(member); } } } } /** * Checks that a {@link CellSet} is valid. * * @param cellSet Cell set */ private void assertCellSetValid(CellSet cellSet) { for (org.olap4j.Cell cell : cellIter(cellSet)) { final Object value = cell.getValue(); // Check that the dummy value used to represent null cells never // leaks into the outside world. Assert.assertNotSame(value, Util.nullValue); Assert.assertFalse( value instanceof Number && ((Number) value).doubleValue() == FunUtil.DoubleNull); // Similarly empty values. Assert.assertNotSame(value, Util.EmptyValue); Assert.assertFalse( value instanceof Number && ((Number) value).doubleValue() == FunUtil.DoubleEmpty); // Cells should be null if and only if they are null or empty. if (cell.getValue() == null) { Assert.assertTrue(cell.isNull()); } else { Assert.assertFalse(cell.isNull()); } } // There should be no null members. for (CellSetAxis axis : cellSet.getAxes()) { for (org.olap4j.Position position : axis.getPositions()) { for (org.olap4j.metadata.Member member : position.getMembers()) { Assert.assertNotNull(member); } } } } /** * Returns an iterator over cells in a result. */ static Iterable cellIter(final Result result) { return new Iterable() { public Iterator iterator() { int[] axisDimensions = new int[result.getAxes().length]; int k = 0; for (Axis axis : result.getAxes()) { axisDimensions[k++] = axis.getPositions().size(); } final CoordinateIterator coordIter = new CoordinateIterator(axisDimensions); return new Iterator() { public boolean hasNext() { return coordIter.hasNext(); } public Cell next() { final int[] ints = coordIter.next(); return result.getCell(ints); } public void remove() { throw new UnsupportedOperationException(); } }; } }; } /** * Returns an iterator over cells in an olap4j cell set. */ static Iterable cellIter(final CellSet cellSet) { return new Iterable() { public Iterator iterator() { int[] axisDimensions = new int[cellSet.getAxes().size()]; int k = 0; for (CellSetAxis axis : cellSet.getAxes()) { axisDimensions[k++] = axis.getPositions().size(); } final CoordinateIterator coordIter = new CoordinateIterator(axisDimensions); return new Iterator() { public boolean hasNext() { return coordIter.hasNext(); } public org.olap4j.Cell next() { final int[] ints = coordIter.next(); final List list = new AbstractList() { public Integer get(int index) { return ints[index]; } public int size() { return ints.length; } }; return cellSet.getCell( list); } public void remove() { throw new UnsupportedOperationException(); } }; } }; } /** * Executes a query, and asserts that it throws an exception which contains * the given pattern. * * @param queryString Query string * @param pattern Pattern which exception must match */ public void assertQueryThrows(String queryString, String pattern) { Throwable throwable; try { Result result = executeQuery(queryString); Util.discard(result); throwable = null; } catch (Throwable e) { throwable = e; } checkThrowable(throwable, pattern); } /** * Executes an expression, and asserts that it gives an error which contains * a particular pattern. The error might occur during parsing, or might * be contained within the cell value. */ public void assertExprThrows(String expression, String pattern) { Throwable throwable = null; try { String cubeName = getDefaultCubeName(); if (cubeName.indexOf(' ') >= 0) { cubeName = Util.quoteMdxIdentifier(cubeName); } expression = Util.replace(expression, "'", "''"); Result result = executeQuery( "with member [Measures].[Foo] as '" + expression + "' select {[Measures].[Foo]} on columns from " + cubeName); Cell cell = result.getCell(new int[]{0}); if (cell.isError()) { throwable = (Throwable) cell.getValue(); } } catch (Throwable e) { throwable = e; } checkThrowable(throwable, pattern); } /** * Returns the name of the default cube. * *

Tests which evaluate scalar expressions, such as * {@link #assertExprReturns(String, String)}, generate queries against this * cube. * * @return the name of the default cube */ public String getDefaultCubeName() { return "Sales"; } /** * Executes the expression in the context of the cube indicated by * cubeName, and returns the result as a Cell. * * @param expression The expression to evaluate * @return Cell which is the result of the expression */ public Cell executeExprRaw(String expression) { final String queryString = generateExpression(expression); Result result = executeQuery(queryString); return result.getCell(new int[]{0}); } private String generateExpression(String expression) { String cubeName = getDefaultCubeName(); if (cubeName.indexOf(' ') >= 0) { cubeName = Util.quoteMdxIdentifier(cubeName); } return "with member [Measures].[Foo] as " + Util.singleQuoteString(expression) + " select {[Measures].[Foo]} on columns from " + cubeName; } /** * Executes an expression and asserts that it returns a given result. */ public void assertExprReturns(String expression, String expected) { final Cell cell = executeExprRaw(expression); if (expected == null) { expected = ""; // null values are formatted as empty string } assertEqualsVerbose(expected, cell.getFormattedValue()); } /** * Asserts that an expression, with a given set of parameter bindings, * returns a given result. * * @param expr Scalar MDX expression * @param expected Expected result * @param paramValues Array of parameter names and values */ public void assertParameterizedExprReturns( String expr, String expected, Object... paramValues) { Connection connection = getConnection(); String queryString = generateExpression(expr); Query query = connection.parseQuery(queryString); assert paramValues.length % 2 == 0; for (int i = 0; i < paramValues.length;) { final String paramName = (String) paramValues[i++]; final Object value = paramValues[i++]; query.setParameter(paramName, value); } final Result result = connection.execute(query); final Cell cell = result.getCell(new int[]{0}); if (expected == null) { expected = ""; // null values are formatted as empty string } assertEqualsVerbose(expected, cell.getFormattedValue()); } /** * Executes a query with a given expression on an axis, and asserts that it * returns the expected string. */ public void assertAxisReturns( String expression, String expected) { Axis axis = executeAxis(expression); assertEqualsVerbose( expected, upgradeActual(toString(axis.getPositions()))); } /** * Massages the actual result of executing a query to handle differences in * unique names betweeen old and new behavior. * *

Even though the new naming is not enabled by default, reference logs * should be in terms of the new naming. * * @see mondrian.olap.MondrianProperties#SsasCompatibleNaming * * @param actual Actual result * @return Expected result massaged for backwards compatibility */ public String upgradeActual(String actual) { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { actual = Util.replace( actual, "[Time.Weekly]", "[Time].[Weekly]"); actual = Util.replace( actual, "[All Time.Weeklys]", "[All Weeklys]"); actual = Util.replace( actual, "Time.Weekly", "Weekly"); // for a few tests in SchemaTest actual = Util.replace( actual, "[Store.MyHierarchy]", "[Store].[MyHierarchy]"); actual = Util.replace( actual, "[All Store.MyHierarchys]", "[All MyHierarchys]"); actual = Util.replace( actual, "[Store2].[All Store2s]", "[Store2].[Store].[All Stores]"); actual = Util.replace( actual, "[Store Type 2.Store Type 2].[All Store Type 2.Store Type 2s]", "[Store Type 2].[All Store Type 2s]"); actual = Util.replace( actual, "[TIME.CALENDAR]", "[TIME].[CALENDAR]"); actual = Util.replace( actual, "true", "1"); actual = Util.replace( actual, "80000.0000", "80000"); } return actual; } /** * Massages an MDX query to handle differences in * unique names betweeen old and new behavior. * *

The main difference addressed is with level naming. The problem * arises when dimension, hierarchy and level have the same name:

    * *
  • In old behavior, the [Gender].[Gender] represents the Gender level, * and [Gender].[Gender].[Gender] is invalid. * *
  • In new behavior, [Gender].[Gender] represents the Gender hierarchy, * and [Gender].[Gender].[Gender].members represents the Gender level. *

* *

So, {@code upgradeQuery("[Gender]")} returns * "[Gender].[Gender]" for old behavior, * "[Gender].[Gender].[Gender]" for new behavior.

* * @see mondrian.olap.MondrianProperties#SsasCompatibleNaming * * @param queryString Original query * @return Massaged query for backwards compatibility */ public String upgradeQuery(String queryString) { if (MondrianProperties.instance().SsasCompatibleNaming.get()) { String[] names = { "[Gender]", "[Education Level]", "[Marital Status]", "[Store Type]", "[Yearly Income]", }; for (String name : names) { queryString = Util.replace( queryString, name + "." + name, name + "." + name + "." + name); } queryString = Util.replace( queryString, "[Time.Weekly].[All Time.Weeklys]", "[Time].[Weekly].[All Weeklys]"); } return queryString; } /** * Compiles a scalar expression in the context of the default cube. * * @param expression The expression to evaluate * @param scalar Whether the expression is scalar * @return String form of the program */ public String compileExpression(String expression, final boolean scalar) { String cubeName = getDefaultCubeName(); if (cubeName.indexOf(' ') >= 0) { cubeName = Util.quoteMdxIdentifier(cubeName); } final String queryString; if (scalar) { queryString = "with member [Measures].[Foo] as " + Util.singleQuoteString(expression) + " select {[Measures].[Foo]} on columns from " + cubeName; } else { queryString = "SELECT {" + expression + "} ON COLUMNS FROM " + cubeName; } Connection connection = getConnection(); Query query = connection.parseQuery(queryString); final Exp exp; if (scalar) { exp = query.getFormulas()[0].getExpression(); } else { exp = query.getAxes()[0].getSet(); } final Calc calc = query.compileExpression(exp, scalar, null); final StringWriter sw = new StringWriter(); final PrintWriter pw = new PrintWriter(sw); final CalcWriter calcWriter = new CalcWriter(pw, false); calc.accept(calcWriter); pw.flush(); return sw.toString(); } /** * Executes a set expression which is expected to return 0 or 1 members. * It is an error if the expression returns tuples (as opposed to members), * or if it returns two or more members. * * @param expression Expression string * @return Null if axis returns the empty set, member if axis returns one * member. Throws otherwise. */ public Member executeSingletonAxis(String expression) { final String cubeName = getDefaultCubeName(); Result result = executeQuery( "select {" + expression + "} on columns from " + cubeName); Axis axis = result.getAxes()[0]; switch (axis.getPositions().size()) { case 0: // The mdx "{...}" operator eliminates null members (that is, // members for which member.isNull() is true). So if "expression" // yielded just the null member, the array will be empty. return null; case 1: // Java nulls should never happen during expression evaluation. Position position = axis.getPositions().get(0); Util.assertTrue(position.size() == 1); Member member = position.get(0); Util.assertTrue(member != null); return member; default: throw Util.newInternal( "expression " + expression + " yielded " + axis.getPositions().size() + " positions"); } } /** * Executes a query with a given expression on an axis, and returns the * whole axis. */ public Axis executeAxis(String expression) { Result result = executeQuery( "select {" + expression + "} on columns from " + getDefaultCubeName()); return result.getAxes()[0]; } /** * Executes a query with a given expression on an axis, and asserts that it * throws an error which matches a particular pattern. The expression is * evaulated against the default cube. */ public void assertAxisThrows( String expression, String pattern) { Throwable throwable = null; Connection connection = getConnection(); try { final String cubeName = getDefaultCubeName(); final String queryString = "select {" + expression + "} on columns from " + cubeName; Query query = connection.parseQuery(queryString); connection.execute(query); } catch (Throwable e) { throwable = e; } checkThrowable(throwable, pattern); } public static void checkThrowable(Throwable throwable, String pattern) { if (throwable == null) { Assert.fail("query did not yield an exception"); } String stackTrace = getStackTrace(throwable); if (stackTrace.indexOf(pattern) < 0) { Assert.fail( "query's error does not match pattern '" + pattern + "'; error is [" + stackTrace + "]"); } } /** * Returns the output writer. */ public PrintWriter getWriter() { return pw; } /** * Executes a query and checks that the result is a given string. */ public void assertQueryReturns(String query, String desiredResult) { Result result = executeQuery(query); String resultString = toString(result); if (desiredResult != null) { assertEqualsVerbose( desiredResult, upgradeActual(resultString)); } } /** * Executes a very simple query. * *

This forces the schema to be loaded and performs a basic sanity check. * If this is a negative schema test, causes schema validation errors to be * thrown. */ public void assertSimpleQuery() { assertQueryReturns( "select from [Sales]", "Axis #0:\n" + "{}\n" + "266,773"); } /** * Checks that an actual string matches an expected string. * *

If they do not, throws a {@link junit.framework.ComparisonFailure} and * prints the difference, including the actual string as an easily pasted * Java string literal. */ public static void assertEqualsVerbose( String expected, String actual) { assertEqualsVerbose(expected, actual, true, null); } /** * Checks that an actual string matches an expected string. * *

If they do not, throws a {@link ComparisonFailure} and prints the * difference, including the actual string as an easily pasted Java string * literal. * * @param expected Expected string * @param actual Actual string * @param java Whether to generate actual string as a Java string literal * if the values are not equal * @param message Message to display, optional */ public static void assertEqualsVerbose( String expected, String actual, boolean java, String message) { assertEqualsVerbose( fold(expected), actual, java, message); } /** * Checks that an actual string matches an expected string. * *

If they do not, throws a {@link ComparisonFailure} and prints the * difference, including the actual string as an easily pasted Java string * literal. * * @param safeExpected Expected string, where all line endings have been * converted into platform-specific line endings * @param actual Actual string * @param java Whether to generate actual string as a Java string literal * if the values are not equal * @param message Message to display, optional */ public static void assertEqualsVerbose( SafeString safeExpected, String actual, boolean java, String message) { String expected = safeExpected == null ? null : safeExpected.s; if ((expected == null) && (actual == null)) { return; } if ((expected != null) && expected.equals(actual)) { return; } if (message == null) { message = ""; } else { message += nl; } message += "Expected:" + nl + expected + nl + "Actual:" + nl + actual + nl; if (java) { message += "Actual java:" + nl + toJavaString(actual) + nl; } throw new ComparisonFailure(message, expected, actual); } private static String toJavaString(String s) { // Convert [string with "quotes" split // across lines] // into ["string with \"quotes\" split\n" // + "across lines // s = Util.replace(s, "\"", "\\\""); s = LineBreakPattern.matcher(s).replaceAll(lineBreak2); s = TabPattern.matcher(s).replaceAll("\\\\t"); s = "\"" + s + "\""; String spurious = nl + indent + "+ \"\""; if (s.endsWith(spurious)) { s = s.substring(0, s.length() - spurious.length()); } return s; } /** * Checks that an actual string matches an expected pattern. * If they do not, throws a {@link ComparisonFailure} and prints the * difference, including the actual string as an easily pasted Java string * literal. */ public void assertMatchesVerbose( Pattern expected, String actual) { Util.assertPrecondition(expected != null, "expected != null"); if (expected.matcher(actual).matches()) { return; } String s = actual; // Convert [string with "quotes" split // across lines] // into ["string with \"quotes\" split" + nl + // "across lines // s = Util.replace(s, "\"", "\\\""); s = LineBreakPattern.matcher(s).replaceAll(lineBreak); s = TabPattern.matcher(s).replaceAll("\\\\t"); s = "\"" + s + "\""; final String spurious = " + " + nl + "\"\""; if (s.endsWith(spurious)) { s = s.substring(0, s.length() - spurious.length()); } String message = "Expected pattern:" + nl + expected + nl + "Actual: " + nl + actual + nl + "Actual java: " + nl + s + nl; throw new ComparisonFailure(message, expected.pattern(), actual); } /** * Converts a {@link Throwable} to a stack trace. */ public static String getStackTrace(Throwable e) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); pw.flush(); return sw.toString(); } /** * Converts a {@link mondrian.olap.Result} to text in traditional format. * *

For more exotic formats, see * {@link org.olap4j.layout.CellSetFormatter}. * * @param result Query result * @return Result as text */ public static String toString(Result result) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); result.print(pw); pw.flush(); return sw.toString(); } /** * Converts a {@link CellSet} to text in traditional format. * *

For more exotic formats, see * {@link org.olap4j.layout.CellSetFormatter}. * * @param cellSet Query result * @return Result as text */ public static String toString(CellSet cellSet) { final StringWriter sw = new StringWriter(); new TraditionalCellSetFormatter().format( cellSet, new PrintWriter(sw)); return sw.toString(); } /** * Returns a test context whose {@link #getOlap4jConnection()} method always * returns the same connection object, and which has an active * {@link org.olap4j.Scenario}, thus enabling writeback. * * @return Test context with active scenario */ public final TestContext withScenario() { return new DelegatingTestContext(this) { OlapConnection connection; public OlapConnection getOlap4jConnection() throws SQLException { if (connection == null) { connection = super.getOlap4jConnection(); connection.setScenario( connection.createScenario()); } return connection; } }; } /** * Converts a set of positions into a string. Useful if you want to check * that an axis has the results you expected. */ public static String toString(List positions) { StringBuilder buf = new StringBuilder(); int i = 0; for (Position position : positions) { if (i > 0) { buf.append(nl); } if (position.size() != 1) { buf.append("{"); } for (int j = 0; j < position.size(); j++) { Member member = position.get(j); if (j > 0) { buf.append(", "); } buf.append(member.getUniqueName()); } if (position.size() != 1) { buf.append("}"); } i++; } return buf.toString(); } /** * Makes a copy of a suite, filtering certain tests. * * @param suite Test suite * @param testPattern Regular expression of name of tests to include * @return copy of test suite */ public static TestSuite copySuite( TestSuite suite, Util.Functor1 testPattern) { TestSuite newSuite = new TestSuite(suite.getName()); //noinspection unchecked for (Test test : Collections.list((Enumeration) suite.tests())) { if (!testPattern.apply(test)) { continue; } if (test instanceof TestCase) { newSuite.addTest(test); } else if (test instanceof TestSuite) { TestSuite subSuite = copySuite((TestSuite) test, testPattern); if (subSuite.countTestCases() > 0) { newSuite.addTest(subSuite); } } else { // some other kind of test newSuite.addTest(test); } } return newSuite; } public void close() { // nothing } /** * Returns a test context based on a particular data set. * * @param dataSet Data set * @return Test context based on given data set */ public TestContext with(DataSet dataSet) { switch (dataSet) { case FOODMART: return withPropertiesReplace( RolapConnectionProperties.Catalog, "FoodMart.xml", "NewFoodMart.xml"); case LEGACY_FOODMART: return withPropertiesReplace( RolapConnectionProperties.Catalog, "NewFoodMart.xml", "FoodMart.xml"); case ANALYZER_FOODMART: return withPropertiesReplace( RolapConnectionProperties.Catalog, "FoodMart.xml", "AnalyzerFoodMart.xml"); case STEELWHEELS: return SteelWheelsTestCase.createContext(this, null); default: throw Util.unexpected(dataSet); } } private TestContext withPropertiesReplace( RolapConnectionProperties property, String find, String replace) { final Util.PropertyList properties = getConnectionProperties().clone(); final String catalog = properties.get(property.name()); String catalog2 = Util.replace(catalog, find, replace); if (catalog.equals(catalog2)) { return this; } properties.put(property.name(), catalog2); return withProperties(properties); } /** * Wrapper around a string that indicates that all line endings have been * converted to platform-specific line endings. * * @see TestContext#fold */ public static class SafeString { public final String s; private SafeString(String s) { this.s = s; } } /** * Replaces line-endings in a string with the platform-dependent * equivalent. If the input string already has platform-dependent * line endings, no replacements are made. * * @param string String whose line endings are to be made platform- * dependent. Typically these are constant "expected * value" string expressions where the linefeed is * represented as linefeed "\n", but sometimes this method * will receive strings created dynamically where the line * endings are already appropriate for the platform. * @return String where all linefeeds have been converted to * platform-specific (CR+LF on Windows, LF on Unix/Linux) */ public static SafeString fold(String string) { if (string == null) { return null; } if (nl.equals("\n") || string.indexOf(nl) != -1) { return new SafeString(string); } return new SafeString(Util.replace(string, "\n", nl)); } /** * Reverses the effect of {@link #fold}; converts platform-specific line * endings in a string info linefeeds. * * @param string String where all linefeeds have been converted to * platform-specific (CR+LF on Windows, LF on Unix/Linux) * @return String where line endings are represented as linefeed "\n" */ public static String unfold(String string) { if (!nl.equals("\n")) { string = Util.replace(string, nl, "\n"); } if (string == null) { return null; } else { return string; } } public synchronized Dialect getDialect() { if (dialect == null) { dialect = getDialectInternal(); } return dialect; } private Dialect getDialectInternal() { DataSource dataSource = getConnection().getDataSource(); return DialectManager.createDialect(dataSource, null); } /** * Creates a dialect without using a connection. * * @return dialect of an Access persuasion */ public static Dialect getFakeDialect() { final DatabaseMetaData metaData = (DatabaseMetaData) Proxy.newProxyInstance( null, new Class[] {DatabaseMetaData.class}, new DelegatingInvocationHandler() { public boolean supportsResultSetConcurrency( int type, int concurrency) { return false; } public String getDatabaseProductName() { return "Access"; } public String getIdentifierQuoteString() { return "\""; } public String getDatabaseProductVersion() { return "1.0"; } public boolean isReadOnly() { return true; } } ); final java.sql.Connection connection = (java.sql.Connection) Proxy.newProxyInstance( null, new Class[] {java.sql.Connection.class}, new DelegatingInvocationHandler() { public DatabaseMetaData getMetaData() { return metaData; } } ); return DialectManager.createDialect(null, connection); } /** * Checks that expected SQL equals actual SQL. * Performs some normalization on the actual SQL to compensate for * differences between dialects. */ public void assertSqlEquals( String expectedSql, String actualSql, int expectedRows) { // if the actual SQL isn't in the current dialect we have some // problems... probably with the dialectize method assertEqualsVerbose(actualSql, dialectize(actualSql)); String transformedExpectedSql = removeQuotes(dialectize(expectedSql)); String transformedActualSql = removeQuotes(actualSql); Assert.assertEquals(transformedExpectedSql, transformedActualSql); checkSqlAgainstDatasource(actualSql, expectedRows); } private static String removeQuotes(String actualSql) { String transformedActualSql = actualSql.replaceAll("`", ""); transformedActualSql = transformedActualSql.replaceAll("\"", ""); return transformedActualSql; } /** * Converts a SQL string into the current dialect. * *

This is not intended to be a general purpose method: it looks for * specific patterns known to occur in tests, in particular "=as=" and * "fname + ' ' + lname". * * @param sql SQL string in generic dialect * @return SQL string converted into current dialect */ private String dialectize(String sql) { final String search = "fname \\+ ' ' \\+ lname"; final Dialect dialect = getDialect(); final Dialect.DatabaseProduct databaseProduct = dialect.getDatabaseProduct(); switch (databaseProduct) { case MYSQL: // Mysql would generate "CONCAT(...)" sql = sql.replaceAll( search, "CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)"); break; case POSTGRESQL: case ORACLE: case LUCIDDB: case TERADATA: sql = sql.replaceAll( search, "`fname` || ' ' || `lname`"); break; case DERBY: sql = sql.replaceAll( search, "`customer`.`fullname`"); break; case INGRES: sql = sql.replaceAll( search, "fullname"); break; case DB2: case DB2_AS400: case DB2_OLD_AS400: sql = sql.replaceAll( search, "CONCAT(CONCAT(`customer`.`fname`, ' '), `customer`.`lname`)"); break; } if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.ORACLE) { // " + tableQualifier + " sql = sql.replaceAll(" =as= ", " "); } else { sql = sql.replaceAll(" =as= ", " as "); } return sql; } private void checkSqlAgainstDatasource( String actualSql, int expectedRows) { Util.PropertyList connectProperties = getConnectionProperties(); java.sql.Connection jdbcConn = null; Statement stmt = null; ResultSet rs = null; try { String jdbcDrivers = connectProperties.get( RolapConnectionProperties.JdbcDrivers.name()); if (jdbcDrivers != null) { RolapUtil.loadDrivers(jdbcDrivers); } final String jdbcDriversProp = MondrianProperties.instance().JdbcDrivers.get(); RolapUtil.loadDrivers(jdbcDriversProp); jdbcConn = java.sql.DriverManager.getConnection( connectProperties.get(RolapConnectionProperties.Jdbc.name()), connectProperties.get( RolapConnectionProperties.JdbcUser.name()), connectProperties.get( RolapConnectionProperties.JdbcPassword.name())); stmt = jdbcConn.createStatement(); if (RolapUtil.SQL_LOGGER.isDebugEnabled()) { StringBuffer sqllog = new StringBuffer(); sqllog.append("mondrian.test.TestContext: executing sql ["); if (actualSql.indexOf('\n') >= 0) { // SQL appears to be formatted as multiple lines. Make it // start on its own line. sqllog.append("\n"); } sqllog.append(actualSql); sqllog.append(']'); RolapUtil.SQL_LOGGER.debug(sqllog.toString()); } long startTime = System.currentTimeMillis(); rs = stmt.executeQuery(actualSql); long time = System.currentTimeMillis(); final long execMs = time - startTime; Util.addDatabaseTime(execMs); RolapUtil.SQL_LOGGER.debug(", exec " + execMs + " ms"); int rows = 0; while (rs.next()) { rows++; } Assert.assertEquals("row count", expectedRows, rows); } catch (SQLException e) { throw new RuntimeException( "ERROR in SQL - invalid for database: " + connectProperties.get(RolapConnectionProperties.Jdbc.name()) + "\n" + actualSql, e); } finally { try { if (rs != null) { rs.close(); } } catch (Exception e1) { // ignore } try { if (stmt != null) { stmt.close(); } } catch (Exception e1) { // ignore } try { if (jdbcConn != null) { jdbcConn.close(); } } catch (Exception e1) { // ignore } } } /** * Asserts that an MDX set-valued expression depends upon a given list of * dimensions. */ public void assertSetExprDependsOn(String expr, String dimList) { // Construct a query, and mine it for a parsed expression. // Use a fresh connection, because some tests define their own dims. final Connection connection = getConnection(); final String queryString = "SELECT {" + expr + "} ON COLUMNS FROM [Sales]"; final Query query = connection.parseQuery(queryString); query.resolve(); final Exp expression = query.getAxes()[0].getSet(); // Build a list of the dimensions which the expression depends upon, // and check that it is as expected. checkDependsOn(query, expression, dimList, false); } /** * Asserts that an MDX member-valued depends upon a given list of * dimensions. */ public void assertMemberExprDependsOn(String expr, String dimList) { assertSetExprDependsOn("{" + expr + "}", dimList); } /** * Asserts that an MDX expression depends upon a given list of dimensions. */ public void assertExprDependsOn(String expr, String hierList) { // Construct a query, and mine it for a parsed expression. // Use a fresh connection, because some tests define their own dims. final Connection connection = getConnection(); final String queryString = "WITH MEMBER [Measures].[Foo] AS " + Util.singleQuoteString(expr) + " SELECT FROM [Sales]"; final Query query = connection.parseQuery(queryString); query.resolve(); final Formula formula = query.getFormulas()[0]; final Exp expression = formula.getExpression(); // Build a list of the dimensions which the expression depends upon, // and check that it is as expected. checkDependsOn(query, expression, hierList, true); } private void checkDependsOn( final Query query, final Exp expression, String expectedHierList, final boolean scalar) { final Calc calc = query.compileExpression( expression, scalar, scalar ? null : ResultStyle.ITERABLE); final List hierarchies = ((RolapCube) query.getCube()).getHierarchies(); StringBuilder buf = new StringBuilder("{"); int dependCount = 0; for (Hierarchy hierarchy : hierarchies) { if (calc.dependsOn(hierarchy)) { if (dependCount++ > 0) { buf.append(", "); } buf.append(hierarchy.getUniqueName()); } } buf.append("}"); String actualHierList = buf.toString(); Assert.assertEquals(expectedHierList, actualHierList); } /** * Creates a TestContext which is based on a variant of the FoodMart * schema, which parameter, cube, named set, and user-defined function * definitions added. * * @param parameterDefs Parameter definitions. If not null, the string is * is inserted into the schema XML in the appropriate place for * parameter definitions. * @param cubeDefs Cube definition(s). If not null, the string is * is inserted into the schema XML in the appropriate place for * cube definitions. * @param virtualCubeDefs Definitions of virtual cubes. If not null, the * string is inserted into the schema XML in the appropriate place for * virtual cube definitions. * @param namedSetDefs Definitions of named sets. If not null, the string * is inserted into the schema XML in the appropriate place for * named set definitions. * @param udfDefs Definitions of user-defined functions. If not null, the * string is inserted into the schema XML in the appropriate place for * UDF definitions. * @param roleDefs Definitions of roles * @return TestContext which reads from a slightly different hymnbook */ public final TestContext create( final String parameterDefs, final String cubeDefs, final String virtualCubeDefs, final String namedSetDefs, final String udfDefs, final String roleDefs) { final String schema = getSchema( parameterDefs, cubeDefs, virtualCubeDefs, namedSetDefs, udfDefs, roleDefs); return withSchema(schema); } /** * Creates a TestContext which contains the given schema text. * * @param schema XML schema content * @return TestContext which contains the given schema */ public final TestContext withSchema(final String schema) { final Util.PropertyList properties = getConnectionProperties().clone(); properties.put( RolapConnectionProperties.CatalogContent.name(), schema); return withProperties(properties); } /** * Creates a TestContext which is like this one but uses the given * connection properties. * * @param properties Connection properties * @return TestContext which contains the given properties */ public TestContext withProperties(final Util.PropertyList properties) { return new DelegatingTestContext(this) { public Util.PropertyList getConnectionProperties() { return properties; } }; } /** * Creates a TestContext, adding hierarchy definitions to a cube definition. * * @param cubeName Name of a cube in the schema (cube must exist) * @param dimensionDefs String defining dimensions, or null * @return TestContext with modified cube defn */ public final TestContext createSubstitutingCube( final String cubeName, final String dimensionDefs) { return createSubstitutingCube(cubeName, dimensionDefs, null); } /** * Creates a TestContext, adding hierarchy and calculated member definitions * to a cube definition. * * @param cubeName Name of a cube in the schema (cube must exist) * @param dimensionDefs String defining dimensions, or null * @param memberDefs String defining calculated members, or null * @return TestContext with modified cube defn */ public final TestContext createSubstitutingCube( final String cubeName, final String dimensionDefs, final String memberDefs) { return createSubstitutingCube( cubeName, dimensionDefs, null, memberDefs, null); } /** * Creates a TestContext, adding hierarchy and calculated member definitions * to a cube definition. * * @param cubeName Name of a cube in the schema (cube must exist) * @param dimensionDefs String defining dimensions, or null * @param measureDefs String defining measures, or null * @param memberDefs String defining calculated members, or null * @param namedSetDefs String defining named set definitions, or null * @return TestContext with modified cube defn */ public final TestContext createSubstitutingCube( final String cubeName, final String dimensionDefs, final String measureDefs, final String memberDefs, final String namedSetDefs) { final String schema = substituteSchema( getRawFoodMartSchema(), cubeName, dimensionDefs, measureDefs, memberDefs, namedSetDefs); return withSchema(schema); } /** * Returns a TestContext similar to this one, but using the given role. * * @param roleName Role name * @return Test context with the given role */ public final TestContext withRole(final String roleName) { final Util.PropertyList properties = getConnectionProperties().clone(); properties.put( RolapConnectionProperties.Role.name(), roleName); return new DelegatingTestContext(this) { public Util.PropertyList getConnectionProperties() { return properties; } }; } /** * Returns a TestContext similar to this one, but using the given cube as * default for tests such as {@link #assertExprReturns(String, String)}. * * @param cubeName Cube name * @return Test context with the given default cube */ public final TestContext withCube(final String cubeName) { return new DelegatingTestContext(this) { public String getDefaultCubeName() { return cubeName; } }; } /** * Returns a {@link TestContext} similar to this one, but which uses a given * connection. * * @param connection Connection * @return Test context which uses the given connection */ public final TestContext withConnection(final Connection connection) { return new DelegatingTestContext(this) { public Connection getConnection() { return connection; } @Override public void close() { connection.close(); } }; } /** * Generates a string containing all dimensions except those given. * Useful as an argument to {@link #assertExprDependsOn(String, String)}. * * @return string containing all dimensions except those given */ public static String allHiersExcept(String ... hiers) { for (String hier : hiers) { assert contains(AllHiers, hier) : "unknown hierarchy " + hier; } StringBuilder buf = new StringBuilder("{"); int j = 0; for (String hier : AllHiers) { if (!contains(hiers, hier)) { if (j++ > 0) { buf.append(", "); } buf.append(hier); } } buf.append("}"); return buf.toString(); } public static boolean contains(String[] a, String s) { for (String anA : a) { if (anA.equals(s)) { return true; } } return false; } public static String allHiers() { return allHiersExcept(); } /** * Creates a FoodMart connection with "Ignore=true" and returns the list * of warnings in the schema. * * @return Warnings encountered while loading schema */ public List getSchemaWarnings() { final Util.PropertyList propertyList = getConnectionProperties().clone(); propertyList.put( RolapConnectionProperties.Ignore.name(), "true"); final Connection connection = withProperties(propertyList).getConnection(); return connection.getSchema().getWarnings(); } public OlapConnection getOlap4jConnection() throws SQLException { try { Class.forName("mondrian.olap4j.MondrianOlap4jDriver"); } catch (ClassNotFoundException e) { throw new RuntimeException("Driver not found"); } String connectString = getConnectString(); if (connectString.startsWith("Provider=mondrian; ")) { connectString = connectString.substring("Provider=mondrian; ".length()); } final java.sql.Connection connection = java.sql.DriverManager.getConnection( "jdbc:mondrian:" + connectString); return ((OlapWrapper) connection).unwrap(OlapConnection.class); } /** * Tests whether the database is valid. Allows tests that depend on optional * databases to figure out whether to proceed. * * @return whether a database is present and correct */ public boolean databaseIsValid() { try { Connection connection = getConnection(); String cubeName = getDefaultCubeName(); if (cubeName.indexOf(' ') >= 0) { cubeName = Util.quoteMdxIdentifier(cubeName); } Query query = connection.parseQuery("select from " + cubeName); Result result = connection.execute(query); Util.discard(result); connection.close(); return true; } catch (RuntimeException e) { Util.discard(e); return false; } } public static String hierarchyName(String dimension, String hierarchy) { return MondrianProperties.instance().SsasCompatibleNaming.get() ? "[" + dimension + "].[" + hierarchy + "]" : (hierarchy.equals(dimension) ? "[" + dimension + "]" : "[" + dimension + "." + hierarchy + "]"); } public static String levelName( String dimension, String hierarchy, String level) { return hierarchyName(dimension, hierarchy) + ".[" + level + "]"; } /** * Returns count copies of a string. Format strings within string are * substituted, per {@link java.lang.String#format}. * * @param count Number of copies * @param format String template * @return Multiple copies of a string */ public static String repeatString( final int count, String format) { final Formatter formatter = new Formatter(); for (int i = 0; i < count; i++) { formatter.format(format, i); } return formatter.toString(); } //~ Inner classes ---------------------------------------------------------- public static class SnoopingSchemaProcessor extends FilterDynamicSchemaProcessor { public static final ThreadLocal THREAD_RESULT = new ThreadLocal(); protected String filter( String schemaUrl, Util.PropertyList connectInfo, InputStream stream) throws Exception { String catalogContent = super.filter(schemaUrl, connectInfo, stream); THREAD_RESULT.set(catalogContent); return catalogContent; } } /** * Schema processor that flags dimensions as high-cardinality if they * appear in the list of values in the * {@link MondrianProperties#TestHighCardinalityDimensionList} property. * It's a convenient way to run the whole suite against high-cardinality * dimensions without modifying FoodMart.xml. */ public static class HighCardDynamicSchemaProcessor extends FilterDynamicSchemaProcessor { protected String filter( String schemaUrl, Util.PropertyList connectInfo, InputStream stream) throws Exception { String s = super.filter(schemaUrl, connectInfo, stream); final String highCardDimensionList = MondrianProperties.instance() .TestHighCardinalityDimensionList.get(); if (highCardDimensionList != null && !highCardDimensionList.equals("")) { for (String dimension : highCardDimensionList.split(",")) { final String match = "Test cases in this test could, in principle, be moved to olap4j's test. * * @author jhyde */ public class Olap4jTest extends FoodMartTestCase { @SuppressWarnings({"UnusedDeclaration"}) public Olap4jTest() { super(); } @SuppressWarnings({"UnusedDeclaration"}) public Olap4jTest(String name) { super(name); } /** * Test case for bug * MONDRIAN-920, "olap4j: inconsistent measure's member type". * * @throws java.sql.SQLException on error */ public void testSameMemberByVariousMeans() throws SQLException { Random random = new Random(); for (int i = 0; i < 20; i++) { int n = random.nextInt(7); Member member = foo(n); String s = "source #" + n; assertEquals(s, "Unit Sales", member.getName()); assertEquals(s, Member.Type.MEASURE, member.getMemberType()); } } private Member foo(int i) throws SQLException { final OlapConnection connection = getTestContext().getOlap4jConnection(); final Cube cube; final Hierarchy measuresHierarchy; final CellSet cellSet; switch (i) { case 0: cellSet = connection.createStatement().executeOlapQuery( "select [Measures].[Unit Sales] on 0\n" + "from [Sales]"); return cellSet.getAxes().get(0).getPositions().get(0) .getMembers().get(0); case 1: cellSet = connection.createStatement().executeOlapQuery( "select [Measures].Members on 0\n" + "from [Sales]"); return cellSet.getAxes().get(0).getPositions().get(0) .getMembers().get(0); case 2: cellSet = connection.createStatement().executeOlapQuery( "select [Measures].[Measures].Members on 0\n" + "from [Sales]"); return cellSet.getAxes().get(0).getPositions().get(0) .getMembers().get(0); case 3: cube = connection.getOlapSchema().getCubes().get("Sales"); measuresHierarchy = cube.getHierarchies().get("Measures"); final NamedList rootMembers = measuresHierarchy.getRootMembers(); return rootMembers.get(0); case 4: cube = connection.getOlapSchema().getCubes().get("Sales"); measuresHierarchy = cube.getHierarchies().get("Measures"); final Level measuresLevel = measuresHierarchy.getLevels().get(0); final List levelMembers = measuresLevel.getMembers(); return levelMembers.get(0); case 5: cube = connection.getOlapSchema().getCubes().get("Sales"); measuresHierarchy = cube.getHierarchies().get("Measures"); return measuresHierarchy.getDefaultMember(); case 6: cube = connection.getOlapSchema().getCubes().get("Sales"); return cube.lookupMember( IdentifierNode.parseIdentifier("[Measures].[Unit Sales]") .getSegmentList()); default: throw new IllegalArgumentException("bad index " + i); } } public void testAnnotation() throws SQLException { final OlapConnection connection = getTestContext().getOlap4jConnection(); final CellSet cellSet = connection.createStatement().executeOlapQuery( "select from [Sales]"); final CellSetMetaData metaData = cellSet.getMetaData(); final Cube salesCube = metaData.getCube(); Annotated annotated = ((OlapWrapper) salesCube).unwrap(Annotated.class); final Annotation annotation = annotated.getAnnotationMap().get("caption.fr_FR"); assertEquals("Ventes", annotation.getValue()); final Map map = XmlaHandler.getExtra(connection).getAnnotationMap(salesCube); assertEquals("Ventes", map.get("caption.fr_FR")); } public void testFormatString() throws SQLException { final OlapConnection connection = getTestContext().getOlap4jConnection(); final CellSet cellSet = connection.createStatement().executeOlapQuery( "with member [Measures].[Foo] as 1, FORMAT_STRING = Iif(1 < 2, '##.0%', 'foo')\n" + "select\n" + " [Measures].[Foo] DIMENSION PROPERTIES FORMAT_EXP on 0\n" + "from [Sales]"); final CellSetAxis axis = cellSet.getAxes().get(0); final Member member = axis.getPositions().get(0).getMembers().get(0); Property property = findProperty(axis, "FORMAT_EXP"); assertNotNull(property); // Note that the expression is returned, unevaluated. You can tell from // the parentheses and quotes that it has been un-parsed. assertEquals( "IIf((1 < 2), \"##.0%\", \"foo\")", member.getPropertyValue(property)); } /** * Tests that a property that is not a standard olap4j property but is a * Mondrian-builtin property (viz, "FORMAT_EXP") is included among a level's * properties. * * @throws SQLException on error */ public void testLevelProperties() throws SQLException { final OlapConnection connection = getTestContext().getOlap4jConnection(); final CellSet cellSet = connection.createStatement().executeOlapQuery( "select [Store].[Store Name].Members on 0\n" + "from [Sales]"); final CellSetAxis axis = cellSet.getAxes().get(0); final Member member = axis.getPositions().get(0).getMembers().get(0); final NamedList properties = member.getLevel().getProperties(); // UNIQUE_NAME is an olap4j standard property. assertNotNull(properties.get("MEMBER_UNIQUE_NAME")); // FORMAT_EXP is a Mondrian built-in but not olap4j standard property. assertNotNull(properties.get("FORMAT_EXP")); // [Store Type] is a property of the level. assertNotNull(properties.get("Store Type")); } private Property findProperty(CellSetAxis axis, String name) { for (Property property : axis.getAxisMetaData().getProperties()) { if (property.getName().equals(name)) { return property; } } return null; } public void testCellProperties() throws SQLException { final OlapConnection connection = getTestContext().getOlap4jConnection(); final CellSet cellSet = connection.createStatement().executeOlapQuery( "with member [Customers].[USA].[CA WA] as\n" + " Aggregate({[Customers].[USA].[CA], [Customers].[USA].[WA]})\n" + "select [Measures].[Unit Sales] on 0,\n" + " {[Customers].[USA].[CA], [Customers].[USA].[CA WA]} on 1\n" + "from [Sales]\n" + "cell properties ACTION_TYPE, DRILLTHROUGH_COUNT"); final CellSetMetaData metaData = cellSet.getMetaData(); final Property actionTypeProperty = metaData.getCellProperties().get("ACTION_TYPE"); final Property drillthroughCountProperty = metaData.getCellProperties().get("DRILLTHROUGH_COUNT"); // Cell [0, 0] is drillable final Cell cell0 = cellSet.getCell(0); final int actionType0 = (Integer) cell0.getPropertyValue(actionTypeProperty); assertEquals(0x100, actionType0); // MDACTION_TYPE_DRILLTHROUGH final int drill0 = (Integer) cell0.getPropertyValue(drillthroughCountProperty); assertEquals(24442, drill0); // Cell [0, 1] is not drillable final Cell cell1 = cellSet.getCell(1); final int actionType1 = (Integer) cell1.getPropertyValue(actionTypeProperty); assertEquals(0x0, actionType1); final int drill1 = (Integer) cell1.getPropertyValue(drillthroughCountProperty); assertEquals(-1, drill1); } /** * Same case as * {@link mondrian.test.BasicQueryTest#testQueryIterationLimit()}, but this * time, check that the OlapException has the required SQLstate. * * @throws SQLException on error */ public void testLimit() throws SQLException { propSaver.set(MondrianProperties.instance().IterationLimit, 11); String queryString = "With Set [*NATIVE_CJ_SET] as " + "'NonEmptyCrossJoin([*BASE_MEMBERS_Dates], [*BASE_MEMBERS_Stores])' " + "Set [*BASE_MEMBERS_Dates] as '{[Time].[1997].[Q1], [Time].[1997].[Q2], [Time].[1997].[Q3], [Time].[1997].[Q4]}' " + "Set [*GENERATED_MEMBERS_Dates] as 'Generate([*NATIVE_CJ_SET], {[Time].[Time].CurrentMember})' " + "Set [*GENERATED_MEMBERS_Measures] as '{[Measures].[*SUMMARY_METRIC_0]}' " + "Set [*BASE_MEMBERS_Stores] as '{[Store].[USA].[CA], [Store].[USA].[WA], [Store].[USA].[OR]}' " + "Set [*GENERATED_MEMBERS_Stores] as 'Generate([*NATIVE_CJ_SET], {[Store].CurrentMember})' " + "Member [Time].[Time].[*SM_CTX_SEL] as 'Aggregate([*GENERATED_MEMBERS_Dates])' " + "Member [Measures].[*SUMMARY_METRIC_0] as '[Measures].[Unit Sales]/([Measures].[Unit Sales],[Time].[*SM_CTX_SEL])' " + "Member [Time].[Time].[*SUBTOTAL_MEMBER_SEL~SUM] as 'sum([*GENERATED_MEMBERS_Dates])' " + "Member [Store].[*SUBTOTAL_MEMBER_SEL~SUM] as 'sum([*GENERATED_MEMBERS_Stores])' " + "select crossjoin({[Time].[*SUBTOTAL_MEMBER_SEL~SUM]}, {[Store].[*SUBTOTAL_MEMBER_SEL~SUM]}) " + "on columns from [Sales]"; final OlapConnection connection = getTestContext().getOlap4jConnection(); try { final CellSet cellSet = connection.createStatement().executeOlapQuery(queryString); fail("expected exception, got " + cellSet); } catch (OlapException e) { assertEquals("ResourceLimitExceeded", e.getSQLState()); } } public void testCloseOnCompletion() throws Exception { if (Util.JdbcVersion < 0x0401) { // Statement.closeOnCompletion added in JDBC 4.1 / JDK 1.7. return; } final OlapConnection connection = getTestContext().getOlap4jConnection(); for (boolean b : new boolean[] {false, true}) { final OlapStatement statement = connection.createStatement(); final CellSet cellSet = statement.executeOlapQuery( "select [Measures].[Unit Sales] on 0\n" + "from [Sales]"); if (b) { closeOnCompletion(statement); } assertFalse(isClosed(cellSet)); assertFalse(isClosed(statement)); cellSet.close(); assertTrue(isClosed(cellSet)); assertEquals(b, isClosed(statement)); statement.close(); // not error to close twice } } /** * Calls {@link java.sql.Statement#isClosed()} or * {@link java.sql.ResultSet#isClosed()} via reflection. * * @param statement Statement or result set * @return Whether statement or result set is closed * @throws Exception on error */ static boolean isClosed(Object statement) throws Exception { Method method = (statement instanceof Statement ? java.sql.Statement.class : java.sql.ResultSet.class).getMethod("isClosed"); return (Boolean) method.invoke(statement); } /** * Calls {@link java.sql.Statement#closeOnCompletion()} via reflection. * * @param statement Statement or result set * @throws Exception on error */ static void closeOnCompletion(Object statement) throws Exception { Method method = java.sql.Statement.class.getMethod("closeOnCompletion"); method.invoke(statement); } public void testParse() throws SQLException { assertParseQueryFails( "select\nfrom", "Mondrian Error:Syntax error at line 2, column 5, token ''"); assertParseQueryFails( "`\nselect\nfrom Sales\n", "Lexical error at line 1, column 1. Encountered: \"`\" (96), after : \"\""); assertParseQueryFails( "'\nselect\nfrom Sales\n", "Lexical error at line 4, column 0. Encountered: after : \"\\'\\nselect\\nfrom Sales\\n\""); assertParseQueryFails( "select\nfrom 'Sales", "Lexical error at line 3, column 0. Encountered: after : \"\\'Sales\\n\""); assertParseQueryFails( "with member Measures.Test as '\n" + "select\n" + "from Sales", "Lexical error at line 4, column 0. Encountered: after : \"\\'\\nselect\\nfrom Sales\\n\""); } private void assertParseQueryFails(String mdx, String expectedError) throws SQLException { final OlapConnection connection = getTestContext().getOlap4jConnection(); try { final PreparedOlapStatement statement = connection.prepareOlapStatement(mdx); fail("expected exception, got " + statement); } catch (OlapException e) { final String actual = TestContext.getStackTrace(e); assertTrue(actual, actual.contains(expectedError)); } finally { connection.close(); } } } // End Olap4jTest.java mondrian-3.4.1/testsrc/main/mondrian/test/StandAlone.java0000644000175000017500000003355711735330606023337 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; import java.io.*; import java.text.MessageFormat; import java.util.*; public class StandAlone { private static final String[] indents = new String[]{ " ", " ", " ", " " }; private static Connection cxn; private static String cellProp; private static boolean printMemberProps = false; private static BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in)); public static final String ConnectionString = "Provider=mondrian;" + "Jdbc=jdbc:JSQLConnect://engdb04:1433/database=MondrianFoodmart/user=mondrian/password=password;" + "Catalog=file:demo\\FoodMart.xml;" + "JdbcDrivers=com.jnetdirect.jsql.JSQLDriver;"; public static void main(String[] args) { long now = System.currentTimeMillis(); // java.sql.DriverManager.setLogWriter(new PrintWriter(System.err)); cxn = DriverManager.getConnection(ConnectionString, null); System.out.println( "Connected in " + (System.currentTimeMillis() - now) + " usec"); processCommands(); } private static void processCommands() { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); long startTime = System.currentTimeMillis(); inputLoop: for (; ;) { try { String line = in.readLine(); if (line == null) { break inputLoop; } if (line.equals("\\q")) { break inputLoop; } else if (line.startsWith("\\")) { processSlashCommand(line); } else { StringBuilder buf = new StringBuilder(); buf.append(line); for (;;) { System.out.print("> "); line = in.readLine(); if (line == null) { break inputLoop; } if (line.equals(".")) { break; } buf.append(' '); buf.append(line); } long queryStart = System.currentTimeMillis(); String queryString = buf.toString(); boolean printResults = false; if (buf.substring(0, 1).equals("-")) { queryString = buf.substring(1); printResults = true; } Query query = cxn.parseQuery(queryString); Result result = cxn.execute(query); displayElapsedTime(queryStart, "Elapsed time"); printResult(result, printResults); } } catch (IOException ioe) { ioe.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } } displayElapsedTime(startTime, "Connect time"); } private static void displayElapsedTime(long startTime, String message) { long elapsed = System.currentTimeMillis() - startTime; int seconds, msecs; msecs = (int) (elapsed % 1000); seconds = (int) (elapsed / 1000); System.out.println( MessageFormat.format( "{2}: {0,number,0}.{1,number,000} ({3})", seconds, msecs, message, elapsed)); } private static void printResult(Result result, boolean outputResults) { Axis slicer = result.getSlicerAxis(); int nonNullCellCount = 0; int cellCount = 0; int numRows = 0; int numColumns; List slicerpositions = slicer.getPositions(); int numSlicers = slicer.getPositions().size(); if (numSlicers > 0 && outputResults) { System.out.print("Slicers: {"); for (Position pos : slicerpositions) { printMembers(pos); } System.out.println("}"); } Axis[] axes = result.getAxes(); if (axes.length == 0) { numColumns = 0; cellCount = 1; if (outputResults) { System.out.println("No axes."); Cell cell = result.getCell(new int[0]); printCell(cell); } } else if (axes.length == 1) { // Only columns List cols = axes[0].getPositions(); numColumns = cols.size(); for (int idx = 0; idx < cols.size(); idx++) { Position col = cols.get(idx); if (outputResults) { System.out.print("Column " + idx + ": "); printMembers(col); } Cell cell = result.getCell(new int[]{idx}); if (!cell.isNull()) { nonNullCellCount++; } cellCount++; if (outputResults) { printCell(cell); } } } else { List colPositions = axes[0].getPositions(); List rowPositions = axes[1].getPositions(); numColumns = colPositions.size(); numRows = rowPositions.size(); int[] coords = new int[2]; if (outputResults) { System.out.println("Column tuples: "); } for (int colIdx = 0; colIdx < colPositions.size(); colIdx++) { Position col = colPositions.get(colIdx); if (outputResults) { System.out.print("Column " + colIdx + ": "); printMembers(col); System.out.println(); } for (int rowIdx = 0; rowIdx < rowPositions.size(); rowIdx++) { if (outputResults) { System.out.print("(" + colIdx + ", " + rowIdx + ") "); printMembers(rowPositions.get(rowIdx)); System.out.print("} = "); } coords[0] = colIdx; coords[1] = rowIdx; Cell cell = result.getCell(coords); if (!cell.isNull()) { nonNullCellCount++; } cellCount++; if (outputResults) { printCell(cell); } } } } System.out.println("cellCount: " + cellCount); System.out.println("nonNullCellCount: " + nonNullCellCount); System.out.println("numSlicers: " + numSlicers); System.out.println("numColumns: " + numColumns); System.out.println("numRows: " + numRows); } private static void printCell(Cell cell) { Object cellPropValue; if (cellProp != null) { cellPropValue = cell.getPropertyValue(cellProp); System.out.print("(" + cellPropValue + ")"); } System.out.println(cell.getFormattedValue()); } private static void printMembers(Position pos) { boolean needComma = false; for (Member member : pos) { if (needComma) { System.out.print(','); } needComma = true; System.out.print(member.getUniqueName()); if (printMemberProps) { Property[] props = member.getProperties(); if (props.length > 0) { System.out.print(" {"); for (int idx = 0; idx < props.length; idx++) { if (idx > 1) { System.out.print(", "); } Property prop = props[idx]; System.out.print( prop.getName() + ": " + member.getPropertyValue(prop.getName())); } System.out.print("}"); } } } } private static void processSlashCommand(String line) throws IOException { if (line.equals("\\schema")) { printSchema(cxn.getSchema()); } else if (line.equals("\\dbg")) { PrintWriter out = java.sql.DriverManager.getLogWriter(); if (out == null) { java.sql.DriverManager.setLogWriter( new PrintWriter(System.err)); System.out.println("SQL driver logging enabled"); } else { java.sql.DriverManager.setLogWriter(null); System.out.println("SQL driver logging disabled"); } cxn.close(); cxn = DriverManager.getConnection(ConnectionString, null); } else if (line.equals("\\cp")) { System.out.print("Enter cell property: "); cellProp = stdin.readLine(); if (cellProp == null || cellProp.length() == 0) { cellProp = null; } } else if (line.equals("\\mp")) { printMemberProps ^= true; System.out.println("Print member properties: " + printMemberProps); } else if (line.startsWith("\\test ")) { StringTokenizer st = new StringTokenizer(line, " ", false); st.nextToken(); // throw away /test String threads = st.nextToken(); String seconds = st.nextToken(); String useRandom; try { useRandom = st.nextToken(); } catch (NoSuchElementException nse) { useRandom = "false"; } try { runTest( Integer.parseInt(threads), Integer.parseInt(seconds), Boolean.valueOf(useRandom)); } catch (NumberFormatException nfe) { System.out.println( "Please enter a valid integer for the number of threads " + "and the execution time"); } } else { System.out.println("Commands:"); System.out.println("\t\\q Quit"); System.out.println("\t\\schema Print the schema"); System.out.println("\t\\dbg Toggle SQL driver debugging"); } } private static void runTest( int numThreads, int seconds, boolean randomQueries) { QueryRunner[] runners = new QueryRunner[numThreads]; System.out.println( "Running multi-threading test with " + numThreads + " threads for " + seconds + " seconds."); System.out.println( "Queries will " + (randomQueries ? "" : "not ") + "be random."); for (int idx = 0; idx < runners.length; idx++) { runners[idx] = new QueryRunner(idx, seconds, randomQueries); } for (QueryRunner runner : runners) { runner.start(); } for (QueryRunner runner : runners) { try { runner.join(); } catch (InterruptedException e) { e.printStackTrace(); } } for (QueryRunner runner : runners) { runner.report(System.out); } } private static void printSchema(Schema schema) { Cube[] cubes = schema.getCubes(); Hierarchy[] hierarchies = schema.getSharedHierarchies(); System.out.println( "Schema: " + schema.getName() + " " + cubes.length + " cubes and " + hierarchies.length + " shared hierarchies"); System.out.println("---Cubes "); for (int idx = 0; idx < cubes.length; idx++) { printCube(cubes[idx]); System.out.println("-------------------------------------------"); } System.out.println("---Shared hierarchies"); for (int idx = 0; idx < hierarchies.length; idx++) { printHierarchy(0, hierarchies[idx]); } } private static void printCube(Cube cube) { System.out.println("Cube " + cube.getName()); Dimension[] dims = cube.getDimensions(); for (Dimension dim : dims) { printDimension(dim); } } private static void printDimension(Dimension dim) { DimensionType dimensionType = dim.getDimensionType(); System.out.println( "\tDimension " + dim.getName() + " type: " + dimensionType.name()); System.out.println("\t Description: " + dim.getDescription()); Hierarchy[] hierarchies = dim.getHierarchies(); for (Hierarchy hierarchy : hierarchies) { printHierarchy(1, hierarchy); } } private static void printHierarchy(int indent, Hierarchy hierarchy) { String indentString = indents[indent]; System.out.println(indentString + " Hierarchy " + hierarchy.getName()); System.out.println( indentString + " Description: " + hierarchy.getDescription()); System.out.println( indentString + " Default member: " + hierarchy.getDefaultMember().getUniqueName()); Level[] levels = hierarchy.getLevels(); for (Level level : levels) { printLevel(indent + 1, level); } } private static void printLevel(int indent, Level level) { String indentString = indents[indent]; System.out.println(indentString + "Level " + level.getName()); System.out.print(level.getUniqueName()); } } // End StandAlone.java mondrian-3.4.1/testsrc/main/mondrian/test/MondrianServerTest.java0000644000175000017500000000474311735330606025100 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2011 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.olap.MondrianServer; import mondrian.server.StringRepositoryContentFinder; import mondrian.server.UrlRepositoryContentFinder; import mondrian.xmla.test.XmlaTestContext; import junit.framework.TestCase; import org.olap4j.OlapConnection; import org.olap4j.metadata.Catalog; import org.olap4j.metadata.NamedList; import java.net.MalformedURLException; import java.sql.SQLException; /** * Test suite for server functionality in {@link MondrianServer}. * * @author jhyde * @since 2010/11/22 */ public class MondrianServerTest extends TestCase { /** * Tests an embedded server. */ public void testEmbedded() { TestContext testContext = TestContext.instance(); final MondrianServer server = MondrianServer.forConnection(testContext.getConnection()); final int id = server.getId(); assertNotNull(id); server.shutdown(); } /** * Tests a server with its own repository. */ public void testStringRepository() throws MalformedURLException { final MondrianServer server = MondrianServer.createWithRepository( new StringRepositoryContentFinder("foo bar"), null); final int id = server.getId(); assertNotNull(id); server.shutdown(); } /** * Tests a server that reads its repository from a file URL. */ public void testRepository() throws MalformedURLException, SQLException { final XmlaTestContext xmlaTestContext = new XmlaTestContext(); final MondrianServer server = MondrianServer.createWithRepository( new UrlRepositoryContentFinder( "inline:" + xmlaTestContext.getDataSourcesString()), null); final int id = server.getId(); assertNotNull(id); OlapConnection connection = server.getConnection("FoodMart", "FoodMart", null); final NamedList catalogs = connection.getOlapCatalogs(); assertEquals(1, catalogs.size()); assertEquals("FoodMart", catalogs.get(0).getName()); server.shutdown(); } } // End MondrianServerTest.java mondrian-3.4.1/testsrc/main/mondrian/test/ConcurrentValidatingQueryRunner.java0000644000175000017500000002742411735330606027650 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; import java.io.PrintStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; /** * Utility class to run set of MDX queries in multiple threads and * validate the results. * Queries are run against the FoodMart database. * * @author Thiyagu, Ajit */ public class ConcurrentValidatingQueryRunner extends Thread { private long mRunTime; private long mStartTime; private long mStopTime; private volatile List mExceptions = new ArrayList(); private String threadName; private int mRunCount; private int mSuccessCount; private boolean mRandomQueries; // if mRandomCacheFlush is true, toss a unfair coin, // if the result of the coin toss is favorable, flush a random region // of cache private boolean mRandomCacheFlush; // a real number from 0 to 1 inclusive represents the bias of the coin // 0.5 is a fair coin private double mRandomFlushFrequency = 0.5; private ConcurrentMdxTest concurrentMdxTest = new ConcurrentMdxTest(); private FoodMartTestCase.QueryAndResult[] mdxQueries; // mutex to isolate sections that run MDX and sections that flush cache // tests fail intermittenly if this mutex is removed private static Object lock = new Object(); /** * Runs concurrent queries without flushing cache. This constructor * provides backward compatibilty for usage in {@link ConcurrentMdxTest}. * * @param numSeconds Running time * @param useRandomQuery If set to true, the runner will * pick a random query from the set. If set to false, * the runner will circle through queries sequentially * @param queriesAndResults The array of pairs of query and expected result */ public ConcurrentValidatingQueryRunner( int numSeconds, boolean useRandomQuery, FoodMartTestCase.QueryAndResult[] queriesAndResults) { this.mdxQueries = queriesAndResults; mRunTime = numSeconds * 1000; mRandomQueries = useRandomQuery; mRandomCacheFlush = false; } /** * Runs concurrent queries with random cache flush. * * @param numSeconds Running time * @param useRandomQuery If set to true, the runner will * pick a random query from the set. If set to false, * the runner will circle through queries sequentially * @param randomCacheFlush If set to true, the runner will * do a coin toss before running the query. If the result of the * experiment is favorable, runner will flush a random region * of aggregation cache * @param queriesAndResults The array of pairs of query and expected result */ public ConcurrentValidatingQueryRunner( int numSeconds, boolean useRandomQuery, boolean randomCacheFlush, FoodMartTestCase.QueryAndResult[] queriesAndResults) { this.mdxQueries = queriesAndResults; mRunTime = numSeconds * 1000; mRandomQueries = useRandomQuery; mRandomCacheFlush = randomCacheFlush; } /** * Runs a number of queries until time expires. For each iteration, * if cache is to be flushed, do it before running the query. */ public void run() { mStartTime = System.currentTimeMillis(); threadName = Thread.currentThread().getName(); try { int queryIndex = -1; while (System.currentTimeMillis() - mStartTime < mRunTime) { try { if (mRandomQueries) { queryIndex = (int) (Math.random() * mdxQueries.length); } else { queryIndex = mRunCount % mdxQueries.length; } mRunCount++; synchronized (lock) { // flush a random region of cache if (mRandomCacheFlush && (Math.random() < mRandomFlushFrequency)) { flushRandomRegionOfCache(); } // flush the whole schema if (mRandomCacheFlush && (Math.random() < mRandomFlushFrequency)) { flushSchema(); } } synchronized (lock) { concurrentMdxTest.assertQueryReturns( mdxQueries[queryIndex].query, mdxQueries[queryIndex].result); mSuccessCount++; } } catch (Exception e) { mExceptions.add( new Exception( "Exception occurred in iteration " + mRunCount + " of thread " + Thread.currentThread().getName(), e)); } } mStopTime = System.currentTimeMillis(); } catch (Exception e) { mExceptions.add(e); } catch (Error e) { mExceptions.add(e); } } /** * Prints result of this test run. * * @param out Output stream */ private void report(PrintStream out) { String message = MessageFormat.format( " {0} ran {1} queries, {2} successfully in {3} milliseconds", threadName, mRunCount, mSuccessCount, mStopTime - mStartTime); out.println(message); for (Object throwable : mExceptions) { if (throwable instanceof Exception) { ((Exception) throwable).printStackTrace(out); } else { System.out.println(throwable); } } } /** * Creates and runs concurrent threads of tests without flushing cache. * This method provides backward compatibilty for usage in * {@link ConcurrentMdxTest}. * * @param numThreads Number of concurrent threads * @param runTimeInSeconds Running Time * @param randomQueries Whether to pick queries in random or in sequence * @param printReport Whether to print report * @param queriesAndResults Array of pairs of query and expected result * @return The list of failures */ static List runTest( int numThreads, int runTimeInSeconds, boolean randomQueries, boolean printReport, FoodMartTestCase.QueryAndResult[] queriesAndResults) { return runTest( numThreads, runTimeInSeconds, randomQueries, false, printReport, queriesAndResults); } /** * Creates and runs concurrent threads of tests with random cache flush. * * @param numThreads Number of concurrent threads * @param runTimeInSeconds Running Time * @param randomQueries Whether to pick queries in random or in sequence * @param randomCacheFlush Whether to flush cache before running queries * @param printReport Whether to print report * @param queriesAndResults Array of pairs of query and expected result * @return The list of failures */ static List runTest( int numThreads, int runTimeInSeconds, boolean randomQueries, boolean randomCacheFlush, boolean printReport, FoodMartTestCase.QueryAndResult[] queriesAndResults) { ConcurrentValidatingQueryRunner[] runners = new ConcurrentValidatingQueryRunner[numThreads]; List allExceptions = new ArrayList(); for (int idx = 0; idx < runners.length; idx++) { runners[idx] = new ConcurrentValidatingQueryRunner( runTimeInSeconds, randomQueries, randomCacheFlush, queriesAndResults); } for (int idx = 0; idx < runners.length; idx++) { runners[idx].start(); } for (int idx = 0; idx < runners.length; idx++) { try { runners[idx].join(); } catch (InterruptedException e) { e.printStackTrace(); } } for (int idx = 0; idx < runners.length; idx++) { allExceptions.addAll(runners[idx].mExceptions); if (printReport) { runners[idx].report(System.out); } } return allExceptions; } /** * Flushes the whole schema. * */ private void flushSchema() { Connection connection = concurrentMdxTest.getConnection(); CacheControl cacheControl = connection.getCacheControl(null); Cube salesCube = connection.getSchema().lookupCube("Sales", true); CacheControl.CellRegion measuresRegion = cacheControl.createMeasuresRegion(salesCube); cacheControl.flush(measuresRegion); Cube whsalesCube = connection.getSchema().lookupCube("Warehouse and Sales", true); measuresRegion = cacheControl.createMeasuresRegion(whsalesCube); cacheControl.flush(measuresRegion); } /** * Flushes a random region of cache. This is not truly random yet; the * method pick one of the three US states to be flushed. */ private void flushRandomRegionOfCache() { // todo: more dimensions for randomizing Connection connection = concurrentMdxTest.getConnection(); CacheControl cacheControl = connection.getCacheControl(null); // Lookup members Cube salesCube = connection.getSchema().lookupCube( "Sales", true); SchemaReader schemaReader = salesCube.getSchemaReader(null); CacheControl.CellRegion measuresRegion = cacheControl.createMeasuresRegion( salesCube); try { String[] tsegments = new String[] {"Time", "1997"}; Id tid = new Id(Id.Segment.toList(tsegments)); Member memberTime97 = schemaReader.getMemberByUniqueName(tid.getSegments(), false); CacheControl.CellRegion regionTime97 = cacheControl.createMemberRegion( memberTime97, true); String[] states = {"CA", "OR", "WA"}; int idx = (int) (Math.random() * states.length); String[] ssegments = new String[] {"Customers", "All Customers", "USA", states[idx]}; Id sid = new Id(Id.Segment.toList(ssegments)); Member memberCustomerState = schemaReader.getMemberByUniqueName(sid.getSegments(), false); CacheControl.CellRegion regionCustomerState = cacheControl.createMemberRegion( memberCustomerState, true); CacheControl.CellRegion region97State = cacheControl.createCrossjoinRegion( measuresRegion, regionTime97, regionCustomerState); cacheControl.flush(region97State); } catch (Exception e) { // do nothing when a wrong region was picked // don't throw exception } } } // End ConcurrentValidatingQueryRunner.java mondrian-3.4.1/testsrc/main/mondrian/test/TestCalculatedMembers.java0000644000175000017500000025040011735330606025507 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; import mondrian.rolap.BatchTestCase; import mondrian.spi.Dialect; import junit.framework.Assert; import junit.framework.AssertionFailedError; /** * Tests the expressions used for calculated members. Please keep in sync * with the actual code used by the wizard. * * @author jhyde * @since 5 October, 2002 */ public class TestCalculatedMembers extends BatchTestCase { public TestCalculatedMembers() { super(); } public TestCalculatedMembers(String name) { super(name); } public void testCalculatedMemberInCube() { assertExprReturns("[Measures].[Profit]", "$339,610.90"); // Testcase for bug 829012. assertQueryReturns( "select {[Measures].[Avg Salary], [Measures].[Org Salary]} ON columns,\n" + "{([Time].[1997], [Store].[All Stores], [Employees].[All Employees])} ON rows\n" + "from [HR]\n" + "where [Pay Type].[Hourly]", "Axis #0:\n" + "{[Pay Type].[Hourly]}\n" + "Axis #1:\n" + "{[Measures].[Avg Salary]}\n" + "{[Measures].[Org Salary]}\n" + "Axis #2:\n" + "{[Time].[1997], [Store].[All Stores], [Employees].[All Employees]}\n" + "Row #0: $40.31\n" + "Row #0: $11,406.75\n"); } public void testCalculatedMemberInCubeViaApi() { Cube salesCube = getSalesCube("Sales"); salesCube.createCalculatedMember( ""); String s = executeExpr("[Measures].[Profit2]"); Assert.assertEquals("339,610.90", s); // should fail if member of same name exists try { salesCube.createCalculatedMember( ""); throw new AssertionFailedError("expected error, got none"); } catch (RuntimeException e) { final String msg = e.getMessage(); if (!msg.equals( "Mondrian Error:Calculated member " + "'[Measures].[Measures].[Profit2]' " + "already exists in cube 'Sales'")) { throw e; } } } /** * Tests a calculated member with spaces in its name against a virtual * cube with spaces in its name. */ public void testCalculatedMemberInCubeWithSpace() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Warehouse and Sales", null, "") .withCube("Warehouse and Sales"); Cell s = testContext.executeExprRaw("[Measures].[Profit With Spaces]"); Assert.assertEquals("339,610.90", s.getFormattedValue()); } public void testCalculatedMemberInCubeWithProps() { Cube salesCube = getSalesCube("Sales"); // member with a property salesCube.createCalculatedMember( "" + " " + ""); // note that result uses format string Result result = TestContext.instance().executeQuery( "select {[Measures].[Profit3]} on columns from Sales"); String s = result.getCell(new int[]{0}).getFormattedValue(); Assert.assertEquals("339611", s); // should fail if member property has expr and value try { salesCube.createCalculatedMember( "" + " " + ""); throw new AssertionFailedError("expected error, got none"); } catch (RuntimeException e) { final String msg = e.getMessage(); if (!msg.equals( "Mondrian Error:Member property must have a value or an " + "expression. (Property 'FORMAT_STRING' of member 'Profit4' " + "of cube 'Sales'.)")) { throw e; } } // should fail if member property both expr and value try { salesCube.createCalculatedMember( "" + " " + ""); throw new AssertionFailedError("expected error, got none"); } catch (RuntimeException e) { final String msg = e.getMessage(); if (!msg.equals( "Mondrian Error:Member property must not have both a value and " + "an expression. (Property 'FORMAT_STRING' of member " + "'Profit4' of cube 'Sales'.)")) { throw e; } } // should fail if member property's expression is invalid try { salesCube.createCalculatedMember( "" + " " + ""); throw new AssertionFailedError("expected error, got none"); } catch (RuntimeException e) { final String msg = e.getMessage(); if (!msg.equals( "Mondrian Error:Named set in cube 'Sales' has bad formula")) { throw e; } } // should succeed if we switch the property to ignore invalid // members; the create will succeed and in the select, it will // return null for the member and therefore a 0 in the calculation propSaver.set( MondrianProperties.instance().IgnoreInvalidMembers, true); salesCube.createCalculatedMember( "" + ""); result = TestContext.instance().executeQuery( "select {[Measures].[Profit4]} on columns from Sales"); s = result.getCell(new int[]{0}).getFormattedValue(); Assert.assertEquals("339,610.90", s); } private Cube getSalesCube(String cubeName) { Cube[] cubes = getConnection().getSchema().getSchemaReader().getCubes(); for (Cube cube : cubes) { if (cube.getName().equals(cubeName)) { return cube; } } return null; } public void testCalculatedMemberInCubeAndQuery() { // Profit is defined in the cube. // Profit Change is defined in the query. assertQueryReturns( "WITH MEMBER [Measures].[Profit Change]\n" + " AS '[Measures].[Profit] - ([Measures].[Profit], [Time].[Time].PrevMember)'\n" + "SELECT {[Measures].[Profit], [Measures].[Profit Change]} ON COLUMNS,\n" + " {[Time].[1997].[Q2].children} ON ROWS\n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Profit]}\n" + "{[Measures].[Profit Change]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[5]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "Row #0: $25,766.55\n" + "Row #0: -$4,289.24\n" + "Row #1: $26,673.73\n" + "Row #1: $907.18\n" + "Row #2: $27,261.76\n" + "Row #2: $588.03\n"); } public void testQueryCalculatedMemberOverridesCube() { // Profit is defined in the cube, and has a format string "$#,###". // We define it in a query to make sure that the format string in the // cube doesn't change. assertQueryReturns( "WITH MEMBER [Measures].[Profit]\n" + " AS '(Measures.[Store Sales] - Measures.[Store Cost])', FORMAT_STRING='#,###' \n" + "SELECT {[Measures].[Profit]} ON COLUMNS,\n" + " {[Time].[1997].[Q2]} ON ROWS\n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Profit]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q2]}\n" + "Row #0: 79,702\n"); // Note that the Profit measure defined against the cube has // a format string preceded by "$". assertQueryReturns( "SELECT {[Measures].[Profit]} ON COLUMNS,\n" + " {[Time].[1997].[Q2]} ON ROWS\n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Profit]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q2]}\n" + "Row #0: $79,702.05\n"); } public void testQueryCalcMemberOverridesShallowerStoredMember() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { // functionality requires new name resolver return; } // Does "[Time].[Time2].[1998]" resolve to // the stored member "[Time].[Time2].[1998]" // or the calculated member "[Time].[Time2].[1997].[1998]"? // In SSAS, the calc member gets chosen, even though it is not as // good a match. assertQueryReturns( "with member [Time].[Weekly].[1998].[1997] as 4\n" + " select [Time].[Weekly].[1997] on 0\n" + " from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[Weekly].[1998].[1997]}\n" + "Row #0: 4\n"); // does not match if last segment is different assertQueryReturns( "with member [Time].[Weekly].[1998].[1997xxx] as 4\n" + " select [Time].[Weekly].[1997] on 0\n" + " from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[Weekly].[1997]}\n" + "Row #0: 266,773\n"); } /** * If there are multiple calc members with the same name, the first is * chosen, even if it is not the best match. */ public void testEarlierCalcMember() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { // functionality requires new name resolver return; } // SSAS returns 2 assertQueryReturns( "with\n" + " member [Time].[Time].[1997].[Q1].[1999] as 1\n" + " member [Time].[Time].[1997].[Q1].[1998] as 2\n" + " member [Time].[Time].[1997].[Q2].[1998] as 3\n" + " member [Time].[Time].[1997].[1998] as 4\n" + " select [Time].[Time].[1998] on 0\n" + " from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997].[Q1].[1998]}\n" + "Row #0: 2\n"); } public void _testWhole() { // "allmembers" tests compatibility with MSAS executeQuery( "with\n" + "member [Measures].[Total Store Sales by Product Name] as\n" + " 'Sum([Product].[Product Name].members, [Measures].[Store Sales])'\n" + "\n" + "member [Measures].[Average Store Sales by Product Name] as\n" + " 'Avg([Product].[Product Name].allmembers, [Measures].[Store Sales])'\n" + "\n" + "member [Measures].[Number of Product Name members] as\n" + " 'Count([Product].[Product Name].members)'\n" + "\n" + "member [Measures].[Standard Deviation of Store Sales for Product Name] as\n" + " 'Stddev([Product].[Product Name].members, [Measures].[Store Sales])'\n" + "\n" + "member [Measures].[Variance between Store Sales and Store Cost] as\n" + " '[Measures].[Store Sales] - [Measures].[Store Cost]'\n" + "\n" + "member [Measures].[% Variance between Store Sales and Store Cost] as\n" + " 'iif([Measures].[Store Cost] = 0, 1, [Measures].[Store Sales] / [Measures].[Store Cost])'\n" + ", format_string='Percent'\n" + "\n" + "member [Measures].[% Difference between Store Sales and Store Cost] as\n" + " 'iif([Measures].[Store Sales] = 0, -1, ([Measures].[Store Sales] - [Measures].[Store Cost]) / [Measures].[Store Sales])'\n" + ", format_string='Percent'\n" + "\n" + "member [Measures].[% Markup between Store Sales and Store Cost] as\n" + " 'iif([Measures].[Store Cost] = 0, 1, ([Measures].[Store Sales] - [Measures].[Store Cost]) / [Measures].[Store Cost])'\n" + ", format_string='Percent'\n" + "\n" + "member [Measures].[Growth of Store Sales since previous period] as\n" + " '[Measures].[Store Sales] - ([Measures].[Store Sales], ParallelPeriod([Time].CurrentMember.level, 1))'\n" + "\n" + "member [Measures].[% Growth of Store Sales since previous period] as\n" + " 'iif(([Measures].[Store Sales], ParallelPeriod([Time].CurrentMember.level, 1)) = 0, 1, ([Measures].[Store Sales] - ([Measures].[Store Sales], ParallelPeriod([Time].CurrentMember.level, 1))) / ([Measures].[Store Sales], ParallelPeriod([Time].CurrentMember.level, 1)))'\n" + ", format_string='Percent'\n" + "\n" + "member [Measures].[Growth of Store Sales since previous year] as\n" + " '[Measures].[Store Sales] - ([Measures].[Store Sales], ParallelPeriod([Time].[Year], 1))'\n" + "\n" + "member [Measures].[% Growth of Store Sales since previous year] as\n" + " 'iif(([Measures].[Store Sales], ParallelPeriod([Time].[Year], 1)) = 0, 1, ([Measures].[Store Sales] - ([Measures].[Store Sales], ParallelPeriod([Time].[Year], 1))) / ([Measures].[Store Sales], ParallelPeriod([Time].[Year], 1)))'\n" + ", format_string='Percent'\n" + "\n" + "member [Measures].[Store Sales as % of parent Store] as\n" + " 'iif(([Measures].[Store Sales], [Store].CurrentMember.Parent) = 0, 1, [Measures].[Store Sales] / ([Measures].[Store Sales], [Store].CurrentMember.Parent))'\n" + ", format_string='Percent'\n" + "\n" + "member [Measures].[Store Sales as % of all Store] as\n" + " 'iif(([Measures].[Store Sales], [Store].Members.Item(0)) = 0, 1, [Measures].[Store Sales] / ([Measures].[Store Sales], [Store].Members.Item(0)))'\n" + ", format_string='Percent'\n" + "\n" + "member [Measures].[Total Store Sales, period to date] as\n" + " 'sum(PeriodsToDate([Time].CurrentMember.Parent.Level), [Measures].[Store Sales])'\n" + "\n" + "member [Measures].[Total Store Sales, Quarter to date] as\n" + " 'sum(PeriodsToDate([Time].[Quarter]), [Measures].[Store Sales])'\n" + "\n" + "member [Measures].[Average Store Sales, period to date] as\n" + " 'avg(PeriodsToDate([Time].CurrentMember.Parent.Level), [Measures].[Store Sales])'\n" + "\n" + "member [Measures].[Average Store Sales, Quarter to date] as\n" + " 'avg(PeriodsToDate([Time].[Quarter]), [Measures].[Store Sales])'\n" + "\n" + "member [Measures].[Rolling Total of Store Sales over previous 3 periods] as\n" + " 'sum([Time].CurrentMember.Lag(2) : [Time].CurrentMember, [Measures].[Store Sales])'\n" + "\n" + "member [Measures].[Rolling Average of Store Sales over previous 3 periods] as\n" + " 'avg([Time].CurrentMember.Lag(2) : [Time].CurrentMember, [Measures].[Store Sales])'\n" + "\n" + "select\n" + " CrossJoin(\n" + " {[Time].[1997], [Time].[1997].[Q2]},\n" + " {[Store].[All Stores], \n" + " [Store].[USA],\n" + " [Store].[USA].[CA],\n" + " [Store].[USA].[CA].[San Francisco]}) on columns,\n" + " AddCalculatedMembers([Measures].members) on rows\n" + " from Sales"); // Repeat time-related measures with more time members. executeQuery( "with\n" + "member [Measures].[Total Store Sales, Quarter to date] as\n" + " 'sum(PeriodsToDate([Time].[Quarter]), [Measures].[Store Sales])'\n" + "\n" + "member [Measures].[Average Store Sales, period to date] as\n" + " 'avg(PeriodsToDate([Time].CurrentMember.Parent.Level), [Measures].[Store Sales])'\n" + "\n" + "member [Measures].[Average Store Sales, Quarter to date] as\n" + " 'avg(PeriodsToDate([Time].[Quarter]), [Measures].[Store Sales])'\n" + "\n" + "member [Measures].[Rolling Total of Store Sales over previous 3 periods] as\n" + " 'sum([Time].CurrentMember.Lag(2) : [Time].CurrentMember, [Measures].[Store Sales])'\n" + "\n" + "member [Measures].[Rolling Average of Store Sales over previous 3 periods] as\n" + " 'avg([Time].CurrentMember.Lag(2) : [Time].CurrentMember, [Measures].[Store Sales])'\n" + "\n" + "select\n" + " CrossJoin(\n" + " {[Store].[USA].[CA],\n" + " [Store].[USA].[CA].[San Francisco]},\n" + " [Time].[Month].members) on columns,\n" + " AddCalculatedMembers([Measures].members) on rows\n" + " from Sales"); } public void testCalculatedMemberCaption() { String mdx = "select {[Measures].[Profit Growth]} on columns from Sales"; Result result = TestContext.instance().executeQuery(mdx); Axis axis0 = result.getAxes()[0]; Position pos0 = axis0.getPositions().get(0); Member profGrowth = pos0.get(0); String caption = profGrowth.getCaption(); Assert.assertEquals(caption, "Gewinn-Wachstum"); } public void testCalcMemberIsSetFails() { // A member which is a set, and more important, cannot be converted to // a value, is an error. String queryString = "with member [Measures].[Foo] as ' Filter([Product].members, 1 <> 0) '" + "select {[Measures].[Foo]} on columns from [Sales]"; String pattern = "Member expression 'Filter([Product].Members, (1 <> 0))' must " + "not be a set"; assertQueryThrows(queryString, pattern); // A tuple is OK, because it can be converted to a scalar expression. queryString = "with member [Measures].[Foo] as ' ([Measures].[Unit Sales], [Gender].[F]) '" + "select {[Measures].[Foo]} on columns from [Sales]"; final Result result = executeQuery(queryString); Util.discard(result); // Level cannot be converted. assertExprThrows( "[Customers].[Country]", "Member expression '[Customers].[Country]' must not be a set"); // Hierarchy can be converted. assertExprReturns("[Customers].[Customers]", "266,773"); // Dimension can be converted, if unambiguous. assertExprReturns("[Customers]", "266,773"); if (MondrianProperties.instance().SsasCompatibleNaming.get()) { // SSAS 2005 does not have default hierarchies. assertExprThrows( "[Time]", "The 'Time' dimension contains more than one hierarchy, " + "therefore the hierarchy must be explicitly specified."); } else { // Default to first hierarchy. assertExprReturns("[Time]", "266,773"); } // Explicit hierarchy OK. assertExprReturns("[Time].[Time]", "266,773"); // Member can be converted. assertExprReturns("[Customers].[USA]", "266,773"); // Tuple can be converted. assertExprReturns( "([Customers].[USA], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer])", "1,683"); // Set of tuples cannot be converted. assertExprThrows( "{([Customers].[USA], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer])}", "Member expression '{([Customers].[USA], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer])}' must not be a set"); assertExprThrows( "{([Customers].[USA], [Product].[Food])," + "([Customers].[USA], [Product].[Drink])}", "{([Customers].[USA], [Product].[Food]), ([Customers].[USA], [Product].[Drink])}' must not be a set"); // Sets cannot be converted. assertExprThrows( "{[Product].[Food]}", "Member expression '{[Product].[Food]}' must not be a set"); } /** * Tests that calculated members can have brackets in their names. * (Bug 1251683.) */ public void testBracketInCalcMemberName() { assertQueryReturns( "with member [Measures].[has a [bracket]] in it] as \n" + "' [Measures].CurrentMember.Name '\n" + "select {[Measures].[has a [bracket]] in it]} on columns\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[has a [bracket]] in it]}\n" + "Row #0: Unit Sales\n"); } /** * Tests that IIf works OK even if its argument returns the NULL * value. (Bug 1418689.) */ public void testNpeInIif() { assertQueryReturns( "WITH MEMBER [Measures].[Foo] AS ' 1 / [Measures].[Unit Sales] ',\n" + " FORMAT_STRING=IIf([Measures].[Foo] < .3, \"|0.0|style=red\",\"0.0\")\n" + "SELECT {[Store].[USA].[WA].children} on columns\n" + "FROM Sales\n" + "WHERE ([Time].[1997].[Q4].[12],\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth].[Portsmouth Imported Beer],\n" + " [Measures].[Foo])", "Axis #0:\n" + "{[Time].[1997].[Q4].[12], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth].[Portsmouth Imported Beer], [Measures].[Foo]}\n" + "Axis #1:\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "{[Store].[USA].[WA].[Bremerton]}\n" + "{[Store].[USA].[WA].[Seattle]}\n" + "{[Store].[USA].[WA].[Spokane]}\n" + "{[Store].[USA].[WA].[Tacoma]}\n" + "{[Store].[USA].[WA].[Walla Walla]}\n" + "{[Store].[USA].[WA].[Yakima]}\n" + "Row #0: Infinity\n" + "Row #0: Infinity\n" + "Row #0: 0.5\n" + "Row #0: Infinity\n" + "Row #0: |0.1|style=red\n" + "Row #0: Infinity\n" + "Row #0: |0.3|style=red\n"); } /** * Tests that calculated members defined in the schema can have brackets in * their names. (Bug 1251683.) */ public void testBracketInCubeCalcMemberName() { final String cubeName = "Sales_BracketInCubeCalcMemberName"; String s = "\n" + "

\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""; final TestContext testContext = TestContext.instance().create( null, s, null, null, null, null); testContext.assertQueryThrows( "select {[Measures].[With a [bracket] inside it]} on columns,\n" + " {[Gender].Members} on rows\n" + "from [" + cubeName + "]", "Syntax error at line 1, column 38, token 'inside'"); testContext.assertQueryReturns( "select {[Measures].[With a [bracket]] inside it]} on columns,\n" + " {[Gender].Members} on rows\n" + "from [" + cubeName + "]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[With a [bracket]] inside it]}\n" + "Axis #2:\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: $1,315,580.00\n" + "Row #1: $1,352,150.00\n"); } public void testPropertyReferencesCalcMember() { assertQueryReturns( "with member [Measures].[Foo] as ' [Measures].[Unit Sales] * 2 '," + " FORMAT_STRING=IIf([Measures].[Foo] < 600000, \"|#,##0|style=red\",\"#,##0\") " + "select {[Measures].[Foo]} on columns " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: |533,546|style=red\n"); } public void testCalcMemberWithQuote() { // MSAS ignores single-quotes assertQueryReturns( "with member [Measures].[Foo] as '1 + 2'\n" + "select from [Sales] where [Measures].[Foo]", "Axis #0:\n" + "{[Measures].[Foo]}\n" + "3"); // As above assertQueryReturns( "with member [Measures].[Foo] as 1 + 2\n" + "select from [Sales] where [Measures].[Foo]", "Axis #0:\n" + "{[Measures].[Foo]}\n" + "3"); // MSAS treats doubles-quotes as strings assertQueryReturns( "with member [Measures].[Foo] as \"1 + 2\"\n" + "select from [Sales] where [Measures].[Foo]", "Axis #0:\n" + "{[Measures].[Foo]}\n" + "1 + 2"); // single-quote inside double-quoted string literal // MSAS does not allow this assertQueryThrows( "with member [Measures].[Foo] as ' \"quoted string with 'apostrophe' in it\" ' " + "select {[Measures].[Foo]} on columns " + "from [Sales]", "Mondrian Error:" + "Lexical error at line 2, column 0. " + "Encountered: after : \"\\\"quoted string with \\n\""); // Escaped single quote in double-quoted string literal inside // single-quoted member declaration. assertQueryReturns( "with member [Measures].[Foo] as ' \"quoted string with ''apostrophe'' in it\" ' " + "select {[Measures].[Foo]} on columns " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: quoted string with 'apostrophe' in it\n"); // escaped double-quote inside double-quoted string literal assertQueryReturns( "with member [Measures].[Foo] as ' \"quoted string with \"\"double-quote\"\" in it\" ' " + "select {[Measures].[Foo]} on columns " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: quoted string with \"double-quote\" in it\n"); // escaped double-quote inside double-quoted string literal assertQueryReturns( "with member [Measures].[Foo] as \"quoted string with 'apos' in it\" " + "select {[Measures].[Foo]} on columns " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: quoted string with 'apos' in it\n"); // Double-escaped single-quote // inside escaped single-quoted string literal // inside single-quoted formula. // MSAS does not allow this, but I think it should. assertQueryReturns( "with member [Measures].[Foo] as ' ''quoted string and ''''apos''''.'' ' " + "select {[Measures].[Foo]} on columns " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: quoted string and 'apos'.\n"); // Escaped single-quote // inside double-quoted string literal // inside single-quoted formula. assertQueryReturns( "with member [Measures].[Foo] as ' \"quoted string and ''apos''.\" ' " + "select {[Measures].[Foo]} on columns " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: quoted string and 'apos'.\n"); // single quote in format expression assertQueryReturns( "with member [Measures].[Colored Profit] as ' [Measures].[Store Sales] - [Measures].[Store Cost] ', " + " FORMAT_STRING = Iif([Measures].[Colored Profit] < 0, '|($#,##0.00)|style=red', '|$#,##0.00|style=green') " + "select {[Measures].[Colored Profit]} on columns," + " {[Product].Children} on rows " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Colored Profit]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: |$29,358.98|style=green\n" + "Row #1: |$245,764.87|style=green\n" + "Row #2: |$64,487.05|style=green\n"); // double quote in format expression assertQueryReturns( "with member [Measures].[Colored Profit] as ' [Measures].[Store Sales] - [Measures].[Store Cost] ', " + " FORMAT_STRING = Iif([Measures].[Colored Profit] < 0, \"|($#,##0.00)|style=red\", \"|$#,##0.00|style=green\") " + "select {[Measures].[Colored Profit]} on columns," + " {[Product].Children} on rows " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Colored Profit]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: |$29,358.98|style=green\n" + "Row #1: |$245,764.87|style=green\n" + "Row #2: |$64,487.05|style=green\n"); } /** * Testcase for bug * MONDRIAN-137, "error if calc member in schema file contains single * quotes". */ public void testQuoteInCalcMember() { final String cubeName = "Sales_Bug1410383"; String s = "\n" + "
\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""; final TestContext testContext = TestContext.instance().create( null, s, null, null, null, null); testContext.assertQueryReturns( "select {[Measures].[Apos in dq], [Measures].[Dq in dq], [Measures].[Apos in apos], [Measures].[Dq in apos], [Measures].[Colored Profit]} on columns,\n" + " {[Gender].Members} on rows\n" + "from [" + cubeName + "]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Apos in dq]}\n" + "{[Measures].[Dq in dq]}\n" + "{[Measures].[Apos in apos]}\n" + "{[Measures].[Dq in apos]}\n" + "{[Measures].[Colored Profit]}\n" + "Axis #2:\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: an 'apos' in dq\n" + "Row #0: a \"dq\" in dq\n" + "Row #0: an 'apos' in apos\n" + "Row #0: a \"dq\" in apos\n" + "Row #0: |$168,448.73|style=green\n" + "Row #1: an 'apos' in dq\n" + "Row #1: a \"dq\" in dq\n" + "Row #1: an 'apos' in apos\n" + "Row #1: a \"dq\" in apos\n" + "Row #1: |$171,162.17|style=green\n"); } public void testChildrenOfCalcMembers() { assertQueryReturns( "with member [Time].[Time].[# Months Product Sold] as 'Count(Descendants([Time].[Time].LastSibling, [Time].[Month]), EXCLUDEEMPTY)'\n" + "select Crossjoin([Time].[# Months Product Sold].Children,\n" + " [Store].[All Stores].Children) ON COLUMNS,\n" + " [Product].[All Products].Children ON ROWS from [Sales] where [Measures].[Unit Sales]", "Axis #0:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #1:\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n"); } public void testNonCharacterMembers() { assertQueryReturns( "with member [Has Coffee Bar].[Maybe] as \n" + "'SUM([Has Coffee Bar].members)' \n" + "SELECT {[Has Coffee Bar].[Maybe]} on rows, \n" + "{[Measures].[Store Sqft]} on columns \n" + "FROM [Store]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sqft]}\n" + "Axis #2:\n" + "{[Has coffee bar].[Maybe]}\n" + "Row #0: 1,143,192\n"); } public void testFormatString() { // Verify that // (a) a calculated member without a format string does not // override the format string of a base measure // ([Highly Profitable States] does not override [Store Sales]) // and // (b) a calculated member with a format string does // override the format string of a base measure // ([Plain States] does override [Store Sales]) // and // (c) the format string for conflicting calculated members // is chosen according to solve order // ([Plain States] overrides [Profit]) assertQueryReturns( "WITH MEMBER [Customers].[Highly Profitable States]\n" + "AS 'SUM(FILTER([Customers].[USA].children,([Measures].[Profit] > 90000)))'\n" + "MEMBER [Customers].[Plain States]\n" + " AS 'SUM([Customers].[USA].children)', SOLVE_ORDER = 5, FORMAT_STRING='#,###'\n" + "SELECT {[Measures].[Store Sales], [Measures].[Profit]} ON COLUMNS,\n" + "UNION([Customers].[USA].children,{[Customers].[Highly Profitable States],[Customers].[Plain States]}) ON ROWS\n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Profit]}\n" + "Axis #2:\n" + "{[Customers].[USA].[CA]}\n" + "{[Customers].[USA].[OR]}\n" + "{[Customers].[USA].[WA]}\n" + "{[Customers].[Highly Profitable States]}\n" + "{[Customers].[Plain States]}\n" + "Row #0: 159,167.84\n" + "Row #0: $95,637.41\n" + "Row #1: 142,277.07\n" + "Row #1: $85,504.57\n" + "Row #2: 263,793.22\n" + "Row #2: $158,468.91\n" + "Row #3: 422,961.06\n" + "Row #3: $254,106.33\n" + "Row #4: 565,238\n" + "Row #4: 339,611\n"); } /** * Testcase for * bug MONDRIAN-263, Negative Solve Orders broken. */ public void testNegativeSolveOrder() { // Negative solve orders are OK. assertQueryReturns( "with member measures.blah as 'measures.[unit sales]', SOLVE_ORDER = -6 select {measures.[unit sales], measures.blah} on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[blah]}\n" + "Row #0: 266,773\n" + "Row #0: 266,773\n"); // Member with a negative solve order is trumped by a stored measure // (which has solve order 0), which in turn is trumped by a calc member // with a positive solve order. assertQueryReturns( "with member [Product].[Foo] as ' 1 ', SOLVE_ORDER = -6\n" + " member [Gender].[Bar] as ' 2 ', SOLVE_ORDER = 3\n" + "select {[Measures].[Unit Sales]} on 0,\n" + " {[Product].[Foo], [Product].[Drink]} *\n" + " {[Gender].[M], [Gender].[Bar]} on 1\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Foo], [Gender].[M]}\n" + "{[Product].[Foo], [Gender].[Bar]}\n" + "{[Product].[Drink], [Gender].[M]}\n" + "{[Product].[Drink], [Gender].[Bar]}\n" + "Row #0: 1\n" + "Row #1: 2\n" + "Row #2: 12,395\n" + "Row #3: 2\n"); } public void testCalcMemberCustomFormatterInQuery() { // calc measure defined in query assertQueryReturns( "with member [Measures].[Foo] as ' [Measures].[Unit Sales] * 2 ',\n" + " CELL_FORMATTER='" + mondrian.test.UdfTest.FooBarCellFormatter.class.getName() + "' \n" + "select {[Measures].[Unit Sales], [Measures].[Foo]} on 0,\n" + " {[Store].Children} on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Foo]}\n" + "Axis #2:\n" + "{[Store].[Canada]}\n" + "{[Store].[Mexico]}\n" + "{[Store].[USA]}\n" + "Row #0: \n" + "Row #0: foo1.2345E-8bar\n" + "Row #1: \n" + "Row #1: foo1.2345E-8bar\n" + "Row #2: 266,773\n" + "Row #2: foo533546.0bar\n"); } public void testCalcMemberCustomFormatterInQueryNegative() { assertQueryThrows( "with member [Measures].[Foo] as ' [Measures].[Unit Sales] * 2 ',\n" + " CELL_FORMATTER='mondrian.test.NonExistentCellFormatter' \n" + "select {[Measures].[Unit Sales], [Measures].[Foo]} on 0,\n" + " {[Store].Children} on rows\n" + "from [Sales]", "Failed to load formatter class 'mondrian.test.NonExistentCellFormatter' for member '[Measures].[Foo]'."); } public void testCalcMemberCustomFormatterInQueryNegative2() { String query = "with member [Measures].[Foo] as ' [Measures].[Unit Sales] * 2 ',\n" + " CELL_FORMATTER='java.lang.String' \n" + "select {[Measures].[Unit Sales], [Measures].[Foo]} on 0,\n" + " {[Store].Children} on rows\n" + "from [Sales]"; assertQueryThrows( query, "Failed to load formatter class 'java.lang.String' for member '[Measures].[Foo]'."); assertQueryThrows( query, Util.PreJdk15 ? "java.lang.ClassCastException" : "java.lang.ClassCastException: java.lang.String"); } public void testCalcMemberCustomFormatterInNonMeasureInQuery() { // CELL_FORMATTER is ignored for calc members which are not measures. // // We could change this behavior if it makes sense. In fact, we would // allow ALL properties to be inherited from the member with the // highest solve order. Need to check whether this is consistent with // the MDX spec. -- jhyde, 2007/9/5. assertQueryReturns( "with member [Store].[CA or OR] as ' Aggregate({[Store].[USA].[CA], [Store].[USA].[OR]}) ',\n" + " CELL_FORMATTER='mondrian.test.UdfTest.FooBarCellFormatter'\n" + "select {[Store].[USA], [Store].[CA or OR]} on columns\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA]}\n" + "{[Store].[CA or OR]}\n" + "Row #0: 266,773\n" + "Row #0: 142,407\n"); } public void testCalcMemberCustomFormatterInSchema() { // calc member defined in schema String cubeName = "Sales"; TestContext testContext = TestContext.instance().createSubstitutingCube( cubeName, null, "\n" + " \n" + " \n" + "\n"); testContext.assertQueryReturns( "select {[Measures].[Unit Sales], [Measures].[Profit Formatted]} on 0,\n" + " {[Store].Children} on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Profit Formatted]}\n" + "Axis #2:\n" + "{[Store].[Canada]}\n" + "{[Store].[Mexico]}\n" + "{[Store].[USA]}\n" + "Row #0: \n" + "Row #0: foo1.2345E-8bar\n" + "Row #1: \n" + "Row #1: foo1.2345E-8bar\n" + "Row #2: 266,773\n" + "Row #2: foo339610.89639999997bar\n"); } public void testCalcMemberCustomFormatterInSchemaNegative() { // calc member defined in schema String cubeName = "Sales"; final TestContext testContext = TestContext.instance().createSubstitutingCube( cubeName, null, " \n" + " \n" + " \n" + "\n"); testContext.assertQueryThrows( "select {[Measures].[Unit Sales], [Measures].[Profit Formatted]} on 0,\n" + " {[Store].Children} on rows\n" + "from [Sales]", "Failed to load formatter class 'mondrian.test.NonExistentCellFormatter' for member '[Measures].[Profit Formatted]'."); } /** * Testcase for bug 1784617, "Using StrToTuple() in schema errors out". */ public void testStrToSetInCubeCalcMember() { // calc member defined in schema String cubeName = "Sales"; final TestContext testContext = TestContext.instance().createSubstitutingCube( cubeName, null, "\n"); String desiredResult = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[My Tuple]}\n" + "Row #0: 68,755\n"; testContext.assertQueryReturns( "select {[Measures].[My Tuple]} on 0 from [Sales]", desiredResult); // same result if calc member is defined in query TestContext.instance().assertQueryReturns( "with member [Measures].[My Tuple] as\n" + " 'StrToTuple(\"([Gender].[M], [Marital Status].[S])\", [Gender], [Marital Status])'\n" + "select {[Measures].[My Tuple]} on 0 from [Sales]", desiredResult); } public void testCreateCalculatedMember() { // REVIEW: What is the purpose of this test? String query = "WITH MEMBER [Product].[Calculated Member] as 'AGGREGATE({})'\n" + "SELECT {[Measures].[Unit Sales]} on 0\n" + "FROM [Sales]\n" + "WHERE ([Product].[Calculated Member])"; String derbySQL = "select \"product_class\".\"product_family\" from \"product\" as \"product\", \"product_class\" as \"product_class\" where \"product\".\"product_class_id\" = \"product_class\".\"product_class_id\" and UPPER(\"product_class\".\"product_family\") = UPPER('Calculated Member') group by \"product_class\".\"product_family\" order by \"product_class\".\"product_family\" ASC"; String mysqlSQL = "select `product_class`.`product_family` as `c0` from `product` as `product`, `product_class` as `product_class` where `product`.`product_class_id` = `product_class`.`product_class_id` and UPPER(`product_class`.`product_family`) = UPPER('Calculated Member') group by `product_class`.`product_family` order by ISNULL(`product_class`.`product_family`), `product_class`.`product_family` ASC"; SqlPattern[] patterns = { new SqlPattern(Dialect.DatabaseProduct.DERBY, derbySQL, derbySQL), new SqlPattern(Dialect.DatabaseProduct.MYSQL, mysqlSQL, mysqlSQL) }; assertQuerySqlOrNot( this.getTestContext(), query, patterns, true, true, true); } /** * Tests a calculated member which aggregates over a set which would seem * to include the calculated member (but does not). */ public void testSetIncludesSelf() { assertQueryReturns( "with set [Top Products] as ' [Product].Children '\n" + "member [Product].[Top Product Total] as ' Aggregate([Top Products]) '\n" + "select {[Product].[Food], [Product].[Top Product Total]} on 0," + " [Gender].Members on 1\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Food]}\n" + "{[Product].[Top Product Total]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 191,940\n" + "Row #0: 266,773\n" + "Row #1: 94,814\n" + "Row #1: 131,558\n" + "Row #2: 97,126\n" + "Row #2: 135,215\n"); } /** * Tests that if a filter is associated with input to a cal member with * lower solve order; the filter computation uses the context that contains * the other cal members(those with higher solve order). */ public void testNegativeSolveOrderForCalMemberWithFilter() { assertQueryReturns( "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Education Level],[*BASE_MEMBERS_Product])' " + "Set [*METRIC_CJ_SET] as 'Filter([*NATIVE_CJ_SET],[Measures].[*Unit Sales_SEL~SUM] > 10000.0)' " + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}' " + "Set [*BASE_MEMBERS_Education Level] as '{[Education Level].[All Education Levels].[Bachelors Degree],[Education Level].[All Education Levels].[Graduate Degree]}' " + "Set [*NATIVE_MEMBERS_Education Level] as 'Generate([*NATIVE_CJ_SET], {[Education Level].CurrentMember})' " + "Set [*METRIC_MEMBERS_Education Level] as 'Generate([*METRIC_CJ_SET], {[Education Level].CurrentMember})' " + "Set [*BASE_MEMBERS_Product] as '{[Product].[All Products].[Food],[Product].[All Products].[Non-Consumable]}' " + "Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' " + "Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' " + "Member [Measures].[*Unit Sales_SEL~SUM] as '([Measures].[Unit Sales],[Education Level].CurrentMember,[Product].CurrentMember)', SOLVE_ORDER=200 " + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Unit Sales]', FORMAT_STRING = '#,##0', SOLVE_ORDER=300 " + "Member [Education Level].[*CTX_MEMBER_SEL~SUM] as 'Sum([*METRIC_MEMBERS_Education Level])', SOLVE_ORDER=-100 " + "Member [Product].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter([*METRIC_MEMBERS_Product], [Measures].[*Unit Sales_SEL~SUM] > 10000.0))', SOLVE_ORDER=-200 " + "Select " + "[*BASE_MEMBERS_Measures] on columns, " + "Non Empty Union(" + "NonEmptyCrossJoin({[Education Level].[*CTX_MEMBER_SEL~SUM]},{[Product].[*CTX_MEMBER_SEL~SUM]})," + "Generate([*METRIC_CJ_SET], {([Education Level].CurrentMember,[Product].CurrentMember)})) on rows " + "From [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*FORMATTED_MEASURE_0]}\n" + "Axis #2:\n" + "{[Education Level].[*CTX_MEMBER_SEL~SUM], [Product].[*CTX_MEMBER_SEL~SUM]}\n" + "{[Education Level].[Bachelors Degree], [Product].[Food]}\n" + "{[Education Level].[Bachelors Degree], [Product].[Non-Consumable]}\n" + "{[Education Level].[Graduate Degree], [Product].[Food]}\n" + "Row #0: 73,671\n" + "Row #1: 49,365\n" + "Row #2: 13,051\n" + "Row #3: 11,255\n"); } /** * Tests that if a filter is associated with input to a cal member with * higher solve order; the filter computation ignores the other cal members. */ public void testNegativeSolveOrderForCalMemberWithFilters2() { assertQueryReturns( "With " + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Education Level],[*BASE_MEMBERS_Product])' " + "Set [*METRIC_CJ_SET] as 'Filter([*NATIVE_CJ_SET],[Measures].[*Unit Sales_SEL~SUM] > 10000.0)' " + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}' " + "Set [*BASE_MEMBERS_Education Level] as '{[Education Level].[All Education Levels].[Bachelors Degree],[Education Level].[All Education Levels].[Graduate Degree]}' " + "Set [*NATIVE_MEMBERS_Education Level] as 'Generate([*NATIVE_CJ_SET], {[Education Level].CurrentMember})' " + "Set [*METRIC_MEMBERS_Education Level] as 'Generate([*METRIC_CJ_SET], {[Education Level].CurrentMember})' " + "Set [*BASE_MEMBERS_Product] as '{[Product].[All Products].[Food],[Product].[All Products].[Non-Consumable]}' " + "Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' " + "Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' " + "Member [Measures].[*Unit Sales_SEL~SUM] as '([Measures].[Unit Sales],[Education Level].CurrentMember,[Product].CurrentMember)', SOLVE_ORDER=200 " + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Unit Sales]', FORMAT_STRING = '#,##0', SOLVE_ORDER=300 " + "Member [Education Level].[*CTX_MEMBER_SEL~SUM] as 'Sum([*METRIC_MEMBERS_Education Level])', SOLVE_ORDER=-200 " + "Member [Product].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter([*METRIC_MEMBERS_Product], [Measures].[*Unit Sales_SEL~SUM] > 10000.0))', SOLVE_ORDER=-100 " + "Select " + "[*BASE_MEMBERS_Measures] on columns, " + "Non Empty Union(" + "NonEmptyCrossJoin({[Education Level].[*CTX_MEMBER_SEL~SUM]},{[Product].[*CTX_MEMBER_SEL~SUM]})," + "Generate([*METRIC_CJ_SET], {([Education Level].CurrentMember,[Product].CurrentMember)})) on rows " + "From [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*FORMATTED_MEASURE_0]}\n" + "Axis #2:\n" + "{[Education Level].[*CTX_MEMBER_SEL~SUM], [Product].[*CTX_MEMBER_SEL~SUM]}\n" + "{[Education Level].[Bachelors Degree], [Product].[Food]}\n" + "{[Education Level].[Bachelors Degree], [Product].[Non-Consumable]}\n" + "{[Education Level].[Graduate Degree], [Product].[Food]}\n" + "Row #0: 76,661\n" + "Row #1: 49,365\n" + "Row #2: 13,051\n" + "Row #3: 11,255\n"); } /** * Test case for * MONDRIAN-335, * "Issues with calculated members". * Verify that the calculated member [Product].[Food].[Test] * definition does not throw errors and returns expected * results. */ public void testNonTopLevelCalculatedMember() { assertQueryReturns( "with member [Product].[Test] as '[Product].[Food]' " + "select {[Measures].[Unit Sales]} on columns, " + "{[Product].[Test]} on rows " + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Test]}\n" + "Row #0: 191,940\n"); assertQueryReturns( "with member [Product].[Food].[Test] as '[Product].[Food]' " + "select {[Measures].[Unit Sales]} on columns, " + "{[Product].[Food].[Test]} on rows " + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Test]}\n" + "Row #0: 191,940\n"); } /** * Test case for * MONDRIAN-335, * "Issues with calculated members". * Verify that the calculated member [Product].[Test] * returns an empty children list vs. invalid behavior * of returning [All Products].Children */ public void testCalculatedMemberChildren() { assertQueryReturns( "with member [Product].[Test] as '[Product].[Food]' " + "select {[Measures].[Unit Sales]} on columns, " + "[Product].[Test].children on rows " + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n"); assertQueryReturns( "with member [Product].[Food].[Test] as '[Product].[Food]' " + "select {[Measures].[Unit Sales]} on columns, " + "[Product].[Food].[Test].children on rows " + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n"); } public void testCalculatedMemberMSASCompatibility() { propSaver.set(MondrianProperties.instance().CaseSensitive, false); assertQueryReturns( "with " + "member gender.calculated as 'gender.m' " + "member gender.[All Gender].calculated as 'gender.m' " + "member measures.countChildren as 'gender.calculated.children.Count' " + "member measures.parentIsAll as 'gender.calculated.Parent IS gender.[All Gender]' " + "member measures.levelOrdinal as 'gender.calculated.Level.Ordinal' " + "member measures.definedOnAllLevelOrdinal as 'gender.[all gender].calculated.Level.Ordinal' " + "member measures.definedOnAllLevelParentIsAll as 'gender.[all gender].calculated.Parent IS gender.[All Gender]' " + "member measures.definedOnAllLevelChildren as 'gender.[all gender].calculated.Children.Count' " + "member measures.definedOnAllLevelSiblings as 'gender.[all gender].calculated.Siblings.Count' " + "select { " + " measures.[countChildren], " // -- returns 0 + " measures.parentIsAll, " // -- returns 0 + " measures.levelOrdinal, " // -- returns 0 + " measures.definedOnAllLevelOrdinal, " // -- returns 1 + " measures.definedOnAllLevelParentIsAll, " // -- returns 1 + " measures.definedOnAllLevelChildren, " // -- returns 0 + " measures.definedOnAllLevelSiblings " // -- returns 2 + "} on 0 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[countChildren]}\n" + "{[Measures].[parentIsAll]}\n" + "{[Measures].[levelOrdinal]}\n" + "{[Measures].[definedOnAllLevelOrdinal]}\n" + "{[Measures].[definedOnAllLevelParentIsAll]}\n" + "{[Measures].[definedOnAllLevelChildren]}\n" + "{[Measures].[definedOnAllLevelSiblings]}\n" + "Row #0: 0\n" + "Row #0: false\n" + "Row #0: 0\n" + "Row #0: 1\n" + "Row #0: true\n" + "Row #0: 0\n" + "Row #0: 2\n"); } /** * Query that simulates a compound slicer by creating a calculated member * that aggregates over a set and places it in the WHERE clause. */ public void testSimulatedCompoundSlicer() { assertQueryReturns( "with\n" + " member [Measures].[Price per Unit] as\n" + " [Measures].[Store Sales] / [Measures].[Unit Sales]\n" + " set [Top Products] as\n" + " TopCount(\n" + " [Product].[Brand Name].Members,\n" + " 3,\n" + " ([Measures].[Unit Sales], [Time].[1997].[Q3]))\n" + " member [Product].[Top] as\n" + " Aggregate([Top Products])\n" + "select {\n" + " [Measures].[Unit Sales],\n" + " [Measures].[Price per Unit]} on 0,\n" + " [Gender].Children * [Marital Status].Children on 1\n" + "from [Sales]\n" + "where ([Product].[Top], [Time].[1997].[Q3])", "Axis #0:\n" + "{[Product].[Top], [Time].[1997].[Q3]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Price per Unit]}\n" + "Axis #2:\n" + "{[Gender].[F], [Marital Status].[M]}\n" + "{[Gender].[F], [Marital Status].[S]}\n" + "{[Gender].[M], [Marital Status].[M]}\n" + "{[Gender].[M], [Marital Status].[S]}\n" + "Row #0: 779\n" + "Row #0: 2.40\n" + "Row #1: 811\n" + "Row #1: 2.24\n" + "Row #2: 829\n" + "Row #2: 2.23\n" + "Row #3: 886\n" + "Row #3: 2.25\n"); // Now the equivalent query, using a set in the slicer. assertQueryReturns( "with\n" + " member [Measures].[Price per Unit] as\n" + " [Measures].[Store Sales] / [Measures].[Unit Sales]\n" + " set [Top Products] as\n" + " TopCount(\n" + " [Product].[Brand Name].Members,\n" + " 3,\n" + " ([Measures].[Unit Sales], [Time].[1997].[Q3]))\n" + "select {\n" + " [Measures].[Unit Sales],\n" + " [Measures].[Price per Unit]} on 0,\n" + " [Gender].Children * [Marital Status].Children on 1\n" + "from [Sales]\n" + "where [Top Products] * [Time].[1997].[Q3]", "Axis #0:\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos], [Time].[1997].[Q3]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale], [Time].[1997].[Q3]}\n" + "{[Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony], [Time].[1997].[Q3]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Price per Unit]}\n" + "Axis #2:\n" + "{[Gender].[F], [Marital Status].[M]}\n" + "{[Gender].[F], [Marital Status].[S]}\n" + "{[Gender].[M], [Marital Status].[M]}\n" + "{[Gender].[M], [Marital Status].[S]}\n" + "Row #0: 779\n" + "Row #0: 2.40\n" + "Row #1: 811\n" + "Row #1: 2.24\n" + "Row #2: 829\n" + "Row #2: 2.23\n" + "Row #3: 886\n" + "Row #3: 2.25\n"); } public void testCompoundSlicerOverTuples() { // reference query assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + " TopCount(\n" + " [Product].[Product Category].Members\n" + " * [Customers].[City].Members,\n" + " 10) on 1\n" + "from [Sales]\n" + "where [Time].[1997].[Q3]", "Axis #0:\n" + "{[Time].[1997].[Q3]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Burnaby]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Cliffside]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Haney]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Ladner]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Langford]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Langley]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Metchosin]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[N. Vancouver]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Newton]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine], [Customers].[Canada].[BC].[Oak Bay]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: \n" + "Row #3: \n" + "Row #4: \n" + "Row #5: \n" + "Row #6: \n" + "Row #7: \n" + "Row #8: \n" + "Row #9: \n"); // The actual query. Note that the set in the slicer has two dimensions. // This could not be expressed using calculated members and the // Aggregate function. assertQueryReturns( "with\n" + " member [Measures].[Price per Unit] as\n" + " [Measures].[Store Sales] / [Measures].[Unit Sales]\n" + " set [Top Product Cities] as\n" + " TopCount(\n" + " [Product].[Product Category].Members\n" + " * [Customers].[City].Members,\n" + " 3,\n" + " ([Measures].[Unit Sales], [Time].[1997].[Q3]))\n" + "select {\n" + " [Measures].[Unit Sales],\n" + " [Measures].[Price per Unit]} on 0,\n" + " [Gender].Children * [Marital Status].Children on 1\n" + "from [Sales]\n" + "where [Top Product Cities] * [Time].[1997].[Q3]", "Axis #0:\n" + "{[Product].[Food].[Snack Foods].[Snack Foods], [Customers].[USA].[WA].[Spokane], [Time].[1997].[Q3]}\n" + "{[Product].[Food].[Produce].[Vegetables], [Customers].[USA].[WA].[Spokane], [Time].[1997].[Q3]}\n" + "{[Product].[Food].[Snack Foods].[Snack Foods], [Customers].[USA].[WA].[Puyallup], [Time].[1997].[Q3]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Price per Unit]}\n" + "Axis #2:\n" + "{[Gender].[F], [Marital Status].[M]}\n" + "{[Gender].[F], [Marital Status].[S]}\n" + "{[Gender].[M], [Marital Status].[M]}\n" + "{[Gender].[M], [Marital Status].[S]}\n" + "Row #0: 483\n" + "Row #0: 2.21\n" + "Row #1: 419\n" + "Row #1: 2.21\n" + "Row #2: 422\n" + "Row #2: 2.22\n" + "Row #3: 332\n" + "Row #3: 2.20\n"); } /** * Testcase for bug * MONDRIAN-608, "Performance issue with large number of measures". */ public void testExponentialPerformanceBugMondrian608() { // Run variants of the same query with increasing expression complexity. // With MONDRIAN-608, running time triples each iteration (for // example, i=10 takes 2.7s, i=11 takes 9.6s), so 20 would be very // noticeable! final boolean print = false; for (int i = 0; i < 20; ++i) { checkForExponentialPerformance(i, print); } } /** * Runs a query with an calculated member whose expression is a given * complexity. * * @param n Expression complexity * @param print Whether to print timings */ private void checkForExponentialPerformance( int n, boolean print) { StringBuilder buf = new StringBuilder(); for (int i = 0; i < n; ++i) { buf.append( "+ [Measures].[Sales Count] - [Measures].[Sales Count]\n"); } final long t0 = System.currentTimeMillis(); final String mdx = "with member [Measures].[M0] as\n" + " [Measures].[Unit Sales]\n" + " + [Measures].[Store Cost]\n" + " + [Measures].[Store Sales]\n" + " + [Measures].[Customer Count]\n" + buf + " set [#DataSet#] as NonEmptyCrossjoin(\n" + " {[Product].[Food]},\n" + " {Descendants([Store].[USA], 1)})\n" + "select {[Measures].[M0]} on columns,\n" + " NON EMPTY Hierarchize({[#DataSet#]}) on rows\n" + "FROM [Sales]"; assertQueryReturns( mdx, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[M0]}\n" + "Axis #2:\n" + "{[Product].[Food], [Store].[USA].[CA]}\n" + "{[Product].[Food], [Store].[USA].[OR]}\n" + "{[Product].[Food], [Store].[USA].[WA]}\n" + "Row #0: 217,506\n" + "Row #1: 193,104\n" + "Row #2: 359,162\n"); // Check for a similar issue in the visitor that analyzes a calculated // member's expression to see whether a cell based on that member can // be drilled through. final Result result = executeQuery(mdx); assertTrue(result.getCell(new int[] {0, 0}).canDrillThrough()); final long t1 = System.currentTimeMillis(); if (print) { System.out.println( "For n=" + n + ", took " + (t1 - t0) + " millis"); } } /** * Test case for * MONDRIAN-638, * "Stack trace when grand total turned on". The cause of the problem were * negative SOLVE_ORDER values. We were incorrectly populating * RolapEvaluator.expandingMember from the parent evaluator, which made it * look like two evaluation contexts were expanding the same member. */ public void testCycleFalsePositive() { if (MondrianProperties.instance().SsasCompatibleNaming.get()) { // This test uses old-style [dimension.hierarchy] names. return; } final TestContext testContext = TestContext.instance().create( null, " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + " \n" + " \n" + " \n" + " Numbers \n" + " \n" + " \n" + " \n" + " \n" + " Numbers \n" + " \n" + " 1 \n" + " \n" + " ", null, null, null, null); testContext.assertQueryReturns( "With \n" + "Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Country],[*BASE_MEMBERS_Store Type.Store Types Hierarchy])' \n" + "Set [*SORTED_ROW_AXIS] as 'Order([*CJ_ROW_AXIS],[Country].CurrentMember.OrderKey,BASC,[Store Type.Store Types Hierarchy].CurrentMember.OrderKey,BASC)' \n" + "Set [*BASE_MEMBERS_Country] as '[Country].[Country].Members' \n" + "Set [*NATIVE_MEMBERS_Country] as 'Generate([*NATIVE_CJ_SET], {[Country].CurrentMember})' \n" + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}' \n" + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Country].currentMember,[Store Type.Store Types Hierarchy].currentMember)})' \n" + "Set [*BASE_MEMBERS_Store Type.Store Types Hierarchy] as '[Store Type.Store Types Hierarchy].[Store Type].Members' \n" + "Set [*NATIVE_MEMBERS_Store Type.Store Types Hierarchy] as 'Generate([*NATIVE_CJ_SET], {[Store Type.Store Types Hierarchy].CurrentMember})' \n" + "Set [*CJ_COL_AXIS] as '[*NATIVE_CJ_SET]' \n" + "Member [Store Type.Store Types Hierarchy].[*TOTAL_MEMBER_SEL~SUM] as 'Sum({[Store Type.Store Types Hierarchy].[All Store Types Member Name]})', SOLVE_ORDER=-101 \n" + "Member [Country].[*TOTAL_MEMBER_SEL~SUM] as 'Sum({[Country].[All Countrys]})', SOLVE_ORDER=-100 \n" + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Store Sqft]', FORMAT_STRING = '#,###', SOLVE_ORDER=400 \n" + "Select \n" + "[*BASE_MEMBERS_Measures] on columns, \n" + "Non Empty Union(NonEmptyCrossJoin({[Country].[*TOTAL_MEMBER_SEL~SUM]},{[Store Type.Store Types Hierarchy].[*TOTAL_MEMBER_SEL~SUM]}),[*SORTED_ROW_AXIS]) on rows \n" + "From [Store5] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*FORMATTED_MEASURE_0]}\n" + "Axis #2:\n" + "{[Country].[*TOTAL_MEMBER_SEL~SUM], [Store Type.Store Types Hierarchy].[*TOTAL_MEMBER_SEL~SUM]}\n" + "{[Country].[Canada], [Store Type.Store Types Hierarchy].[Deluxe Supermarket]}\n" + "{[Country].[Canada], [Store Type.Store Types Hierarchy].[Mid-Size Grocery]}\n" + "{[Country].[Mexico], [Store Type.Store Types Hierarchy].[Deluxe Supermarket]}\n" + "{[Country].[Mexico], [Store Type.Store Types Hierarchy].[Gourmet Supermarket]}\n" + "{[Country].[Mexico], [Store Type.Store Types Hierarchy].[Mid-Size Grocery]}\n" + "{[Country].[Mexico], [Store Type.Store Types Hierarchy].[Small Grocery]}\n" + "{[Country].[Mexico], [Store Type.Store Types Hierarchy].[Supermarket]}\n" + "{[Country].[USA], [Store Type.Store Types Hierarchy].[Deluxe Supermarket]}\n" + "{[Country].[USA], [Store Type.Store Types Hierarchy].[Gourmet Supermarket]}\n" + "{[Country].[USA], [Store Type.Store Types Hierarchy].[Small Grocery]}\n" + "{[Country].[USA], [Store Type.Store Types Hierarchy].[Supermarket]}\n" + "Row #0: 571,596\n" + "Row #1: 23,112\n" + "Row #2: 34,452\n" + "Row #3: 61,381\n" + "Row #4: 23,759\n" + "Row #5: 74,891\n" + "Row #6: 24,597\n" + "Row #7: 58,384\n" + "Row #8: 61,552\n" + "Row #9: 23,688\n" + "Row #10: 50,684\n" + "Row #11: 135,096\n"); } /** * Testcase for bug * MONDRIAN-852, "Using the generate command, cast and calculated measures * causes ClassCastException". * *

The problem is in the implicit conversion that occurs when using * a member as a numeric value. (In this case the conversion occurs because * we apply the numeric operator '/'.) We have to assume at prepare time * that the current measure will be numeric, but at run time the value may * be a string. * *

We were wrongly throwing a ClassCastException. Correct behavior is an * evaluation exception: the cell is in error, but the query as a whole * succeeds. */ public void testBugMondrian852() { // Simpler repro case. assertQueryReturns( "with member [Measures].[Bar] as cast(123 as string)\n" + " member [Measures].[Foo] as [Measures].[Bar] / 2\n" + "select [Measures].[Foo] on 0\n" + "from [Sales]\n", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: #ERR: mondrian.olap.fun.MondrianEvaluationException: Expected value of type NUMERIC; got value '123' (STRING)\n"); // Tom's original query should generate a cast error (not a // ClassCastException) because solve orders are wrong. assertQueryReturns( "with\n" + " member [Measures].[Tom1] as\n" + " ([Measures].[Store Sales] / [Measures].[Unit Sales])\n" + " set spark1 as\n" + " ([Time].[1997].[Q1].[1] : [Time].[1997].[Q2].[4])\n" + " member [Time].[Time].[Past 4 months] as\n" + " Generate(\n" + " [spark1],\n" + " CAST(([Measures].CurrentMember + 0.0) AS String),\n" + " \", \")\n" + "select {[Time].[Past 4 months]} ON COLUMNS,\n" + " {[Measures].[Unit Sales], [Measures].[Tom1]} ON ROWS\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[Past 4 months]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Tom1]}\n" + "Row #0: 21628.0, 20957.0, 23706.0, 20179.0\n" + "Row #1: #ERR: mondrian.olap.fun.MondrianEvaluationException: Expected value of type NUMERIC; got value '45539.69, 44058.79, 50029.87, 42878.25' (STRING)\n"); // Solve orders to achieve what Tom intended. assertQueryReturns( "with\n" + " member [Measures].[Tom1] as\n" + " ([Measures].[Store Sales] / [Measures].[Unit Sales]),\n" + " solve_order = 1\n" + " set spark1 as\n" + " ([Time].[1997].[Q1].[1] : [Time].[1997].[Q2].[4])\n" + " member [Time].[Time].[Past 4 months] as\n" + " Generate(\n" + " [spark1],\n" + " CAST(([Measures].CurrentMember + 0.0) AS String),\n" + " \", \")," + " solve_order = 2\n" + "select {[Time].[Past 4 months]} ON COLUMNS,\n" + " {[Measures].[Unit Sales], [Measures].[Tom1]} ON ROWS\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[Past 4 months]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Tom1]}\n" + "Row #0: 21628.0, 20957.0, 23706.0, 20179.0\n" + "Row #1: 2.10558951359349, 2.1023424154220547, 2.1104306926516494, 2.124894692502106\n"); } /** * Tests referring to a calc member by a name other than its canonical * unique name. */ public void testNonCanonical() { // define without 'all', refer with 'all' final String expected = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[Foo]}\n" + "Row #0: 266,773\n"; assertQueryReturns( "with member [Store].[USA].[Foo] as\n" + " [Store].[USA] + [Store].[Canada].[BC]\n" + "select [Store].[All Stores].[USA].[Foo] on 0\n" + "from [Sales]", expected); // and vice versa: define without 'all', refer with 'all' assertQueryReturns( "with member [Store].[All Stores].[USA].[Foo] as\n" + " [Store].[USA] + [Store].[Canada].[BC]\n" + "select [Store].[USA].[Foo] on 0\n" + "from [Sales]", expected); } public void testCalcMemberParentOfCalcMember() { // SSAS fails with "The X calculated member cannot be used as a parent // of another calculated member." assertQueryThrows( "with member [Gender].[X] as 4\n" + " member [Gender].[X].[Y] as 5\n" + " select [Gender].[X].[Y] on 0\n" + " from [Sales]", "The '[Gender].[X]' calculated member cannot be used as a parent " + "of another calculated member."); } public void testCalcMemberSameNameDifferentHierarchies() { assertQueryReturns( "with member [Gender].[X] as 4\n" + " member [Marital Status].[X] as 5\n" + " member [Promotion Media].[X] as 6\n" + " select [Marital Status].[X] on 0\n" + " from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Marital Status].[X]}\n" + "Row #0: 5\n"); } public void testCalcMemberTooDeep() { // SSAS fails with "The X calculated member cannot be created because // its parent is at the lowest level in the Gender hierarchy." assertQueryThrows( "with member [Gender].[M].[X] as 4\n" + " select [Gender].[M].[X] on 0\n" + " from [Sales]", "The 'X' calculated member cannot be created because its parent is " + "at the lowest level in the [Gender] hierarchy."); } /** * System test for bug * * MONDRIAN-1098, "Trying to get formatted value of a cell results in * ArrayIndexOutOfBoundsException". * * @see mondrian.util.FormatTest#testBugMondrian1098() */ public void testBugMondrian1098() { final TestContext testContext = getTestContext().with(TestContext.DataSet.ANALYZER_FOODMART); Result result = testContext.executeQuery( "With\n" + "Set [*NATIVE_CJ_SET] as 'Filter([*BASE_MEMBERS_Fact Attribute], Not IsEmpty ([Measures].[Formatted Profit]))'\n" + "Set [*SORTED_ROW_AXIS] as 'Order([*CJ_ROW_AXIS],[Fact Attribute].CurrentMember.OrderKey,BASC)'\n" + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}'\n" + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Fact Attribute].currentMember)})'\n" + "Set [*BASE_MEMBERS_Fact Attribute] as '[Fact Attribute].[Customer ID].Members'\n" + "Set [*CJ_COL_AXIS] as '[*NATIVE_CJ_SET]'\n" + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Formatted Profit]', FORMAT_STRING = '$000,000,000.00', SOLVE_ORDER=400\n" + "Select\n" + "[*BASE_MEMBERS_Measures] on columns,\n" + "[*SORTED_ROW_AXIS] on rows\n" + "From [Sales]"); String resultString = TestContext.toString(result); assertTrue( resultString, resultString.endsWith("Row #5580: $000,000,018.32\n")); } } // End TestCalculatedMembers.java mondrian-3.4.1/testsrc/main/mondrian/test/comp/0000755000175000017500000000000011735330606021365 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/test/comp/package.html0000644000175000017500000000017711735330606023653 0ustar drazzibdrazzib Package mondrian.test.comp Utilities for comparison-based testing. mondrian-3.4.1/testsrc/main/mondrian/test/comp/XmlUtility.java0000644000175000017500000001300011735330606024346 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.test.comp; import mondrian.olap.Util; import org.apache.xml.serialize.OutputFormat; import org.apache.xml.serialize.XMLSerializer; import org.eigenbase.xom.XOMUtil; import org.eigenbase.xom.wrappers.W3CDOMWrapper; import org.w3c.dom.*; import org.xml.sax.*; import java.io.*; import java.util.regex.Pattern; import javax.xml.parsers.*; /** * XML utility methods. */ class XmlUtility { static final Pattern WhitespacePattern = Pattern.compile("\\s*"); public static DocumentBuilder createDomParser( boolean validate, boolean ignoreIgnorableWhitespace, boolean usingSchema, ErrorHandler handler) { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); try { factory.setNamespaceAware(true); factory.setIgnoringElementContentWhitespace( ignoreIgnorableWhitespace); factory.setValidating(validate); // if this is true we are using XML Schema validation and not a DTD if (usingSchema) { factory.setAttribute( "http://xml.org/sax/features/validation", Boolean.TRUE); factory.setAttribute( "http://apache.org/xml/features/validation/schema", Boolean.TRUE); factory.setAttribute( "http://apache.org/xml/features/validation/schema-full-checking", Boolean.TRUE); factory.setAttribute( "http://apache.org/xml/features/validation/dynamic", Boolean.TRUE); } DocumentBuilder documentBuilder = factory.newDocumentBuilder(); if (handler != null) { documentBuilder.setErrorHandler(handler); } return documentBuilder; } catch (ParserConfigurationException e) { return null; } } public static Document getDocument(File file) throws IOException, SAXException { DocumentBuilder builder = createDomParser( true, true, true, new UtilityErrorHandler()); Document result = builder.parse(file); return result; } public static void save(Writer writer, Document document) throws IOException { OutputFormat outputFormat = new OutputFormat(document); outputFormat.setIndenting(true); outputFormat.setLineWidth(Integer.MAX_VALUE); outputFormat.setLineSeparator(Util.nl); try { XMLSerializer serializer = new XMLSerializer(writer, outputFormat); serializer.serialize(document); } finally { if (writer != null) { writer.close(); } } } public static String decodeEncodedString(String enc) { if (enc.indexOf('&') == -1) { return enc; } int len = enc.length(); StringBuilder result = new StringBuilder(len); for (int idx = 0; idx < len; idx++) { char ch = enc.charAt(idx); if (ch == '&' && enc.charAt(idx + 1) == 'l' && enc.charAt(idx + 2) == 't' && enc.charAt(idx + 3) == ';') { result.append('<'); idx += 3; } else if (ch == '&' && enc.charAt(idx + 1) == 'g' && enc.charAt(idx + 2) == 't' && enc.charAt(idx + 3) == ';') { result.append('>'); idx += 3; } else { result.append(ch); } } return result.toString(); } public static void stripWhitespace(Element element) { final NodeList childNodeList = element.getChildNodes(); for (int i = 0; i < childNodeList.getLength(); i++) { Node node = childNodeList.item(i); switch (node.getNodeType()) { case Node.TEXT_NODE: case Node.CDATA_SECTION_NODE: final String text = ((CharacterData) node).getData(); if (WhitespacePattern.matcher(text).matches()) { element.removeChild(node); --i; } break; case Node.ELEMENT_NODE: stripWhitespace((Element) node); break; } } } public static String toString(Element xmlRoot) { stripWhitespace(xmlRoot); return XOMUtil.wrapperToXml(new W3CDOMWrapper(xmlRoot, null), false); } public static class UtilityErrorHandler implements ErrorHandler { public void error(SAXParseException exc) { System.err.println("Error parsing file: " + exc); //exc.printStackTrace(System.err); } public void fatalError(SAXParseException exc) { System.err.println("Fatal error parsing file: " + exc); // exc.printStackTrace(System.err); } public void warning(SAXParseException exc) { System.err.println("SAX parsing exception: " + exc); // exc.printStackTrace(System.err); } } } // End XmlUtility.java mondrian-3.4.1/testsrc/main/mondrian/test/comp/ResultComparatorTest.java0000644000175000017500000001555511735330606026411 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.test.comp; import mondrian.olap.*; import mondrian.test.FoodMartTestCase; import junit.framework.TestSuite; import org.w3c.dom.Document; import org.w3c.dom.Element; import java.io.File; import java.io.FilenameFilter; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; /** * Unit test based upon an XML file. * *

The file consists of an MDX statement and the expected result. The * executes the MDX statement and fails if the actual result does not match the * expected result. * *

Here is a typical XML file: *

<mdbTest>
* <mdxQuery>
* WITH MEMBER [Customers].[Hierarchy Name]
* AS '[Customers].[All Customers].[USA].[CA].hierarchy.Name'
* SELECT {[Customers].[Hierarchy Name]} on columns
* From [Sales]
* </mdxQuery>
* <dataResult>
* <slicer>
* <dimensions>
* <dim>[Measures]</dim>
* <dim>[Time]</dim>
* <dim>[Product]</dim>
* <dim>[Store]</dim>
* <dim>[Store Size in SQFT]</dim>
* <dim>[Store Type]</dim>
* <dim>[Promotions]</dim>
* <dim>[Education Level]</dim>
* <dim>[Marital Status]</dim>
* <dim>[Yearly Income]</dim>
* <dim>[Promotion Media]</dim>
* <dim>[Gender]</dim>
* </dimensions>
* <tuples>
* <tuple>
* <member>[Measures].[Unit Sales]</member>
* <member>[Time].[1997]</member>
* <member>[Product].[All * Products]</member>
* <member>[Store].[All Stores]</member>
* <member>[Store Size in SQFT].[All Store Size in * SQFTs]</member>
* <member>[Store Type].[All Store * Types]</member>
* <member>[Promotions].[All * Promotions]</member>
* <member>[Education Level].[All Education * Levels]</member>
* <member>[Marital Status].[All Marital * Status]</member>
* <member>[Yearly Income].[All Yearly * Incomes]</member>
* <member>[Promotion Media].[All * Media]</member>
* <member>[Gender].[All Gender]</member>
* </tuple>
* </tuples>
* </slicer>
* <columns>
* <dimensions>
* <dim>[Customers]</dim>
* </dimensions>
* <tuples>
* <tuple>
* <member>[Customers].[Hierarchy * Name]</member>
* </tuple>
* </tuples>
* </columns>
* <data>
* <drow>
* <cell>Customers</cell>
* </drow>
* </data>
* </dataResult>
* </mdbTest> *
*/ public class ResultComparatorTest extends FoodMartTestCase { private File file; public ResultComparatorTest(String name) { super(name); file = new File(name); } public ResultComparatorTest() { } public ResultComparatorTest(File file) { super(file.getName()); this.file = file; } protected void runTest() throws Exception { DocumentBuilder db = XmlUtility.createDomParser( false, true, false, new XmlUtility.UtilityErrorHandler()); Document doc = db.parse(file); Element queryNode = (Element) doc.getElementsByTagName("mdxQuery").item(0); Element expectedResult = (Element) doc.getElementsByTagName("dataResult").item(0); if (!isDefaultNullMemberRepresentation() && resultHasDefaultNullMemberRepresentation(expectedResult)) { return; } String queryString = XmlUtility.decodeEncodedString( queryNode.getFirstChild().getNodeValue()); Connection cxn = getConnection(); try { Query query = cxn.parseQuery(queryString); Result result = cxn.execute(query); ResultComparator comp = new ResultComparator(expectedResult, result); comp.compareResults(); } finally { cxn.close(); } } private boolean resultHasDefaultNullMemberRepresentation( Element expectedResult) { return XmlUtility.toString(expectedResult).indexOf("#null") != -1; } public static TestSuite suite() { TestSuite suite = new TestSuite(); MondrianProperties properties = MondrianProperties.instance(); String filePattern = properties.QueryFilePattern.get(); String fileDirectory = properties.QueryFileDirectory.get(); final Pattern pattern = filePattern == null ? null : Pattern.compile(filePattern); final String directory = fileDirectory == null ? "testsrc" + File.separatorChar + "queryFiles" : fileDirectory; File[] files = new File(directory).listFiles( new FilenameFilter() { public boolean accept(File dir, String name) { if (name.startsWith("query") && name.endsWith(".xml")) { if (pattern == null) { return true; } else { return pattern.matcher(name).matches(); } } return false; } }); if (files == null) { files = new File[0]; } for (int idx = 0; idx < files.length; idx++) { suite.addTest(new ResultComparatorTest(files[idx])); } return suite; } } // End ResultComparatorTest.java mondrian-3.4.1/testsrc/main/mondrian/test/comp/ResultComparator.java0000644000175000017500000004605011735330606025543 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.test.comp; import mondrian.olap.*; import mondrian.test.TestContext; import junit.framework.Assert; import org.w3c.dom.*; import java.util.HashSet; import java.util.List; import javax.xml.parsers.DocumentBuilder; /** * Compares the {@link Result} produced by a query with the expected result * read from an XML file. * * @see ResultComparatorTest */ class ResultComparator { private final Element xmlRoot; private final Result result; public ResultComparator(Element xmlRoot, Result result) { this.xmlRoot = xmlRoot; this.result = result; } public void compareResults() { compareSlicers(); compareColumns(); compareRows(); compareData(); } private void compareSlicers() { NodeList slicerList = xmlRoot.getElementsByTagName("slicer"); Cube cube = result.getQuery().getCube(); Dimension[] dims = cube.getDimensions(); HashSet defaultDimMembers = new HashSet(); for (Dimension dim : dims) { String uniqueName = dim.getHierarchies()[0].getDefaultMember().getUniqueName(); defaultDimMembers.add(uniqueName); } Axis slicerAxis = result.getSlicerAxis(); List members = slicerAxis.getPositions().get(0); Element slicerTuple = (Element) slicerList.item(0); if (slicerTuple == null) { _assertEquals("Expected no slicers", 0, members.size()); return; } final Element tuples = (Element) slicerTuple.getElementsByTagName("tuples").item(0); final Element tuple = (Element) tuples.getElementsByTagName("tuple").item(0); NodeList expectedTuple = tuple.getElementsByTagName("member"); // For each of the expected members, make sure that it's either in the // result members[] array or the default member for the dimension. int numMembers = expectedTuple.getLength(); int seenMembers = 0; for (int idx = 0; idx < numMembers; idx++) { String expectedMemberName = expectedTuple.item(idx).getFirstChild().getNodeValue(); if (resultMembersContainsExpected(expectedMemberName, members)) { seenMembers++; } else if (defaultDimMembers.contains(expectedMemberName)) { } else { Assert.fail("Missing slicer: " + expectedMemberName); } } _assertEquals( "The query returned more slicer members than were expected", members.size(), seenMembers); } private boolean resultMembersContainsExpected( String expectedMemberName, List members) { for (Member member : members) { if (member.getUniqueName().equals(expectedMemberName)) { return true; } } return false; } private void compareColumns() { Axis[] axes = result.getAxes(); NodeList columnList = xmlRoot.getElementsByTagName("columns"); if (axes.length >= 1) { compareTuples("Column", columnList, axes[0].getPositions()); } else { Assert.assertTrue( "Must be no columns", columnList.getLength() == 0); } } private void compareRows() { Axis[] axes = result.getAxes(); NodeList rowList = xmlRoot.getElementsByTagName("rows"); switch (axes.length) { case 0: case 1: Assert.assertTrue("Must be no rows", rowList.getLength() == 0); break; case 2: compareTuples("Row", rowList, axes[1].getPositions()); break; default: Assert.fail( "Too many axes returned. " + "Expected 0, 1 or 2 but got " + axes.length); break; } } private void _failNotEquals( String message, Object expected, Object actual) { if (message != null) { message += "; "; } else { message = ""; } message += "; expected=" + expected + "; actual=" + actual + Util.nl + "Query: " + Util.unparse(result.getQuery()) + Util.nl; TestContext.assertEqualsVerbose( TestContext.fold( XmlUtility.toString(xmlRoot)), toString(result), false, message); } private String toString(Result result) { Element element = toXml(result); return XmlUtility.toString(element); } private Element toXml(Result result) { DocumentBuilder db = XmlUtility.createDomParser( false, true, false, new XmlUtility.UtilityErrorHandler()); final Document document = db.newDocument(); final Element dataResultXml = document.createElement("dataResult"); slicerAxisToXml(document, dataResultXml, result); final Axis[] axes = result.getAxes(); for (int i = 0; i < axes.length; i++) { Axis axis = axes[i]; String axisName = AxisOrdinal.StandardAxisOrdinal.forLogicalOrdinal(i).name() .toLowerCase(); axisToXml(document, dataResultXml, axis, axisName); } final Element dataXml = document.createElement("data"); dataResultXml.appendChild(dataXml); final int axisCount = result.getAxes().length; int[] pos = new int[axisCount]; cellsToXml(document, result, dataXml, pos, axisCount - 1); return dataResultXml; } private void cellsToXml( Document document, Result result, Element parentXml, int[] pos, int axisOrdinal) { Axis axis = result.getAxes()[axisOrdinal]; for (int i = 0; i < axis.getPositions().size(); i++) { pos[axisOrdinal] = i; if (axisOrdinal == 0) { Cell cell = result.getCell(pos); final Element cellXml = document.createElement("cell"); parentXml.appendChild(cellXml); final Text textXml = document.createTextNode(String.valueOf(cell.getValue())); cellXml.appendChild(textXml); } else { final Element drowXml = document.createElement("drow"); parentXml.appendChild(drowXml); cellsToXml(document, result, drowXml, pos, axisOrdinal - 1); } } } private void slicerAxisToXml( final Document document, final Element dataResultXml, final Result result) { final Dimension[] dimensions = result.getQuery().getCube().getDimensions(); String axisName = "slicer"; final Axis slicerAxis = result.getSlicerAxis(); final Element axisXml = document.createElement(axisName); dataResultXml.appendChild(axisXml); final Element dimensionsXml = document.createElement("dimensions"); axisXml.appendChild(dimensionsXml); final Element tuplesXml = document.createElement("tuples"); axisXml.appendChild(tuplesXml); final Element tupleXml = document.createElement("tuple"); tuplesXml.appendChild(tupleXml); for (Dimension dimension : dimensions) { Member member = findSlicerAxisMember(result, dimension); if (member == null) { continue; } // Append to the element final Element dimXml = document.createElement("dim"); dimensionsXml.appendChild(dimXml); final Text textXml = document.createTextNode( member.getDimension().getUniqueName()); dimXml.appendChild(textXml); // Append to the element. final Element memberXml = document.createElement("member"); tupleXml.appendChild(memberXml); final Text memberTextXml = document.createTextNode( member.getUniqueName()); memberXml.appendChild(memberTextXml); } } /** * Returns which member of a given dimension appears in the slicer * axis.

*

* If the dimension occurs on one of the other axes, the answer is null. * If the dimension occurs in the slicer axis, the answer is that member. * Otherwise it is the default member of the dimension. */ private Member findSlicerAxisMember(Result result, Dimension dimension) { final Axis slicerAxis = result.getSlicerAxis(); if (slicerAxis != null && slicerAxis.getPositions().size() == 1) { final List members = slicerAxis.getPositions().get(0); for (Member member : members) { if (member.getDimension() == dimension) { return member; } } } final Axis[] axes = result.getAxes(); for (Axis axis : axes) { if (axis.getPositions().size() > 0) { final List members = axis.getPositions().get(0); for (Member member : members) { if (member.getDimension() == dimension) { // Dimension occurs on non-slicer axis, so it should // not appear in the slicer. return null; } } } } return dimension.getHierarchies()[0].getDefaultMember(); } private void axisToXml( final Document document, final Element dataResultXml, final Axis axis, final String axisName) { final Element axisXml = document.createElement(axisName); dataResultXml.appendChild(axisXml); if (axis.getPositions().size() > 0) { final Element dimensionsXml = document.createElement("dimensions"); axisXml.appendChild(dimensionsXml); final Position position0 = axis.getPositions().get(0); for (Member member : position0) { final Element dimXml = document.createElement("dim"); dimensionsXml.appendChild(dimXml); final Text textXml = document.createTextNode( member.getDimension().getUniqueName()); dimXml.appendChild(textXml); } final Element tuplesXml = document.createElement("tuples"); axisXml.appendChild(tuplesXml); for (Position position : axis.getPositions()) { final Element tupleXml = document.createElement("tuple"); tuplesXml.appendChild(tupleXml); for (final Member member : position) { final Element memberXml = document.createElement("member"); tupleXml.appendChild(memberXml); final Text textXml = document.createTextNode( member.getUniqueName()); memberXml.appendChild(textXml); } } } } private void _assertEquals(String message, int expected, int actual) { if (expected != actual) { _failNotEquals(message, expected, actual); } } private void _assertEquals( String message, Object expected, Object actual) { if (expected == null) { if (actual == null) { return; } } else { if (expected.equals(actual)) { return; } } _failNotEquals(message, expected, actual); } private void _assertEquals( String message, double expected, double actual, double delta) { if (Double.isInfinite(expected)) { if (!(expected == actual)) { _failNotEquals(message, expected, actual); } } else if (!(Math.abs(expected - actual) <= delta)) { // Because comparison with NaN always returns false _failNotEquals(message, expected, actual); } } private void compareTuples( String message, NodeList axisValues, List resultTuples) { NodeList expectedTuples = null; NodeList expectedDims = null; if (axisValues.getLength() != 0) { Element axisNode = (Element) axisValues.item(0); final Element dims = (Element) axisNode.getElementsByTagName("dimensions").item(0); expectedDims = dims.getElementsByTagName("dim"); final Element tuples = (Element) axisNode.getElementsByTagName("tuples").item(0); expectedTuples = tuples.getElementsByTagName("tuple"); } int numExpectedTuples = expectedTuples == null ? 0 : expectedTuples.getLength(); _assertEquals( message + " number of tuples", numExpectedTuples, resultTuples.size()); if (numExpectedTuples != 0) { _assertEquals( "Invalid test case. Number of dimensions does not match tuple lengths", expectedDims.getLength(), ((Element) expectedTuples.item(0)) .getElementsByTagName("member").getLength()); } for (int idx = 0; idx < numExpectedTuples; idx++) { compareTuple( message + " tuple " + idx, (Element) expectedTuples.item(idx), resultTuples.get(idx)); } } private void compareTuple( String message, Element expectedTuple, Position resultTuple) { // expectedTuple is a definition, containing members NodeList expectedMembers = expectedTuple.getElementsByTagName("member"); int numExpectedMembers = expectedMembers.getLength(); _assertEquals( message + " number of members", numExpectedMembers, resultTuple.size()); for (int idx = 0; idx < numExpectedMembers; idx++) { String resultName = resultTuple.get(idx).getUniqueName(); String expectedName = expectedMembers.item(idx).getFirstChild().getNodeValue(); _assertEquals(message + " member " + idx, expectedName, resultName); } } private void compareData() { Element dataElement = (Element) xmlRoot.getElementsByTagName("data").item(0); NodeList expectedRows = dataElement.getElementsByTagName("drow"); Axis[] axes = result.getAxes(); int numAxes = axes.length; switch (numAxes) { case 0: compareZeroAxes(expectedRows); break; case 1: compareColumnsOnly(expectedRows, axes); break; case 2: compareRowsAndColumns(expectedRows, axes); break; } } private void compareZeroAxes(NodeList expectedRow) { int numRows = expectedRow.getLength(); _assertEquals("Unexpected number of rows", 1, numRows); NodeList cellList = ((Element) expectedRow.item(0)).getElementsByTagName("cell"); int numColumns = cellList.getLength(); _assertEquals("Unexpected number of columns", numColumns, 1); int[] coord = new int[0]; Cell cell = result.getCell(coord); String expectedValue = cellList.item(0).getFirstChild().getNodeValue(); compareCell(coord, expectedValue, cell); } private void compareColumnsOnly(NodeList expectedRow, Axis[] axes) { int numRows = expectedRow.getLength(); _assertEquals("Unexpected number of rows", 1, numRows); NodeList cellList = ((Element) expectedRow.item(0)).getElementsByTagName("cell"); int numColumns = cellList.getLength(); _assertEquals( "Unexpected number of columns", numColumns, axes[0].getPositions().size()); int[] coord = new int[1]; for (int colIdx = 0; colIdx < numColumns; colIdx++) { coord[0] = colIdx; Cell cell = result.getCell(coord); String expectedValue = cellList.item(colIdx).getFirstChild().getNodeValue(); compareCell(coord, expectedValue, cell); } } private void compareRowsAndColumns(NodeList expectedRows, Axis[] axes) { int numRows = expectedRows.getLength(); int[] coord = new int[2]; _assertEquals( "Number of row tuples must match", numRows, axes[1].getPositions().size()); for (int rowIdx = 0; rowIdx < numRows; rowIdx++) { Element drow = (Element) expectedRows.item(rowIdx); NodeList cellList = drow.getElementsByTagName("cell"); if (rowIdx == 0) { _assertEquals( "Number of data columns: ", cellList.getLength(), axes[0].getPositions().size()); } coord[1] = rowIdx; for (int colIdx = 0; colIdx < axes[0].getPositions() .size(); colIdx++) { coord[0] = colIdx; Cell cell = result.getCell(coord); String expectedValue = cellList.item(colIdx).getFirstChild().getNodeValue(); compareCell(coord, expectedValue, cell); } } } private void compareCell(int[] coord, String expectedValue, Cell cell) { if (expectedValue.equalsIgnoreCase("#Missing")) { if (!cell.isNull()) { _failNotEquals( getErrorMessage( "Expected missing value but got " + cell.getValue() + " at ", coord), null, null); } } else if (cell.getValue() instanceof Number) { Number cellValue = (Number) cell.getValue(); double expectedDouble = Double.parseDouble(expectedValue); _assertEquals( getErrorMessage("Values don't match at ", coord), expectedDouble, cellValue.doubleValue(), 0.001); } else { _assertEquals( getErrorMessage("Values don't match at ", coord), expectedValue, cell.getValue()); } } private String getErrorMessage(String s, int[] coord) { StringBuilder errorAddr = new StringBuilder(); errorAddr.append(s); errorAddr.append(" ("); for (int idx = 0; idx < coord.length; idx++) { if (idx != 0) { errorAddr.append(", "); } errorAddr.append(coord[idx]); } errorAddr.append(')'); return errorAddr.toString(); } } // End ResultComparator.java mondrian-3.4.1/testsrc/main/mondrian/test/ConcurrentMdxTest.java0000644000175000017500000014226611735330606024740 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.olap.MondrianProperties; /** * Runs specified set of MDX queries concurrently. * This Class is not added to the Main test suite. * Purpose of this test is to simulate Concurrent access to Aggregation and data * load. Simulation will be more effective if we run this single test again and * again with a fresh connection. * * @author Thiyagu,Ajit */ public class ConcurrentMdxTest extends FoodMartTestCase { private MondrianProperties props; static final QueryAndResult[] mdxQueries = new QueryAndResult[]{ new QueryAndResult( "select {[Measures].[Sales Count]} on 0 from [Sales] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Sales Count]}\n" + "Row #0: 86,837\n"), new QueryAndResult( "select {[Measures].[Store Cost]} on 0 from [Sales] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Cost]}\n" + "Row #0: 225,627.23\n"), new QueryAndResult( "select {[Measures].[Sales Count], " + "[Measures].[Store Invoice]} on 0 from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Sales Count]}\n" + "{[Measures].[Store Invoice]}\n" + "Row #0: 86,837\n" + "Row #0: 102,278.409\n"), new QueryAndResult( "select {[Measures].[Sales Count], " + "[Measures].[Store Invoice]} on 0 from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Sales Count]}\n" + "{[Measures].[Store Invoice]}\n" + "Row #0: 86,837\n" + "Row #0: 102,278.409\n"), new QueryAndResult( "select {[Measures].[Sales Count], " + "[Measures].[Store Invoice]} on 0, " + "{[Time].[1997],[Time].[1998]} on 1 " + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Sales Count]}\n" + "{[Measures].[Store Invoice]}\n" + "Axis #2:\n" + "{[Time].[1997]}\n" + "{[Time].[1998]}\n" + "Row #0: 86,837\n" + "Row #0: 102,278.409\n" + "Row #1: \n" + "Row #1: \n"), new QueryAndResult( "select {[Measures].[Sales Count], " + "[Measures].[Store Invoice]} on 0, " + "{([Gender].[M],[Time].[1997])} on 1 " + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Sales Count]}\n" + "{[Measures].[Store Invoice]}\n" + "Axis #2:\n" + "{[Gender].[M], [Time].[1997]}\n" + "Row #0: 44,006\n" + "Row #0: \n"), new QueryAndResult( "select {[Measures].[Sales Count], " + "[Measures].[Store Invoice]} on 0, " + "{([Gender].[F],[Time].[1997])} on 1 " + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Sales Count]}\n" + "{[Measures].[Store Invoice]}\n" + "Axis #2:\n" + "{[Gender].[F], [Time].[1997]}\n" + "Row #0: 42,831\n" + "Row #0: \n"), new QueryAndResult( "select {[Measures].[Store Cost], " + "[Measures].[Supply Time]} on 0 from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Supply Time]}\n" + "Row #0: 225,627.23\n" + "Row #0: 10,425\n"), new QueryAndResult( "select {[Measures].[Store Sales], " + "[Measures].[Units Ordered]} on 0 from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Units Ordered]}\n" + "Row #0: 565,238.13\n" + "Row #0: 227238.0\n"), new QueryAndResult( "select {[Measures].[Unit Sales], " + "[Measures].[Units Ordered]} on 0 from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Units Ordered]}\n" + "Row #0: 266,773\n" + "Row #0: 227238.0\n"), new QueryAndResult( "select {[Measures].[Profit], " + "[Measures].[Units Shipped]} on 0 from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Profit]}\n" + "{[Measures].[Units Shipped]}\n" + "Row #0: $339,610.90\n" + "Row #0: 207726.0\n"), new QueryAndResult( "select {[Measures].[Unit Sales]} on columns\n" + " from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 266,773\n"), new QueryAndResult( "select \n" + "{[Measures].[Unit Sales]} on columns,\n" + "order(except([Promotion Media].[Media Type].members," + "{[Promotion Media].[Media Type].[No Media]})," + "[Measures].[Unit Sales],DESC) on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Promotion Media].[Daily Paper, Radio, TV]}\n" + "{[Promotion Media].[Daily Paper]}\n" + "{[Promotion Media].[Product Attachment]}\n" + "{[Promotion Media].[Daily Paper, Radio]}\n" + "{[Promotion Media].[Cash Register Handout]}\n" + "{[Promotion Media].[Sunday Paper, Radio]}\n" + "{[Promotion Media].[Street Handout]}\n" + "{[Promotion Media].[Sunday Paper]}\n" + "{[Promotion Media].[Bulk Mail]}\n" + "{[Promotion Media].[In-Store Coupon]}\n" + "{[Promotion Media].[TV]}\n" + "{[Promotion Media].[Sunday Paper, Radio, TV]}\n" + "{[Promotion Media].[Radio]}\n" + "Row #0: 9,513\n" + "Row #1: 7,738\n" + "Row #2: 7,544\n" + "Row #3: 6,891\n" + "Row #4: 6,697\n" + "Row #5: 5,945\n" + "Row #6: 5,753\n" + "Row #7: 4,339\n" + "Row #8: 4,320\n" + "Row #9: 3,798\n" + "Row #10: 3,607\n" + "Row #11: 2,726\n" + "Row #12: 2,454\n"), new QueryAndResult( "select\n" + "{ [Measures].[Units Shipped], [Measures].[Units Ordered] }" + " on columns,\n" + "NON EMPTY [Store].[Store Name].members on rows\n" + "from Warehouse", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Units Shipped]}\n" + "{[Measures].[Units Ordered]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Store].[USA].[WA].[Bellingham].[Store 2]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "{[Store].[USA].[WA].[Walla Walla].[Store 22]}\n" + "{[Store].[USA].[WA].[Yakima].[Store 23]}\n" + "Row #0: 10759.0\n" + "Row #0: 11699.0\n" + "Row #1: 24587.0\n" + "Row #1: 26463.0\n" + "Row #2: 23835.0\n" + "Row #2: 26270.0\n" + "Row #3: 1696.0\n" + "Row #3: 1875.0\n" + "Row #4: 8515.0\n" + "Row #4: 9109.0\n" + "Row #5: 32393.0\n" + "Row #5: 35797.0\n" + "Row #6: 2348.0\n" + "Row #6: 2454.0\n" + "Row #7: 22734.0\n" + "Row #7: 24610.0\n" + "Row #8: 24110.0\n" + "Row #8: 26703.0\n" + "Row #9: 11889.0\n" + "Row #9: 12828.0\n" + "Row #10: 32411.0\n" + "Row #10: 35930.0\n" + "Row #11: 1860.0\n" + "Row #11: 2074.0\n" + "Row #12: 10589.0\n" + "Row #12: 11426.0\n"), new QueryAndResult( "with member [Measures].[Store Sales Last Period] as" + " '([Measures].[Store Sales], Time.PrevMember)'\n" + "select\n" + " {[Measures].[Store Sales Last Period]} on columns,\n" + " {TopCount([Product].[Product Department].members,5," + " [Measures].[Store Sales Last Period])} on rows\n" + "from Sales\n" + "where ([Time].[1998])", "Axis #0:\n" + "{[Time].[1998]}\n" + "Axis #1:\n" + "{[Measures].[Store Sales Last Period]}\n" + "Axis #2:\n" + "{[Product].[Food].[Produce]}\n" + "{[Product].[Food].[Snack Foods]}\n" + "{[Product].[Non-Consumable].[Household]}\n" + "{[Product].[Food].[Frozen Foods]}\n" + "{[Product].[Food].[Canned Foods]}\n" + "Row #0: 82,248.42\n" + "Row #1: 67,609.82\n" + "Row #2: 60,469.89\n" + "Row #3: 55,207.50\n" + "Row #4: 39,774.34\n"), new QueryAndResult( "with member [Measures].[Total Store Sales] as" + "'Sum(YTD(),[Measures].[Store Sales])'\n" + "select\n" + "{[Measures].[Total Store Sales]} on columns,\n" + "{TopCount([Product].[Product Department].members,5," + "[Measures].[Total Store Sales])} on rows\n" + "from Sales\n" + "where ([Time].[1997].[Q2].[4])", "Axis #0:\n" + "{[Time].[1997].[Q2].[4]}\n" + "Axis #1:\n" + "{[Measures].[Total Store Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Produce]}\n" + "{[Product].[Food].[Snack Foods]}\n" + "{[Product].[Non-Consumable].[Household]}\n" + "{[Product].[Food].[Frozen Foods]}\n" + "{[Product].[Food].[Canned Foods]}\n" + "Row #0: 26,526.67\n" + "Row #1: 21,897.10\n" + "Row #2: 19,980.90\n" + "Row #3: 17,882.63\n" + "Row #4: 12,963.23\n"), new QueryAndResult( "with member [Measures].[Store Profit Rate] as" + "'([Measures].[Store Sales]-[Measures].[Store Cost])/" + "[Measures].[Store Cost]', format = '#.00%'\n" + "select\n" + " {[Measures].[Store Cost],[Measures].[Store Sales]," + "[Measures].[Store Profit Rate]} on columns,\n" + " Order([Product].[Product Department].members, " + "[Measures].[Store Profit Rate], BDESC) on rows\n" + "from Sales\n" + "where ([Time].[1997])", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Store Profit Rate]}\n" + "Axis #2:\n" + "{[Product].[Food].[Breakfast Foods]}\n" + "{[Product].[Non-Consumable].[Carousel]}\n" + "{[Product].[Food].[Canned Products]}\n" + "{[Product].[Food].[Baking Goods]}\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "{[Product].[Non-Consumable].[Health and Hygiene]}\n" + "{[Product].[Food].[Snack Foods]}\n" + "{[Product].[Food].[Baked Goods]}\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Food].[Frozen Foods]}\n" + "{[Product].[Non-Consumable].[Periodicals]}\n" + "{[Product].[Food].[Produce]}\n" + "{[Product].[Food].[Seafood]}\n" + "{[Product].[Food].[Deli]}\n" + "{[Product].[Food].[Meat]}\n" + "{[Product].[Food].[Canned Foods]}\n" + "{[Product].[Non-Consumable].[Household]}\n" + "{[Product].[Food].[Starchy Foods]}\n" + "{[Product].[Food].[Eggs]}\n" + "{[Product].[Food].[Snacks]}\n" + "{[Product].[Food].[Dairy]}\n" + "{[Product].[Drink].[Dairy]}\n" + "{[Product].[Non-Consumable].[Checkout]}\n" + "Row #0: 2,756.80\n" + "Row #0: 6,941.46\n" + "Row #0: 151.79%\n" + "Row #1: 595.97\n" + "Row #1: 1,500.11\n" + "Row #1: 151.71%\n" + "Row #2: 1,317.13\n" + "Row #2: 3,314.52\n" + "Row #2: 151.65%\n" + "Row #3: 15,370.61\n" + "Row #3: 38,670.41\n" + "Row #3: 151.59%\n" + "Row #4: 5,576.79\n" + "Row #4: 14,029.08\n" + "Row #4: 151.56%\n" + "Row #5: 12,972.99\n" + "Row #5: 32,571.86\n" + "Row #5: 151.07%\n" + "Row #6: 26,963.34\n" + "Row #6: 67,609.82\n" + "Row #6: 150.75%\n" + "Row #7: 6,564.09\n" + "Row #7: 16,455.43\n" + "Row #7: 150.69%\n" + "Row #8: 11,069.53\n" + "Row #8: 27,748.53\n" + "Row #8: 150.67%\n" + "Row #9: 22,030.66\n" + "Row #9: 55,207.50\n" + "Row #9: 150.59%\n" + "Row #10: 3,614.55\n" + "Row #10: 9,056.76\n" + "Row #10: 150.56%\n" + "Row #11: 32,831.33\n" + "Row #11: 82,248.42\n" + "Row #11: 150.52%\n" + "Row #12: 1,520.70\n" + "Row #12: 3,809.14\n" + "Row #12: 150.49%\n" + "Row #13: 10,108.87\n" + "Row #13: 25,318.93\n" + "Row #13: 150.46%\n" + "Row #14: 1,465.42\n" + "Row #14: 3,669.89\n" + "Row #14: 150.43%\n" + "Row #15: 15,894.53\n" + "Row #15: 39,774.34\n" + "Row #15: 150.24%\n" + "Row #16: 24,170.73\n" + "Row #16: 60,469.89\n" + "Row #16: 150.18%\n" + "Row #17: 4,705.91\n" + "Row #17: 11,756.07\n" + "Row #17: 149.82%\n" + "Row #18: 3,684.90\n" + "Row #18: 9,200.76\n" + "Row #18: 149.69%\n" + "Row #19: 5,827.58\n" + "Row #19: 14,550.05\n" + "Row #19: 149.68%\n" + "Row #20: 12,228.85\n" + "Row #20: 30,508.85\n" + "Row #20: 149.48%\n" + "Row #21: 2,830.92\n" + "Row #21: 7,058.60\n" + "Row #21: 149.34%\n" + "Row #22: 1,525.04\n" + "Row #22: 3,767.71\n" + "Row #22: 147.06%\n"), new QueryAndResult( "with\n" + " member [Product].[All Products].[Drink].[Percent of Alcoholic Drinks]" + " as '[Product].[All Products].[Drink].[Alcoholic Beverages]/" + "[Product].[All Products].[Drink]', format = '#.00%'\n" + "select\n" + " { [Product].[Drink].[Percent of Alcoholic Drinks] }" + " on columns,\n" + " order([Customers].[All Customers].[USA].[WA].Children," + " [Product].[Drink].[Percent of Alcoholic Drinks],BDESC)" + " on rows\n" + "from Sales\n" + "where ([Measures].[Unit Sales])", "Axis #0:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #1:\n" + "{[Product].[Drink].[Percent of Alcoholic Drinks]}\n" + "Axis #2:\n" + "{[Customers].[USA].[WA].[Seattle]}\n" + "{[Customers].[USA].[WA].[Kirkland]}\n" + "{[Customers].[USA].[WA].[Marysville]}\n" + "{[Customers].[USA].[WA].[Anacortes]}\n" + "{[Customers].[USA].[WA].[Olympia]}\n" + "{[Customers].[USA].[WA].[Ballard]}\n" + "{[Customers].[USA].[WA].[Bremerton]}\n" + "{[Customers].[USA].[WA].[Puyallup]}\n" + "{[Customers].[USA].[WA].[Yakima]}\n" + "{[Customers].[USA].[WA].[Tacoma]}\n" + "{[Customers].[USA].[WA].[Everett]}\n" + "{[Customers].[USA].[WA].[Renton]}\n" + "{[Customers].[USA].[WA].[Issaquah]}\n" + "{[Customers].[USA].[WA].[Bellingham]}\n" + "{[Customers].[USA].[WA].[Port Orchard]}\n" + "{[Customers].[USA].[WA].[Redmond]}\n" + "{[Customers].[USA].[WA].[Spokane]}\n" + "{[Customers].[USA].[WA].[Burien]}\n" + "{[Customers].[USA].[WA].[Lynnwood]}\n" + "{[Customers].[USA].[WA].[Walla Walla]}\n" + "{[Customers].[USA].[WA].[Edmonds]}\n" + "{[Customers].[USA].[WA].[Sedro Woolley]}\n" + "Row #0: 44.05%\n" + "Row #1: 34.41%\n" + "Row #2: 34.20%\n" + "Row #3: 32.93%\n" + "Row #4: 31.05%\n" + "Row #5: 30.84%\n" + "Row #6: 30.69%\n" + "Row #7: 29.81%\n" + "Row #8: 28.82%\n" + "Row #9: 28.70%\n" + "Row #10: 28.37%\n" + "Row #11: 26.67%\n" + "Row #12: 26.60%\n" + "Row #13: 26.47%\n" + "Row #14: 26.42%\n" + "Row #15: 26.28%\n" + "Row #16: 25.96%\n" + "Row #17: 24.70%\n" + "Row #18: 21.89%\n" + "Row #19: 21.47%\n" + "Row #20: 17.47%\n" + "Row #21: 13.79%\n"), new QueryAndResult( "with member [Measures].[Accumulated Sales] as " + "'Sum(YTD(),[Measures].[Store Sales])'\n" + "select\n" + " {[Measures].[Store Sales],[Measures].[Accumulated Sales]}" + " on columns,\n" + " {Descendants([Time].[1997],[Time].[Month])} on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Accumulated Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1].[1]}\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[5]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q3].[7]}\n" + "{[Time].[1997].[Q3].[8]}\n" + "{[Time].[1997].[Q3].[9]}\n" + "{[Time].[1997].[Q4].[10]}\n" + "{[Time].[1997].[Q4].[11]}\n" + "{[Time].[1997].[Q4].[12]}\n" + "Row #0: 45,539.69\n" + "Row #0: 45,539.69\n" + "Row #1: 44,058.79\n" + "Row #1: 89,598.48\n" + "Row #2: 50,029.87\n" + "Row #2: 139,628.35\n" + "Row #3: 42,878.25\n" + "Row #3: 182,506.60\n" + "Row #4: 44,456.29\n" + "Row #4: 226,962.89\n" + "Row #5: 45,331.73\n" + "Row #5: 272,294.62\n" + "Row #6: 50,246.88\n" + "Row #6: 322,541.50\n" + "Row #7: 46,199.04\n" + "Row #7: 368,740.54\n" + "Row #8: 43,825.97\n" + "Row #8: 412,566.51\n" + "Row #9: 42,342.27\n" + "Row #9: 454,908.78\n" + "Row #10: 53,363.71\n" + "Row #10: 508,272.49\n" + "Row #11: 56,965.64\n" + "Row #11: 565,238.13\n"), new QueryAndResult( "select\n" + " {[Measures].[Unit Sales]} on columns,\n" + " [Gender].members on rows\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 266,773\n" + "Row #1: 131,558\n" + "Row #2: 135,215\n"), new QueryAndResult( "WITH\n" + " MEMBER [Measures].[StoreType] AS \n" + " '[Store].CurrentMember.Properties(\"Store Type\")',\n" + " SOLVE_ORDER = 2\n" + " MEMBER [Measures].[ProfitPct] AS \n" + " '((Measures.[Store Sales] - Measures.[Store Cost]) /" + " Measures.[Store Sales])',\n" + " SOLVE_ORDER = 1, FORMAT_STRING = 'Percent'\n" + "SELECT non empty\n" + " { [Store].[Store Name].Members} ON COLUMNS,\n" + " { [Measures].[Store Sales], [Measures].[Store Cost]," + " [Measures].[StoreType],\n" + " [Measures].[ProfitPct] } ON ROWS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Store].[USA].[WA].[Bellingham].[Store 2]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "{[Store].[USA].[WA].[Walla Walla].[Store 22]}\n" + "{[Store].[USA].[WA].[Yakima].[Store 23]}\n" + "Axis #2:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[StoreType]}\n" + "{[Measures].[ProfitPct]}\n" + "Row #0: 45,750.24\n" + "Row #0: 54,545.28\n" + "Row #0: 54,431.14\n" + "Row #0: 4,441.18\n" + "Row #0: 55,058.79\n" + "Row #0: 87,218.28\n" + "Row #0: 4,739.23\n" + "Row #0: 52,896.30\n" + "Row #0: 52,644.07\n" + "Row #0: 49,634.46\n" + "Row #0: 74,843.96\n" + "Row #0: 4,705.97\n" + "Row #0: 24,329.23\n" + "Row #1: 18,266.44\n" + "Row #1: 21,771.54\n" + "Row #1: 21,713.53\n" + "Row #1: 1,778.92\n" + "Row #1: 21,948.94\n" + "Row #1: 34,823.56\n" + "Row #1: 1,896.62\n" + "Row #1: 21,121.96\n" + "Row #1: 20,956.80\n" + "Row #1: 19,795.49\n" + "Row #1: 29,959.28\n" + "Row #1: 1,880.34\n" + "Row #1: 9,713.81\n" + "Row #2: Gourmet Supermarket\n" + "Row #2: Supermarket\n" + "Row #2: Supermarket\n" + "Row #2: Small Grocery\n" + "Row #2: Supermarket\n" + "Row #2: Deluxe Supermarket\n" + "Row #2: Small Grocery\n" + "Row #2: Supermarket\n" + "Row #2: Supermarket\n" + "Row #2: Supermarket\n" + "Row #2: Deluxe Supermarket\n" + "Row #2: Small Grocery\n" + "Row #2: Mid-Size Grocery\n" + "Row #3: 60.07%\n" + "Row #3: 60.09%\n" + "Row #3: 60.11%\n" + "Row #3: 59.94%\n" + "Row #3: 60.14%\n" + "Row #3: 60.07%\n" + "Row #3: 59.98%\n" + "Row #3: 60.07%\n" + "Row #3: 60.19%\n" + "Row #3: 60.12%\n" + "Row #3: 59.97%\n" + "Row #3: 60.04%\n" + "Row #3: 60.07%\n"), new QueryAndResult( "WITH\n" + " MEMBER [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[BigSeller] AS\n" + " 'IIf([Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine] > 100, \"Yes\",\"No\")'\n" + "SELECT\n" + " {[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[BigSeller]} ON COLUMNS,\n" + " {Store.[Store Name].Members} ON ROWS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[BigSeller]}\n" + "Axis #2:\n" + "{[Store].[Canada].[BC].[Vancouver].[Store 19]}\n" + "{[Store].[Canada].[BC].[Victoria].[Store 20]}\n" + "{[Store].[Mexico].[DF].[Mexico City].[Store 9]}\n" + "{[Store].[Mexico].[DF].[San Andres].[Store 21]}\n" + "{[Store].[Mexico].[Guerrero].[Acapulco].[Store 1]}\n" + "{[Store].[Mexico].[Jalisco].[Guadalajara].[Store 5]}\n" + "{[Store].[Mexico].[Veracruz].[Orizaba].[Store 10]}\n" + "{[Store].[Mexico].[Yucatan].[Merida].[Store 8]}\n" + "{[Store].[Mexico].[Zacatecas].[Camacho].[Store 4]}\n" + "{[Store].[Mexico].[Zacatecas].[Hidalgo].[Store 12]}\n" + "{[Store].[Mexico].[Zacatecas].[Hidalgo].[Store 18]}\n" + "{[Store].[USA].[CA].[Alameda].[HQ]}\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Store].[USA].[WA].[Bellingham].[Store 2]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "{[Store].[USA].[WA].[Walla Walla].[Store 22]}\n" + "{[Store].[USA].[WA].[Yakima].[Store 23]}\n" + "Row #0: No\n" + "Row #1: No\n" + "Row #2: No\n" + "Row #3: No\n" + "Row #4: No\n" + "Row #5: No\n" + "Row #6: No\n" + "Row #7: No\n" + "Row #8: No\n" + "Row #9: No\n" + "Row #10: No\n" + "Row #11: No\n" + "Row #12: Yes\n" + "Row #13: Yes\n" + "Row #14: Yes\n" + "Row #15: No\n" + "Row #16: Yes\n" + "Row #17: Yes\n" + "Row #18: No\n" + "Row #19: Yes\n" + "Row #20: Yes\n" + "Row #21: Yes\n" + "Row #22: Yes\n" + "Row #23: No\n" + "Row #24: Yes\n"), new QueryAndResult( "WITH\n" + " MEMBER [Measures].[ProfitPct] AS \n" + " '((Measures.[Store Sales] - Measures.[Store Cost]) /" + " Measures.[Store Sales])',\n" + " SOLVE_ORDER = 1, FORMAT_STRING = 'Percent'\n" + " MEMBER [Measures].[ProfitValue] AS \n" + " '[Measures].[Store Sales] * [Measures].[ProfitPct]',\n" + " SOLVE_ORDER = 2, FORMAT_STRING = 'Currency'\n" + "SELECT non empty \n" + " { [Store].[Store Name].Members} ON COLUMNS,\n" + " { [Measures].[Store Sales], [Measures].[Store Cost]," + " [Measures].[ProfitValue],\n" + " [Measures].[ProfitPct] } ON ROWS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Store].[USA].[WA].[Bellingham].[Store 2]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "{[Store].[USA].[WA].[Walla Walla].[Store 22]}\n" + "{[Store].[USA].[WA].[Yakima].[Store 23]}\n" + "Axis #2:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[ProfitValue]}\n" + "{[Measures].[ProfitPct]}\n" + "Row #0: 45,750.24\n" + "Row #0: 54,545.28\n" + "Row #0: 54,431.14\n" + "Row #0: 4,441.18\n" + "Row #0: 55,058.79\n" + "Row #0: 87,218.28\n" + "Row #0: 4,739.23\n" + "Row #0: 52,896.30\n" + "Row #0: 52,644.07\n" + "Row #0: 49,634.46\n" + "Row #0: 74,843.96\n" + "Row #0: 4,705.97\n" + "Row #0: 24,329.23\n" + "Row #1: 18,266.44\n" + "Row #1: 21,771.54\n" + "Row #1: 21,713.53\n" + "Row #1: 1,778.92\n" + "Row #1: 21,948.94\n" + "Row #1: 34,823.56\n" + "Row #1: 1,896.62\n" + "Row #1: 21,121.96\n" + "Row #1: 20,956.80\n" + "Row #1: 19,795.49\n" + "Row #1: 29,959.28\n" + "Row #1: 1,880.34\n" + "Row #1: 9,713.81\n" + "Row #2: $27,483.80\n" + "Row #2: $32,773.74\n" + "Row #2: $32,717.61\n" + "Row #2: $2,662.26\n" + "Row #2: $33,109.85\n" + "Row #2: $52,394.72\n" + "Row #2: $2,842.61\n" + "Row #2: $31,774.34\n" + "Row #2: $31,687.27\n" + "Row #2: $29,838.97\n" + "Row #2: $44,884.68\n" + "Row #2: $2,825.63\n" + "Row #2: $14,615.42\n" + "Row #3: 60.07%\n" + "Row #3: 60.09%\n" + "Row #3: 60.11%\n" + "Row #3: 59.94%\n" + "Row #3: 60.14%\n" + "Row #3: 60.07%\n" + "Row #3: 59.98%\n" + "Row #3: 60.07%\n" + "Row #3: 60.19%\n" + "Row #3: 60.12%\n" + "Row #3: 59.97%\n" + "Row #3: 60.04%\n" + "Row #3: 60.07%\n"), new QueryAndResult( "WITH MEMBER MEASURES.ProfitPercent AS\n" + " '([Measures].[Store Sales]-[Measures].[Store Cost])/" + "([Measures].[Store Cost])',\n" + " FORMAT_STRING = '#.00%', SOLVE_ORDER = 1\n" + " Member [Time].[Time].[First Half 97] AS '[Time].[1997].[Q1] +" + " [Time].[1997].[Q2]'\n" + " Member [Time].[Time].[Second Half 97] AS '[Time].[1997].[Q3] +" + " [Time].[1997].[Q4]'\n" + " SELECT {[Time].[First Half 97],\n" + " [Time].[Second Half 97],\n" + " [Time].[1997].CHILDREN} ON COLUMNS,\n" + " {[Store].[Store Country].[USA].CHILDREN} ON ROWS\n" + " FROM [Sales]\n" + " WHERE ([Measures].[ProfitPercent])", "Axis #0:\n" + "{[Measures].[ProfitPercent]}\n" + "Axis #1:\n" + "{[Time].[First Half 97]}\n" + "{[Time].[Second Half 97]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q4]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 150.55%\n" + "Row #0: 150.53%\n" + "Row #0: 150.68%\n" + "Row #0: 150.44%\n" + "Row #0: 151.35%\n" + "Row #0: 149.81%\n" + "Row #1: 150.15%\n" + "Row #1: 151.08%\n" + "Row #1: 149.80%\n" + "Row #1: 150.60%\n" + "Row #1: 151.37%\n" + "Row #1: 150.78%\n" + "Row #2: 150.59%\n" + "Row #2: 150.34%\n" + "Row #2: 150.72%\n" + "Row #2: 150.45%\n" + "Row #2: 150.39%\n" + "Row #2: 150.29%\n"), new QueryAndResult( "with member [Measures].[Accumulated Sales] as" + " 'Sum(YTD(),[Measures].[Store Sales])'\n" + "select\n" + " {[Measures].[Store Sales],[Measures].[Accumulated Sales]}" + " on columns,\n" + " {Descendants([Time].[1997],[Time].[Month])} on rows\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Accumulated Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1].[1]}\n" + "{[Time].[1997].[Q1].[2]}\n" + "{[Time].[1997].[Q1].[3]}\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[5]}\n" + "{[Time].[1997].[Q2].[6]}\n" + "{[Time].[1997].[Q3].[7]}\n" + "{[Time].[1997].[Q3].[8]}\n" + "{[Time].[1997].[Q3].[9]}\n" + "{[Time].[1997].[Q4].[10]}\n" + "{[Time].[1997].[Q4].[11]}\n" + "{[Time].[1997].[Q4].[12]}\n" + "Row #0: 45,539.69\n" + "Row #0: 45,539.69\n" + "Row #1: 44,058.79\n" + "Row #1: 89,598.48\n" + "Row #2: 50,029.87\n" + "Row #2: 139,628.35\n" + "Row #3: 42,878.25\n" + "Row #3: 182,506.60\n" + "Row #4: 44,456.29\n" + "Row #4: 226,962.89\n" + "Row #5: 45,331.73\n" + "Row #5: 272,294.62\n" + "Row #6: 50,246.88\n" + "Row #6: 322,541.50\n" + "Row #7: 46,199.04\n" + "Row #7: 368,740.54\n" + "Row #8: 43,825.97\n" + "Row #8: 412,566.51\n" + "Row #9: 42,342.27\n" + "Row #9: 454,908.78\n" + "Row #10: 53,363.71\n" + "Row #10: 508,272.49\n" + "Row #11: 56,965.64\n" + "Row #11: 565,238.13\n"), // Virtual cube. Note that Unit Sales is independent of Warehouse. new QueryAndResult( "select non empty CrossJoin(\r\n" + " {[Warehouse].DefaultMember, [Warehouse].[USA].children},\n" + " {[Measures].[Unit Sales], [Measures].[Units Shipped]}) on" + " columns,\n" + " [Time].children on rows\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Warehouse].[All Warehouses], [Measures].[Unit Sales]}\n" + "{[Warehouse].[All Warehouses], [Measures].[Units Shipped]}\n" + "{[Warehouse].[USA].[CA], [Measures].[Units Shipped]}\n" + "{[Warehouse].[USA].[OR], [Measures].[Units Shipped]}\n" + "{[Warehouse].[USA].[WA], [Measures].[Units Shipped]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q4]}\n" + "Row #0: 66,291\n" + "Row #0: 50951.0\n" + "Row #0: 8539.0\n" + "Row #0: 7994.0\n" + "Row #0: 34418.0\n" + "Row #1: 62,610\n" + "Row #1: 49187.0\n" + "Row #1: 15726.0\n" + "Row #1: 7575.0\n" + "Row #1: 25886.0\n" + "Row #2: 65,848\n" + "Row #2: 57789.0\n" + "Row #2: 20821.0\n" + "Row #2: 8673.0\n" + "Row #2: 28295.0\n" + "Row #3: 72,024\n" + "Row #3: 49799.0\n" + "Row #3: 15791.0\n" + "Row #3: 16666.0\n" + "Row #3: 17342.0\n"), // should allow dimension to be used as shorthand for member new QueryAndResult( "select {[Measures].[Unit Sales]} on columns,\n" + " {[Store], [Store].children} on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[All Stores]}\n" + "{[Store].[Canada]}\n" + "{[Store].[Mexico]}\n" + "{[Store].[USA]}\n" + "Row #0: 266,773\n" + "Row #1: \n" + "Row #2: \n" + "Row #3: 266,773\n"), // crossjoins on rows and columns, and a slicer new QueryAndResult( "select\n" + " CrossJoin(\n" + " {[Measures].[Unit Sales], [Measures].[Store Sales]},\n" + " {[Time].[1997].[Q2].children}) on columns, \n" + " CrossJoin(\n" + " CrossJoin(\n" + " [Gender].members,\n" + " [Marital Status].members),\n" + " {[Store], [Store].children}) on rows\n" + "from [Sales]\n" + "where (\n" + " [Product].[Food],\n" + " [Education Level].[High School Degree],\n" + " [Promotions].DefaultMember)", "Axis #0:\n" + "{[Product].[Food], [Education Level].[High School Degree], " + "[Promotions].[All Promotions]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales], [Time].[1997].[Q2].[4]}\n" + "{[Measures].[Unit Sales], [Time].[1997].[Q2].[5]}\n" + "{[Measures].[Unit Sales], [Time].[1997].[Q2].[6]}\n" + "{[Measures].[Store Sales], [Time].[1997].[Q2].[4]}\n" + "{[Measures].[Store Sales], [Time].[1997].[Q2].[5]}\n" + "{[Measures].[Store Sales], [Time].[1997].[Q2].[6]}\n" + "Axis #2:\n" + "{[Gender].[All Gender], [Marital Status].[All Marital Status], [Store].[All Stores]}\n" + "{[Gender].[All Gender], [Marital Status].[All Marital Status], [Store].[Canada]}\n" + "{[Gender].[All Gender], [Marital Status].[All Marital Status], [Store].[Mexico]}\n" + "{[Gender].[All Gender], [Marital Status].[All Marital Status], [Store].[USA]}\n" + "{[Gender].[All Gender], [Marital Status].[M], [Store].[All Stores]}\n" + "{[Gender].[All Gender], [Marital Status].[M], [Store].[Canada]}\n" + "{[Gender].[All Gender], [Marital Status].[M], [Store].[Mexico]}\n" + "{[Gender].[All Gender], [Marital Status].[M], [Store].[USA]}\n" + "{[Gender].[All Gender], [Marital Status].[S], [Store].[All Stores]}\n" + "{[Gender].[All Gender], [Marital Status].[S], [Store].[Canada]}\n" + "{[Gender].[All Gender], [Marital Status].[S], [Store].[Mexico]}\n" + "{[Gender].[All Gender], [Marital Status].[S], [Store].[USA]}\n" + "{[Gender].[F], [Marital Status].[All Marital Status], [Store].[All Stores]}\n" + "{[Gender].[F], [Marital Status].[All Marital Status], [Store].[Canada]}\n" + "{[Gender].[F], [Marital Status].[All Marital Status], [Store].[Mexico]}\n" + "{[Gender].[F], [Marital Status].[All Marital Status], [Store].[USA]}\n" + "{[Gender].[F], [Marital Status].[M], [Store].[All Stores]}\n" + "{[Gender].[F], [Marital Status].[M], [Store].[Canada]}\n" + "{[Gender].[F], [Marital Status].[M], [Store].[Mexico]}\n" + "{[Gender].[F], [Marital Status].[M], [Store].[USA]}\n" + "{[Gender].[F], [Marital Status].[S], [Store].[All Stores]}\n" + "{[Gender].[F], [Marital Status].[S], [Store].[Canada]}\n" + "{[Gender].[F], [Marital Status].[S], [Store].[Mexico]}\n" + "{[Gender].[F], [Marital Status].[S], [Store].[USA]}\n" + "{[Gender].[M], [Marital Status].[All Marital Status], [Store].[All Stores]}\n" + "{[Gender].[M], [Marital Status].[All Marital Status], [Store].[Canada]}\n" + "{[Gender].[M], [Marital Status].[All Marital Status], [Store].[Mexico]}\n" + "{[Gender].[M], [Marital Status].[All Marital Status], [Store].[USA]}\n" + "{[Gender].[M], [Marital Status].[M], [Store].[All Stores]}\n" + "{[Gender].[M], [Marital Status].[M], [Store].[Canada]}\n" + "{[Gender].[M], [Marital Status].[M], [Store].[Mexico]}\n" + "{[Gender].[M], [Marital Status].[M], [Store].[USA]}\n" + "{[Gender].[M], [Marital Status].[S], [Store].[All Stores]}\n" + "{[Gender].[M], [Marital Status].[S], [Store].[Canada]}\n" + "{[Gender].[M], [Marital Status].[S], [Store].[Mexico]}\n" + "{[Gender].[M], [Marital Status].[S], [Store].[USA]}\n" + "Row #0: 4,284\n" + "Row #0: 3,972\n" + "Row #0: 4,476\n" + "Row #0: 9,014.60\n" + "Row #0: 8,595.99\n" + "Row #0: 9,480.21\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #3: 4,284\n" + "Row #3: 3,972\n" + "Row #3: 4,476\n" + "Row #3: 9,014.60\n" + "Row #3: 8,595.99\n" + "Row #3: 9,480.21\n" + "Row #4: 1,942\n" + "Row #4: 1,843\n" + "Row #4: 2,128\n" + "Row #4: 4,133.87\n" + "Row #4: 4,007.53\n" + "Row #4: 4,541.97\n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #7: 1,942\n" + "Row #7: 1,843\n" + "Row #7: 2,128\n" + "Row #7: 4,133.87\n" + "Row #7: 4,007.53\n" + "Row #7: 4,541.97\n" + "Row #8: 2,342\n" + "Row #8: 2,129\n" + "Row #8: 2,348\n" + "Row #8: 4,880.73\n" + "Row #8: 4,588.46\n" + "Row #8: 4,938.24\n" + "Row #9: \n" + "Row #9: \n" + "Row #9: \n" + "Row #9: \n" + "Row #9: \n" + "Row #9: \n" + "Row #10: \n" + "Row #10: \n" + "Row #10: \n" + "Row #10: \n" + "Row #10: \n" + "Row #10: \n" + "Row #11: 2,342\n" + "Row #11: 2,129\n" + "Row #11: 2,348\n" + "Row #11: 4,880.73\n" + "Row #11: 4,588.46\n" + "Row #11: 4,938.24\n" + "Row #12: 2,093\n" + "Row #12: 1,981\n" + "Row #12: 1,918\n" + "Row #12: 4,380.67\n" + "Row #12: 4,338.31\n" + "Row #12: 4,130.34\n" + "Row #13: \n" + "Row #13: \n" + "Row #13: \n" + "Row #13: \n" + "Row #13: \n" + "Row #13: \n" + "Row #14: \n" + "Row #14: \n" + "Row #14: \n" + "Row #14: \n" + "Row #14: \n" + "Row #14: \n" + "Row #15: 2,093\n" + "Row #15: 1,981\n" + "Row #15: 1,918\n" + "Row #15: 4,380.67\n" + "Row #15: 4,338.31\n" + "Row #15: 4,130.34\n" + "Row #16: 901\n" + "Row #16: 942\n" + "Row #16: 837\n" + "Row #16: 1,905.00\n" + "Row #16: 2,069.44\n" + "Row #16: 1,865.44\n" + "Row #17: \n" + "Row #17: \n" + "Row #17: \n" + "Row #17: \n" + "Row #17: \n" + "Row #17: \n" + "Row #18: \n" + "Row #18: \n" + "Row #18: \n" + "Row #18: \n" + "Row #18: \n" + "Row #18: \n" + "Row #19: 901\n" + "Row #19: 942\n" + "Row #19: 837\n" + "Row #19: 1,905.00\n" + "Row #19: 2,069.44\n" + "Row #19: 1,865.44\n" + "Row #20: 1,192\n" + "Row #20: 1,039\n" + "Row #20: 1,081\n" + "Row #20: 2,475.67\n" + "Row #20: 2,268.87\n" + "Row #20: 2,264.90\n" + "Row #21: \n" + "Row #21: \n" + "Row #21: \n" + "Row #21: \n" + "Row #21: \n" + "Row #21: \n" + "Row #22: \n" + "Row #22: \n" + "Row #22: \n" + "Row #22: \n" + "Row #22: \n" + "Row #22: \n" + "Row #23: 1,192\n" + "Row #23: 1,039\n" + "Row #23: 1,081\n" + "Row #23: 2,475.67\n" + "Row #23: 2,268.87\n" + "Row #23: 2,264.90\n" + "Row #24: 2,191\n" + "Row #24: 1,991\n" + "Row #24: 2,558\n" + "Row #24: 4,633.93\n" + "Row #24: 4,257.68\n" + "Row #24: 5,349.87\n" + "Row #25: \n" + "Row #25: \n" + "Row #25: \n" + "Row #25: \n" + "Row #25: \n" + "Row #25: \n" + "Row #26: \n" + "Row #26: \n" + "Row #26: \n" + "Row #26: \n" + "Row #26: \n" + "Row #26: \n" + "Row #27: 2,191\n" + "Row #27: 1,991\n" + "Row #27: 2,558\n" + "Row #27: 4,633.93\n" + "Row #27: 4,257.68\n" + "Row #27: 5,349.87\n" + "Row #28: 1,041\n" + "Row #28: 901\n" + "Row #28: 1,291\n" + "Row #28: 2,228.87\n" + "Row #28: 1,938.09\n" + "Row #28: 2,676.53\n" + "Row #29: \n" + "Row #29: \n" + "Row #29: \n" + "Row #29: \n" + "Row #29: \n" + "Row #29: \n" + "Row #30: \n" + "Row #30: \n" + "Row #30: \n" + "Row #30: \n" + "Row #30: \n" + "Row #30: \n" + "Row #31: 1,041\n" + "Row #31: 901\n" + "Row #31: 1,291\n" + "Row #31: 2,228.87\n" + "Row #31: 1,938.09\n" + "Row #31: 2,676.53\n" + "Row #32: 1,150\n" + "Row #32: 1,090\n" + "Row #32: 1,267\n" + "Row #32: 2,405.06\n" + "Row #32: 2,319.59\n" + "Row #32: 2,673.34\n" + "Row #33: \n" + "Row #33: \n" + "Row #33: \n" + "Row #33: \n" + "Row #33: \n" + "Row #33: \n" + "Row #34: \n" + "Row #34: \n" + "Row #34: \n" + "Row #34: \n" + "Row #34: \n" + "Row #34: \n" + "Row #35: 1,150\n" + "Row #35: 1,090\n" + "Row #35: 1,267\n" + "Row #35: 2,405.06\n" + "Row #35: 2,319.59\n" + "Row #35: 2,673.34\n"), new QueryAndResult( "select from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "565,238.13"), }; public void testConcurrentValidatingQueriesInRandomOrder() { propSaver.set(props.UseAggregates, false); propSaver.set(props.ReadAggregates, false); propSaver.set(props.DisableCaching, false); FoodMartTestCase.QueryAndResult[] singleQuery = {mdxQueries[0]}; assertTrue( ConcurrentValidatingQueryRunner.runTest( 1, 1, false, true, singleQuery) .size() == 0); //ensures same global aggregation is used by 2 or more threads and // all of them load the same segment. FoodMartTestCase.QueryAndResult[] singleQueryFor2Threads = { mdxQueries[1] }; assertTrue( ConcurrentValidatingQueryRunner.runTest( 2, 5, false, true, singleQueryFor2Threads) .size() == 0); assertTrue( ConcurrentValidatingQueryRunner.runTest( 10, 45, true, true, mdxQueries) .size() == 0); } protected void tearDown() throws Exception { super.tearDown(); } protected void setUp() throws Exception { super.setUp(); props = MondrianProperties.instance(); } } // End ConcurrentMdxTest.java mondrian-3.4.1/testsrc/main/mondrian/test/Olap4jTckTest.java0000644000175000017500000000743011735330606023731 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2011 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.olap.Util; import junit.framework.*; import org.olap4j.test.TestContext; import java.util.Properties; /** * Test suite that runs the olap4j Test Compatiblity Kit (TCK) against * mondrian's olap4j driver. * * @author jhyde * @since 2010/11/22 */ public class Olap4jTckTest extends TestCase { private static final Util.Functor1 CONDITION = new Util.Functor1() { public Boolean apply(Test test) { if (!(test instanceof TestCase)) { return true; } final TestCase testCase = (TestCase) test; final String testCaseName = testCase.getName(); return !testCaseName.equals("testStatementTimeout") && !testCaseName.equals("testStatementCancel") && !testCaseName.equals("testDatabaseMetaDataGetCatalogs") && !testCaseName.equals("testCellSetBug"); } }; public static TestSuite suite() { final Util.PropertyList list = mondrian.test.TestContext.instance() .getConnectionProperties(); final String connStr = "jdbc:mondrian:" + list; final String catalog = list.get("Catalog"); final TestSuite suite = new TestSuite(); if (Util.PreJdk15) { // olap4j doesn't run on JDK1.4. (Not without effort.) return suite; } suite.setName("olap4j TCK"); suite.addTest(createMondrianSuite(connStr, false)); suite.addTest(createMondrianSuite(connStr, true)); suite.addTest(createXmlaSuite(connStr, catalog, false)); suite.addTest(createXmlaSuite(connStr, catalog, true)); return suite; } private static TestSuite createXmlaSuite( String connStr, String catalog, boolean wrapper) { final Properties properties = new Properties(); properties.setProperty("org.olap4j.test.connectUrl", connStr); properties.setProperty( "org.olap4j.test.helperClassName", "org.olap4j.XmlaTester"); properties.setProperty("org.olap4j.XmlaTester.CatalogUrl", catalog); properties.setProperty( "org.olap4j.test.wrapper", wrapper ? "NONE" : "DBCP"); String name = "XMLA olap4j driver talking to mondrian's XMLA server"; if (wrapper) { name += " (DBCP wrapper)"; } final TestSuite suite = TestContext.createTckSuite(properties, name); if (CONDITION == null) { return suite; } return mondrian.test.TestContext.copySuite(suite, CONDITION); } private static TestSuite createMondrianSuite( String connStr, boolean wrapper) { final Properties properties = new Properties(); properties.setProperty("org.olap4j.test.connectUrl", connStr); properties.setProperty( "org.olap4j.test.helperClassName", MondrianOlap4jTester.class.getName()); properties.setProperty( "org.olap4j.test.wrapper", wrapper ? "NONE" : "DBCP"); final String name = "mondrian olap4j driver" + (wrapper ? " (DBCP wrapper)" : ""); final TestSuite suite = TestContext.createTckSuite(properties, name); if (CONDITION == null) { return suite; } return mondrian.test.TestContext.copySuite(suite, CONDITION); } } // End Olap4jTckTest.java mondrian-3.4.1/testsrc/main/mondrian/test/MultipleHierarchyTest.java0000644000175000017500000003754411735330606025601 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.olap.MondrianProperties; /** * Tests multiple hierarchies within the same dimension. * * @author jhyde * @since Dec 15, 2005 */ public class MultipleHierarchyTest extends FoodMartTestCase { private static final String timeWeekly = TestContext.hierarchyName("Time", "Weekly"); private static final String timeTime = TestContext.hierarchyName("Time", "Time"); public MultipleHierarchyTest(String name) { super(name); } public void testWeekly() { if (MondrianProperties.instance().SsasCompatibleNaming.get()) { // [Time.Weekly] has an 'all' member, but [Time] does not. assertAxisReturns( "{[Time].[Time].CurrentMember}", "[Time].[1997]"); assertAxisReturns( "{[Time].[Weekly].CurrentMember}", "[Time].[Weekly].[All Weeklys]"); } else { // [Time.Weekly] has an 'all' member, but [Time] does not. assertAxisReturns( "{[Time].CurrentMember}", "[Time].[1997]"); assertAxisReturns( "{[Time.Weekly].CurrentMember}", "[Time].[Weekly].[All Weeklys]"); } } public void testWeekly2() { // When the context is one hierarchy, // the current member of other hierarchy must be its default member. assertQueryReturns( "with\n" + " member [Measures].[Foo] as ' " + timeWeekly + ".CurrentMember.UniqueName '\n" + " member [Measures].[Foo2] as ' " + timeTime + ".CurrentMember.UniqueName '\n" + "select\n" + " {[Measures].[Unit Sales], [Measures].[Foo], [Measures].[Foo2]} on columns,\n" + " {" + timeTime + ".children} on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Foo]}\n" + "{[Measures].[Foo2]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q4]}\n" + "Row #0: 66,291\n" + "Row #0: [Time].[Weekly].[All Weeklys]\n" + "Row #0: [Time].[1997].[Q1]\n" + "Row #1: 62,610\n" + "Row #1: [Time].[Weekly].[All Weeklys]\n" + "Row #1: [Time].[1997].[Q2]\n" + "Row #2: 65,848\n" + "Row #2: [Time].[Weekly].[All Weeklys]\n" + "Row #2: [Time].[1997].[Q3]\n" + "Row #3: 72,024\n" + "Row #3: [Time].[Weekly].[All Weeklys]\n" + "Row #3: [Time].[1997].[Q4]\n"); } public void testMultipleMembersOfSameDimensionInSlicerFails() { assertQueryThrows( "select {[Measures].[Unit Sales]} on columns,\n" + " {[Store].children} on rows\n" + "from [Sales]\n" + "where ([Gender].[M], [Time].[1997], [Time].[1997].[Q1])", "Tuple contains more than one member of hierarchy '[Time]'."); } public void testMembersOfHierarchiesInSameDimensionInSlicer() { assertQueryReturns( "select {[Measures].[Unit Sales]} on columns,\n" + " {[Store].children} on rows\n" + "from [Sales]\n" + "where ([Gender].[M], " + TestContext.hierarchyName("Time", "Weekly") + ".[1997], [Time].[1997].[Q1])", "Axis #0:\n" + "{[Gender].[M], [Time].[Weekly].[1997], [Time].[1997].[Q1]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[Canada]}\n" + "{[Store].[Mexico]}\n" + "{[Store].[USA]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: 33,381\n"); } public void testCalcMember() { assertQueryReturns( "with member [Measures].[Sales to Date] as \n" + " ' Sum(PeriodsToDate([Time].[Year], [Time].[Time].CurrentMember), [Measures].[Unit Sales])'\n" + "select {[Measures].[Sales to Date]} on columns,\n" + " {[Time].[1997].[Q2].[4]," + " [Time].[1997].[Q2].[5]} on rows\n" + "from [Sales]", // msas give 86740, 107551 "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Sales to Date]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q2].[4]}\n" + "{[Time].[1997].[Q2].[5]}\n" + "Row #0: 86,470\n" + "Row #1: 107,551\n"); assertQueryReturns( "with member [Measures].[Sales to Date] as \n" + " ' Sum(PeriodsToDate(" + timeWeekly + ".[Year], " + timeWeekly + ".CurrentMember), [Measures].[Unit Sales])'\n" + "select {[Measures].[Sales to Date]} on columns,\n" + " {" + timeWeekly + ".[1997].[14] : " + timeWeekly + ".[1997].[16]} on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Sales to Date]}\n" + "Axis #2:\n" + "{[Time].[Weekly].[1997].[14]}\n" + "{[Time].[Weekly].[1997].[15]}\n" + "{[Time].[Weekly].[1997].[16]}\n" + "Row #0: 81,670\n" + "Row #1: 86,300\n" + "Row #2: 90,139\n"); } /** * Tests * bug MONDRIAN-191, "Properties not working with multiple hierarchies". */ public void testProperty() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + "\n" + "

\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + ""); final String nuStore = TestContext.hierarchyName("NuStore", "NuStore"); testContext.assertQueryReturns( "with member [Measures].[Store level] as '" + nuStore + ".CurrentMember.Level.Name'\n" + "member [Measures].[Store type] as 'IIf((" + nuStore + ".CurrentMember.Level.Name = \"NuStore Name\"), CAST(" + nuStore + ".CurrentMember.Properties(\"NuStore Type\") AS STRING), \"No type\")'\n" + "member [Measures].[Store Sqft] as 'IIf((" + nuStore + ".CurrentMember.Level.Name = \"NuStore Name\"), CAST(" + nuStore + ".CurrentMember.Properties(\"NuStore Sqft\") AS INTEGER), 0.0)'\n" + "select {" + "[Measures].[Unit Sales], " + "[Measures].[Store Cost], " + "[Measures].[Store Sales], " + "[Measures].[Store level], " + "[Measures].[Store type], " + "[Measures].[Store Sqft]" + "} ON COLUMNS,\n" + "{" + nuStore + ".[All NuStores], " + nuStore + ".[Canada], " + nuStore + ".[Canada].[BC], " + nuStore + ".[Canada].[BC].[Vancouver], " + nuStore + ".[Canada].[BC].[Vancouver].[Store 19], " + nuStore + ".[Canada].[BC].[Victoria], " + nuStore + ".[Mexico], " + nuStore + ".[USA]" + "} ON ROWS\n" + "from [Sales]\n" + "where [Time].[1997] ", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Store level]}\n" + "{[Measures].[Store type]}\n" + "{[Measures].[Store Sqft]}\n" + "Axis #2:\n" + "{[NuStore].[All NuStores]}\n" + "{[NuStore].[Canada]}\n" + "{[NuStore].[Canada].[BC]}\n" + "{[NuStore].[Canada].[BC].[Vancouver]}\n" + "{[NuStore].[Canada].[BC].[Vancouver].[Store 19]}\n" + "{[NuStore].[Canada].[BC].[Victoria]}\n" + "{[NuStore].[Mexico]}\n" + "{[NuStore].[USA]}\n" + "Row #0: 266,773\n" + "Row #0: 225,627.23\n" + "Row #0: 565,238.13\n" + "Row #0: (All)\n" + "Row #0: No type\n" + "Row #0: 0\n" + "Row #1: \n" + "Row #1: \n" + "Row #1: \n" + "Row #1: NuStore Country\n" + "Row #1: No type\n" + "Row #1: 0\n" + "Row #2: \n" + "Row #2: \n" + "Row #2: \n" + "Row #2: NuStore State\n" + "Row #2: No type\n" + "Row #2: 0\n" + "Row #3: \n" + "Row #3: \n" + "Row #3: \n" + "Row #3: NuStore City\n" + "Row #3: No type\n" + "Row #3: 0\n" + "Row #4: \n" + "Row #4: \n" + "Row #4: \n" + "Row #4: NuStore Name\n" + "Row #4: Deluxe Supermarket\n" + "Row #4: 23,112\n" + "Row #5: \n" + "Row #5: \n" + "Row #5: \n" + "Row #5: NuStore City\n" + "Row #5: No type\n" + "Row #5: 0\n" + "Row #6: \n" + "Row #6: \n" + "Row #6: \n" + "Row #6: NuStore Country\n" + "Row #6: No type\n" + "Row #6: 0\n" + "Row #7: 266,773\n" + "Row #7: 225,627.23\n" + "Row #7: 565,238.13\n" + "Row #7: NuStore Country\n" + "Row #7: No type\n" + "Row #7: 0\n"); } /** * Tests that mondrian detects an ambiguous hierarchy in a calculated member * at compile time. (SSAS detects at run time, and generates a cell error, * but this is better.) */ public void testAmbiguousHierarchyInCalcMember() { final String query = "with member [Measures].[Time Child Count] as\n" + " [Time].Children.Count\n" + "select [Measures].[Time Child Count] on 0\n" + "from [Sales]"; if (MondrianProperties.instance().SsasCompatibleNaming.get()) { assertQueryThrows( query, "The 'Time' dimension contains more than one hierarchy, " + "therefore the hierarchy must be explicitly specified."); } else { assertQueryReturns( query, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Time Child Count]}\n" + "Row #0: 4\n"); } } /** * Tests * bug MONDRIAN-750, "... multiple hierarchies beneath a single dimension * throws exception". */ public void testDefaultNamedHierarchy() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "
\n" + " \n" + " \n" + "\n" + ""); final String nuStore = TestContext.hierarchyName("NuStore", "NuStore"); testContext.assertQueryReturns( "with set [*NATIVE_CJ_SET] as '[*BASE_MEMBERS_NuStore]' " + "set [*SORTED_ROW_AXIS] as 'Order([*CJ_ROW_AXIS], " + nuStore + ".CurrentMember.OrderKey, BASC)' " + "set [*BASE_MEMBERS_NuStore] as '" + nuStore + ".[NuStore Country].Members' " + "set [*BASE_MEMBERS_Measures] as '{[Measures].[*ZERO]}' " + "set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {" + nuStore + ".CurrentMember})' " + "set [*CJ_COL_AXIS] as '[*NATIVE_CJ_SET]' " + "member [Measures].[*ZERO] as '0.0', SOLVE_ORDER = 0.0 " + "select [*BASE_MEMBERS_Measures] ON COLUMNS, " + "[*SORTED_ROW_AXIS] ON ROWS " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*ZERO]}\n" + "Axis #2:\n" + "{[NuStore].[Canada]}\n" + "{[NuStore].[Mexico]}\n" + "{[NuStore].[USA]}\n" + "Row #0: 0\n" + "Row #1: 0\n" + "Row #2: 0\n"); } } // End MultipleHierarchyTest.java mondrian-3.4.1/testsrc/main/mondrian/test/Ssas2005CompatibilityTest.java0000644000175000017500000017634511735330606026124 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2008-2011 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; import mondrian.util.Bug; import java.sql.SQLException; /** * Unit tests that check compatibility with Microsoft SQL Server Analysis * Services 2005. * *

This suite contains a MDX collection of queries that were run on SSAS. The * queries cover a variety of issues, including multiple hierarchies in a * dimension, attribute hierarchies, and name resolution. Expect to find tests * for these areas in dedicated tests also. * *

There are tests for features which are unimplemented or where mondrian's * behavior differs from SSAS2005. These tests will appear in this file * disabled or with (clearly marked) incorrect results. * * @author jhyde * @since December 15, 2008 */ public class Ssas2005CompatibilityTest extends FoodMartTestCase { /** * Whether member naming rules are implemented. */ private static final boolean MEMBER_NAMING_IMPL = false; /** * Whether attribute hierarchies are implemented. */ public static final boolean ATTR_HIER_IMPL = false; /** * Whether the AXIS function has been are implemented. */ public static final boolean AXIS_IMPL = false; /** * Keys as part of member names. */ public static final boolean KEY_IMPL = Bug.BugMondrian485Fixed; /** * Catch-all for tests that depend on something that hasn't been * implemented. */ private static final boolean IMPLEMENTED = false; /** * Creates a Ssas2005CompatibilityTest. * * @param name Testcase name */ public Ssas2005CompatibilityTest(String name) { super(name); } private void runQ(String s) { Result result = getTestContext().executeQuery(s); Util.discard(TestContext.toString(result)); } @Override public TestContext getTestContext() { // Key features: // 1. Dimension [Product] has hierarchies [Products] and at least one // other. // 2. Dimension [Currency] has one unnamed hierarchy // 3. Dimnsion [Time] has hierarchies [Time2] and [Time by Week] // (intentionally named hierarchy differently from dimension) return TestContext.instance().withSchema( "\n" + "\n" + "

\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " " /* + " \n" + " \n" + "
\n" + " \n" + " \n" + " " */ + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" /* + " \n" + "
\n" + " \n" + " \n" */ + " " + " \n" + "\n" + "").withCube("Warehouse and Sales"); } public void testUniqueName() { // TODO: // Unique mmbers: // [Time].[Time2].[Year2].[1997] // Non unique: // [Time].[Time2].[Quarter].&[Q1]&[1997] // All: // [Time].[Time2].[All] // Unique id: // [Currency].[Currency].&[1] } public void testDimensionDotHierarchyAmbiguous() { // If there is a dimension, hierarchy, level with the same name X, // then [X].[X] might reasonably resolve to hierarchy or the level. // SSAS resolves to hierarchy, old mondrian resolves to level. if (MondrianProperties.instance().SsasCompatibleNaming.get()) { // SSAS gives error with the .Ordinal function: // The ORDINAL function expects a level expression for // the argument. A hierarchy expression was used. getTestContext().assertExprThrows( "[Currency].[Currency].Ordinal", "No function matches signature '.Ordinal'"); // SSAS succeeds with the '.Levels()' // function, returns 2 getTestContext().assertExprReturns( "[Currency].[Currency].Levels(0).Name", "(All)"); // There are 4 hierarchy members (including 'Any currency') getTestContext().assertExprReturns( "[Currency].[Currency].Members.Count", "15"); // There are 3 level members getTestContext().assertExprReturns( "[Currency].[Currency].[Currency].Members.Count", "14"); } else { // Old mondrian behavior prefers level. getTestContext().assertExprReturns( "[Currency].[Currency].Ordinal", "1"); // In old mondrian, [Currency].[Currency] resolves to a level, // then gets implicitly converted to a hierarchy. getTestContext().assertExprReturns( "[Currency].[Currency].Levels(0).Name", "(All)"); // Returns the level "[Currency].[Currency]"; the hierarchy would be // "[Currency]" getTestContext().assertExprReturns( "[Currency].[Currency].UniqueName", "[Currency].[Currency]"); // In old mondrian, [Currency].[Currency] resolves to level. There // are 14 hierarchy members (which do not include 'Any currency') getTestContext().assertExprReturns( "[Currency].[Currency].Members.Count", "14"); // Fails to parse 3 levels getTestContext().assertExprThrows( "[Currency].[Currency].[Currency].Members.Count", "MDX object '[Currency].[Currency].[Currency]' not found in cube 'Warehouse and Sales'"); } } public void testHierarchyLevelsFunction() { if (!IMPLEMENTED) { return; } // The .Levels function is not implemented in mondrian; // only .Levels() // and .Levels() // SSAS returns 7. getTestContext().assertExprReturns( "[Product].[Products].Levels.Count", "7"); } public void testDimensionDotHierarchyDotLevelDotMembers() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // [dimension].[hierarchy].[level] is valid on dimension with multiple // hierarchies; // SSAS2005 succeeds runQ( "select [Time].[Time by Week].[Week].MEMBERS on 0\n" + "from [Warehouse and Sales]"); } public void testDimensionDotHierarchyDotLevel() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // [dimension].[hierarchy].[level] is valid on dimension with single // hierarchy // SSAS2005 succeeds assertQueryReturns( "select [Store].[Stores].[Store State].MEMBERS on 0\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[Stores].[Canada].[BC]}\n" + "{[Store].[Stores].[Mexico].[DF]}\n" + "{[Store].[Stores].[Mexico].[Guerrero]}\n" + "{[Store].[Stores].[Mexico].[Jalisco]}\n" + "{[Store].[Stores].[Mexico].[Veracruz]}\n" + "{[Store].[Stores].[Mexico].[Yucatan]}\n" + "{[Store].[Stores].[Mexico].[Zacatecas]}\n" + "{[Store].[Stores].[USA].[CA]}\n" + "{[Store].[Stores].[USA].[OR]}\n" + "{[Store].[Stores].[USA].[WA]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 74,748\n" + "Row #0: 67,659\n" + "Row #0: 124,366\n"); } public void testNamingDimensionDotLevel() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // [dimension].[level] is valid if level name is unique within all // hierarchies. (Note that [Week] is a level in hierarchy // [Time].[Time by Week]; here is no attribute [Time].[Week].) // SSAS2005 succeeds runQ( "select [Time].[Week].MEMBERS on 0\n" + "from [Warehouse and Sales]"); // [dimension].[level] is valid if level name is unique within all // hierarchies. (Note that [Week] is a level in hierarchy // [Time].[Time by Week]; here is no attribute [Time].[Week].) // SSAS returns "[Time].[Time By Week].[Year2]". assertQueryReturns( "with member [Measures].[Foo] as ' [Time].[Year2].UniqueName '\n" + "select [Measures].[Foo] on 0\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: [Time].[Time By Week].[Year2]\n"); } public void testNamingDimensionDotLevel2() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // Date2 is a level that occurs in only 1 hierarchy // There is no attribute called Date2 runQ( "select [Time].[Date2].MEMBERS on 0 from [Warehouse and Sales]"); // SSAS returns [Time].[Time By Week].[Date2] runQ( "with member [Measures].[Foo] as ' [Time].[Date2].UniqueName '\n" + "select [Measures].[Foo] on 0\n" + "from [Warehouse and Sales]"); } public void testNamingDimensionDotLevelNotUnique() { if (!IMPLEMENTED) { return; } // Year2 is a level that occurs in only 2 hierarchies: // [Time].[Time2].[Year2] and [Time].[Time By Week].[Year2]. // There is no attribute called Year2 runQ( "select [Time].[Year2].MEMBERS on 0 from [Warehouse and Sales]"); // SSAS2005 returns [Time].[Time By Week].[Year2] // (Presumably because it comes first.) runQ( "with member [Measures].[Foo] as ' [Time].[Year2].UniqueName '\n" + "select [Measures].[Foo] on 0\n" + "from [Warehouse and Sales]"); } public void testDimensionMembersOnSingleHierarchyDimension() { // [dimension].members for a dimension with one hierarchy // (and no attributes) // SSAS2005 succeeds assertQueryReturns( "select [Currency].Members on 0\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Currency].[All Currencys]}\n" + "{[Currency].[Bulk Mail]}\n" + "{[Currency].[Cash Register Handout]}\n" + "{[Currency].[Daily Paper]}\n" + "{[Currency].[Daily Paper, Radio]}\n" + "{[Currency].[Daily Paper, Radio, TV]}\n" + "{[Currency].[In-Store Coupon]}\n" + "{[Currency].[No Media]}\n" + "{[Currency].[Product Attachment]}\n" + "{[Currency].[Radio]}\n" + "{[Currency].[Street Handout]}\n" + "{[Currency].[Sunday Paper]}\n" + "{[Currency].[Sunday Paper, Radio]}\n" + "{[Currency].[Sunday Paper, Radio, TV]}\n" + "{[Currency].[TV]}\n" + "Row #0: 266,773\n" + "Row #0: 4,320\n" + "Row #0: 6,697\n" + "Row #0: 7,738\n" + "Row #0: 6,891\n" + "Row #0: 9,513\n" + "Row #0: 3,798\n" + "Row #0: 195,448\n" + "Row #0: 7,544\n" + "Row #0: 2,454\n" + "Row #0: 5,753\n" + "Row #0: 4,339\n" + "Row #0: 5,945\n" + "Row #0: 2,726\n" + "Row #0: 3,607\n"); } public void testMultipleHierarchyRequiresQualification() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // [dimension].members for a dimension with one hierarchy // (and some attributes) // SSAS2005 gives error: // Query (1, 8) The 'Product' dimension contains more than // one hierarchy, therefore the hierarchy must be explicitly // specified. assertQueryThrows( "select [Product].Members on 0\n" + "from [Warehouse and Sales]", "The 'Product' dimension contains more than one hierarchy, " + "therefore the hierarchy must be explicitly specified."); } /** * Tests that it is an error to define a calc member in a dimension * with multiple hierarchies without specifying hierarchy. * Based on {@link mondrian.test.BasicQueryTest#testHalfYears()}. */ public void testCalcMemberAmbiguousHierarchy() { String mdx = "WITH MEMBER [Measures].[ProfitPercent] AS\n" + " '([Measures].[Store Sales]-[Measures].[Store Cost])/" + "([Measures].[Store Cost])',\n" + " FORMAT_STRING = '#.00%', SOLVE_ORDER = 1\n" + " MEMBER [Time].[First Half 97] AS '[Time].[1997].[Q1] + " + "[Time].[1997].[Q2]'\n" + " MEMBER [Time].[Second Half 97] AS '[Time].[1997].[Q3] + " + "[Time].[1997].[Q4]'\n" + " SELECT {[Time].[First Half 97],\n" + " [Time].[Second Half 97],\n" + " [Time].[1997].CHILDREN} ON COLUMNS,\n" + " {[Store].[Store Country].[USA].CHILDREN} ON ROWS\n" + " FROM [Sales]\n" + " WHERE ([Measures].[ProfitPercent])"; if (MondrianProperties.instance().SsasCompatibleNaming.get()) { TestContext.instance().assertQueryThrows( mdx, "Hierarchy for calculated member '[Time].[First Half 97]' not found"); } else { TestContext.instance().assertQueryReturns( mdx, "Axis #0:\n" + "{[Measures].[ProfitPercent]}\n" + "Axis #1:\n" + "{[Time].[First Half 97]}\n" + "{[Time].[Second Half 97]}\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "{[Time].[1997].[Q3]}\n" + "{[Time].[1997].[Q4]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 150.55%\n" + "Row #0: 150.53%\n" + "Row #0: 150.68%\n" + "Row #0: 150.44%\n" + "Row #0: 151.35%\n" + "Row #0: 149.81%\n" + "Row #1: 150.15%\n" + "Row #1: 151.08%\n" + "Row #1: 149.80%\n" + "Row #1: 150.60%\n" + "Row #1: 151.37%\n" + "Row #1: 150.78%\n" + "Row #2: 150.59%\n" + "Row #2: 150.34%\n" + "Row #2: 150.72%\n" + "Row #2: 150.45%\n" + "Row #2: 150.39%\n" + "Row #2: 150.29%\n"); } } // TODO: public void testUnqualifiedHierarchy() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // [hierarchy].members for a dimension with one hierarchy // (and some attributes) // SSAS2005 succeeds // Note that 'Product' is the dimension, 'Products' is the hierarchy runQ( "select [Products].Members on 0\n" + "from [Warehouse and Sales]"); runQ( "select {[Products]} on 0\n" + "from [Warehouse and Sales]"); // TODO: run this in SSAS // [Measures] is both a dimension and a hierarchy; // [Products] is just a hierarchy. // SSAS returns 557863 runQ( "select [Measures].[Unit Sales] on 0,\n" + " [Products].[Food] on 1\n" + "from [Warehouse and Sales]"); } /** * Tests that time functions such as Ytd behave correctly when there are * multiple time hierarchies. */ public void testYtd() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // We use 'Generate' to establish context for Ytd without passing it // an explicit argument. // SSAS returns [Q1], [Q2], [Q3]. assertQueryReturns( "select Generate(\n" + " {[Time].[Time2].[1997].[Q3]},\n" + " {Ytd()}) on 0,\n" + " [Products].Children on 1\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "Axis #2:\n" + "{[Product].[Products].[Drink]}\n" + "{[Product].[Products].[Food]}\n" + "{[Product].[Products].[Non-Consumable]}\n"); } public void testAxesOutOfOrder() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // TODO: run this in SSAS // Ssas2000 disallowed out-of-order axes. Don't know about Ssas2005. assertQueryReturns( "select [Measures].[Unit Sales] on 1,\n" + "[Products].Children on 0\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Products].[Drink]}\n" + "{[Product].[Products].[Food]}\n" + "{[Product].[Products].[Non-Consumable]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 24,597\n" + "Row #0: 191,940\n" + "Row #0: 50,236\n"); } public void testDimensionMembersRequiresHierarchyQualification() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // [dimension].members for a dimension with multiple hierarchies // SSAS2005 gives error: // Query (1, 8) The 'Time' dimension contains more than one // hierarchy, therefore the hierarchy must be explicitly // specified. assertQueryThrows( "select [Time].Members on 0\n" + "from [Warehouse and Sales]", "The 'Time' dimension contains more than one hierarchy, therefore the hierarchy must be explicitly specified."); } public void testDimensionMemberRequiresHierarchyQualification() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // [dimension].CurrentMember // SSAS2005 gives error: // Query (1, 8) The 'Product' dimension contains more than // one hierarchy, therefore the hierarchy must be explicitly // specified. final String[] exprs = { "[Product].CurrentMember", // TODO: Verify that this does indeed fail on SSAS "[Product].DefaultMember", // TODO: Verify that this does indeed fail on SSAS "[Product].AllMembers", "Dimensions(3).CurrentMember", "Dimensions(3).DefaultMember", "Dimensions(3).AllMembers", }; final String expectedException = "The 'Product' dimension contains more than one hierarchy, " + "therefore the hierarchy must be explicitly specified."; assertQueryThrows( "select [Product].CurrentMember on 0\n" + "from [Warehouse and Sales]", expectedException); assertQueryThrows( "select [Product].DefaultMember on 0\n" + "from [Warehouse and Sales]", expectedException); assertQueryThrows( "select [Product].AllMembers on 0\n" + "from [Warehouse and Sales]", expectedException); // The following are OK because Dimensions() returns a hierarchy. final String expectedResult = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[Time2].[1997]}\n" + "Row #0: 266,773\n"; assertQueryReturns( "select Dimensions(3).CurrentMember on 0\n" + "from [Warehouse and Sales]", expectedResult); assertQueryReturns( "select Dimensions(3).DefaultMember on 0\n" + "from [Warehouse and Sales]", expectedResult); assertQueryReturns( "select Head(Dimensions(7).AllMembers, 3) on 0\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Currency].[All Currencys]}\n" + "{[Currency].[Bulk Mail]}\n" + "{[Currency].[Cash Register Handout]}\n" + "Row #0: 266,773\n" + "Row #0: 4,320\n" + "Row #0: 6,697\n"); } public void testImplicitCurrentMemberRequiresHierarchyQualification() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // a function that causes an implicit call to CurrentMember // SSAS2005 gives error: // Query (1, 8) The 'Product' dimension contains more than // one hierarchy, therefore the hierarchy must be explicitly // specified. assertQueryThrows( "select Ascendants([Product]) on 0\n" + "from [Warehouse and Sales]", "The 'Product' dimension contains more than one hierarchy, therefore the hierarchy must be explicitly specified."); // Works for [Store], which has only one hierarchy. // TODO: check SSAS assertQueryReturns( "select Ascendants([Store]) on 0\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[Stores].[All Storess]}\n" + "Row #0: 266,773\n"); } public void testUnqualifiedHierarchyCurrentMember() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // [hierarchy].CurrentMember // SSAS2005 succeeds runQ( "select [Products].CurrentMember on 0\n" + "from [Warehouse and Sales]"); } public void testCannotDistinguishMdxFromSql() { // Cannot tell whether statement is MDX or SQL // SSAS2005 gives error: // Parser: The statement dialect could not be resolved due // to ambiguity. assertQueryThrows( "select [Time].Members\n" + "from [Warehouse and Sales]", "Syntax error at line 2, column 1, token 'from'"); } public void testNamingDimensionAttr() { if (!ATTR_HIER_IMPL) { return; } // [dimension].[attribute] succeeds // (There is no level called [Store Manager]) runQ( "select [Store].[Store Manager].Members on 0 from [Warehouse and Sales]"); } public void testNamingDimensionAttrVsLevel() { if (!ATTR_HIER_IMPL) { return; } // [dimension].[attribute] // (There is a level called [Store City], but the attribute is chosen in // preference.) // SSAS2005 succeeds runQ( "select [Store].[Store City].Members on 0\n" + "from [Warehouse and Sales]"); } public void testAttrHierarchyMemberParent() { if (!ATTR_HIER_IMPL) { return; } // parent of member of attribute hierarchy // SSAS2005 returns "[Store].[Store City].[All]" runQ( "with member [Measures].[Foo] as ' [Store].[Store City].[San Francisco].Parent.UniqueName '\n" + "select [Measures].[Foo] on 0\n" + "from [Warehouse and Sales]"); } public void testAttrHierarchyMemberChildren() { if (!ATTR_HIER_IMPL) { return; } // children of member of attribute hierarchy // SSAS2005 returns empty set runQ( "select [Store].[Store City].[San Francisco].Children on 0\n" + "from [Warehouse and Sales]"); } public void testAttrHierarchyAllMemberChildren() { if (!ATTR_HIER_IMPL) { return; } // children of all member of attribute hierarchy // SSAS2005 succeeds runQ( "select [Store].[Store City].Children on 0\n" + "from [Warehouse and Sales]"); } public void testAttrHierarchyMemberLevel() { if (!ATTR_HIER_IMPL) { return; } // level of member of attribute hierarchy // SSAS2005 returns "[Store].[Store City].[Store City]" runQ( "with member [Measures].[Foo] as [Store].[Store City].[San Francisco].Level.UniqueName\n" + "select [Measures].[Foo] on 0\n" + "from [Warehouse and Sales]"); } public void testAttrHierarchyUniqueName() { if (!ATTR_HIER_IMPL) { return; } // Returns [Store].[Store City] runQ( "with member [Measures].[Foo] as [Store].[Store City].UniqueName\n" + "select [Measures].[Foo] on 0\n" + "from [Warehouse and Sales]"); } public void testMemberAddressedByLevelAndKey() { if (!MEMBER_NAMING_IMPL) { return; } // [dimension].[hierarchy].[level].&[key] // (Returns 31, 5368) runQ( "select {[Time].[Time By Week].[Week].[31]} on 0\n" + "from [Warehouse and Sales]"); } public void testMemberAddressedByCompoundKey() { if (!MEMBER_NAMING_IMPL) { return; } // compound key // SSAS2005 returns 1 row runQ( "select [Time].[Time By Week].[Year2].[1998].&[30]&[1998] on 0\n" + "from [Warehouse and Sales]"); } public void testMemberAddressedByPartialCompoundKey() { if (!MEMBER_NAMING_IMPL) { return; } // compound key, partially specified // SSAS2005 returns 0 rows but no error runQ( "select [Time].[Time By Week].[Year2].[1998].&[30] on 0\n" + "from [Warehouse and Sales]"); } public void testMemberAddressedByNonUniqueName() { if (!MEMBER_NAMING_IMPL) { return; } // address member by non-unique name // [dimension].[hierarchy].[level].[name] // SSAS2005 returns first member that matches, 1997.January runQ( "select [Time].[Time2].[Month].[January] on 0\n" + "from [Warehouse and Sales]"); } public void testMemberAddressedByLevelAndCompoundKey() { if (!MEMBER_NAMING_IMPL) { return; } // SSAS2005 returns [Time].[Time2].[Month].&[1]&[1997] runQ( "with member [Measures].[Foo] as ' [Time].[Time2].[Month].[January].UniqueName '\n" + "select [Measures].[Foo] on 0\n" + "from [Warehouse and Sales]"); } public void testMemberAddressedByLevelAndName() { if (!MEMBER_NAMING_IMPL) { return; } // similarly // [dimension].[level].[member name] runQ( "with member [Measures].[Foo] as ' [Store].[Store City].[Month].[January].UniqueName '\n" + "select [Measures].[Foo] on 0\n" + "from [Warehouse and Sales]"); } public void testFoo31() { // [dimension].[member name] // returns [Product].[Products].[Product Department].[Dairy] // note that there are members // [Product].[Drink].[Dairy] // [Product].[Drink].[Dairy].[Dairy] // [Product].[Food].[Dairy] // [Product].[Food].[Dairy].[Dairy] runQ( "select Measures on 0,[Product].[Product Department].Members on 1\n" + "from [Warehouse and Sales]"); } public void testFoo32() { if (!IMPLEMENTED) { return; } // returns [Product].[Products].[Product Department].[Dairy] // In my opinion this is weird unique name, because there is a // Food.Dairy and a Drink.Dairy. But behavior is consistent with // returning first member that matches. runQ( "with member [Measures].[U] as ' [Product].UniqueName '\n" + " member [Measures].[PU] as ' [Product].Parent.UniqueName '\n" + "select {[Measures].[U], [Measures].[PU]} on 0,\n" + " [Product].[Dairy] on 1\n" + "from [Warehouse and Sales]"); } public void testNamingAttrVsLevel() { if (!ATTR_HIER_IMPL) { return; } // [attribute] vs. [level] // SSAS2005 succeeds runQ( "select [Store City].Members on 0\n" + "from [Warehouse and Sales]"); // the attribute hierarchy wins over the level // SSAS2005 returns [Store].[Store City] assertQueryReturns( "with member [Measures].[Foo] as [Store City].UniqueName\n" + "select [Measures].[Foo] on 0\n" + "from [Warehouse and Sales]", "xxxxx"); } public void testUnqualifiedLevel() { if (!IMPLEMENTED) { return; } // [level] // SSAS2005 succeeds runQ( "select [Week].Members on 0\n" + "from [Warehouse and Sales]"); } public void testDimensionAsScalarExpression() { if (!IMPLEMENTED) { return; } // Dimension used as scalar expression fails. // SSAS2005 gives error: // The function expects a string or numeric expression for // the argument. A level expression was used. runQ( "with member [Measures].[Foo] as [Date2]\n" + "select [Measures].[Foo] on 0\n" + "from [Warehouse and Sales]"); } public void testDimensionWithMultipleHierarchiesDotParent() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // [Dimension].Parent // SSAS2005 returns error: // The 'Product' dimension contains more than one hierarchy, // therefore the hierarchy must be explicitly specified. assertExprThrows( "[Time].Parent.UniqueName", "The 'Time' dimension contains more than one hierarchy, " + "therefore the hierarchy must be explicitly specified."); } public void testDimensionDotHierarchyInBrackets() { // [dimension.hierarchy] is valid // SSAS2005 succeeds runQ( "select {[Time.Time By Week].Members} on 0\n" + "from [Warehouse and Sales]"); } /** * Test case for bug 2688790, "Hierarchy Naming Compatibility issue". * Occurs when dimension and hierarchy have the same name and are used with * [name.name]. */ public void testDimensionDotHierarchySameNameInBrackets() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "" + " " + "
" + " " + " " + "", null); testContext.assertQueryReturns( "select [Store Type 2.Store Type 2].[Store Type].members ON columns " + "from [Sales] where [Time].[1997]", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Store Type 2].[Deluxe Supermarket]}\n" + "{[Store Type 2].[Gourmet Supermarket]}\n" + "{[Store Type 2].[HeadQuarters]}\n" + "{[Store Type 2].[Mid-Size Grocery]}\n" + "{[Store Type 2].[Small Grocery]}\n" + "{[Store Type 2].[Supermarket]}\n" + "Row #0: 76,837\n" + "Row #0: 21,333\n" + "Row #0: \n" + "Row #0: 11,491\n" + "Row #0: 6,557\n" + "Row #0: 150,555\n"); } public void testDimensionDotLevelDotHierarchyInBrackets() { // [dimension.hierarchy.level] // SSAS2005 gives error: // Query (1, 8) The dimension '[Time.Time2.Quarter]' was not // found in the cube when the string, [Time.Time2.Quarter], // was parsed. assertQueryThrows( "select [Time.Time2.Quarter].Members on 0\n" + "from [Warehouse and Sales]", "MDX object '[Time.Time2.Quarter]' not found in cube 'Warehouse and Sales'"); } public void testDimensionDotInvalidHierarchyInBrackets() { // invalid hierarchy name // SSAS2005 gives error: // Query (1, 9) The dimension '[Time.Time By Week55]' was not // found in the cube when the string, [Time.Time By Week55], // was parsed. assertQueryThrows( "select {[Time.Time By Week55].Members} on 0\n" + "from [Warehouse and Sales]", "MDX object '[Time.Time By Week55]' not found in cube 'Warehouse and Sales'"); } public void testDimensionDotDimensionInBrackets() { // [dimension.dimension] is invalid. SSAS2005 gives similar // error to above. (The Time dimension has hierarchies called // [Time2] and [Time By Day]. but no hierarchy [Time].) assertQueryThrows( "select {[Time.Time].Members} on 0\n" + "from [Warehouse and Sales]", "MDX object '[Time.Time]' not found in cube 'Warehouse and Sales'"); } public void testDimensionDotHierarchyDotNonExistentLevel() { if (!IMPLEMENTED) { return; } // Non-existent level of hierarchy. // SSAS2005 gives error: // Query (1, 8) The MEMBERS function expects a hierarchy // expression for the argument. A member expression was used. // // Mondrian currently gives // MDX object '[Time].[Time By Week].[Month]' not found in // cube 'Warehouse and Sales' // which is not good enough. runQ( "select [Time].[Time By Week].[Month].Members on 0\n" + "from [Warehouse and Sales]"); } public void testDimensionDotHierarchyDotLevelMembers() { if (!IMPLEMENTED) { return; } // SSAS2005 returns 8 quarters. runQ( "select [Time].[Time2].[Quarter].Members on 0\n" + "from [Warehouse and Sales]"); } public void testDupHierarchyOnAxes() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // same hierarchy on both axes // SSAS2005 gives error: // The Products hierarchy already appears in the Axis0 axis. // SSAS query: // select [Products] on 0, // [Products] on 1 // from [Warehouse and Sales] assertQueryThrows( "select {[Products]} on 0,\n" + " {[Products]} on 1\n" + "from [Warehouse and Sales]", "Hierarchy '[Product].[Products]' appears in more than one independent axis."); } public void testDimensionOnAxis() { if (!IMPLEMENTED) { return; } // Dimension is implicitly converted to member // so is OK on axis. runQ("select [Product] on 0 from [Warehouse and Sales]"); } public void testDimensionDotHierarchyOnAxis() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // Dimension is implicitly converted to member // so is OK on axis. runQ( "select [Product].[Products] on 0,\n" + "[Customer].[Customer] on 1\n" + "from [Warehouse and Sales]"); } public void testHierarchiesFromSameDimensionOnAxes() { if (!IMPLEMENTED) { return; } // different hierarchies from same dimension // SSAS2005 succeeds runQ( "select [Time].[Time2] on 0,\n" + " [Time].[Time By Week] on 1\n" + "from [Warehouse and Sales]"); } // TODO: public void testDifferentHierarchiesFromSameDimensionOnAxes() { if (!IMPLEMENTED) { return; } // different hierarchies from same dimension // SSAS2005 succeeds // Note that [Time].[1997] resolves to [Time].[Time2].[1997] runQ( "select [Time].[Time2] on 0,\n" + " [Time].[Time By Week] on 1\n" + "from [Warehouse and Sales]\n" + "where [Time].[1997]"); } // TODO: public void testDifferentHierarchiesFromSameDimensionInCrossjoin() { if (!IMPLEMENTED) { return; } // crossjoin different hierarchies from same dimension // SSAS2005 succeeds runQ( "select Crossjoin([Time].[Time By Week].Children, [Time].[Time2].Members) on 0\n" + "from [Warehouse and Sales]"); } public void testHierarchyUsedTwiceInCrossjoin() { if (!IMPLEMENTED) { return; } // SSAS2005 gives error: // Query (2, 4) The Time By Week hierarchy is used more than // once in the Crossjoin function. runQ( "select \n" + " [Time].[Time By Week].Children\n" + " * [Time].[Time2].Children\n" + " * [Time].[Time By Week].Children on 0\n" + "from [Warehouse and Sales]"); } public void testAttributeHierarchyUsedTwiceInCrossjoin() { if (!ATTR_HIER_IMPL) { return; } // Attribute hierarchy used more than once in Crossjoin. // SSAS2005 gives error: // Query (2, 4) The SKU hierarchy is used more than once in // the Crossjoin function. runQ( "select \n" + " [Product].[SKU].Children\n" + " * [Product].[Products].Members\n" + " * [Time].[Time By Week].Children\n" + " * [Product].[SKU].Members on 0\n" + "from [Warehouse and Sales]"); } public void testFoo50() { if (!ATTR_HIER_IMPL) { return; } // Mixing attributes in a set // SSAS2005 gives error: // Members belong to different hierarchies in the function. runQ( "select {[Store].[Store Country].[USA], [Store].[Stores].[Store Country].[USA]} on 0\n" + "from [Warehouse and Sales]"); } public void testQuoteInStringInQuotedFormula() { // Quoted formulas vs. unquoted formulas // Single quote in string // SSAS2005 returns 5 assertQueryReturns( "with member [Measures].[Foo] as ' len(\"can''t\") '\n" + "select [Measures].[Foo] on 0\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: 5\n"); } public void testQuoteInStringInUnquotedFormula() { // SSAS2005 returns 6 assertQueryReturns( "with member [Measures].[Foo] as len(\"can''t\")\n" + "select [Measures].[Foo] on 0\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: 6\n"); } public void testMemberIdentifiedByDimensionAndKey() { if (!KEY_IMPL) { return; } // member identified by dimension, key // works on SSAS // gives {[Washington Berry Juice], 231} runQ( "select [Measures].[Unit Sales] on 0,\n" + "[Product].[Products].&[1] on 1\n" + "from [Warehouse and Sales]"); } public void testDimensionHierarchyKey() { if (!KEY_IMPL) { return; } // member identified by dimension, hierarchy, key // works on SSAS // gives {[Washington Berry Juice], 231} runQ( "select [Measures].[Unit Sales] on 0,\n" + "[Product].[Products].&[1] on 1\n" + "from [Warehouse and Sales]"); } public void testCompoundKey() { if (!KEY_IMPL) { return; } // compound key // succeeds on SSAS runQ( "select [Measures].[Unit Sales] on 0,\n" + "[Product].[Products].[Brand].&[43]&[Walrus] on 1\n" + "from [Warehouse and Sales]"); } public void testCompoundKeySyntaxError() { if (!KEY_IMPL) { return; } // without [] fails on SSAS (syntax error because a number) runQ( "select [Measures].[Unit Sales] on 0,\n" + "[Product].[Products].[Brand].&43&[Walrus] on 1\n" + "from [Warehouse and Sales]"); } public void testCompoundKeyString() { if (!KEY_IMPL) { return; } // succeeds on SSAS (gives 1 row) runQ( "select [Measures].[Unit Sales] on 0,\n" + "[Product].[Products].[Brand].&[43]&Walrus on 1\n" + "from [Warehouse and Sales]"); } public void testFoo56() { if (!IMPLEMENTED) { return; } // succeeds on SSAS (gives 1 row) runQ( "select [Measures].[Unit Sales] on 0,\n" + "[Product].[Products].[Brand].[Walrus] on 1\n" + "from [Warehouse and Sales]"); } public void testKeyNonExistent() { if (!KEY_IMPL) { return; } // SSAS gives 0 rows runQ( "select [Measures].[Unit Sales] on 0,\n" + "[Product].[Products].[Brand].&[43] on 1\n" + "from [Warehouse and Sales]"); } public void testAxesLabelsOutOfSequence() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // succeeds on SSAS assertQueryReturns( "select [Measures].[Unit Sales] on 1,\n" + "[Product].[Products] on 0\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Products].[All Productss]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 266,773\n"); } public void testAxisLabelsNotContiguousFails() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // SSAS gives error: // Query (1, 8) Axis numbers specified in a query must be sequentially // specified, and cannot contain gaps. assertQueryThrows( "select [Measures].[Unit Sales] on 1,\n" + "[Product].[Products].Children on 2\n" + "from [Warehouse and Sales]", "Axis numbers specified in a query must be sequentially " + "specified, and cannot contain gaps. Axis 0 (COLUMNS) is missing."); } public void testLotsOfAxes() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // lots of axes, mixed ways of specifying axes // SSAS succeeds, although Studio says: // Results cannot be displayed for cellsets with more than two axes. runQ( "select [Measures].[Unit Sales] on axis(0),\n" + "[Product].[Products] on rows,\n" + "[Customer].[Customer] on pages,\n" + "[Currency] on 3,\n" + "[Promotion] on axis(4),\n" + "[Time].[Time2] on 5,\n" + "[Time].[Time by Week] on 6\n" + "from [Warehouse and Sales]"); } public void testOnAxesFails() { // axes(n) is not an acceptable alternative to axis(n) // SSAS gives: // Query (1, 35) Parser: The syntax for 'axes' is incorrect. assertQueryThrows( "select [Measures].[Unit Sales] on axes(0)\n" + "from [Warehouse and Sales]", "Syntax error at line 1, column 35, token 'axes'"); } public void testOnExpression() { // SSAS gives syntax error assertQueryThrows( "select [Measures].[Unit Sales] on 0 + 1\n" + "from [Warehouse and Sales]", "Syntax error at line 1, column 37, token '+'"); } public void testOnFractionFails() { // SSAS gives syntax error assertQueryThrows( "select [Measures].[Unit Sales] on 0.4\n" + "from [Warehouse and Sales]", "Invalid axis specification. The axis number must be non-negative" + " integer, but it was 0.4."); } public void testAxisFunction() { // AXIS(n) function as expression // SSAS succeeds if (!AXIS_IMPL) { return; } runQ( "WITH MEMBER MEASURES.AXISDEMO AS\n" + " SUM(AXIS(1), [Measures].[Unit Sales])\n" + "SELECT {[Measures].[Unit Sales],MEASURES.AXISDEMO} ON 0,\n" + "{[Time].[Time by Week].Children} ON 1\n" + "FROM [Warehouse and Sales]"); } public void testAxisAppliedToExpr() { // Axis applied to an expression ('3 - 2' in place of '1' above). // SSAS succeeds. // When we implement Axis, it may be acceptable for Mondrian to fail in // this case - or perhaps struggle on with less type information. if (!AXIS_IMPL) { return; } assertQueryReturns( "WITH MEMBER MEASURES.AXISDEMO AS\n" + " SUM(AXIS(1), [Measures].[Unit Sales])\n" + "SELECT {[Measures].[Unit Sales],MEASURES.AXISDEMO} ON 0,\n" + "{[Time].[Time by Week].Children} ON 1\n" + "FROM [Warehouse and Sales]", "xxx"); } public void testAxisFunctionReferencesPreviousAxis() { // reference axis 0 while computing axis 1 // SSAS succeeds if (!AXIS_IMPL) { return; } assertQueryReturns( "WITH MEMBER MEASURES.AXISDEMO AS\n" + " SUM(AXIS(0), [Measures].CurrentMember)\n" + "SELECT {[Measures].[Store Sales],MEASURES.AXISDEMO} ON 0,\n" + "{Filter([Time].[Time by Week].Members, Measures.AxisDemo > 0)} ON 1\n" + "FROM [Warehouse and Sales]", "xxx"); } public void testAxisFunctionReferencesSameAxisFails() { // reference axis 1 while computing axis 1, not ok // SSAS gives: // Infinite recursion detected. The loop of dependencies is: AXISDEMO // -> AXISDEMO. if (!AXIS_IMPL) { return; } assertQueryThrows( "WITH MEMBER MEASURES.AXISDEMO AS\n" + " SUM(AXIS(1), [Measures].CurrentMember)\n" + "SELECT {[Measures].[Store Sales],MEASURES.AXISDEMO} ON 0,\n" + "{Filter([Time].[Time by Week].Members, Measures.AxisDemo > 0)} ON 1\n" + "FROM [Warehouse and Sales]", "xxx"); } public void testAxisFunctionReferencesSameAxisZeroFails() { // reference axis 0 while computing axis 0, not ok // SSAS gives: // Infinite recursion detected. The loop of dependencies is: AXISDEMO // -> AXISDEMO. if (!AXIS_IMPL) { return; } assertQueryThrows( "WITH MEMBER MEASURES.AXISDEMO AS\n" + " SUM(AXIS(0), [Measures].CurrentMember)\n" + "SELECT {[Measures].[Store Sales],MEASURES.AXISDEMO} ON 1,\n" + "{Filter([Time].[Time by Week].Members, Measures.AxisDemo > 0)} ON 0\n" + "FROM [Warehouse and Sales]", "xxx"); } public void testAxisFunctionReferencesLaterAxis() { // reference axis 1 while computing axis 0, ok // The SSAS online doc says: // An axis can reference only a prior axis. For example, Axis(0) must // occur after the COLUMNS axis has been evaluated, such as on a ROW // or PAGE axis. // but nevertheless SSAS does the right thing and allows this query. if (!AXIS_IMPL) { return; } assertQueryReturns( "WITH MEMBER MEASURES.AXISDEMO AS\n" + " SUM(AXIS(1), [Measures].CurrentMember)\n" + "SELECT {[Measures].[Store Sales],MEASURES.AXISDEMO} ON 1,\n" + "{Filter([Time].[Time by Week].Members, Measures.AxisDemo > 0)} ON 0\n" + "FROM [Warehouse and Sales]", "xxx"); } public void testAxisFunctionReferencesSameAxisInlineFails() { // If we inline the member, SSAS runs out of memory. // SSAS gives error: // Memory error: Allocation failure : The paging file is too small for // this operation to complete. . // (Should give cyclicity error.) if (!AXIS_IMPL) { return; } assertQueryThrows( "SELECT [Measures].[Store Sales] ON 1,\n" + "{Filter([Time].[Time by Week].Members, SUM(AXIS(0), [Measures].CurrentMember) > 0)} ON 0\n" + "FROM [Warehouse and Sales]", "xxx cyclic something"); } public void testCrossjoinMember() { if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { // Can't resolve [Products] under old mondrian return; } // Mondrian currently gives error: // No function matches signature 'crossjoin(, )' if (!IMPLEMENTED) { return; } // Apply crossjoin(Member,Set) // SSAS gives 626866, 626866, 626866. assertQueryReturns( "select crossjoin([Products].DefaultMember, [Gender].Members) on 0\n" + "from [Warehouse and Sales]", "xx"); } /** * Tests the ambiguity between a level and a member of the same name, * both in SSAS compatible mode and in regular mode. * @throws SQLException If the test fails. */ public void testCanHaveMemberWithSameNameAsLevel() throws SQLException { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 1\n" + " SameName\n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""); org.olap4j.metadata.Member member = testContext.getOlap4jConnection() .getOlapSchema().getCubes().get("Sales").getDimensions() .get("SameName").getHierarchies().get("SameName").getLevels() .get("SameName").getMembers().get(0); assertEquals( "[SameName].[SameName].[SameName]", member.getUniqueName()); testContext.assertQueryThrows( "select {" + (MondrianProperties.instance().SsasCompatibleNaming.get() ? "[SameName].[SameName].[SameName]" : "[SameName].[SameName]") + "} on 0 from Sales", "Mondrian Error:No function matches signature '{}'"); if (MondrianProperties.instance().SsasCompatibleNaming.get()) { testContext.assertQueryReturns( "select {[SameName].[SameName].[SameName].[SameName]} on 0 from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[SameName].[SameName].[SameName]}\n" + "Row #0: \n"); } else { testContext.assertQueryReturns( "select {[SameName].[SameName].[SameName]} on 0 from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[SameName].[SameName].[SameName]}\n" + "Row #0: \n"); } } public void testMemberNameSortCaseSensitivity() { // In SSAS, "MacDougal" occurs between "Maccietto" and "Macha". This // would not occur if sort was case-sensitive. final TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "
\n" + " \n" + " \n" + " \n"); testContext.assertAxisReturns( "head(\n" + " filter(\n" + " [Customer Last Name].[Last Name].Members," + " Left([Customer Last Name].[Last Name].CurrentMember.Name, " + "1) = \"M\"),\n" + " 10)", "[Customer Last Name].[Mabe]\n" + "[Customer Last Name].[Macaluso]\n" + "[Customer Last Name].[MacBride]\n" + "[Customer Last Name].[Maccietto]\n" + "[Customer Last Name].[MacDougal]\n" + "[Customer Last Name].[Macha]\n" + "[Customer Last Name].[Macias]\n" + "[Customer Last Name].[Mack]\n" + "[Customer Last Name].[Mackin]\n" + "[Customer Last Name].[Maddalena]"); testContext.assertAxisReturns( "order(\n" + " head(\n" + " filter(\n" + " [Customer Last Name].[Last Name].Members," + " Left([Customer Last Name].[Last Name].CurrentMember.Name, 1) = \"M\"),\n" + " 10),\n" + " [Customer Last Name].[Last Name].CurrentMember.Name)", "[Customer Last Name].[Mabe]\n" + "[Customer Last Name].[Macaluso]\n" + "[Customer Last Name].[MacBride]\n" + "[Customer Last Name].[Maccietto]\n" + "[Customer Last Name].[MacDougal]\n" + "[Customer Last Name].[Macha]\n" + "[Customer Last Name].[Macias]\n" + "[Customer Last Name].[Mack]\n" + "[Customer Last Name].[Mackin]\n" + "[Customer Last Name].[Maddalena]"); } /** * SSAS can resolve root members of a hierarchy even if not qualified * by hierarchy, and even if the dimension has more than one hierarchy. */ public void testRootMembers() { // for member defined in the database final String timeByWeek = TestContext.hierarchyName("Time", "Time By Week"); assertExprReturns( "[Time].[1997].Level.UniqueName", timeByWeek + ".[Year2]"); if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return; } // now for a calc member defined in a query assertQueryReturns( "with member [Time].[Time2].[Foo] as\n" + "[Time].[Time2].[1997] + [Time].[Time2].[1997].[Q3]\n" + "select [Time].[Foo] on 0\n" + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[Time2].[Foo]}\n" + "Row #0: 332,621\n"); } /** * Subclass of {@link mondrian.test.Ssas2005CompatibilityTest} that runs * with {@link mondrian.olap.MondrianProperties#SsasCompatibleNaming}=false. */ public static class OldBehaviorTest extends Ssas2005CompatibilityTest { /** * Creates an OldBehaviorTest. * * @param name Testcase name */ public OldBehaviorTest(String name) { super(name); } protected void setUp() throws Exception { propSaver.set( MondrianProperties.instance().SsasCompatibleNaming, false); } } /** * Subclass of {@link mondrian.test.Ssas2005CompatibilityTest} that runs * with {@link mondrian.olap.MondrianProperties#SsasCompatibleNaming}=true. */ public static class NewBehaviorTest extends Ssas2005CompatibilityTest { /** * Creates a NewBehaviorTest. * * @param name Testcase name */ public NewBehaviorTest(String name) { super(name); } protected void setUp() throws Exception { propSaver.set( MondrianProperties.instance().SsasCompatibleNaming, true); } } } // End Ssas2005CompatibilityTest.java mondrian-3.4.1/testsrc/main/mondrian/test/InlineTableTest.java0000644000175000017500000002772211735330606024332 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.olap.MondrianProperties; import mondrian.spi.Dialect; /** * Unit test for the InlineTable element, defining tables whose values are held * in the Mondrian schema file, not in the database. * * @author jhyde */ public class InlineTableTest extends FoodMartTestCase { public InlineTableTest(String name) { super(name); } public void testInlineTable() { final String cubeName = "Sales_inline"; TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 0\n" + " Promo0\n" + " \n" + " \n" + " 1\n" + " Promo1\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select {[Alternative Promotion].[All Alternative Promotions].children} ON COLUMNS\n" + "from [" + cubeName + "] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Alternative Promotion].[Promo0]}\n" + "{[Alternative Promotion].[Promo1]}\n" + "Row #0: 195,448\n" + "Row #0: \n"); } public void testInlineTableInSharedDim() { final String cubeName = "Sales_inline_shared"; final TestContext testContext = TestContext.instance().create( null, " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 0\n" + " First promo\n" + " \n" + " \n" + " 1\n" + " Second promo\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select {[Shared Alternative Promotion].[All Shared Alternative Promotions].children} ON COLUMNS\n" + "from [" + cubeName + "] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Shared Alternative Promotion].[First promo]}\n" + "{[Shared Alternative Promotion].[Second promo]}\n" + "Row #0: 195,448\n" + "Row #0: \n"); } public void testInlineTableSnowflake() { if (getTestContext().getDialect().getDatabaseProduct() == Dialect.DatabaseProduct.INFOBRIGHT) { // Infobright has a bug joining an inline table. Gives error // "Illegal mix of collations (ascii_bin,IMPLICIT) and // (utf8_general_ci,COERCIBLE) for operation '='". return; } final String cubeName = "Sales_inline_snowflake"; final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " USA\n" + " US\n" + " \n" + " \n" + " Mexico\n" + " MX\n" + " \n" + " \n" + " Canada\n" + " CA\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select {[Store].children} ON COLUMNS\n" + "from [" + cubeName + "] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[CA]}\n" + "{[Store].[MX]}\n" + "{[Store].[US]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: 266,773\n"); } public void testInlineTableDate() { final String cubeName = "Sales_Inline_Date"; final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 1\n" + " 2008-04-29\n" + " \n" + " \n" + " 2\n" + " 2007-01-20\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); // With grouping sets, mondrian will join fact table to the inline // dimension table, them sum to compute the 'all' value. That semi-joins // away too many fact table rows, and the 'all' value comes out too low // (zero, in fact). It causes a test exception, but is valid mondrian // behavior. (Behavior is unspecified if schema does not have // referential integrity.) if (MondrianProperties.instance().EnableGroupingSets.get()) { return; } testContext.assertQueryReturns( "select {[Alternative Promotion].Members} ON COLUMNS\n" + "from [" + cubeName + "] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Alternative Promotion].[All Alternative Promotions]}\n" + "{[Alternative Promotion].[2008-04-29]}\n" + "{[Alternative Promotion].[2007-01-20]}\n" + "Row #0: 266,773\n" + "Row #0: \n" + "Row #0: \n"); } } // End InlineTableTest.java mondrian-3.4.1/testsrc/main/mondrian/test/MondrianOlap4jTester.java0000644000175000017500000000221111735330606025276 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2010 Pentaho // All Rights Reserved. */ package mondrian.test; /** * Implementation of {@link org.olap4j.test.TestContext.Tester} for Mondrian's * olap4j driver. * * @author Julian Hyde */ public class MondrianOlap4jTester extends AbstractMondrianOlap4jTester { /** * Public constructor with {@link org.olap4j.test.TestContext} parameter as * required by {@link org.olap4j.test.TestContext.Tester} API. * * @param testContext Test context */ public MondrianOlap4jTester(org.olap4j.test.TestContext testContext) { super( testContext, DRIVER_URL_PREFIX, DRIVER_CLASS_NAME, Flavor.MONDRIAN); } public static final String DRIVER_CLASS_NAME = "mondrian.olap4j.MondrianOlap4jDriver"; public static final String DRIVER_URL_PREFIX = "jdbc:mondrian:"; } // End MondrianOlap4jTester.java mondrian-3.4.1/testsrc/main/mondrian/test/AbstractMondrianOlap4jTester.java0000644000175000017500000000465411735330606026777 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2010 Pentaho // All Rights Reserved. */ package mondrian.test; import org.olap4j.test.TestContext; import java.sql.*; import java.util.Properties; /** * Abstract implementation of the {@link org.olap4j.test.TestContext.Tester} * callback required by olap4j's Test Compatability Kit (TCK). * * @author Julian Hyde */ abstract class AbstractMondrianOlap4jTester implements TestContext.Tester { private final TestContext testContext; private final String driverUrlPrefix; private final String driverClassName; private final Flavor flavor; protected AbstractMondrianOlap4jTester( TestContext testContext, String driverUrlPrefix, String driverClassName, Flavor flavor) { this.testContext = testContext; this.driverUrlPrefix = driverUrlPrefix; this.driverClassName = driverClassName; this.flavor = flavor; } public TestContext getTestContext() { return testContext; } public Connection createConnection() throws SQLException { try { Class.forName(getDriverClassName()); } catch (ClassNotFoundException e) { throw new RuntimeException("oops", e); } return DriverManager.getConnection( getURL(), new Properties()); } public Connection createConnectionWithUserPassword() throws SQLException { return DriverManager.getConnection( getURL(), USER, PASSWORD); } public String getDriverUrlPrefix() { return driverUrlPrefix; } public String getDriverClassName() { return driverClassName; } public String getURL() { // This property is usually defined in build.properties. See // examples in that file. return testContext.getProperties().getProperty( TestContext.Property.CONNECT_URL.path); } public Flavor getFlavor() { return flavor; } public TestContext.Wrapper getWrapper() { return TestContext.Wrapper.NONE; } private static final String USER = "sa"; private static final String PASSWORD = "sa"; } // End AbstractMondrianOlap4jTester.java mondrian-3.4.1/testsrc/main/mondrian/test/ParameterTest.java0000644000175000017500000013434511735330606024064 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. // // jhyde, Feb 13, 2003 */ package mondrian.test; import mondrian.olap.*; import mondrian.rolap.RolapConnectionProperties; import junit.framework.Assert; import org.eigenbase.util.property.Property; import org.olap4j.impl.Olap4jUtil; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.Time; import java.sql.Timestamp; import java.util.*; /** * A ParameterTest is a test suite for functionality relating to * parameters. * * @author jhyde * @since Feb 13, 2003 */ public class ParameterTest extends FoodMartTestCase { public ParameterTest(String name) { super(name); } // -- Helper methods ---------- private void assertSetPropertyFails(String propName, String scope) { Query q = getConnection().parseQuery("select from [Sales]"); try { q.setParameter(propName, "foo"); fail( "expected exception, trying to set " + "non-overrideable property '" + propName + "'"); } catch (Exception e) { assertTrue(e.getMessage().indexOf( "Parameter '" + propName + "' (defined at '" + scope + "' scope) is not modifiable") >= 0); } } // -- Tests -------------- public void testChangeable() { // jpivot needs to set a parameters value before the query is executed String mdx = "select {Parameter(\"Foo\",[Time],[Time].[1997],\"Foo\")} " + "ON COLUMNS from [Sales]"; Query query = getConnection().parseQuery(mdx); SchemaReader sr = query.getSchemaReader(false).withLocus(); Member m = sr.getMemberByUniqueName( Id.Segment.toList("Time", "1997", "Q2", "5"), true); Parameter p = sr.getParameter("Foo"); p.setValue(m); assertEquals(m, p.getValue()); query.resolve(); p.setValue(m); assertEquals(m, p.getValue()); mdx = query.toString(); TestContext.assertEqualsVerbose( "select {Parameter(\"Foo\", [Time], [Time].[1997].[Q2].[5], \"Foo\")} ON COLUMNS\n" + "from [Sales]\n", mdx); } public void testParameterInFormatString() { assertQueryReturns( "with member [Measures].[X] as '[Measures].[Store Sales]',\n" + "format_string = Parameter(\"fmtstrpara\", STRING, \"#\")\n" + "select {[Measures].[X]} ON COLUMNS\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[X]}\n" + "Row #0: 565238\n"); } public void testParameterInFormatString_Bug1584439() { String queryString = "with member [Measures].[X] as '[Measures].[Store Sales]',\n" + "format_string = Parameter(\"fmtstrpara\", STRING, \"#\")\n" + "select {[Measures].[X]} ON COLUMNS\n" + "from [Sales]"; // this used to crash Connection connection = getConnection(); Query query = connection.parseQuery(queryString); query.toString(); } public void testParameterOnAxis() { assertQueryReturns( "select {[Measures].[Unit Sales]} on rows,\n" + " {Parameter(\"GenderParam\",[Gender],[Gender].[M],\"Which gender?\")} on columns\n" + "from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[M]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 135,215\n"); } public void testNumericParameter() { String s = executeExpr("Parameter(\"N\",NUMERIC,2+3,\"A numeric parameter\")"); Assert.assertEquals("5", s); } public void testStringParameter() { String s = executeExpr( "Parameter(\"S\",STRING,\"x\" || \"y\"," + "\"A string parameter\")"); Assert.assertEquals("xy", s); } public void testStringParameterNull() { getTestContext().assertParameterizedExprReturns( "Parameter('foo', STRING, 'default')", "xxx", "foo", "xxx"); // explicitly set parameter to null and you should not get default value getTestContext().assertParameterizedExprReturns( "Parameter('foo', STRING, 'default')", "", "foo", null); getTestContext().assertParameterizedExprReturns( "Len(Parameter('foo', STRING, 'default'))", "0", "foo", null); getTestContext().assertParameterizedExprReturns( "Parameter('foo', STRING, 'default') = 'default'", "false", "foo", null); getTestContext().assertParameterizedExprReturns( "Parameter('foo', STRING, 'default') = ''", "false", "foo", null); } public void testNumericParameterNull() { getTestContext().assertParameterizedExprReturns( "Parameter('foo', NUMERIC, 12.3)", "234", "foo", 234); // explicitly set parameter to null and you should not get default value getTestContext().assertParameterizedExprReturns( "Parameter('foo', NUMERIC, 12.3)", "", "foo", null); getTestContext().assertParameterizedExprReturns( "Parameter('foo', NUMERIC, 12.3) * 10", "", "foo", null); } public void testMemberParameterNull() { getTestContext().assertParameterizedExprReturns( "Parameter('foo', [Gender], [Gender].[F]).Name", "M", "foo", "[Gender].[M]"); // explicitly set parameter to null and you should not get default value getTestContext().assertParameterizedExprReturns( "Parameter('foo', [Gender], [Gender].[F]).Name", "#null", "foo", null); getTestContext().assertParameterizedExprReturns( "Parameter('foo', [Gender], [Gender].[F]).Hierarchy.Name", "Gender", "foo", null); getTestContext().assertParameterizedExprReturns( "Parameter('foo', [Gender], [Gender].[F]) is null", "true", "foo", null); getTestContext().assertParameterizedExprReturns( "Parameter('foo', [Gender], [Gender].[F]) is [Gender].Parent", "true", "foo", null); // assign null then assign something else getTestContext().assertParameterizedExprReturns( "Parameter('foo', [Gender], [Gender].[F]).Name", "M", "foo", null, "foo", "[Gender].[All Gender].[M]"); } /** * Test case for bug * MONDRIAN-745, * "NullPointerException when passing in null param value". */ public void testNullStrToMember() { Connection connection = getConnection(); Query query = connection.parseQuery( "select NON EMPTY {[Time].[1997]} ON COLUMNS, " + "NON EMPTY {StrToMember(Parameter(\"sProduct\", STRING, \"[Gender].[Gender].[F]\"))} ON ROWS " + "from [Sales]"); // Execute #1: Parameter unset Parameter[] parameters = query.getParameters(); final Parameter parameter0 = parameters[0]; assertFalse(parameter0.isSet()); // ideally, parameter's default value would be available before // execution; but it is what it is assertNull(parameter0.getValue()); Result result = connection.execute(query); assertEquals("[Gender].[Gender].[F]", parameter0.getValue()); final String expected = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "Axis #2:\n" + "{[Gender].[F]}\n" + "Row #0: 131,558\n"; TestContext.assertEqualsVerbose(expected, TestContext.toString(result)); // Execute #2: Parameter set to null assertFalse(parameter0.isSet()); parameter0.setValue(null); assertTrue(parameter0.isSet()); assertEquals(null, parameter0.getValue()); Throwable throwable; try { result = connection.execute(query); Util.discard(result); throwable = null; } catch (Throwable e) { throwable = e; } TestContext.checkThrowable( throwable, "An MDX expression was expected. An empty expression was specified."); // Execute #3: Parameter unset, reverts to default value assertTrue(parameter0.isSet()); parameter0.unsetValue(); assertFalse(parameter0.isSet()); // ideally, parameter's default value would be available before // execution; but it is what it is assertNull(parameter0.getValue()); result = connection.execute(query); assertEquals("[Gender].[Gender].[F]", parameter0.getValue()); TestContext.assertEqualsVerbose(expected, TestContext.toString(result)); assertFalse(parameter0.isSet()); } public void testSetUnsetParameter() { Connection connection = getConnection(); Query query = connection.parseQuery( "with member [Measures].[Foo] as\n" + " len(Parameter(\"sProduct\", STRING, \"foobar\"))\n" + "select {[Measures].[Foo]} ON COLUMNS\n" + "from [Sales]"); Parameter[] parameters = query.getParameters(); final Parameter parameter0 = parameters[0]; assertFalse(parameter0.isSet()); if (new Random().nextBoolean()) { // harmless to unset a parameter which is unset parameter0.unsetValue(); } final String expect6 = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: 6\n"; final String expect0 = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: 0\n"; final String expect3 = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: 3\n"; // before parameter is set, should get len of default value, viz 6 Result result = connection.execute(query); TestContext.assertEqualsVerbose(expect6, TestContext.toString(result)); // after parameter is set to null, should get len of null, viz 0 parameter0.setValue(null); assertTrue(parameter0.isSet()); result = connection.execute(query); TestContext.assertEqualsVerbose(expect0, TestContext.toString(result)); assertTrue(parameter0.isSet()); // after parameter is set to "foo", should get len of foo, viz 3 parameter0.setValue("foo"); assertTrue(parameter0.isSet()); result = connection.execute(query); TestContext.assertEqualsVerbose(expect3, TestContext.toString(result)); assertTrue(parameter0.isSet()); // after unset, should get len of default value, viz 6 parameter0.unsetValue(); result = connection.execute(query); TestContext.assertEqualsVerbose(expect6, TestContext.toString(result)); assertFalse(parameter0.isSet()); } public void testNumericParameterStringValueFails() { assertExprThrows( "Parameter(\"S\",NUMERIC,\"x\" || \"y\",\"A string parameter\")", "java.lang.NumberFormatException: For input string: \"xy\""); } public void testParameterDimension() { assertExprReturns( "Parameter(\"Foo\",[Time],[Time].[1997],\"Foo\").Name", "1997"); assertExprReturns( "Parameter(\"Foo\",[Time],[Time].[1997].[Q2].[5],\"Foo\").Name", "5"); // wrong dimension assertExprThrows( "Parameter(\"Foo\",[Time],[Product].[All Products],\"Foo\").Name", "Default value of parameter 'Foo' is not consistent with the parameter type 'MemberType"); // non-existent member assertExprThrows( "Parameter(\"Foo\",[Time],[Time].[1997].[Q5],\"Foo\").Name", "MDX object '[Time].[1997].[Q5]' not found in cube 'Sales'"); } public void testParameterHierarchy() { assertExprReturns( "Parameter(\"Foo\", [Time.Weekly], [Time.Weekly].[1997].[40],\"Foo\").Name", "40"); // right dimension, wrong hierarchy final String levelName = MondrianProperties.instance().SsasCompatibleNaming.get() ? "[Time].[Weekly]" : "[Time.Weekly]"; assertExprThrows( "Parameter(\"Foo\",[Time.Weekly],[Time].[1997].[Q1],\"Foo\").Name", "Default value of parameter 'Foo' is not consistent with the parameter type 'MemberType"); // wrong dimension assertExprThrows( "Parameter(\"Foo\",[Time.Weekly],[Product].[All Products],\"Foo\").Name", "Default value of parameter 'Foo' is not consistent with the parameter type 'MemberType"); // garbage assertExprThrows( "Parameter(\"Foo\",[Time.Weekly],[Widget].[All Widgets],\"Foo\").Name", "MDX object '[Widget].[All Widgets]' not found in cube 'Sales'"); } public void testParameterLevel() { assertExprReturns( "Parameter(\"Foo\",[Time].[Quarter], [Time].[1997].[Q3], \"Foo\").Name", "Q3"); assertExprThrows( "Parameter(\"Foo\",[Time].[Quarter], [Time].[1997].[Q3].[8], \"Foo\").Name", "Default value of parameter 'Foo' is not consistent with the parameter type 'MemberType"); } public void testParameterMemberFails() { // type of a param can be dimension, hierarchy, level but not member assertExprThrows( "Parameter(\"Foo\",[Time].[1997].[Q2],[Time].[1997],\"Foo\")", "Invalid type for parameter 'Foo'; expecting NUMERIC, STRING or a hierarchy"); } /** * Tests that member parameter fails validation if the level name is * invalid. */ public void testParameterMemberFailsBadLevel() { assertExprThrows( "Parameter(\"Foo\", [Customers].[State], [Customers].[USA].[CA], \"\")", "MDX object '[Customers].[State]' not found in cube 'Sales'"); assertExprReturns( "Parameter(\"Foo\", [Customers].[State Province], [Customers].[USA].[CA], \"\")", "74,748"); } /** * Tests that a dimension name can be used as the default value of a * member-valued parameter. It is interpreted to mean the default value of * that dimension. */ public void testParameterMemberDefaultValue() { // "[Time]" is shorthand for "[Time].CurrentMember" assertExprReturns( "Parameter(\"Foo\", [Time], [Time].[Time], \"Description\").UniqueName", "[Time].[1997]"); assertExprReturns( "Parameter(\"Foo\", [Time], [Time].[Time].Children.Item(2), \"Description\").UniqueName", "[Time].[1997].[Q3]"); } /** * Non-trivial default value. Example shows how to set the parameter to * the last month that someone in Bellflower, CA had a good beer. You can * use it to solve the more common problem "How do I automatically set the * time dimension to the latest date for which there are transactions?". */ public void testParameterMemberDefaultValue2() { assertQueryReturns( "select [Measures].[Unit Sales] on 0,\n" + " [Product].Children on 1\n" + "from [Sales]" + "where Parameter(\n" + " \"Foo\",\n" + " [Time].[Time],\n" + " Tail(\n" + " {\n" + " [Time].[Time],\n" + " Filter(\n" + " [Time].[Month].Members,\n" + " 0 < ([Customers].[USA].[CA].[Bellflower],\n" + " [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good]))\n" + " },\n" + " 1),\n" + " \"Description\")", "Axis #0:\n" + "{[Time].[1997].[Q4].[11]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 2,344\n" + "Row #1: 18,278\n" + "Row #2: 4,648\n"); } public void testParameterWithExpressionForHierarchyFails() { assertExprThrows( "Parameter(\"Foo\",[Gender].DefaultMember.Hierarchy,[Gender].[M],\"Foo\")", "Invalid parameter 'Foo'. Type must be a NUMERIC, STRING, or a dimension, hierarchy or level"); } /** * Tests a parameter derived from another parameter. OK as long as it is * not cyclic. */ public void testDerivedParameter() { assertExprReturns( "Parameter(\"X\", NUMERIC, Parameter(\"Y\", NUMERIC, 1) + 2)", "3"); } public void testParameterInSlicer() { assertQueryReturns( "select {[Measures].[Unit Sales]} on rows,\n" + " {[Marital Status].children} on columns\n" + "from Sales where Parameter(\"GenderParam\",[Gender],[Gender].[M],\"Which gender?\")", "Axis #0:\n" + "{[Gender].[M]}\n" + "Axis #1:\n" + "{[Marital Status].[M]}\n" + "{[Marital Status].[S]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 66,460\n" + "Row #0: 68,755\n"); } /** * Parameter in slicer and expression on columns axis are both of [Gender] * hierarchy, which is illegal. */ public void _testParameterDuplicateDimensionFails() { assertQueryThrows( "select {[Measures].[Unit Sales]} on rows,\n" + " {[Gender].[F]} on columns\n" + "from Sales where Parameter(\"GenderParam\",[Gender],[Gender].[M],\"Which gender?\")", "Invalid hierarchy for parameter 'GenderParam'"); } /** Mondrian can not handle forward references */ public void dontTestParamRef() { String s = executeExpr( "Parameter(\"X\",STRING,\"x\",\"A string\") || " + "ParamRef(\"Y\") || " + "\".\" ||" + "ParamRef(\"X\") || " + "Parameter(\"Y\",STRING,\"y\" || \"Y\",\"Other string\")"); Assert.assertEquals("xyY.xyY", s); } public void testParamRefWithoutParamFails() { assertExprThrows("ParamRef(\"Y\")", "Unknown parameter 'Y'"); } public void testParamDefinedTwiceFails() { assertQueryThrows( "select {[Measures].[Unit Sales]} on rows,\n" + " {Parameter(\"P\",[Gender],[Gender].[M],\"Which gender?\"),\n" + " Parameter(\"P\",[Gender],[Gender].[F],\"Which gender?\")} on columns\n" + "from Sales", "Parameter 'P' is defined more than once"); } public void testParamBadTypeFails() { assertExprThrows( "Parameter(\"P\", 5)", "No function matches signature 'Parameter(, )'"); } public void testParamCyclicOk() { assertExprReturns( "Parameter(\"P\", NUMERIC, ParamRef(\"Q\") + 1) + " + "Parameter(\"Q\", NUMERIC, Iif(1 = 0, ParamRef(\"P\"), 2))", "5"); } public void testParamCyclicFails() { assertExprThrows( "Parameter(\"P\", NUMERIC, ParamRef(\"Q\") + 1) + " + "Parameter(\"Q\", NUMERIC, Iif(1 = 1, ParamRef(\"P\"), 2))", "Cycle occurred while evaluating parameter 'P'"); } public void testParameterMetadata() { Connection connection = getConnection(); Query query = connection.parseQuery( "with member [Measures].[A string] as \n" + " Parameter(\"S\",STRING,\"x\" || \"y\",\"A string parameter\")\n" + " member [Measures].[A number] as \n" + " Parameter(\"N\",NUMERIC,2+3,\"A numeric parameter\")\n" + "select {[Measures].[Unit Sales]} on rows,\n" + " {Parameter(\"P\",[Gender],[Gender].[F],\"Which gender?\"),\n" + " Parameter(\"Q\",[Gender],[Gender].DefaultMember,\"Another gender?\")} on columns\n" + "from Sales"); Parameter[] parameters = query.getParameters(); Assert.assertEquals(4, parameters.length); Assert.assertEquals("S", parameters[0].getName()); Assert.assertEquals("N", parameters[1].getName()); Assert.assertEquals("P", parameters[2].getName()); Assert.assertEquals("Q", parameters[3].getName()); final Member member = query.getSchemaReader(true).getMemberByUniqueName( Id.Segment.toList("Gender", "M"), true); parameters[2].setValue(member); TestContext.assertEqualsVerbose( "with member [Measures].[A string] as 'Parameter(\"S\", STRING, (\"x\" || \"y\"), \"A string parameter\")'\n" + " member [Measures].[A number] as 'Parameter(\"N\", NUMERIC, (2 + 3), \"A numeric parameter\")'\n" + "select {Parameter(\"P\", [Gender], [Gender].[M], \"Which gender?\"), Parameter(\"Q\", [Gender], [Gender].DefaultMember, \"Another gender?\")} ON COLUMNS,\n" + " {[Measures].[Unit Sales]} ON ROWS\n" + "from [Sales]\n", Util.unparse(query)); } public void testTwoParametersBug1425153() { Connection connection = getTestContext().getConnection(); Query query = connection.parseQuery( "select \n" + "{[Measures].[Unit Sales]} on columns, \n" + "{Parameter(\"ProductMember\", [Product], [Product].[All Products].[Food], \"wat willste?\").children} ON rows \n" + "from Sales where Parameter(\"Time\",[Time],[Time].[1997].[Q1])"); // Execute before setting parameters. Result result = connection.execute(query); String resultString = TestContext.toString(result); TestContext.assertEqualsVerbose( "Axis #0:\n" + "{[Time].[1997].[Q1]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Baked Goods]}\n" + "{[Product].[Food].[Baking Goods]}\n" + "{[Product].[Food].[Breakfast Foods]}\n" + "{[Product].[Food].[Canned Foods]}\n" + "{[Product].[Food].[Canned Products]}\n" + "{[Product].[Food].[Dairy]}\n" + "{[Product].[Food].[Deli]}\n" + "{[Product].[Food].[Eggs]}\n" + "{[Product].[Food].[Frozen Foods]}\n" + "{[Product].[Food].[Meat]}\n" + "{[Product].[Food].[Produce]}\n" + "{[Product].[Food].[Seafood]}\n" + "{[Product].[Food].[Snack Foods]}\n" + "{[Product].[Food].[Snacks]}\n" + "{[Product].[Food].[Starchy Foods]}\n" + "Row #0: 1,932\n" + "Row #1: 5,045\n" + "Row #2: 820\n" + "Row #3: 4,737\n" + "Row #4: 400\n" + "Row #5: 3,262\n" + "Row #6: 2,985\n" + "Row #7: 918\n" + "Row #8: 6,624\n" + "Row #9: 391\n" + "Row #10: 9,499\n" + "Row #11: 412\n" + "Row #12: 7,750\n" + "Row #13: 1,718\n" + "Row #14: 1,316\n", resultString); // Set one parameter and execute again. query.setParameter( "ProductMember", "[Product].[All Products].[Food].[Eggs]"); result = connection.execute(query); resultString = TestContext.toString(result); TestContext.assertEqualsVerbose( "Axis #0:\n" + "{[Time].[1997].[Q1]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Eggs].[Eggs]}\n" + "Row #0: 918\n", resultString); // Now set both parameters and execute again. query.setParameter( "ProductMember", "[Product].[All Products].[Food].[Deli]"); query.setParameter("Time", "[Time].[1997].[Q2].[4]"); result = connection.execute(query); resultString = TestContext.toString(result); TestContext.assertEqualsVerbose( "Axis #0:\n" + "{[Time].[1997].[Q2].[4]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food].[Deli].[Meat]}\n" + "{[Product].[Food].[Deli].[Side Dishes]}\n" + "Row #0: 621\n" + "Row #1: 187\n", resultString); } /** * Positive and negative tests assigning values to a parameter of type * NUMERIC. */ public void testAssignNumericParameter() { final String para = "Parameter(\"x\", NUMERIC, 1)"; assertAssignParameter(para, false, "8", null); assertAssignParameter(para, false, "8.24", null); assertAssignParameter(para, false, 8, null); assertAssignParameter(para, false, -8.56, null); assertAssignParameter(para, false, new BigDecimal("12.345"), null); assertAssignParameter(para, false, new BigInteger("12345"), null); // Formatted date will depends on time zone. Only match part of message. assertAssignParameter( para, false, new Date(), "' for parameter 'x', type NUMERIC"); assertAssignParameter( para, false, new Timestamp(new Date().getTime()), "' for parameter 'x', type NUMERIC"); assertAssignParameter( para, false, new Time(new Date().getTime()), "' for parameter 'x', type NUMERIC"); // OK to assign null assertAssignParameter(para, false, null, null); } /** * Positive and negative tests assigning values to a parameter of type * STRING. */ public void testAssignStringParameter() { final String para = "Parameter(\"x\", STRING, 'xxx')"; assertAssignParameter(para, false, "8", null); assertAssignParameter(para, false, "8.24", null); assertAssignParameter(para, false, 8, null); assertAssignParameter(para, false, -8.56, null); assertAssignParameter(para, false, new BigDecimal("12.345"), null); assertAssignParameter(para, false, new BigInteger("12345"), null); assertAssignParameter(para, false, new Date(), null); assertAssignParameter( para, false, new Timestamp(new Date().getTime()), null); assertAssignParameter( para, false, new Time(new Date().getTime()), null); assertAssignParameter(para, false, null, null); } /** * Positive and negative tests assigning values to a parameter whose type is * a member. */ public void testAssignMemberParameter() { final String para = "Parameter(\"x\", [Customers], [Customers].[USA])"; assertAssignParameter( para, false, "8", "MDX object '[8]' not found in cube 'Sales'"); assertAssignParameter( para, false, "8.24", "MDX object '[8].[24]' not found in cube 'Sales'"); assertAssignParameter( para, false, 8, "Invalid value '8' for parameter 'x'," + " type MemberType"); assertAssignParameter( para, false, -8.56, "Invalid value '-8.56' for parameter 'x'," + " type MemberType"); assertAssignParameter( para, false, new BigDecimal("12.345"), "Invalid value '12.345' for parameter 'x'," + " type MemberType"); assertAssignParameter( para, false, new Date(), "' for parameter 'x', type MemberType"); assertAssignParameter( para, false, new Timestamp(new Date().getTime()), "' for parameter 'x', type MemberType"); assertAssignParameter( para, false, new Time(new Date().getTime()), "' for parameter 'x', type MemberType"); // string is OK assertAssignParameter(para, false, "[Customers].[Mexico]", null); // now with spurious 'all' assertAssignParameter( para, false, "[Customers].[All Customers].[Canada].[BC]", null); // non-existent member assertAssignParameter( para, false, "[Customers].[Canada].[Bear Province]", "MDX object '[Customers].[Canada].[Bear Province]' not found in " + "cube 'Sales'"); // Valid to set to null. It means use the default member of the // hierarchy. (Not necessarily the same as the default value of the // parameter. There's no way to get back to the default value of a // parameter once you've set it -- even by setting it to null.) assertAssignParameter(para, false, null, null); SchemaReader sr = TestContext.instance().getConnection() .parseQuery("select from [Sales]").getSchemaReader(true) .withLocus(); // Member of wrong hierarchy. assertAssignParameter( para, false, sr.getMemberByUniqueName( Id.Segment.toList("Time", "1997", "Q2", "5"), true), "Invalid value '[Time].[1997].[Q2].[5]' for parameter 'x', " + "type MemberType"); // Member of right hierarchy. assertAssignParameter( para, false, sr.getMemberByUniqueName( Id.Segment.toList("Customers", "All Customers"), true), null); // Member of wrong level of right hierarchy. assertAssignParameter( "Parameter(\"x\", [Customers].[State Province], [Customers].[USA].[CA])", false, sr.getMemberByUniqueName( Id.Segment.toList("Customers", "USA"), true), "Invalid value '[Customers].[USA]' for parameter " + "'x', type MemberType"); // Same, using string. assertAssignParameter( "Parameter(\"x\", [Customers].[State Province], [Customers].[USA].[CA])", false, "[Customers].[USA]", "Invalid value '[Customers].[USA]' for parameter " + "'x', type MemberType"); // Member of right level. assertAssignParameter( "Parameter(\"x\", [Customers].[State Province], [Customers].[USA].[CA])", false, sr.getMemberByUniqueName( Id.Segment.toList("Customers", "USA", "OR"), true), null); } /** * Positive and negative tests assigning values to a parameter whose type is * a set of members. */ public void testAssignSetParameter() { final String para = "Parameter(\"x\", [Customers], {[Customers].[USA], [Customers].[USA].[CA]})"; assertAssignParameter( para, true, "8", "MDX object '[8]' not found in cube 'Sales'"); assertAssignParameter( para, true, "foobar", "MDX object '[foobar]' not found in cube 'Sales'"); assertAssignParameter( para, true, 8, "Invalid value '8' for parameter 'x', type SetType"); assertAssignParameter( para, true, -8.56, "Invalid value '-8.56' for parameter 'x', type SetType"); assertAssignParameter( para, true, new BigDecimal("12.345"), "Invalid value '12.345' for parameter 'x', type SetType"); assertAssignParameter( para, true, new Date(), "' for parameter 'x', type SetType"); assertAssignParameter( para, true, new Timestamp(new Date().getTime()), "' for parameter 'x', type SetType"); assertAssignParameter( para, true, new Time(new Date().getTime()), "' for parameter 'x', type SetType"); // strings are OK assertAssignParameter( para, true, "{[Customers].[USA], [Customers].[All Customers].[Canada].[BC]}", null); // also OK without braces assertAssignParameter( para, true, "[Customers].[USA], [Customers].[All Customers].[Canada].[BC]", null); // also OK with non-standard spacing assertAssignParameter( para, true, "[Customers] . [USA] , [Customers].[Canada].[BC],[Customers].[Mexico]", null); // error if one of the members does not exist assertAssignParameter( para, true, "{[Customers].[USA], [Customers].[Canada].[BC].[Bear City]}", "MDX object '[Customers].[Canada].[BC].[Bear City]' not found in cube 'Sales'"); List list; SchemaReader sr = TestContext.instance().getConnection() .parseQuery("select from [Sales]").getSchemaReader(true) .withLocus(); // Empty list is OK. list = Collections.emptyList(); assertAssignParameter(para, true, list, null); // empty string is ok assertAssignParameter(para, true, "", null); // empty string is ok assertAssignParameter(para, true, "{}", null); // empty string is ok assertAssignParameter(para, true, " { } ", null); // Not valid to set list to null. assertAssignParameter( para, true, null, "Invalid value 'null' for parameter 'x', type SetType>"); // List that contains one member of wrong hierarchy. list = Arrays.asList( sr.getMemberByUniqueName( Id.Segment.toList("Customers", "Mexico"), true), sr.getMemberByUniqueName( Id.Segment.toList("Time", "1997", "Q2", "5"), true)); assertAssignParameter( para, true, list, "Invalid value '[Time].[1997].[Q2].[5]' for parameter 'x', " + "type MemberType"); // as above, strings assertAssignParameter( para, true, "{[Customers].[Mexico], [Time].[1997].[Q2].[5]}", "Invalid value '[Time].[1997].[Q2].[5]' for parameter 'x', " + "type MemberType"); // List that contains members of correct hierarchy. list = Arrays.asList( sr.getMemberByUniqueName( Id.Segment.toList("Customers", "Mexico"), true), sr.getMemberByUniqueName( Id.Segment.toList("Customers", "Canada"), true)); assertAssignParameter(para, true, list, null); // List that contains member of wrong level of right hierarchy. list = Arrays.asList( sr.getMemberByUniqueName( Id.Segment.toList("Customers", "USA", "CA"), true), sr.getMemberByUniqueName( Id.Segment.toList("Customers", "Mexico"), true)); assertAssignParameter( "Parameter(\"x\", [Customers].[State Province], {[Customers].[USA].[CA]})", true, list, "Invalid value '[Customers].[Mexico]' for parameter " + "'x', type MemberType"); // as above, strings assertAssignParameter( "Parameter(\"x\", [Customers].[State Province], {[Customers].[USA].[CA]})", true, "{[Customers].[USA].[CA], [Customers].[Mexico]}", "Invalid value '[Customers].[Mexico]' for parameter " + "'x', type MemberType"); // List that contains members of right level, and a null member. list = Arrays.asList( sr.getMemberByUniqueName( Id.Segment.toList("Customers", "USA", "CA"), true), null, sr.getMemberByUniqueName( Id.Segment.toList("Customers", "USA", "OR"), true)); assertAssignParameter( "Parameter(\"x\", [Customers].[State Province], {[Customers].[USA].[CA]})", true, list, null); } /** * Checks that assigning a given value to a parameter does (or, if * {@code expectedMsg} is null, does not) give an error. * * @param parameterMdx MDX expression declaring parameter * @param set Whether parameter is a set (as opposed to a member or scalar) * @param value Value to assign to parameter * @param expectedMsg Expected message, or null if it should succeed */ private void assertAssignParameter( String parameterMdx, boolean set, Object value, String expectedMsg) { Connection connection = getTestContext().getConnection(); try { String mdx = set ? "with set [Foo] as " + parameterMdx + " \n" + "select [Foo] on columns,\n" + "{Time.Time.Children} on rows\n" + "from [Sales]" : "with member [Measures].[s] as " + parameterMdx + " \n" + "select {[Measures].[s]} on columns,\n" + "{Time.Time.Children} on rows\n" + "from [Sales]"; Query query = connection.parseQuery(mdx); if (expectedMsg == null) { query.setParameter("x", value); final Result result = connection.execute(query); assertNotNull(result); } else { try { query.setParameter("x", value); final Result result = connection.execute(query); fail("expected error, got " + TestContext.toString(result)); } catch (Exception e) { TestContext.checkThrowable(e, expectedMsg); } } } finally { connection.close(); } } /** * Tests a parameter whose type is a set of members. */ public void testParamSet() { Connection connection = getTestContext().getConnection(); try { String mdx = "select [Measures].[Unit Sales] on 0,\n" + " Parameter(\"Foo\", [Time], {}, \"Foo\") on 1\n" + "from [Sales]"; Query query = connection.parseQuery(mdx); SchemaReader sr = query.getSchemaReader(false); Member m1 = sr.getMemberByUniqueName( Id.Segment.toList("Time", "1997", "Q2", "5"), true); Member m2 = sr.getMemberByUniqueName( Id.Segment.toList("Time", "1997", "Q3"), true); Parameter p = sr.getParameter("Foo"); final List list = Arrays.asList(m1, m2); p.setValue(list); assertEquals(list, p.getValue()); query.resolve(); p.setValue(list); assertEquals(list, p.getValue()); mdx = query.toString(); TestContext.assertEqualsVerbose( "select {[Measures].[Unit Sales]} ON COLUMNS,\n" + " Parameter(\"Foo\", [Time], {[Time].[1997].[Q2].[5], [Time].[1997].[Q3]}, \"Foo\") ON ROWS\n" + "from [Sales]\n", mdx); final Result result = connection.execute(query); TestContext.assertEqualsVerbose( "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q2].[5]}\n" + "{[Time].[1997].[Q3]}\n" + "Row #0: 21,081\n" + "Row #1: 65,848\n", TestContext.toString(result)); } finally { connection.close(); } } // -- Tests for connection properties -------------- /** * Tests that certain connection properties which should be null, are. */ public void testConnectionPropsWhichShouldBeNull() { // properties which must always return null assertExprReturns("ParamRef(\"JdbcPassword\")", ""); assertExprReturns("ParamRef(\"CatalogContent\")", ""); } /** * Tests that non-overrideable properties cannot be overridden in a * statement. */ public void testConnectionPropsCannotBeOverridden() { Set overrideableProps = Olap4jUtil.enumSetOf( RolapConnectionProperties.Catalog, RolapConnectionProperties.Locale); for (RolapConnectionProperties prop : RolapConnectionProperties.class.getEnumConstants()) { if (!overrideableProps.contains(prop)) { // try to override prop assertSetPropertyFails(prop.name(), "Connection"); } } } // -- Tests for system properties -------------- /** * Tests accessing system properties as parameters in a statement. */ public void testSystemPropsGet() { final List propertyList = MondrianProperties.instance().getPropertyList(); for (Property property : propertyList) { assertExprReturns( "ParamRef(" + Util.singleQuoteString(property.getPath()) + ")", property.stringValue()); } } /** * Tests getting a java system property. */ public void testSystemPropsGetJava() { final String javaVersion = System.getProperty("java.version"); assertExprReturns( "ParamRef(\"java.version\")", javaVersion); } /** * Tests getting a mondrian property. */ public void testMondrianPropsGetJava() { final String jdbcDrivers = MondrianProperties.instance().JdbcDrivers.get(); assertExprReturns( "ParamRef(\"mondrian.jdbcDrivers\")", jdbcDrivers); } /** * Tests setting system properties. */ public void testSystemPropsSet() { final List propertyList = MondrianProperties.instance().getPropertyList(); for (Property property : propertyList) { final String propName = property.getPath(); assertSetPropertyFails(propName, "System"); } } // -- Tests for schema properties -------------- /** * Tests a schema property with a default value. */ public void testSchemaProp() { final TestContext tc = TestContext.instance().create( "", null, null, null, null, null); tc.assertExprReturns("ParamRef(\"prop\")", "foo bar"); } /** * Tests a schema property with a default value. */ public void testSchemaPropDupFails() { final TestContext tc = TestContext.instance().create( "\n" + "\n" + "\n", null, null, null, null, null); tc.assertExprThrows( "ParamRef(\"foo\")", "Duplicate parameter 'foo' in schema"); } public void testSchemaPropIllegalTypeFails() { final TestContext tc = TestContext.instance().create( "", null, null, null, null, null); tc.assertExprThrows( "1", "In Schema: In Parameter: " + "Value 'Bad type' of attribute 'type' has illegal value 'Bad type'. " + "Legal values: {String, Numeric, Integer, Boolean, Date, Time, Timestamp, Member}"); } public void testSchemaPropInvalidDefaultExpFails() { final TestContext tc = TestContext.instance().create( "", null, null, null, null, null); tc.assertExprThrows( "ParamRef(\"Product Current Member\")", "No function matches signature '.Children()'"); } /** * Tests that a schema property fails if it references dimensions which * are not available. */ public void testSchemaPropContext() { final TestContext tc = TestContext.instance().create( "", null, null, null, null, null); tc.assertQueryReturns( "with member [Measures].[Foo] as ' ParamRef(\"Customer Current Member\").Name '\n" + "select {[Measures].[Foo]} on columns\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Foo]}\n" + "Row #0: USA\n"); tc.assertQueryThrows( "with member [Measures].[Foo] as ' ParamRef(\"Customer Current Member\").Name '\n" + "select {[Measures].[Foo]} on columns\n" + "from [Warehouse]", "MDX object '[Customers]' not found in cube 'Warehouse'"); } } // End ParameterTest.java mondrian-3.4.1/testsrc/main/mondrian/test/CaptionTest.java0000644000175000017500000012204111735330606023527 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; import mondrian.spi.DynamicSchemaProcessor; import junit.framework.Assert; import junit.framework.TestCase; import java.util.List; /** * Unit test special "caption" settings. * * @author hhaas */ public class CaptionTest extends TestCase { /** * set caption "Anzahl Verkauf" for measure "Unit Sales" */ public void testMeasureCaption() { final Connection monConnection = TestContext.instance() .withSchemaProcessor(MyFoodmart.class) .getConnection(); String mdxQuery = "SELECT {[Measures].[Unit Sales]} ON COLUMNS, " + "{[Time].[1997].[Q1]} ON ROWS FROM [Sales]"; mondrian.olap.Query monQuery = monConnection.parseQuery(mdxQuery); mondrian.olap.Result monResult = monConnection.execute(monQuery); Axis[] axes = monResult.getAxes(); List positions = axes[0].getPositions(); Member m0 = positions.get(0).get(0); String caption = m0.getCaption(); Assert.assertEquals("Anzahl Verkauf", caption); } /** * set caption "Werbemedium" for nonshared dimension "Promotion Media" */ public void testDimCaption() { final Connection monConnection = TestContext.instance() .withSchemaProcessor(MyFoodmart.class) .getConnection(); String mdxQuery = "SELECT {[Measures].[Unit Sales]} ON COLUMNS, " + "{[Promotion Media].[All Media]} ON ROWS FROM [Sales]"; mondrian.olap.Query monQuery = monConnection.parseQuery(mdxQuery); mondrian.olap.Result monResult = monConnection.execute(monQuery); Axis[] axes = monResult.getAxes(); List positions = axes[1].getPositions(); Member mall = positions.get(0).get(0); String caption = mall.getHierarchy().getCaption(); Assert.assertEquals("Werbemedium", caption); } /** * set caption "Quadrat-Fuesse:-)" for shared dimension "Store Size in SQFT" */ public void testDimCaptionShared() { String mdxQuery = "SELECT {[Measures].[Unit Sales]} ON COLUMNS, " + "{[Store Size in SQFT].[All Store Size in SQFTs]} ON ROWS " + "FROM [Sales]"; final Connection monConnection = TestContext.instance() .withSchemaProcessor(MyFoodmart.class) .getConnection(); mondrian.olap.Query monQuery = monConnection.parseQuery(mdxQuery); mondrian.olap.Result monResult = monConnection.execute(monQuery); Axis[] axes = monResult.getAxes(); List positions = axes[1].getPositions(); Member mall = positions.get(0).get(0); String caption = mall.getHierarchy().getCaption(); Assert.assertEquals("Quadrat-Fuesse:-)", caption); } /** * Tests the <CaptionExpression> element. The caption for * [Time].[1997] should be "1997-12-31". * *

Test case for * Bug MONDRIAN-683, * "Caption expression for dimension levels missing implementation". */ public void testLevelCaptionExpression() { TestContext tc = TestContext.instance(); switch (tc.getDialect().getDatabaseProduct()) { case ACCESS: case ORACLE: case MYSQL: break; default: // Due to provider-specific SQL in CaptionExpression, only Access, // Oracle and MySQL are supported in this test. return; } final Connection monConnection = tc.withSchemaProcessor(MyFoodmart.class) .getConnection(); String mdxQuery = "SELECT {[Measures].[Unit Sales]} ON COLUMNS, " + "{[Time].[Year].Members} ON ROWS FROM [Sales]"; mondrian.olap.Query monQuery = monConnection.parseQuery(mdxQuery); mondrian.olap.Result monResult = monConnection.execute(monQuery); Axis[] axes = monResult.getAxes(); List positions = axes[1].getPositions(); Member mall = positions.get(0).get(0); String caption = mall.getCaption(); Assert.assertEquals("1997-12-31", caption); } /** * created from foodmart.xml via perl script, * some captions added. */ public static class MyFoodmart implements DynamicSchemaProcessor { static final String foodmart = "\n" + "\n" + "\n" + " \n" + " \n" + "

\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + "cstr(the_year) + '-12-31'\n" + " \n" + " \n" + "concat(cast(`the_year` as char(4)), '-12-31')\n" + " \n" + " \n" + "'foobar'\n" + " \n" + " \n" + "\"the_year\" || '-12-31'\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "
\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\"fname\" || ' ' || \"lname\"\n" + " \n" + " \n" + "fname, ' ', lname\n" + " \n" + " \n" + "\"fname\" || ' ' || \"lname\"\n" + " \n" + " \n" + "CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)\n" + " \n" + " \n" + "fname, ' ', lname\n" + " \n" + " \n" + "lname\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "
\n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "\n" + "
\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\"fname\" || ' ' || \"lname\"\n" + " \n" + " \n" + "fname, ' ', lname\n" + " \n" + " \n" + "\"fname\" || ' ' || \"lname\"\n" + " \n" + " \n" + "CONCAT(`customer`.`fname`, ' ', `customer`.`lname`)\n" + " \n" + " \n" + "fname, ' ', lname\n" + " \n" + " \n" + "\"lname\"\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "\n" + " \n" + " \n" + " \n" + "\n" + ""; public String processSchema( String schemaUrl, Util.PropertyList properties) throws Exception { return foodmart; } } } // End CaptionTest.java mondrian-3.4.1/testsrc/main/mondrian/test/CompatibilityTest.java0000644000175000017500000005536311735330606024757 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 SAS Institute, Inc. // Copyright (C) 2006-2011 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; import mondrian.spi.Dialect; import junit.framework.Assert; /** * CompatibilityTest is a test case which tests * MDX syntax compatibility with Microsoft and SAS servers. * There is no MDX spec document per se, so compatibility with de-facto * standards from the major vendors is important. Uses the FoodMart database. * * @author sasebb * @since March 30, 2005 */ public class CompatibilityTest extends FoodMartTestCase { private boolean originalNeedDimensionPrefix; private final MondrianProperties props = MondrianProperties.instance(); public CompatibilityTest(String name) { super(name); originalNeedDimensionPrefix = props.NeedDimensionPrefix.get(); } /** * Cube names are case insensitive. */ public void testCubeCase() { String queryFrom = "select {[Measures].[Unit Sales]} on columns from "; String result = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 266,773\n"; assertQueryReturns(queryFrom + "[Sales]", result); assertQueryReturns(queryFrom + "[SALES]", result); assertQueryReturns(queryFrom + "[sAlEs]", result); assertQueryReturns(queryFrom + "[sales]", result); } /** * Brackets around cube names are optional. */ public void testCubeBrackets() { String queryFrom = "select {[Measures].[Unit Sales]} on columns from "; String result = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 266,773\n"; assertQueryReturns(queryFrom + "Sales", result); assertQueryReturns(queryFrom + "SALES", result); assertQueryReturns(queryFrom + "sAlEs", result); assertQueryReturns(queryFrom + "sales", result); } /** * See how we are at diagnosing reserved words. */ public void testReservedWord() { assertAxisThrows( "with member [Measures].ordinal as '1'\n" + " select {[Measures].ordinal} on columns from Sales", "Syntax error"); assertQueryReturns( "with member [Measures].[ordinal] as '1'\n" + " select {[Measures].[ordinal]} on columns from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[ordinal]}\n" + "Row #0: 1\n"); } /** * Dimension names are case insensitive. */ public void testDimensionCase() { checkAxis("[Measures].[Unit Sales]", "[Measures].[Unit Sales]"); checkAxis("[Measures].[Unit Sales]", "[MEASURES].[Unit Sales]"); checkAxis("[Measures].[Unit Sales]", "[mEaSuReS].[Unit Sales]"); checkAxis("[Measures].[Unit Sales]", "[measures].[Unit Sales]"); checkAxis("[Customers].[All Customers]", "[Customers].[All Customers]"); checkAxis("[Customers].[All Customers]", "[CUSTOMERS].[All Customers]"); checkAxis("[Customers].[All Customers]", "[cUsToMeRs].[All Customers]"); checkAxis("[Customers].[All Customers]", "[customers].[All Customers]"); } /** * Brackets around dimension names are optional. */ public void testDimensionBrackets() { checkAxis("[Measures].[Unit Sales]", "Measures.[Unit Sales]"); checkAxis("[Measures].[Unit Sales]", "MEASURES.[Unit Sales]"); checkAxis("[Measures].[Unit Sales]", "mEaSuReS.[Unit Sales]"); checkAxis("[Measures].[Unit Sales]", "measures.[Unit Sales]"); checkAxis("[Customers].[All Customers]", "Customers.[All Customers]"); checkAxis("[Customers].[All Customers]", "CUSTOMERS.[All Customers]"); checkAxis("[Customers].[All Customers]", "cUsToMeRs.[All Customers]"); checkAxis("[Customers].[All Customers]", "customers.[All Customers]"); } /** * Member names are case insensitive. */ public void testMemberCase() { checkAxis("[Measures].[Unit Sales]", "[Measures].[UNIT SALES]"); checkAxis("[Measures].[Unit Sales]", "[Measures].[uNiT sAlEs]"); checkAxis("[Measures].[Unit Sales]", "[Measures].[unit sales]"); checkAxis("[Measures].[Profit]", "[Measures].[Profit]"); checkAxis("[Measures].[Profit]", "[Measures].[pRoFiT]"); checkAxis("[Measures].[Profit]", "[Measures].[PROFIT]"); checkAxis("[Measures].[Profit]", "[Measures].[profit]"); checkAxis("[Customers].[All Customers]", "[Customers].[All Customers]"); checkAxis("[Customers].[All Customers]", "[Customers].[ALL CUSTOMERS]"); checkAxis("[Customers].[All Customers]", "[Customers].[aLl CuStOmErS]"); checkAxis("[Customers].[All Customers]", "[Customers].[all customers]"); checkAxis("[Customers].[Mexico]", "[Customers].[Mexico]"); checkAxis("[Customers].[Mexico]", "[Customers].[MEXICO]"); checkAxis("[Customers].[Mexico]", "[Customers].[mExIcO]"); checkAxis("[Customers].[Mexico]", "[Customers].[mexico]"); } /** * Calculated member names are case insensitive. */ public void testCalculatedMemberCase() { propSaver.set(MondrianProperties.instance().CaseSensitive, false); assertQueryReturns( "with member [Measures].[CaLc] as '1'\n" + " select {[Measures].[CaLc]} on columns from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[CaLc]}\n" + "Row #0: 1\n"); assertQueryReturns( "with member [Measures].[CaLc] as '1'\n" + " select {[Measures].[cAlC]} on columns from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[CaLc]}\n" + "Row #0: 1\n"); assertQueryReturns( "with member [mEaSuReS].[CaLc] as '1'\n" + " select {[MeAsUrEs].[cAlC]} on columns from Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[CaLc]}\n" + "Row #0: 1\n"); } /** * Solve order is case insensitive. */ public void testSolveOrderCase() { checkSolveOrder("SOLVE_ORDER"); checkSolveOrder("SoLvE_OrDeR"); checkSolveOrder("solve_order"); } private void checkSolveOrder(String keyword) { assertQueryReturns( "WITH\n" + " MEMBER [Store].[StoreCalc] as '0', " + keyword + "=0\n" + " MEMBER [Product].[ProdCalc] as '1', " + keyword + "=1\n" + "SELECT\n" + " { [Product].[ProdCalc] } ON columns,\n" + " { [Store].[StoreCalc] } ON rows\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[ProdCalc]}\n" + "Axis #2:\n" + "{[Store].[StoreCalc]}\n" + "Row #0: 1\n"); } /** * Brackets around member names are optional. */ public void testMemberBrackets() { checkAxis("[Measures].[Profit]", "[Measures].Profit"); checkAxis("[Measures].[Profit]", "[Measures].pRoFiT"); checkAxis("[Measures].[Profit]", "[Measures].PROFIT"); checkAxis("[Measures].[Profit]", "[Measures].profit"); checkAxis( "[Customers].[Mexico]", "[Customers].Mexico"); checkAxis( "[Customers].[Mexico]", "[Customers].MEXICO"); checkAxis( "[Customers].[Mexico]", "[Customers].mExIcO"); checkAxis( "[Customers].[Mexico]", "[Customers].mexico"); } /** * Hierarchy names of the form [Dim].[Hier], [Dim.Hier], and * Dim.Hier are accepted. */ public void testHierarchyNames() { checkAxis("[Customers].[All Customers]", "[Customers].[All Customers]"); checkAxis( "[Customers].[All Customers]", "[Customers].[Customers].[All Customers]"); checkAxis( "[Customers].[All Customers]", "Customers.[Customers].[All Customers]"); checkAxis( "[Customers].[All Customers]", "[Customers].Customers.[All Customers]"); if (false) { // don't know if this makes sense checkAxis( "[Customers].[All Customers]", "[Customers.Customers].[All Customers]"); } } private void checkAxis(String result, String expression) { Assert.assertEquals( result, executeSingletonAxis(expression).toString()); } /** * Tests that a #null member on a Hiearchy Level of type String can * still be looked up when case sensitive is off. */ public void testCaseInsensitiveNullMember() { final Dialect dialect = getTestContext().getDialect(); if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.LUCIDDB) { // TODO jvs 29-Nov-2006: LucidDB is strict about // null literals (type can't be inferred in this context); // maybe enhance the inline table to use the columndef // types to apply a CAST. return; } if (!isDefaultNullMemberRepresentation()) { return; } final String cubeName = "Sales_inline"; final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 0\n" + " Promo0\n" + " \n" + " \n" + " 1\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); // This test should work irrespective of the case-sensitivity setting. Util.discard(props.CaseSensitive.get()); testContext.assertQueryReturns( "select {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Alternative Promotion].[#null]} ON ROWS \n" + " from [Sales_inline]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Alternative Promotion].[#null]}\n" + "Row #0: \n"); } /** * Tests that data in Hierarchy.Level attribute "nameColumn" can be null. * This will map to the #null memeber. */ public void testNullNameColumn() { switch (getTestContext().getDialect().getDatabaseProduct()) { case LUCIDDB: // TODO jvs 29-Nov-2006: See corresponding comment in // testCaseInsensitiveNullMember return; case HSQLDB: // This test exposes a bug in hsqldb. The following query should // return 1 row, but returns none. // // select "alt_promotion"."promo_id" as "c0", // "alt_promotion"."promo_name" as "c1" // from ( // select 0 as "promo_id", null as "promo_name" // from "days" where "day" = 1 // union all // select 1 as "promo_id", 'Promo1' as "promo_name" // from "days" where "day" = 1) as "alt_promotion" // where UPPER("alt_promotion"."promo_name") = UPPER('Promo1') // group by "alt_promotion"."promo_id", // "alt_promotion"."promo_name" // order by // CASE WHEN "alt_promotion"."promo_id" IS NULL THEN 1 ELSE 0 END, // "alt_promotion"."promo_id" ASC return; } if (!isDefaultNullMemberRepresentation()) { return; } final String cubeName = "Sales_inline"; final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 0\n" + " \n" + " \n" + " 1\n" + " Promo1\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select {" + "[Alternative Promotion].[#null], " + "[Alternative Promotion].[Promo1]} ON COLUMNS\n" + "from [" + cubeName + "] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Alternative Promotion].[#null]}\n" + "{[Alternative Promotion].[Promo1]}\n" + "Row #0: 195,448\n" + "Row #0: \n"); } /** * Tests that NULL values sort last on all platforms. On some platforms, * such as MySQL, NULLs naturally come before other values, so we have to * generate a modified ORDER BY clause. */ public void testNullCollation() { if (!getTestContext().getDialect().supportsGroupByExpressions()) { // Derby does not support expressions in the GROUP BY clause, // therefore this testing strategy of using an expression for the // store key won't work. Give the test a bye. return; } final String cubeName = "Store_NullsCollation"; final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " Iif(store_name = 'HQ', null, store_name)\n" + " \n" + " \n" + " case \"store_name\" when 'HQ' then null else \"store_name\" end\n" + " \n" + " \n" + " case \"store_name\" when 'HQ' then null else \"store_name\" end\n" + " \n" + " \n" + " case \"store\".\"store_name\" when 'HQ' then null else \"store\".\"store_name\" end\n" + " \n" + " \n" + " case \"store_name\" when 'HQ' then null else \"store_name\" end\n" + " \n" + " \n" + " case \"store_name\" when 'HQ' then null else \"store_name\" end\n" + " \n" + " \n" + " case store_name when 'HQ' then null else store_name end\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select { [Measures].[Store Sqft] } on columns,\n" + " NON EMPTY topcount(\n" + " {[Store].[Store Name].members},\n" + " 5,\n" + " [measures].[store sqft]) on rows\n" + "from [" + cubeName + "] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sqft]}\n" + "Axis #2:\n" + "{[Store].[Store 3]}\n" + "{[Store].[Store 18]}\n" + "{[Store].[Store 9]}\n" + "{[Store].[Store 10]}\n" + "{[Store].[Store 20]}\n" + "Row #0: 39,696\n" + "Row #1: 38,382\n" + "Row #2: 36,509\n" + "Row #3: 34,791\n" + "Row #4: 34,452\n"); } /** * Tests that property names are case sensitive iff the * "mondrian.olap.case.sensitive" property is set. * *

The test does not alter this property: for testing coverage, we assume * that you run the test once with mondrian.olap.case.sensitive=true, * and once with mondrian.olap.case.sensitive=false. */ public void testPropertyCaseSensitivity() { boolean caseSensitive = props.CaseSensitive.get(); // A user-defined property of a member. assertExprReturns( "[Store].[USA].[CA].[Beverly Hills].[Store 6].Properties(\"Store Type\")", "Gourmet Supermarket"); if (caseSensitive) { assertExprThrows( "[Store].[USA].[CA].[Beverly Hills].[Store 6].Properties(\"store tYpe\")", "Property 'store tYpe' is not valid for member '[Store].[USA].[CA].[Beverly Hills].[Store 6]'"); } else { assertExprReturns( "[Store].[USA].[CA].[Beverly Hills].[Store 6].Properties(\"store tYpe\")", "Gourmet Supermarket"); } // A builtin property of a member. assertExprReturns( "[Store].[USA].[CA].[Beverly Hills].[Store 6].Properties(\"LEVEL_NUMBER\")", "4"); if (caseSensitive) { assertExprThrows( "[Store].[USA].[CA].[Beverly Hills].[Store 6].Properties(\"Level_Number\")", "Property 'store tYpe' is not valid for member '[Store].[USA].[CA].[Beverly Hills].[Store 6]'"); } else { assertExprReturns( "[Store].[USA].[CA].[Beverly Hills].[Store 6].Properties(\"Level_Number\")", "4"); } // Cell properties. Result result = executeQuery( "select {[Measures].[Unit Sales],[Measures].[Store Sales]} on columns,\n" + " {[Gender].[M]} on rows\n" + "from Sales"); Cell cell = result.getCell(new int[]{0, 0}); assertEquals("135,215", cell.getPropertyValue("FORMATTED_VALUE")); if (caseSensitive) { assertNull(cell.getPropertyValue("Formatted_Value")); } else { assertEquals("135,215", cell.getPropertyValue("Formatted_Value")); } } public void testWithDimensionPrefix() { assertAxisWithDimensionPrefix(true); assertAxisWithDimensionPrefix(false); } private void assertAxisWithDimensionPrefix(boolean prefixNeeded) { propSaver.set( props.NeedDimensionPrefix, prefixNeeded); assertAxisReturns("[Gender].[M]", "[Gender].[M]"); assertAxisReturns("[Gender].[All Gender].[M]", "[Gender].[M]"); assertAxisReturns("[Store].[USA]", "[Store].[USA]"); assertAxisReturns("[Store].[All Stores].[USA]", "[Store].[USA]"); } public void testWithNoDimensionPrefix() { propSaver.set( props.NeedDimensionPrefix, false); assertAxisReturns("{[M]}", "[Gender].[M]"); assertAxisReturns("{M}", "[Gender].[M]"); assertAxisReturns("{[USA].[CA]}", "[Store].[USA].[CA]"); assertAxisReturns("{USA.CA}", "[Store].[USA].[CA]"); propSaver.set( props.NeedDimensionPrefix, true); assertAxisThrows( "{[M]}", "Mondrian Error:MDX object '[M]' not found in cube 'Sales'"); assertAxisThrows( "{M}", "Mondrian Error:MDX object '[M]' not found in cube 'Sales'"); assertAxisThrows( "{[USA].[CA]}", "Mondrian Error:MDX object '[USA].[CA]' not found in cube 'Sales'"); assertAxisThrows( "{USA.CA}", "Mondrian Error:MDX object '[USA].[CA]' not found in cube 'Sales'"); } } // End CompatibilityTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/0000755000175000017500000000000011735330606022410 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/test/clearview/SummaryMetricPercentTest.ref.xml0000644000175000017500000030762111735330606030700 0ustar drazzibdrazzib 50000.0)' Set [*BASE_MEMBERS_Measures] as '{[Measures].[*SUMMARY_METRIC_0],[Measures].[*SUMMARY_METRIC_1],[Measures].[Unit Sales]}' Set [*BASE_MEMBERS_Product] as '[Product].[Product Department].Members' Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' Member [Product].[Drink].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Product].[Drink]})' Member [Product].[Food].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Product].[Food]})' Member [Product].[Non-Consumable].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Product].[Non-Consumable]})' Member [Measures].[*Unit Sales_SEL~SUM] as '([Measures].[Unit Sales],Ancestor([Product].CurrentMember,[Product].[Product Family]).CalculatedChild("*CTX_METRIC_MEMBER_SEL~SUM"))' Member [Product].[Drink].[*CTX_MEMBER_SEL~AGG] as 'Aggregate({[Product].[Drink]})' Member [Product].[Food].[*CTX_MEMBER_SEL~AGG] as 'Aggregate({[Product].[Food]})' Member [Product].[Non-Consumable].[*CTX_MEMBER_SEL~AGG] as 'Aggregate({[Product].[Non-Consumable]})' Member [Measures].[*SUMMARY_METRIC_0] as '[Measures].[Unit Sales]/([Measures].[Unit Sales],Ancestor([Product].CurrentMember,[Product].[Product Family]).CalculatedChild("*CTX_MEMBER_SEL~AGG"))', FORMAT_STRING = '0.00%' Member [Product].[*CTX_MEMBER_SEL~AGG] as 'Aggregate(Filter([*METRIC_MEMBERS_Product],[Measures].[*Unit Sales_SEL~SUM] > 50000.0))' Member [Measures].[*SUMMARY_METRIC_1] as '[Measures].[Unit Sales]/([Measures].[Unit Sales],[Product].[*CTX_MEMBER_SEL~AGG])', FORMAT_STRING = '0.00%' Member [Product].[Drink].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[Drink]})' Member [Product].[Food].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[Food]})' Member [Product].[Non-Consumable].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[Non-Consumable]})' Member [Product].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter([*METRIC_MEMBERS_Product],[Measures].[*Unit Sales_SEL~SUM] > 50000.0))' Select [*BASE_MEMBERS_Measures] on columns, Non Empty Union({[Product].[*CTX_MEMBER_SEL~SUM]},Union({[Product].[Drink].[*CTX_MEMBER_SEL~SUM],[Product].[Food].[*CTX_MEMBER_SEL~SUM],[Product].[Non-Consumable].[*CTX_MEMBER_SEL~SUM]},Generate([*METRIC_CJ_SET], {([Product].CurrentMember)}))) on rows From [Sales] ]]> 5000.0)' Set [*BASE_MEMBERS_Measures] as '{[Measures].[*SUMMARY_METRIC_0],[Measures].[*SUMMARY_METRIC_1],[Measures].[Unit Sales]}' Set [*BASE_MEMBERS_Product] as '[Product].[Product Department].Members' Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' Member [Measures].[*Unit Sales_SEL~SUM] as '([Measures].[Unit Sales],[Product].CurrentMember)' Member [Product].[Drink].[*CTX_MEMBER_SEL~AGG] as 'Aggregate(Filter(Descendants([Product].[Drink],[Product].[Product Department]),[Measures].[*Unit Sales_SEL~SUM] > 5000.0))' Member [Product].[Food].[*CTX_MEMBER_SEL~AGG] as 'Aggregate(Filter(Descendants([Product].[Food],[Product].[Product Department]),[Measures].[*Unit Sales_SEL~SUM] > 5000.0))' Member [Product].[Non-Consumable].[*CTX_MEMBER_SEL~AGG] as 'Aggregate(Filter(Descendants([Product].[Non-Consumable],[Product].[Product Department]),[Measures].[*Unit Sales_SEL~SUM] > 5000.0))' Member [Measures].[*SUMMARY_METRIC_0] as '[Measures].[Unit Sales]/([Measures].[Unit Sales],Ancestor([Product].CurrentMember,[Product].[Product Family]).CalculatedChild("*CTX_MEMBER_SEL~AGG"))', FORMAT_STRING = '0.00%' Member [Product].[*CTX_MEMBER_SEL~AGG] as 'Aggregate(Filter([*METRIC_MEMBERS_Product],[Measures].[*Unit Sales_SEL~SUM] > 5000.0))' Member [Measures].[*SUMMARY_METRIC_1] as '[Measures].[Unit Sales]/([Measures].[Unit Sales],[Product].[*CTX_MEMBER_SEL~AGG])', FORMAT_STRING = '0.00%' Member [Product].[Drink].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter(Descendants([Product].[Drink],[Product].[Product Department]),[Measures].[*Unit Sales_SEL~SUM] > 5000.0))' Member [Product].[Food].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter(Descendants([Product].[Food],[Product].[Product Department]),[Measures].[*Unit Sales_SEL~SUM] > 5000.0))' Member [Product].[Non-Consumable].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter(Descendants([Product].[Non-Consumable],[Product].[Product Department]),[Measures].[*Unit Sales_SEL~SUM] > 5000.0))' Member [Product].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter([*METRIC_MEMBERS_Product],[Measures].[*Unit Sales_SEL~SUM] > 5000.0))' Select [*BASE_MEMBERS_Measures] on columns, Non Empty Union({[Product].[*CTX_MEMBER_SEL~SUM]},Union({[Product].[Drink].[*CTX_MEMBER_SEL~SUM],[Product].[Food].[*CTX_MEMBER_SEL~SUM],[Product].[Non-Consumable].[*CTX_MEMBER_SEL~SUM]},Generate([*METRIC_CJ_SET], {([Product].CurrentMember)}))) on rows From [Sales] ]]> mondrian-3.4.1/testsrc/main/mondrian/test/clearview/QueryAllTest.ref.xml0000644000175000017500000000112011735330606026275 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/test/clearview/MultiDimTest.ref.xml0000644000175000017500000000710611735330606026275 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/test/clearview/PartialCacheVCTest.ref.xml0000644000175000017500000000622011735330606027316 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/test/clearview/PartialCacheTest.java0000644000175000017500000000261611735330606026440 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2007 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * PartialCacheTest is a test suite which tests * complex queries against the FoodMart database. MDX queries and their * expected results are maintained separately in PartialCacheTest.ref.xml file. * If you would prefer to see them as inlined Java string literals, run * ant target "generateDiffRepositoryJUnit" and then use * file PartialCacheTestJUnit.java which will be generated in this directory. * * @author Khanh Vu */ public class PartialCacheTest extends ClearViewBase { public PartialCacheTest() { super(); } public PartialCacheTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(PartialCacheTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), PartialCacheTest.class); } } // End PartialCacheTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/GrandTotalTest.java0000644000175000017500000000261411735330606026155 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * GrandTotalTest is a test suite which tests scenarios of * using grand total against the FoodMart database. MDX queries and their * expected results are maintained separately in GrandTotalTest.ref.xml file. * If you would prefer to see them as inlined Java string literals, run * ant target "generateDiffRepositoryJUnit" and then use * file GrandTotalTestJUnit.java which will be generated in this directory. * * @author Khanh Vu */ public class GrandTotalTest extends ClearViewBase { public GrandTotalTest() { super(); } public GrandTotalTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(GrandTotalTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), GrandTotalTest.class); } } // End GrandTotalTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/MiscTest.ref.xml0000644000175000017500000002147311735330606025447 0ustar drazzibdrazzib ]]> mondrian-3.4.1/testsrc/main/mondrian/test/clearview/TopBottomTest.ref.xml0000644000175000017500000016122211735330606026500 0ustar drazzibdrazzib 100000.0),[Measures].[*TOP_Unit Sales_SEL~SUM] <= 3)' Set [*TOP_BOTTOM_SET] as 'Order({[Product].[Drink].[*CTX_METRIC_MEMBER_SEL~SUM],[Product].[Food].[*CTX_METRIC_MEMBER_SEL~SUM],[Product].[Non-Consumable].[*CTX_METRIC_MEMBER_SEL~SUM]},([Measures].[Unit Sales]),BDESC)' Set [*BASE_MEMBERS_Measures] as '{[Measures].[Unit Sales]}' Set [*BASE_MEMBERS_Product] as '[Product].[Product Department].Members' Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' Member [Product].[Drink].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Product].[Drink]})' Member [Product].[Food].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Product].[Food]})' Member [Product].[Non-Consumable].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Product].[Non-Consumable]})' Member [Measures].[*Unit Sales_SEL~SUM] as '([Measures].[Unit Sales],Ancestor([Product].CurrentMember,[Product].[Product Family]).CalculatedChild("*CTX_METRIC_MEMBER_SEL~SUM"))' Member [Measures].[*TOP_Unit Sales_SEL~SUM] as 'Rank(Ancestor([Product].CurrentMember,[Product].[Product Family]).CalculatedChild("*CTX_METRIC_MEMBER_SEL~SUM"),[*TOP_BOTTOM_SET])' Member [Product].[Drink].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[Drink]})' Member [Product].[Food].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[Food]})' Member [Product].[Non-Consumable].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[Non-Consumable]})' Member [Product].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter(Filter([*METRIC_MEMBERS_Product],[Measures].[*Unit Sales_SEL~SUM] > 100000.0),[Measures].[*TOP_Unit Sales_SEL~SUM] <= 3))' Select [*BASE_MEMBERS_Measures] on columns, Non Empty Union({[Product].[*CTX_MEMBER_SEL~SUM]},Union({[Product].[Drink].[*CTX_MEMBER_SEL~SUM],[Product].[Food].[*CTX_MEMBER_SEL~SUM],[Product].[Non-Consumable].[*CTX_MEMBER_SEL~SUM]},Generate([*METRIC_CJ_SET], {([Product].CurrentMember)}))) on rows From [Sales] ]]> mondrian-3.4.1/testsrc/main/mondrian/test/clearview/BatchedFillTest.ref.xml0000644000175000017500000001332211735330606026707 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/test/clearview/MetricFilterTest.ref.xml0000644000175000017500000023312711735330606027146 0ustar drazzibdrazzib 500.0)' Set [*BASE_MEMBERS_Store] as '{[Store].[USA].[CA]}' Set [*NATIVE_MEMBERS_Store] as 'Generate([*NATIVE_CJ_SET], {[Store].CurrentMember})' Set [*METRIC_MEMBERS_Store] as 'Generate([*METRIC_CJ_SET], {[Store].CurrentMember})' Set [*BASE_MEMBERS_Measures] as '{[Measures].[Unit Sales]}' Set [*BASE_MEMBERS_Product] as '[Product].[Product Department].Members' Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' Set [*BASE_MEMBERS_Education Level] as '[Education Level].[Education Level].Members' Set [*NATIVE_MEMBERS_Education Level] as 'Generate([*NATIVE_CJ_SET], {[Education Level].CurrentMember})' Set [*METRIC_MEMBERS_Education Level] as 'Generate([*METRIC_CJ_SET], {[Education Level].CurrentMember})' Member [Store].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum([*NATIVE_MEMBERS_Store])' Member [Measures].[*Unit Sales_SEL~SUM] as '([Measures].[Unit Sales],[Product].CurrentMember,[Education Level].CurrentMember,[Store].[*CTX_METRIC_MEMBER_SEL~SUM])' Member [Store].[*CTX_MEMBER_SEL~SUM] as 'Sum([*METRIC_MEMBERS_Store])' Member [Product].[Drink].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[Drink]})' Member [Product].[Food].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[Food]})' Member [Product].[Non-Consumable].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[Non-Consumable]})' Member [Education Level].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter([*METRIC_MEMBERS_Education Level],[Measures].[*Unit Sales_SEL~SUM] > 500.0))' Member [Product].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[All Products]})' Select Union(CrossJoin({[Store].[*CTX_MEMBER_SEL~SUM]},[*BASE_MEMBERS_Measures]),CrossJoin(Generate([*METRIC_CJ_SET], {([Store].CurrentMember)}),[*BASE_MEMBERS_Measures])) on columns, Non Empty Union(CrossJoin({[Product].[*CTX_MEMBER_SEL~SUM]},{[Education Level].[*CTX_MEMBER_SEL~SUM]}),Union(CrossJoin(Generate([*METRIC_CJ_SET], {([Product].CurrentMember)}),{[Education Level].[*CTX_MEMBER_SEL~SUM]}),Union(CrossJoin({[Product].[Drink].[*CTX_MEMBER_SEL~SUM],[Product].[Food].[*CTX_MEMBER_SEL~SUM],[Product].[Non-Consumable].[*CTX_MEMBER_SEL~SUM]},{[Education Level].[*CTX_MEMBER_SEL~SUM]}),Generate([*METRIC_CJ_SET], {([Product].CurrentMember,[Education Level].CurrentMember)})))) on rows From [Sales] ]]> 5000.0)' Set [*BASE_MEMBERS_Store] as '{[Store].[USA].[CA]}' Set [*NATIVE_MEMBERS_Store] as 'Generate([*NATIVE_CJ_SET], {[Store].CurrentMember})' Set [*METRIC_MEMBERS_Store] as 'Generate([*METRIC_CJ_SET], {[Store].CurrentMember})' Set [*BASE_MEMBERS_Measures] as '{[Measures].[Unit Sales]}' Set [*BASE_MEMBERS_Product] as '[Product].[Product Department].Members' Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' Set [*BASE_MEMBERS_Education Level] as '[Education Level].[Education Level].Members' Set [*NATIVE_MEMBERS_Education Level] as 'Generate([*NATIVE_CJ_SET], {[Education Level].CurrentMember})' Set [*METRIC_MEMBERS_Education Level] as 'Generate([*METRIC_CJ_SET], {[Education Level].CurrentMember})' Member [Education Level].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Education Level].[All Education Levels]})' Member [Store].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum([*NATIVE_MEMBERS_Store])' Member [Measures].[*Unit Sales_SEL~SUM] as '([Measures].[Unit Sales],[Product].CurrentMember,[Education Level].[*CTX_METRIC_MEMBER_SEL~SUM],[Store].[*CTX_METRIC_MEMBER_SEL~SUM])' Member [Store].[*CTX_MEMBER_SEL~SUM] as 'Sum([*METRIC_MEMBERS_Store])' Member [Product].[Drink].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter(Descendants([Product].[Drink],[Product].[Product Department]),[Measures].[*Unit Sales_SEL~SUM] > 5000.0))' Member [Product].[Food].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter(Descendants([Product].[Food],[Product].[Product Department]),[Measures].[*Unit Sales_SEL~SUM] > 5000.0))' Member [Product].[Non-Consumable].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter(Descendants([Product].[Non-Consumable],[Product].[Product Department]),[Measures].[*Unit Sales_SEL~SUM] > 5000.0))' Member [Education Level].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Education Level].[All Education Levels]})' Member [Product].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter([*METRIC_MEMBERS_Product],[Measures].[*Unit Sales_SEL~SUM] > 5000.0))' Select Union(CrossJoin({[Store].[*CTX_MEMBER_SEL~SUM]},[*BASE_MEMBERS_Measures]),CrossJoin(Generate([*METRIC_CJ_SET], {([Store].CurrentMember)}),[*BASE_MEMBERS_Measures])) on columns, Non Empty Union(CrossJoin({[Product].[*CTX_MEMBER_SEL~SUM]},{[Education Level].[*CTX_MEMBER_SEL~SUM]}),Union(CrossJoin(Generate([*METRIC_CJ_SET], {([Product].CurrentMember)}),{[Education Level].[*CTX_MEMBER_SEL~SUM]}),Union(CrossJoin({[Product].[Drink].[*CTX_MEMBER_SEL~SUM],[Product].[Food].[*CTX_MEMBER_SEL~SUM],[Product].[Non-Consumable].[*CTX_MEMBER_SEL~SUM]},{[Education Level].[*CTX_MEMBER_SEL~SUM]}),Generate([*METRIC_CJ_SET], {([Product].CurrentMember,[Education Level].CurrentMember)})))) on rows From [Sales] ]]> 50000.0)' Set [*BASE_MEMBERS_Store] as '{[Store].[USA].[CA]}' Set [*NATIVE_MEMBERS_Store] as 'Generate([*NATIVE_CJ_SET], {[Store].CurrentMember})' Set [*METRIC_MEMBERS_Store] as 'Generate([*METRIC_CJ_SET], {[Store].CurrentMember})' Set [*BASE_MEMBERS_Measures] as '{[Measures].[Unit Sales]}' Set [*BASE_MEMBERS_Product] as '[Product].[Product Department].Members' Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' Set [*BASE_MEMBERS_Education Level] as '[Education Level].[Education Level].Members' Set [*NATIVE_MEMBERS_Education Level] as 'Generate([*NATIVE_CJ_SET], {[Education Level].CurrentMember})' Set [*METRIC_MEMBERS_Education Level] as 'Generate([*METRIC_CJ_SET], {[Education Level].CurrentMember})' Member [Product].[Drink].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Product].[Drink]})' Member [Product].[Food].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Product].[Food]})' Member [Product].[Non-Consumable].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Product].[Non-Consumable]})' Member [Education Level].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Education Level].[All Education Levels]})' Member [Store].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum([*NATIVE_MEMBERS_Store])' Member [Measures].[*Unit Sales_SEL~SUM] as '([Measures].[Unit Sales],Ancestor([Product].CurrentMember,[Product].[Product Family]).CalculatedChild("*CTX_METRIC_MEMBER_SEL~SUM"),[Education Level].[*CTX_METRIC_MEMBER_SEL~SUM],[Store].[*CTX_METRIC_MEMBER_SEL~SUM])' Member [Store].[*CTX_MEMBER_SEL~SUM] as 'Sum([*METRIC_MEMBERS_Store])' Member [Product].[Drink].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[Drink]})' Member [Product].[Food].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[Food]})' Member [Product].[Non-Consumable].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[Non-Consumable]})' Member [Education Level].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Education Level].[All Education Levels]})' Member [Product].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter([*METRIC_MEMBERS_Product],[Measures].[*Unit Sales_SEL~SUM] > 50000.0))' Select Union(CrossJoin({[Store].[*CTX_MEMBER_SEL~SUM]},[*BASE_MEMBERS_Measures]),CrossJoin(Generate([*METRIC_CJ_SET], {([Store].CurrentMember)}),[*BASE_MEMBERS_Measures])) on columns, Non Empty Union(CrossJoin({[Product].[*CTX_MEMBER_SEL~SUM]},{[Education Level].[*CTX_MEMBER_SEL~SUM]}),Union(CrossJoin(Generate([*METRIC_CJ_SET], {([Product].CurrentMember)}),{[Education Level].[*CTX_MEMBER_SEL~SUM]}),Union(CrossJoin({[Product].[Drink].[*CTX_MEMBER_SEL~SUM],[Product].[Food].[*CTX_MEMBER_SEL~SUM],[Product].[Non-Consumable].[*CTX_MEMBER_SEL~SUM]},{[Education Level].[*CTX_MEMBER_SEL~SUM]}),Generate([*METRIC_CJ_SET], {([Product].CurrentMember,[Education Level].CurrentMember)})))) on rows From [Sales] ]]> 50000.0)' Set [*BASE_MEMBERS_Measures] as '{[Measures].[Unit Sales]}' Set [*BASE_MEMBERS_Product] as '[Product].[Product Department].Members' Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' Member [Product].[Drink].[*CTX_METRIC_MEMBER_ALL~SUM] as 'Sum({[Product].[Drink]})' Member [Product].[Food].[*CTX_METRIC_MEMBER_ALL~SUM] as 'Sum({[Product].[Food]})' Member [Product].[Non-Consumable].[*CTX_METRIC_MEMBER_ALL~SUM] as 'Sum({[Product].[Non-Consumable]})' Member [Measures].[*Unit Sales_ALL~SUM] as '([Measures].[Unit Sales],Ancestor([Product].CurrentMember,[Product].[Product Family]).CalculatedChild("*CTX_METRIC_MEMBER_ALL~SUM"))' Member [Product].[Drink].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[Drink]})' Member [Product].[Food].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[Food]})' Member [Product].[Non-Consumable].[*CTX_MEMBER_SEL~SUM] as 'Sum({[Product].[Non-Consumable]})' Member [Product].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter([*METRIC_MEMBERS_Product],[Measures].[*Unit Sales_ALL~SUM] > 50000.0))' Member [Product].[Drink].[*CTX_MEMBER_ALL~SUM] as 'Sum({[Product].[Drink]})' Member [Product].[Food].[*CTX_MEMBER_ALL~SUM] as 'Sum({[Product].[Food]})' Member [Product].[Non-Consumable].[*CTX_MEMBER_ALL~SUM] as 'Sum({[Product].[Non-Consumable]})' Member [Product].[*CTX_MEMBER_ALL~SUM] as 'Sum({[Product].[All Products]})' Select [*BASE_MEMBERS_Measures] on columns, Non Empty Union({[Product].[*CTX_MEMBER_ALL~SUM]},Union({[Product].[Drink].[*CTX_MEMBER_ALL~SUM],[Product].[Food].[*CTX_MEMBER_ALL~SUM],[Product].[Non-Consumable].[*CTX_MEMBER_ALL~SUM]},Union({[Product].[*CTX_MEMBER_SEL~SUM]},Union({[Product].[Drink].[*CTX_MEMBER_SEL~SUM],[Product].[Food].[*CTX_MEMBER_SEL~SUM],[Product].[All Products].[Non-Consumable].[*CTX_MEMBER_SEL~SUM]},Generate([*METRIC_CJ_SET], {([Product].CurrentMember)}))))) on rows From [Sales] ]]> 5000.0)' Set [*BASE_MEMBERS_Measures] as '{[Measures].[Unit Sales]}' Set [*BASE_MEMBERS_Product] as '{[Product].[Drink].[Alcoholic Beverages],[Product].[Drink].[Beverages],[Product].[Drink].[Dairy]}' Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' Member [Measures].[*Unit Sales_SEL~SUM] as '([Measures].[Unit Sales],[Product].CurrentMember)' Member [Product].[Drink].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter(Descendants([Product].[Drink],[Product].[Product Department]),[Measures].[*Unit Sales_SEL~SUM] > 5000.0))' Member [Product].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter([*METRIC_MEMBERS_Product],[Measures].[*Unit Sales_SEL~SUM] > 5000.0))' Select [*BASE_MEMBERS_Measures] on columns, Non Empty Union({[Product].[*CTX_MEMBER_SEL~SUM]},Union({[Product].[Drink].[*CTX_MEMBER_SEL~SUM]},Generate([*METRIC_CJ_SET], {([Product].CurrentMember)}))) on rows From [Sales] ]]> 50000.0)' Set [*BASE_MEMBERS_Measures] as '{[Measures].[Unit Sales]}' Set [*BASE_MEMBERS_Product] as '[Product].[Product Department].Members' Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' Member [Product].[Drink].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Product].[Drink]})' Member [Product].[Food].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Product].[Food]})' Member [Product].[Non-Consumable].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Product].[Non-Consumable]})' Member [Measures].[*Unit Sales_SEL~SUM] as '([Measures].[Unit Sales],Ancestor([Product].CurrentMember,[Product].[Product Family]).CalculatedChild("*CTX_METRIC_MEMBER_SEL~SUM"))' Member [Product].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter([*METRIC_MEMBERS_Product],[Measures].[*Unit Sales_SEL~SUM] > 50000.0))' Select [*BASE_MEMBERS_Measures] on columns, Non Empty Union({[Product].[*CTX_MEMBER_SEL~SUM]},Generate([*METRIC_CJ_SET], {([Product].CurrentMember)})) on rows From [Sales] ]]> 10000.0)' Set [*BASE_MEMBERS_Measures] as '{[Measures].[Unit Sales]}' Set [*BASE_MEMBERS_Product] as '[Product].[Product Department].Members' Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' Member [Product].[Drink].[*CTX_METRIC_MEMBER_SEL~AVG] as 'Avg(Descendants([Product].[Drink],[Product].[Product Department]))' Member [Product].[Food].[*CTX_METRIC_MEMBER_SEL~AVG] as 'Avg(Descendants([Product].[Food],[Product].[Product Department]))' Member [Product].[Non-Consumable].[*CTX_METRIC_MEMBER_SEL~AVG] as 'Avg(Descendants([Product].[Non-Consumable],[Product].[Product Department]))' Member [Measures].[*Unit Sales_SEL~AVG] as '([Measures].[Unit Sales],Ancestor([Product].CurrentMember,[Product].[Product Family]).CalculatedChild("*CTX_METRIC_MEMBER_SEL~AVG"))' Member [Product].[Drink].[*CTX_MEMBER_SEL~AVG] as 'Avg(Descendants([Product].[Drink],[Product].[Product Department]))' Member [Product].[Food].[*CTX_MEMBER_SEL~AVG] as 'Avg(Descendants([Product].[Food],[Product].[Product Department]))' Member [Product].[Non-Consumable].[*CTX_MEMBER_SEL~AVG] as 'Avg(Descendants([Product].[Non-Consumable],[Product].[Product Department]))' Member [Product].[*CTX_MEMBER_SEL~AVG] as 'Avg(Filter([*METRIC_MEMBERS_Product],[Measures].[*Unit Sales_SEL~AVG] > 10000.0))' Select [*BASE_MEMBERS_Measures] on columns, Non Empty Union({[Product].[*CTX_MEMBER_SEL~AVG]},Union({[Product].[Drink].[*CTX_MEMBER_SEL~AVG],[Product].[Food].[*CTX_MEMBER_SEL~AVG],[Product].[Non-Consumable].[*CTX_MEMBER_SEL~AVG]},Generate([*METRIC_CJ_SET], {([Product].CurrentMember)}))) on rows From [Sales] ]]> 20000.0 And [Measures].[*Unit Sales_SEL~SUM] < 25000.0)' Set [*BASE_MEMBERS_Measures] as '{[Measures].[Unit Sales]}' Set [*BASE_MEMBERS_Product] as '[Product].[Product Family].Members' Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' Member [Measures].[*Unit Sales_SEL~SUM] as '([Measures].[Unit Sales],[Product].CurrentMember)' Member [Product].[*CTX_MEMBER_SEL~SUM] as 'Sum(Filter([*METRIC_MEMBERS_Product],[Measures].[*Unit Sales_SEL~SUM] > 20000.0 And [Measures].[*Unit Sales_SEL~SUM] < 25000.0))' Select [*BASE_MEMBERS_Measures] on columns, Non Empty Union({[Product].[*CTX_MEMBER_SEL~SUM]},Generate([*METRIC_CJ_SET], {([Product].CurrentMember)})) on rows From [Sales] ]]> 50000.0)' Set [*BASE_MEMBERS_Measures] as '{[Measures].[Unit Sales]}' Set [*BASE_MEMBERS_Education Level] as '[Education Level].[Education Level].Members' Set [*NATIVE_MEMBERS_Education Level] as 'Generate([*NATIVE_CJ_SET], {[Education Level].CurrentMember})' Set [*METRIC_MEMBERS_Education Level] as 'Generate([*METRIC_CJ_SET], {[Education Level].CurrentMember})' Set [*BASE_MEMBERS_Product] as '[Product].[Product Family].Members' Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' Member [Product].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Product].[All Products]})' Member [Measures].[*Store Sales_SEL~SUM] as '([Measures].[Store Sales],[Education Level].CurrentMember,[Product].[*CTX_METRIC_MEMBER_SEL~SUM])' Select [*BASE_MEMBERS_Measures] on columns, Non Empty Generate([*METRIC_CJ_SET], {([Education Level].CurrentMember,[Product].CurrentMember)}) on rows From [Sales] ]]> 50000.0 And [Measures].[*Unit Sales_SEL~MAX] > 50000.0)' Set [*BASE_MEMBERS_Store] as '[Store].[Store Country].Members' Set [*NATIVE_MEMBERS_Store] as 'Generate([*NATIVE_CJ_SET], {[Store].CurrentMember})' Set [*METRIC_MEMBERS_Store] as 'Generate([*METRIC_CJ_SET], {[Store].CurrentMember})' Set [*BASE_MEMBERS_Measures] as '{[Measures].[Store Sales],[Measures].[Unit Sales]}' Set [*BASE_MEMBERS_Education Level] as '[Education Level].[Education Level].Members' Set [*NATIVE_MEMBERS_Education Level] as 'Generate([*NATIVE_CJ_SET], {[Education Level].CurrentMember})' Set [*METRIC_MEMBERS_Education Level] as 'Generate([*METRIC_CJ_SET], {[Education Level].CurrentMember})' Set [*BASE_MEMBERS_Product] as '[Product].[Product Family].Members' Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' Member [Product].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Product].[All Products]})' Member [Store].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Store].[All Stores]})' Member [Measures].[*Store Sales_SEL~SUM] as '([Measures].[Store Sales],[Education Level].CurrentMember,[Product].[*CTX_METRIC_MEMBER_SEL~SUM],[Store].[*CTX_METRIC_MEMBER_SEL~SUM])' Member [Product].[*CTX_METRIC_MEMBER_SEL~MAX] as 'Max([*NATIVE_MEMBERS_Product])' Member [Store].[*CTX_METRIC_MEMBER_SEL~MAX] as 'Max([*NATIVE_MEMBERS_Store])' Member [Measures].[*Unit Sales_SEL~MAX] as '([Measures].[Unit Sales],[Education Level].CurrentMember,[Product].[*CTX_METRIC_MEMBER_SEL~MAX],[Store].[*CTX_METRIC_MEMBER_SEL~MAX])' Select CrossJoin(Generate([*METRIC_CJ_SET], {([Store].CurrentMember)}),[*BASE_MEMBERS_Measures]) on columns, Non Empty Generate([*METRIC_CJ_SET], {([Education Level].CurrentMember,[Product].CurrentMember)}) on rows From [Sales] ]]> mondrian-3.4.1/testsrc/main/mondrian/test/clearview/PredicateFilterTest.java0000644000175000017500000000265611735330606027172 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * PredicateFilterTest is a test suite which tests scenarios of * filtering in the FoodMart database. * MDX queries and their expected results are maintained separately in * PredicateFilterTest.ref.xml file.If you would prefer to see them as inlined * Java string literals, run ant target "generateDiffRepositoryJUnit" and * then use file PredicateFilterTestJUnit.java which will be generated in * this directory. * * @author Khanh Vu */ public class PredicateFilterTest extends ClearViewBase { public PredicateFilterTest() { super(); } public PredicateFilterTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(PredicateFilterTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), PredicateFilterTest.class); } } // End PredicateFilterTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/SummaryTest.java0000644000175000017500000000367111735330606025557 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2011 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.olap.MondrianProperties; import mondrian.test.DiffRepository; import mondrian.util.Bug; import junit.framework.TestSuite; /** * SummaryTest is a test suite which tests scenarios of * summing unit sales against the FoodMart database. * MDX queries and their expected results are maintained separately in * SummaryTest.ref.xml file.If you would prefer to see them as inlined * Java string literals, run ant target "generateDiffRepositoryJUnit" and * then use file SummaryTestJUnit.java which will be generated in * this directory. * * @author Khanh Vu */ public class SummaryTest extends ClearViewBase { public SummaryTest() { super(); } public SummaryTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(SummaryTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), SummaryTest.class); } @Override protected void runTest() throws Exception { if (!Bug.BugMondrian785Fixed && (getName().equals("testRankExpandNonNative") || getName().equals("testCountExpandNonNative") || getName().equals("testCountOverTimeExpandNonNative")) && MondrianProperties.instance().EnableNativeCrossJoin.get()) { // Tests give wrong results if native crossjoin is disabled. return; } super.runTest(); } } // End SummaryTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/SubTotalTest.ref.xml0000644000175000017500000013725111735330606026313 0ustar drazzibdrazzib 1000.0)' Set [*BASE_MEMBERS_Gender] as '[Gender].[Gender].Members' Set [*NATIVE_MEMBERS_Gender] as 'Generate([*NATIVE_CJ_SET], {[Gender].CurrentMember})' Set [*METRIC_MEMBERS_Gender] as 'Generate([*METRIC_CJ_SET], {[Gender].CurrentMember})' Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}' Set [*BASE_MEMBERS_Product] as '{[Product].[Drink].[Alcoholic Beverages],[Product].[Food].[Baked Goods]}' Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' Set [*BASE_MEMBERS_Education Level] as '[Education Level].[Education Level].Members' Set [*NATIVE_MEMBERS_Education Level] as 'Generate([*NATIVE_CJ_SET], {[Education Level].CurrentMember})' Set [*METRIC_MEMBERS_Education Level] as 'Generate([*METRIC_CJ_SET], {[Education Level].CurrentMember})' Member [Gender].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum({[Gender].[All Gender]})', SOLVE_ORDER=-102 Member [Measures].[*Store Cost_SEL~SUM] as '([Measures].[Store Cost],[Product].CurrentMember,[Education Level].CurrentMember,[Gender].[*CTX_METRIC_MEMBER_SEL~SUM])', SOLVE_ORDER=200 Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Store Cost]', FORMAT_STRING = '#,##0', SOLVE_ORDER=300 Member [Education Level].[*TOTAL_MEMBER_SEL~SUM] as 'Sum(Generate(Exists([*METRIC_CJ_SET], {[Product].CurrentMember}), {([Product].CurrentMember, [Education Level].CurrentMember)}))', SOLVE_ORDER=-101 Member [Product].[Drink].[*TOTAL_MEMBER_SEL~SUM] as 'Sum(Generate(Exists([*METRIC_CJ_SET], {[Product].[Drink]}), {([Product].CurrentMember, [Education Level].CurrentMember)}))', SOLVE_ORDER=-100 Member [Product].[Food].[*TOTAL_MEMBER_SEL~SUM] as 'Sum(Generate(Exists([*METRIC_CJ_SET], {[Product].[Food]}), {([Product].CurrentMember, [Education Level].CurrentMember)}))', SOLVE_ORDER=-100 Select CrossJoin(Generate([*METRIC_CJ_SET], {([Gender].CurrentMember)}),[*BASE_MEMBERS_Measures]) on columns, Non Empty Union( CrossJoin(Generate([*METRIC_CJ_SET], {([Product].CurrentMember)}),{[Education Level].[*TOTAL_MEMBER_SEL~SUM]}), Union(NonEmptyCrossJoin({[Product].[Drink].[*TOTAL_MEMBER_SEL~SUM],[Product].[Food].[*TOTAL_MEMBER_SEL~SUM]}, {[Education Level].DefaultMember}), Generate([*METRIC_CJ_SET], {([Product].CurrentMember,[Education Level].CurrentMember)}))) on rows From [Sales] ]]> 1000.0)' Set [*BASE_MEMBERS_Gender] as '{[Gender].[M]}' Set [*NATIVE_MEMBERS_Gender] as 'Generate([*NATIVE_CJ_SET], {[Gender].CurrentMember})' Set [*METRIC_MEMBERS_Gender] as 'Generate([*METRIC_CJ_SET], {[Gender].CurrentMember})' Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0],[Measures].[*SUMMARY_MEASURE_1]}' Set [*BASE_MEMBERS_Product] as '{[Product].[Drink].[Beverages],[Product].[Food].[Baked Goods]}' Set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})' Set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})' Set [*BASE_MEMBERS_Education Level] as '{[Education Level].[High School Degree],[Education Level].[Partial High School]}' Set [*NATIVE_MEMBERS_Education Level] as 'Generate([*NATIVE_CJ_SET], {[Education Level].CurrentMember})' Set [*METRIC_MEMBERS_Education Level] as 'Generate([*METRIC_CJ_SET], {[Education Level].CurrentMember})' Set [*BASE_MEMBERS_Time] as '{[Time].[1997]}' Set [*NATIVE_MEMBERS_Time] as 'Generate([*NATIVE_CJ_SET], {[Time].[Time].CurrentMember})' Set [*METRIC_MEMBERS_Time] as 'Generate([*METRIC_CJ_SET], {[Time].[Time].CurrentMember})' Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Store Cost]', FORMAT_STRING = '#,##0', SOLVE_ORDER=300 Member [Measures].[*SUMMARY_MEASURE_1] as '[Measures].[Store Cost]/([Measures].[Store Cost],Ancestor([Product].CurrentMember,[Product].[Product Family]).CalculatedChild("*TOTAL_MEMBER_SEL~SUM"))', FORMAT_STRING = '###0.00%', SOLVE_ORDER=50 Member [Gender].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum([*NATIVE_MEMBERS_Gender])', SOLVE_ORDER=-102 Member [Time].[Time].[*CTX_METRIC_MEMBER_SEL~SUM] as 'Sum([*NATIVE_MEMBERS_Time])', SOLVE_ORDER=-102 Member [Measures].[*Store Cost_SEL~SUM] as '([Measures].[Store Cost],[Product].CurrentMember,[Education Level].CurrentMember,[Gender].[*CTX_METRIC_MEMBER_SEL~SUM],[Time].[*CTX_METRIC_MEMBER_SEL~SUM])', SOLVE_ORDER=200 Member [Education Level].[*TOTAL_MEMBER_SEL~SUM] as 'Sum(Generate(Exists([*METRIC_CJ_SET], {[Product].CurrentMember}), {([Product].CurrentMember, [Education Level].CurrentMember)}))', SOLVE_ORDER=-101 Member [Product].[Drink].[*TOTAL_MEMBER_SEL~SUM] as 'Sum(Generate(Exists([*METRIC_CJ_SET], {[Product].[Drink]}), {([Product].CurrentMember, [Education Level].CurrentMember)}))', SOLVE_ORDER=-100 Member [Product].[Food].[*TOTAL_MEMBER_SEL~SUM] as 'Sum(Generate(Exists([*METRIC_CJ_SET], {[Product].[Food]}), {([Product].CurrentMember, [Education Level].CurrentMember)}))', SOLVE_ORDER=-100 Member [Product].[*TOTAL_MEMBER_SEL~SUM] as 'Sum(Generate([*METRIC_CJ_SET], {([Product].CurrentMember, [Education Level].CurrentMember)}))', SOLVE_ORDER=-100 Member [Time].[Time].[*FILTER_MEMBER] as 'Aggregate ([*METRIC_MEMBERS_Time])', SOLVE_ORDER=-200 Select CrossJoin(Generate([*METRIC_CJ_SET], {([Gender].CurrentMember)}),[*BASE_MEMBERS_Measures]) on columns, Non Empty Union( NonEmptyCrossJoin({[Product].[*TOTAL_MEMBER_SEL~SUM]},{[Education Level].DefaultMember}), Union( CrossJoin(Generate([*METRIC_CJ_SET], {([Product].CurrentMember)}),{[Education Level].[*TOTAL_MEMBER_SEL~SUM]}), Union(NonEmptyCrossJoin({[Product].[Drink].[*TOTAL_MEMBER_SEL~SUM],[Product].[Food].[*TOTAL_MEMBER_SEL~SUM]}, {[Education Level].DefaultMember}), Generate([*METRIC_CJ_SET], {([Product].CurrentMember,[Education Level].CurrentMember)})))) on rows From [Sales] Where ([Time].[*FILTER_MEMBER]) ]]> mondrian-3.4.1/testsrc/main/mondrian/test/clearview/HangerDimensionTest.ref.xml0000644000175000017500000017334511735330606027634 0ustar drazzibdrazzib 1 ]]> 1 ]]> 1 ]]> 1 ]]> 1 ]]> 1 ]]> 1 ]]> 1 ]]> mondrian-3.4.1/testsrc/main/mondrian/test/clearview/MultiDimVCTest.ref.xml0000644000175000017500000001636311735330606026533 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/test/clearview/SubTotalTest.java0000644000175000017500000000257111735330606025655 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * SubTotalTest is a test suite which tests scenarios of * using sub totals against the FoodMart database. MDX queries and their * expected results are maintained separately in SubTotalTest.ref.xml file. * If you would prefer to see them as inlined Java string literals, run * ant target "generateDiffRepositoryJUnit" and then use * file SubTotalTestJUnit.java which will be generated in this directory. * * @author Khanh Vu */ public class SubTotalTest extends ClearViewBase { public SubTotalTest() { super(); } public SubTotalTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(SubTotalTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), SubTotalTest.class); } } // End SubTotalTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/MiscTest.java0000644000175000017500000000252511735330606025012 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2007 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * MiscTest is a test suite which tests miscellaneous * complex queries against the FoodMart database. MDX queries and their * expected results are maintained separately in MiscTest.ref.xml file. * If you would prefer to see them as inlined Java string literals, run * ant target "generateDiffRepositoryJUnit" and then use * file MiscTestJUnit.java which will be generated in this directory. * * @author Khanh Vu */ public class MiscTest extends ClearViewBase { public MiscTest() { super(); } public MiscTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(MiscTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), MiscTest.class); } } // End MiscTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/MemHungryTest.ref.xml0000644000175000017500000147136611735330606026502 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/test/clearview/PartialCacheVCTest.java0000644000175000017500000000264211735330606026670 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * PartialCacheVCTest is a test suite which tests complex queries * against the FoodMart database. MDX queries and their expected results are * maintained separately in PartialCacheVCTest.ref.xml file. If you would * prefer to see them as inlined Java string literals, run ant target * "generateDiffRepositoryJUnit" and then use file PartialCacheVCTestJUnit.java * which will be generated in this directory. * * @author Khanh Vu */ public class PartialCacheVCTest extends ClearViewBase { public PartialCacheVCTest() { super(); } public PartialCacheVCTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(PartialCacheVCTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), PartialCacheVCTest.class); } } // End PartialCacheVCTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/MetricFilterTest.java0000644000175000017500000000265011735330606026507 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2007 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * MetricFilterTest is a test suite which tests scenarios of * filtering out measures' values in the FoodMart database. * MDX queries and their expected results are maintained separately in * MetricFilterTest.ref.xml file.If you would prefer to see them as inlined * Java string literals, run ant target "generateDiffRepositoryJUnit" and * then use file MetricFilterTestJUnit.java which will be generated in * this directory. * * @author Khanh Vu */ public class MetricFilterTest extends ClearViewBase { public MetricFilterTest() { super(); } public MetricFilterTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(MetricFilterTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), MetricFilterTest.class); } } // End MetricFilterTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/QueryAllTest.java0000644000175000017500000000255311735330606025656 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2007 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * QueryAllTest is a test suite which tests * complex queries against the FoodMart database. MDX queries and their * expected results are maintained separately in QueryAllTest.ref.xml file. * If you would prefer to see them as inlined Java string literals, run * ant target "generateDiffRepositoryJUnit" and then use * file QueryAllTestJUnit.java which will be generated in this directory. * * @author Khanh Vu */ public class QueryAllTest extends ClearViewBase { public QueryAllTest() { super(); } public QueryAllTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(QueryAllTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), QueryAllTest.class); } } // End QueryAllTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/MultiLevelTest.ref.xml0000644000175000017500000000120611735330606026626 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/test/clearview/ClearViewBase.java0000644000175000017500000001376611735330606025744 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. // // jhyde, Feb 14, 2003 */ package mondrian.test.clearview; import mondrian.olap.MondrianProperties; import mondrian.olap.Util; import mondrian.rolap.BatchTestCase; import mondrian.spi.Dialect; import mondrian.test.*; import junit.framework.Test; import junit.framework.TestSuite; import java.lang.reflect.Constructor; /** * ClearViewBase is the base class to build test cases which test * queries against the FoodMart database. A concrete sub class and * a ref.xml file will be needed for each test suites to be added. MDX queries * and their expected results are maintained separately in *.ref.xml files. * If you would prefer to see them as inlined Java string literals, run * ant target "generateDiffRepositoryJUnit" and then use * files *JUnit.java which will be generated in this directory. * * @author John Sichi * @author Richard Emberson * @author Khanh Vu * * @since Jan 25, 2007 */ public abstract class ClearViewBase extends BatchTestCase { public ClearViewBase() { super(); } public ClearViewBase(String name) { super(name); } public abstract DiffRepository getDiffRepos(); // implement TestCase protected void setUp() throws Exception { super.setUp(); DiffRepository diffRepos = getDiffRepos(); diffRepos.setCurrentTestCaseName(getName()); } // implement TestCase protected void tearDown() throws Exception { DiffRepository diffRepos = getDiffRepos(); diffRepos.setCurrentTestCaseName(null); super.tearDown(); } // implement TestCase public static TestSuite constructSuite( DiffRepository diffRepos, Class clazz) { TestSuite suite = new TestSuite(); Class[] types = new Class[] { String.class }; for (String name : diffRepos.getTestCaseNames()) { try { Constructor cons = clazz.getConstructor(types); Object[] args = new Object[] { name }; suite.addTest((Test) cons.newInstance(args)); } catch (Exception e) { throw new Error(e.getMessage()); } } return suite; } // implement TestCase protected void runTest() throws Exception { DiffRepository diffRepos = getDiffRepos(); TestContext testContext = getTestContext(); // add calculated member to a cube if specified in the xml file String cubeName = diffRepos.expand(null, "${modifiedCubeName}").trim(); if (! (cubeName.equals("") || cubeName.equals("${modifiedCubeName}"))) { String customDimensions = diffRepos.expand( null, "${customDimensions}"); customDimensions = (! (customDimensions.equals("") || customDimensions.equals("${customDimensions}"))) ? customDimensions : null; String measures = diffRepos.expand( null, "${measures}"); measures = (! (measures.equals("") || measures.equals("${measures}"))) ? measures : null; String calculatedMembers = diffRepos.expand( null, "${calculatedMembers}"); calculatedMembers = (! (calculatedMembers.equals("") || calculatedMembers.equals("${calculatedMembers}"))) ? calculatedMembers : null; String namedSets = diffRepos.expand( null, "${namedSets}"); namedSets = (! (namedSets.equals("") || namedSets.equals("${namedSets}"))) ? namedSets : null; testContext = TestContext.instance().createSubstitutingCube( cubeName, customDimensions, measures, calculatedMembers, namedSets); } // Set some properties to match the way we configure them // for ClearView. propSaver.set( MondrianProperties.instance().ExpandNonNative, true); String mdx = diffRepos.expand(null, "${mdx}"); String result = Util.nl + TestContext.toString( testContext.executeQuery(mdx)); diffRepos.assertEquals("result", "${result}", result); } protected void assertQuerySql(boolean flushCache) throws Exception { DiffRepository diffRepos = getDiffRepos(); if (buildSqlPatternArray() == null) { return; } super.assertQuerySqlOrNot( getTestContext(), diffRepos.expand(null, "${mdx}"), buildSqlPatternArray(), false, false, flushCache); } protected void assertNoQuerySql(boolean flushCache) throws Exception { DiffRepository diffRepos = getDiffRepos(); if (buildSqlPatternArray() == null) { return; } super.assertQuerySqlOrNot( getTestContext(), diffRepos.expand(null, "${mdx}"), buildSqlPatternArray(), true, false, flushCache); } private SqlPattern[] buildSqlPatternArray() { DiffRepository diffRepos = getDiffRepos(); Dialect d = getTestContext().getDialect(); Dialect.DatabaseProduct dialect = d.getDatabaseProduct(); String testCaseName = getName(); String sql = diffRepos.get( testCaseName, "expectedSql", dialect.name()); if (sql != null) { sql = sql.replaceAll("[ \t\n\f\r]+", " ").trim(); return new SqlPattern[]{ new SqlPattern(dialect, sql, null) }; } return null; } } // End ClearViewBase.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/GrandTotalTest.ref.xml0000644000175000017500000023305211735330606026611 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/test/clearview/PredicateFilterTest.ref.xml0000644000175000017500000010255711735330606027625 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/test/clearview/SummaryTest.ref.xml0000644000175000017500000024151511735330606026212 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/test/clearview/HangerDimensionTest.java0000644000175000017500000000276111735330606027173 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * HangerDimensionTest tests the extended syntax of Order * function. See { @link * http://pub.eigenbase.org/wiki/MondrianOrderFunctionExtension } for * syntax rules. * MDX queries and their expected results are maintained separately in * HangerDimensionTest.ref.xml file.If you would prefer to see them as inlined * Java string literals, run ant target "generateDiffRepositoryJUnit" and * then use file HangerDimensionTestJUnit.java which will be generated in * this directory. * * @author Khanh Vu */ public class HangerDimensionTest extends ClearViewBase { public HangerDimensionTest() { super(); } public HangerDimensionTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(HangerDimensionTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), HangerDimensionTest.class); } } // End HangerDimensionTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/MultiDimTest.java0000644000175000017500000000255311735330606025644 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2007 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * MultiDimTest is a test suite which tests * complex queries against the FoodMart database. MDX queries and their * expected results are maintained separately in MultiDimTest.ref.xml file. * If you would prefer to see them as inlined Java string literals, run * ant target "generateDiffRepositoryJUnit" and then use * file MultiDimTestJUnit.java which will be generated in this directory. * * @author Khanh Vu */ public class MultiDimTest extends ClearViewBase { public MultiDimTest() { super(); } public MultiDimTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(MultiDimTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), MultiDimTest.class); } } // End MultiDimTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/CVBasicTest.ref.xml0000644000175000017500000001200211735330606026012 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/test/clearview/MultiLevelTest.java0000644000175000017500000000257511735330606026206 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2007 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * MultiLevelTest is a test suite which tests * complex queries against the FoodMart database. MDX queries and their * expected results are maintained separately in MultiLevelTest.ref.xml file. * If you would prefer to see them as inlined Java string literals, run * ant target "generateDiffRepositoryJUnit" and then use * file MultiLevelTestJUnit.java which will be generated in this directory. * * @author Khanh Vu */ public class MultiLevelTest extends ClearViewBase { public MultiLevelTest() { super(); } public MultiLevelTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(MultiLevelTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), MultiLevelTest.class); } } // End MultiLevelTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/MultiLevelVCTest.java0000644000175000017500000000261711735330606026434 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2007 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * MultiLevelVCTest is a test suite which tests * complex queries against the FoodMart database. MDX queries and their * expected results are maintained separately in MultiLevelVCTest.ref.xml file. * If you would prefer to see them as inlined Java string literals, run * ant target "generateDiffRepositoryJUnit" and then use * file MultiLevelVCTestJUnit.java which will be generated in this directory. * * @author Khanh Vu */ public class MultiLevelVCTest extends ClearViewBase { public MultiLevelVCTest() { super(); } public MultiLevelVCTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(MultiLevelVCTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), MultiLevelVCTest.class); } } // End MultiLevelVCTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/TopBottomTest.java0000644000175000017500000000262411735330606026046 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * TopBottomTest is a test suite which tests scenarios of * selecting top and bottom records against the FoodMart database. * MDX queries and their expected results are maintained separately in * TopBottomTest.ref.xml file.If you would prefer to see them as inlined * Java string literals, run ant target "generateDiffRepositoryJUnit" and * then use file TopBottomTestJUnit.java which will be generated in * this directory. * * @author Khanh Vu */ public class TopBottomTest extends ClearViewBase { public TopBottomTest() { super(); } public TopBottomTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(TopBottomTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), TopBottomTest.class); } } // End TopBottomTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/CVBasicTest.java0000644000175000017500000000254211735330606025370 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2007 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * CVBasicTest is a test suite which tests * complex queries against the FoodMart database. MDX queries and their * expected results are maintained separately in CVBasicTest.ref.xml file. * If you would prefer to see them as inlined Java string literals, run * ant target "generateDiffRepositoryJUnit" and then use * file CVBasicTestJUnit.java which will be generated in this directory. * * @author Khanh Vu */ public class CVBasicTest extends ClearViewBase { public CVBasicTest() { super(); } public CVBasicTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(CVBasicTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), CVBasicTest.class); } } // End CVBasicTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/PartialCacheTest.ref.xml0000644000175000017500000000670211735330606027072 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/test/clearview/QueryAllVCTest.java0000644000175000017500000000257511735330606026113 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2007 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * QueryAllVCTest is a test suite which tests * complex queries against the FoodMart database. MDX queries and their * expected results are maintained separately in QueryAllVCTest.ref.xml file. * If you would prefer to see them as inlined Java string literals, run * ant target "generateDiffRepositoryJUnit" and then use * file QueryAllVCTestJUnit.java which will be generated in this directory. * * @author Khanh Vu */ public class QueryAllVCTest extends ClearViewBase { public QueryAllVCTest() { super(); } public QueryAllVCTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(QueryAllVCTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), QueryAllVCTest.class); } } // End QueryAllVCTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/QueryAllVCTest.ref.xml0000644000175000017500000000746511735330606026550 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/test/clearview/BatchedFillTest.java0000644000175000017500000000354711735330606026265 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2010 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.olap.MondrianProperties; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * BatchedFillTest is a test suite which tests * complex queries against the FoodMart database. MDX queries and their * expected results are maintained separately in BatchedFillTest.ref.xml file. * If you would prefer to see them as inlined Java string literals, run * ant target "generateDiffRepositoryJUnit" and then use * file BatchedFillTestJUnit.java which will be generated in this directory. * * @author Khanh Vu */ public class BatchedFillTest extends ClearViewBase { public BatchedFillTest() { super(); } public BatchedFillTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(BatchedFillTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), BatchedFillTest.class); } protected void runTest() throws Exception { if (getName().equals("testBatchedFill2") && MondrianProperties.instance().ReadAggregates.get() && MondrianProperties.instance().UseAggregates.get()) { // If agg tables are enabled, the SQL generated is 'better' than // expected. } else { super.assertQuerySql(true); } super.runTest(); } } // End BatchedFillTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/MultiDimVCTest.java0000644000175000017500000000257511735330606026101 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2007 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * MultiDimVCTest is a test suite which tests * complex queries against the FoodMart database. MDX queries and their * expected results are maintained separately in MultiDimVCTest.ref.xml file. * If you would prefer to see them as inlined Java string literals, run * ant target "generateDiffRepositoryJUnit" and then use * file MultiDimVCTestJUnit.java which will be generated in this directory. * * @author Khanh Vu */ public class MultiDimVCTest extends ClearViewBase { public MultiDimVCTest() { super(); } public MultiDimVCTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(MultiDimVCTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), MultiDimVCTest.class); } } // End MultiDimVCTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/MultiLevelVCTest.ref.xml0000644000175000017500000000374711735330606027073 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/test/clearview/SummaryMetricPercentTest.java0000644000175000017500000000301611735330606030235 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * SummaryMetricPercentTest is a test suite which tests scenarios * of computing sums and percentages against the FoodMart database. * MDX queries and their expected results are maintained separately in * SummaryMetricPercentTest.ref.xml file.If you would prefer to see them as * inlined Java string literals, run ant target "generateDiffRepositoryJUnit" * and then use file SummaryMetricPercentTestJUnit.java which will be * generated in this directory. * * @author Khanh Vu */ public class SummaryMetricPercentTest extends ClearViewBase { public SummaryMetricPercentTest() { super(); } public SummaryMetricPercentTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(SummaryMetricPercentTest.class); } public static TestSuite suite() { return constructSuite( getDiffReposStatic(), SummaryMetricPercentTest.class); } } // End SummaryMetricPercentTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/MemHungryTest.java0000644000175000017500000000256211735330606026033 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * MemHungryTest is a test suite which tests * complex queries against the FoodMart database. MDX queries and their * expected results are maintained separately in MemHungryTest.ref.xml file. * If you would prefer to see them as inlined Java string literals, run * ant target "generateDiffRepositoryJUnit" and then use * file MemHungryTestJUnit.java which will be generated in this directory. * * @author Khanh Vu */ public class MemHungryTest extends ClearViewBase { public MemHungryTest() { super(); } public MemHungryTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(MemHungryTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), MemHungryTest.class); } } // End MemHungryTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/OrderTest.java0000644000175000017500000000262711735330606025175 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho and others // All Rights Reserved. */ package mondrian.test.clearview; import mondrian.test.DiffRepository; import junit.framework.TestSuite; /** * OrderTest tests the extended syntax of Order * function. See { @link * http://pub.eigenbase.org/wiki/MondrianOrderFunctionExtension } for * syntax rules. * MDX queries and their expected results are maintained separately in * OrderTest.ref.xml file.If you would prefer to see them as inlined * Java string literals, run ant target "generateDiffRepositoryJUnit" and * then use file OrderTestJUnit.java which will be generated in * this directory. * * @author Khanh Vu */ public class OrderTest extends ClearViewBase { public OrderTest() { super(); } public OrderTest(String name) { super(name); } public DiffRepository getDiffRepos() { return getDiffReposStatic(); } private static DiffRepository getDiffReposStatic() { return DiffRepository.lookup(OrderTest.class); } public static TestSuite suite() { return constructSuite(getDiffReposStatic(), OrderTest.class); } } // End OrderTest.java mondrian-3.4.1/testsrc/main/mondrian/test/clearview/OrderTest.ref.xml0000644000175000017500000012151611735330606025626 0ustar drazzibdrazzib mondrian-3.4.1/testsrc/main/mondrian/test/SteelWheelsTestCase.java0000644000175000017500000000535411735330606025161 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2011 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.olap.Util; import mondrian.rolap.RolapConnectionProperties; import junit.framework.TestCase; /** * Unit test against Pentaho's Steel Wheels sample database. * *

It is not required that the Steel Wheels database be present, so each * test should check whether the database exists and trivially succeed if it * does not. * * @author jhyde * @since 12 March 2009 */ public class SteelWheelsTestCase extends TestCase { /** * Creates a SteelwheelsTestCase. * * @param name Test case name (usually method name) */ public SteelWheelsTestCase(String name) { super(name); } /** * Creates a SteelwheelsTestCase. */ public SteelWheelsTestCase() { } /** * Creates a TestContext which contains the given schema text. * * @param context Base test context * @param schema A XML schema, or null * Used for testing if the connection is valid. * @return TestContext which contains the given schema */ public static TestContext createContext( TestContext context, final String schema) { final Util.PropertyList properties = context.getConnectionProperties().clone(); final String jdbc = properties.get( RolapConnectionProperties.Jdbc.name()); properties.put( RolapConnectionProperties.Jdbc.name(), Util.replace(jdbc, "/foodmart", "/steelwheels")); if (schema != null) { properties.put( RolapConnectionProperties.CatalogContent.name(), schema); properties.remove( RolapConnectionProperties.Catalog.name()); } else { final String catalog = properties.get(RolapConnectionProperties.Catalog.name()); properties.put( RolapConnectionProperties.Catalog.name(), Util.replace( catalog, "FoodMart.xml", "SteelWheels.mondrian.xml")); } return context.withProperties(properties); } /** * Returns the test context. Override this method if you wish to use a * different source for your SteelWheels connection. */ public TestContext getTestContext() { return createContext(TestContext.instance(), null) .withCube("SteelWheelsSales"); } } // End SteelWheelsTestCase.java mondrian-3.4.1/testsrc/main/mondrian/test/ScenarioTest.java0000644000175000017500000004456211735330606023710 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2011 Pentaho // All Rights Reserved. */ package mondrian.test; import org.olap4j.*; import java.sql.SQLException; import java.util.Arrays; /** * Test for writeback functionality. * * @author jhyde * @since 24 April, 2009 */ public class ScenarioTest extends FoodMartTestCase { /** * Tests creating a scenario and setting a connection's active scenario. */ public void testCreateScenario() throws SQLException { final OlapConnection connection = getTestContext().getOlap4jConnection(); try { assertNull(connection.getScenario()); final Scenario scenario = connection.createScenario(); assertNotNull(scenario); connection.setScenario(scenario); assertSame(scenario, connection.getScenario()); connection.setScenario(null); assertNull(connection.getScenario()); final Scenario scenario2 = connection.createScenario(); assertNotNull(scenario2); connection.setScenario(scenario2); } finally { connection.setScenario(null); } } /** * Tests setting the value of one cell. */ public void testSetCell() throws SQLException { final OlapConnection connection = getTestContext().getOlap4jConnection(); try { assertNull(connection.getScenario()); final Scenario scenario = connection.createScenario(); connection.setScenario(scenario); connection.prepareOlapStatement( "select {[Measures].[Unit Sales]} on 0,\n" + "{[Product].Children} on 1\n" + "from [Sales]"); } finally { connection.setScenario(null); } } /** * Tests that setting a cell's value without an active scenario is illegal. */ public void testSetCellWithoutScenarioFails() throws SQLException { final OlapConnection connection = getTestContext().getOlap4jConnection(); try { assertNull(connection.getScenario()); final PreparedOlapStatement pstmt = connection.prepareOlapStatement( "select {[Measures].[Unit Sales]} on 0,\n" + "{[Product].Children} on 1\n" + "from [Sales]"); final CellSet result = pstmt.executeQuery(); final Cell cell = result.getCell(Arrays.asList(0, 1)); try { cell.setValue(123, AllocationPolicy.EQUAL_ALLOCATION); fail("expected error"); } catch (RuntimeException e) { TestContext.checkThrowable(e, "No active scenario"); } } finally { connection.setScenario(null); } } /** * Tests that setting a calculated member is illegal. */ public void testSetCellCalcError() throws SQLException { final TestContext testContext = TestContext.instance().withScenario(); final OlapConnection connection = testContext.getOlap4jConnection(); PreparedOlapStatement pstmt = connection.prepareOlapStatement( "with member [Measures].[Unit Sales Plus One]\n" + " as ' [Measures].[Unit Sales] + 1 '\n" + "select {[Measures].[Unit Sales Plus One]} on 0,\n" + "{[Product].Children} on 1\n" + "from [Sales]"); CellSet cellSet = pstmt.executeQuery(); Cell cell = cellSet.getCell(Arrays.asList(0, 1)); try { cell.setValue(123, AllocationPolicy.EQUAL_ALLOCATION); fail("expected exception"); } catch (RuntimeException e) { TestContext.checkThrowable( e, "Cannot write to cell: one of the coordinates " + "([Measures].[Unit Sales Plus One]) is a calculated member"); } // Calc member on non-measures dimension cellSet = pstmt.executeOlapQuery( "with member [Product].[FoodDrink]\n" + " as Aggregate({[Product].[Food], [Product].[Drink]})\n" + "select {[Measures].[Unit Sales]} on 0,\n" + "{[Product].Children, [Product].[FoodDrink]} on 1\n" + "from [Sales]"); // OK to set ([Measures].[Unit Sales], [Product].[Drink]) cell = cellSet.getCell(Arrays.asList(0, 1)); cell.setValue(123, AllocationPolicy.EQUAL_ALLOCATION); // Not OK to set ([Measures].[Unit Sales], [Product].[FoodDrink]) cell = cellSet.getCell(Arrays.asList(0, 3)); try { cell.setValue(123, AllocationPolicy.EQUAL_ALLOCATION); fail("expected exception"); } catch (RuntimeException e) { TestContext.checkThrowable( e, "Cannot write to cell: one of the coordinates " + "([Product].[FoodDrink]) is a calculated member"); } } /** * Tests that allocation policies that are not supported give an error. */ public void testUnsupportedAllocationPolicyFails() throws SQLException { final TestContext testContext = TestContext.instance().withScenario(); final OlapConnection connection = testContext.getOlap4jConnection(); final PreparedOlapStatement pstmt = connection.prepareOlapStatement( "select {[Measures].[Unit Sales]} on 0,\n" + "{[Product].Children} on 1\n" + "from [Sales]"); final CellSet cellSet = pstmt.executeQuery(); final Cell cell = cellSet.getCell(Arrays.asList(0, 1)); for (AllocationPolicy policy : AllocationPolicy.values()) { switch (policy) { case EQUAL_ALLOCATION: case EQUAL_INCREMENT: continue; } try { cell.setValue(123, policy); fail("expected error"); } catch (RuntimeException e) { TestContext.checkThrowable( e, "Allocation policy " + policy + " is not supported"); } } try { cell.setValue(123, null); fail("expected error"); } catch (RuntimeException e) { TestContext.checkThrowable( e, "Allocation policy must not be null"); } } /** * Tests setting cells by the "equal increment" allocation policy. */ public void testEqualIncrement() throws SQLException { assertAllocation(AllocationPolicy.EQUAL_INCREMENT); } /** * Tests setting cells by the "equal allocation" allocation policy. */ public void testEqualAllocation() throws SQLException { assertAllocation(AllocationPolicy.EQUAL_ALLOCATION); } private void assertAllocation( final AllocationPolicy allocationPolicy) throws SQLException { // TODO: Should not need to explicitly create a scenario. Add element // // to cube definition, and [Scenario] dimension will appear. Also, need // more elegant way for users to create dimensions that only contain // calculated members. final TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", "") .withScenario(); final OlapConnection connection = testContext.getOlap4jConnection(); final Scenario scenario = connection.getScenario(); String id = scenario.getId(); final PreparedOlapStatement pstmt = connection.prepareOlapStatement( "select {[Measures].[Unit Sales]} on 0,\n" + "{[Product].Children} on 1\n" + "from [Sales]\n" + "where [Scenario].[" + id + "]"); CellSet cellSet = pstmt.executeQuery(); // Update ([Product].[Drink], [Measures].[Unit Sales]) // from 24,597 to 23,597. final Cell cell = cellSet.getCell(Arrays.asList(0, 0)); cell.setValue(23597, allocationPolicy); testContext.assertQueryReturns( "select {[Measures].[Unit Sales]} on 0,\n" + "{[Product].[Drink]} on 1\n" + "from [Sales]" + "where [Scenario].[" + id + "]", "Axis #0:\n" + "{[Scenario].[" + id + "]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "Row #0: 23,597\n"); testContext.assertQueryReturns( "select {[Measures].[Unit Sales]} on 0,\n" + "{[Product].Children,\n" + " [Product].[Drink].Children,\n" + " [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda],\n" + " [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].Children} on 1\n" + "from [Sales]" + "where [Scenario].[" + id + "]", "Axis #0:\n" + "{[Scenario].[" + id + "]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Drink].[Dairy]}\n" + "{[Product].[Drink].[Beverages].[Carbonated Beverages].[Soda]}\n" + "{[Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Excellent]}\n" + "{[Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Fabulous]}\n" + "{[Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Skinner]}\n" + "{[Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Token]}\n" + "{[Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Washington]}\n" + (allocationPolicy == AllocationPolicy.EQUAL_INCREMENT ? "Row #0: 23,597\n" + "Row #1: 191,940\n" + "Row #2: 50,236\n" + "Row #3: 6,560\n" + "Row #4: 13,022\n" + "Row #5: 4,015\n" + "Row #6: 3,268\n" + "Row #7: 708\n" + "Row #8: 606\n" + "Row #9: 629\n" + "Row #10: 705\n" + "Row #11: 620\n" : "Row #0: 23,597\n" + "Row #1: 191,940\n" + "Row #2: 50,236\n" + "Row #3: 6,563\n" + "Row #4: 12,990\n" + "Row #5: 4,043\n" + "Row #6: 3,274\n" + "Row #7: 704\n" + "Row #8: 612\n" + "Row #9: 603\n" + "Row #10: 716\n" + "Row #11: 639\n")); // For reference here are the original values: // Row #0: 24,597 // Row #1: 191,940 // Row #2: 50,236 // Row #3: 6,838 // Row #4: 13,573 // Row #5: 4,186 // Row #6: 3,407 // Row #7: 738 // Row #8: 632 // Row #9: 655 // Row #10: 735 // Row #11: 647 // Create a new scenario, and show that the scenario in the slicer // overrides. final Scenario scenario2 = connection.createScenario(); final String id2 = scenario2.getId(); // Connection has scenario1, // slicer has scenario2, // slicer wins. String value; final OlapStatement stmt = connection.createStatement(); cellSet = stmt.executeOlapQuery( "select {[Measures].[Unit Sales]} on 0,\n" + "{[Product].Children} on 1\n" + "from [Sales]\n" + "where [Scenario].[" + id2 + "]"); cellSet.getCell(Arrays.asList(0, 0)).setValue(100, allocationPolicy); // With slicer=scenario1, value as per scenario1. cellSet = stmt.executeOlapQuery( "select {[Measures].[Unit Sales]} on 0,\n" + "{[Product].Children} on 1\n" + "from [Sales]\n" + "where [Scenario].[" + id + "]"); value = cellSet.getCell(Arrays.asList(0, 0)).getFormattedValue(); assertEquals("23,597", value); // With slicer=scenario2, value as per scenario2. cellSet = stmt.executeOlapQuery( "select {[Measures].[Unit Sales]} on 0,\n" + "{[Product].Children} on 1\n" + "from [Sales]\n" + "where [Scenario].[" + id2 + "]"); value = cellSet.getCell(Arrays.asList(0, 0)).getFormattedValue(); assertEquals("100", value); // With no slicer, value as per connection's scenario, scenario1. assert connection.getScenario() == scenario; cellSet = stmt.executeOlapQuery( "select {[Measures].[Unit Sales]} on 0,\n" + "{[Product].Children} on 1\n" + "from [Sales]\n"); value = cellSet.getCell(Arrays.asList(0, 0)).getFormattedValue(); assertEquals("23,597", value); // Set connection's scenario to null, and we get the unmodified value. connection.setScenario(null); cellSet = stmt.executeOlapQuery( "select {[Measures].[Unit Sales]} on 0,\n" + "{[Product].Children} on 1\n" + "from [Sales]\n"); value = cellSet.getCell(Arrays.asList(0, 0)).getFormattedValue(); assertEquals("24,597", value); } /** * Test case for * MONDRIAN-815, * "NPE from query if use a scenario and one of the cells is empty/null". */ public void testBugMondrian815() throws SQLException { final TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", "") .withScenario(); final OlapConnection connection = testContext.getOlap4jConnection(); final Scenario scenario = connection.createScenario(); connection.setScenario(scenario); final String id = scenario.getId(); final String scenarioUniqueName = "[Scenario].[" + id + "]"; final PreparedOlapStatement pstmt = connection.prepareOlapStatement( "select NON EMPTY [Gender].Members ON COLUMNS,\n" + "NON EMPTY Order([Product].[All Products].[Drink].Children,\n" + "[Gender].[All Gender].[F], ASC) ON ROWS\n" + "from [Sales]\n" + "where ([Customers].[All Customers].[USA].[CA].[San Francisco],\n" + " [Time].[1997], " + scenarioUniqueName + ")"); // With bug MONDRIAN-815, got an NPE here, because cell (0, 1) has a // null value. final CellSet cellSet = pstmt.executeQuery(); TestContext.assertEqualsVerbose( "Axis #0:\n" + "{[Customers].[USA].[CA].[San Francisco], [Time].[1997], " + scenarioUniqueName + "}\n" + "Axis #1:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Beverages]}\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "Row #0: 2\n" + "Row #0: \n" + "Row #0: 2\n" + "Row #1: 4\n" + "Row #1: 2\n" + "Row #1: 2\n", TestContext.toString(cellSet)); cellSet.getCell(Arrays.asList(0, 1)) .setValue(10, AllocationPolicy.EQUAL_ALLOCATION); cellSet.getCell(Arrays.asList(1, 0)) .setValue(999, AllocationPolicy.EQUAL_ALLOCATION); final CellSet cellSet2 = pstmt.executeQuery(); TestContext.assertEqualsVerbose( "Axis #0:\n" + "{[Customers].[USA].[CA].[San Francisco], [Time].[1997], " + scenarioUniqueName + "}\n" + "Axis #1:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Axis #2:\n" + "{[Product].[Drink].[Alcoholic Beverages]}\n" + "{[Product].[Drink].[Beverages]}\n" + "Row #0: 10\n" + "Row #0: 5\n" + "Row #0: 5\n" + "Row #1: 1,001\n" + "Row #1: 999\n" + "Row #1: 2\n", TestContext.toString(cellSet2)); } // TODO: test whether it is valid for two connections to have the same // active scenario // TODO: test that assigning a string to a numeric cell succeeds only if // the string contains a valid number // TODO: test that assigning a double to an integer cell succeeds; and some // other data types // TODO: test that EQUAL_ALLOCATION assigns to (a) cells that were // already empty, (b) cells that were null, (c) cells that are not visible // to the caller. I'm not sure that (c) works right now. } // End ScenarioTest.java mondrian-3.4.1/testsrc/main/mondrian/test/NamedSetTest.java0000644000175000017500000014536711735330606023652 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. // // jhyde, Feb 14, 2003 */ package mondrian.test; import mondrian.olap.Result; import mondrian.olap.Util; import mondrian.spi.impl.FilterDynamicSchemaProcessor; import java.io.InputStream; /** * Unit-test for named sets, in all their various forms: WITH SET, * sets defined against cubes, virtual cubes, and at the schema level. * * @author jhyde * @since April 30, 2005 */ public class NamedSetTest extends FoodMartTestCase { public NamedSetTest() { super(); } public NamedSetTest(String name) { super(name); } /** * Set defined in query according measures, hence context-dependent. */ public void testNamedSet() { assertQueryReturns( "WITH\n" + " SET [Top Sellers]\n" + "AS \n" + " 'TopCount([Warehouse].[Warehouse Name].MEMBERS, 10, \n" + " [Measures].[Warehouse Sales])'\n" + "SELECT \n" + " {[Measures].[Warehouse Sales]} ON COLUMNS,\n" + " {[Top Sellers]} ON ROWS\n" + "FROM \n" + " [Warehouse]\n" + "WHERE \n" + " [Time].[Year].[1997]", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Warehouse Sales]}\n" + "Axis #2:\n" + "{[Warehouse].[USA].[OR].[Salem].[Treehouse Distribution]}\n" + "{[Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.]}\n" + "{[Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.]}\n" + "{[Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage]}\n" + "{[Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.]}\n" + "{[Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking]}\n" + "{[Warehouse].[USA].[WA].[Spokane].[Jones International]}\n" + "{[Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods]}\n" + "{[Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse]}\n" + "{[Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.]}\n" + "Row #0: 31,116.375\n" + "Row #1: 30,743.772\n" + "Row #2: 22,907.959\n" + "Row #3: 22,869.79\n" + "Row #4: 22,187.418\n" + "Row #5: 22,046.942\n" + "Row #6: 10,879.674\n" + "Row #7: 10,212.201\n" + "Row #8: 10,156.496\n" + "Row #9: 7,718.678\n"); } /** * Set defined on top of calc member. */ public void testNamedSetOnMember() { switch (getTestContext().getDialect().getDatabaseProduct()) { case INFOBRIGHT: // Mondrian generates 'select ... sum(warehouse_sales) - // sum(warehouse_cost) as c ... order by c4', correctly, but // Infobright gives error "'c4' isn't in GROUP BY". return; } assertQueryReturns( "WITH\n" + " MEMBER [Measures].[Profit]\n" + "AS '[Measures].[Warehouse Sales] - [Measures].[Warehouse Cost] '\n" + " SET [Top Performers]\n" + "AS \n" + " 'TopCount([Warehouse].[Warehouse Name].MEMBERS, 5, \n" + " [Measures].[Profit])'\n" + "SELECT \n" + " {[Measures].[Profit]} ON COLUMNS,\n" + " {[Top Performers]} ON ROWS\n" + "FROM \n" + " [Warehouse]\n" + "WHERE \n" + " [Time].[Year].[1997].[Q2]", "Axis #0:\n" + "{[Time].[1997].[Q2]}\n" + "Axis #1:\n" + "{[Measures].[Profit]}\n" + "Axis #2:\n" + "{[Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.]}\n" + "{[Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage]}\n" + "{[Warehouse].[USA].[OR].[Salem].[Treehouse Distribution]}\n" + "{[Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.]}\n" + "{[Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking]}\n" + "Row #0: 4,516.756\n" + "Row #1: 4,189.36\n" + "Row #2: 4,169.318\n" + "Row #3: 3,848.647\n" + "Row #4: 3,708.717\n"); } /** * Set defined by explicit tlist in query. */ public void testNamedSetAsList() { assertQueryReturns( "WITH SET [ChardonnayChablis] AS\n" + " '{[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Good].[Good Chardonnay],\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Pearl].[Pearl Chardonnay],\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Portsmouth].[Portsmouth Chardonnay],\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Top Measure].[Top Measure Chardonnay],\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Walrus].[Walrus Chardonnay],\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Good].[Good Chablis Wine],\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Pearl].[Pearl Chablis Wine],\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Portsmouth].[Portsmouth Chablis Wine],\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Top Measure].[Top Measure Chablis Wine],\n" + " [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Walrus].[Walrus Chablis Wine]}'\n" + "SELECT\n" + " [ChardonnayChablis] ON COLUMNS,\n" + " {Measures.[Unit Sales]} ON ROWS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Good].[Good Chardonnay]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Pearl].[Pearl Chardonnay]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Portsmouth].[Portsmouth Chardonnay]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Top Measure].[Top Measure Chardonnay]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Walrus].[Walrus Chardonnay]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Good].[Good Chablis Wine]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Pearl].[Pearl Chablis Wine]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Portsmouth].[Portsmouth Chablis Wine]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Top Measure].[Top Measure Chablis Wine]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Walrus].[Walrus Chablis Wine]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 192\n" + "Row #0: 189\n" + "Row #0: 170\n" + "Row #0: 164\n" + "Row #0: 173\n" + "Row #0: 163\n" + "Row #0: 209\n" + "Row #0: 136\n" + "Row #0: 140\n" + "Row #0: 185\n"); } /** * Set defined using filter expression. */ public void testIntrinsic() { assertQueryReturns( "WITH SET [ChardonnayChablis] AS\n" + " 'Filter([Product].Members, (InStr(1, [Product].CurrentMember.Name, \"chardonnay\") <> 0) OR (InStr(1, [Product].CurrentMember.Name, \"chablis\") <> 0))'\n" + "SELECT\n" + " [ChardonnayChablis] ON COLUMNS,\n" + " {Measures.[Unit Sales]} ON ROWS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n"); assertQueryReturns( "WITH SET [BeerMilk] AS\n" + " 'Filter([Product].Members, (InStr(1, [Product].CurrentMember.Name, \"Beer\") <> 0) OR (InStr(1, LCase([Product].CurrentMember.Name), \"milk\") <> 0))'\n" + "SELECT\n" + " [BeerMilk] ON COLUMNS,\n" + " {Measures.[Unit Sales]} ON ROWS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Imported Beer]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Light Beer]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Pearl].[Pearl Imported Beer]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Pearl].[Pearl Light Beer]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth].[Portsmouth Imported Beer]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth].[Portsmouth Light Beer]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Top Measure].[Top Measure Imported Beer]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Top Measure].[Top Measure Light Beer]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Walrus].[Walrus Imported Beer]}\n" + "{[Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Walrus].[Walrus Light Beer]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Booker].[Booker 1% Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Booker].[Booker 2% Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Booker].[Booker Buttermilk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Booker].[Booker Chocolate Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Booker].[Booker Whole Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Carlson].[Carlson 1% Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Carlson].[Carlson 2% Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Carlson].[Carlson Buttermilk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Carlson].[Carlson Chocolate Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Carlson].[Carlson Whole Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Club].[Club 1% Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Club].[Club 2% Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Club].[Club Buttermilk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Club].[Club Chocolate Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Club].[Club Whole Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Even Better].[Even Better 1% Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Even Better].[Even Better 2% Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Even Better].[Even Better Buttermilk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Even Better].[Even Better Chocolate Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Even Better].[Even Better Whole Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Gorilla].[Gorilla 1% Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Gorilla].[Gorilla 2% Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Gorilla].[Gorilla Buttermilk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Gorilla].[Gorilla Chocolate Milk]}\n" + "{[Product].[Drink].[Dairy].[Dairy].[Milk].[Gorilla].[Gorilla Whole Milk]}\n" + "{[Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Atomic].[Atomic Malted Milk Balls]}\n" + "{[Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Choice].[Choice Malted Milk Balls]}\n" + "{[Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Gulf Coast].[Gulf Coast Malted Milk Balls]}\n" + "{[Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Musial].[Musial Malted Milk Balls]}\n" + "{[Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Thresher].[Thresher Malted Milk Balls]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 6,838\n" + "Row #0: 1,683\n" + "Row #0: 154\n" + "Row #0: 115\n" + "Row #0: 175\n" + "Row #0: 210\n" + "Row #0: 187\n" + "Row #0: 175\n" + "Row #0: 145\n" + "Row #0: 161\n" + "Row #0: 174\n" + "Row #0: 187\n" + "Row #0: 4,186\n" + "Row #0: 189\n" + "Row #0: 177\n" + "Row #0: 110\n" + "Row #0: 133\n" + "Row #0: 163\n" + "Row #0: 212\n" + "Row #0: 131\n" + "Row #0: 175\n" + "Row #0: 175\n" + "Row #0: 234\n" + "Row #0: 155\n" + "Row #0: 145\n" + "Row #0: 140\n" + "Row #0: 159\n" + "Row #0: 168\n" + "Row #0: 190\n" + "Row #0: 177\n" + "Row #0: 227\n" + "Row #0: 197\n" + "Row #0: 168\n" + "Row #0: 160\n" + "Row #0: 133\n" + "Row #0: 174\n" + "Row #0: 151\n" + "Row #0: 143\n" + "Row #0: 188\n" + "Row #0: 176\n" + "Row #0: 192\n" + "Row #0: 157\n" + "Row #0: 164\n"); } /** * Tests a named set defined in a query which consists of tuples. */ public void testNamedSetCrossJoin() { assertQueryReturns( "WITH\n" + " SET [Store Types by Country]\n" + "AS\n" + " 'CROSSJOIN({[Store].[Store Country].MEMBERS},\n" + " {[Store Type].[Store Type].MEMBERS})'\n" + "SELECT\n" + " {[Measures].[Units Ordered]} ON COLUMNS,\n" + " NON EMPTY {[Store Types by Country]} ON ROWS\n" + "FROM\n" + " [Warehouse]\n" + "WHERE\n" + " [Time].[1997].[Q2]", "Axis #0:\n" + "{[Time].[1997].[Q2]}\n" + "Axis #1:\n" + "{[Measures].[Units Ordered]}\n" + "Axis #2:\n" + "{[Store].[USA], [Store Type].[Deluxe Supermarket]}\n" + "{[Store].[USA], [Store Type].[Mid-Size Grocery]}\n" + "{[Store].[USA], [Store Type].[Supermarket]}\n" + "Row #0: 16843.0\n" + "Row #1: 2295.0\n" + "Row #2: 34856.0\n"); } // Disabled because fails with error ' = is not a function' // Also, don't know whether [oNormal] will correctly resolve to // [Store Type].[oNormal]. public void _testXxx() { assertQueryReturns( "WITH MEMBER [Store Type].[All Store Type].[oNormal] AS 'Aggregate(Filter([Customers].[Name].Members, [Customers].CurrentMember.Properties(\"Member Card\") = \"Normal\") * {[Store Type].[All Store Type]})'\n" + "MEMBER [Store Type].[All Store Type].[oBronze] AS 'Aggregate(Filter([Customers].[Name].Members, [Customers].CurrentMember.Properties(\"Member Card\") = \"Bronze\") * {[Store Type].[All Store Type]})'\n" + "MEMBER [Store Type].[All Store Type].[oGolden] AS 'Aggregate(Filter([Customers].[Name].Members, [Customers].CurrentMember.Properties(\"Member Card\") = \"Golden\") * {[Store Type].[All Store Type]})'\n" + "MEMBER [Store Type].[All Store Type].[oSilver] AS 'Aggregate(Filter([Customers].[Name].Members, [Customers].CurrentMember.Properties(\"Member Card\") = \"Silver\") * {[Store Type].[All Store Type]})'\n" + "SET CardTypes AS '{[oNormal], [oBronze], [oGolden], [oSilver]}'\n" + "SELECT {[Unit Sales]} ON COLUMNS, CardTypes ON ROWS\n" + "FROM Sales", "xxxx"); } /** * Set used inside expression (Crossjoin). */ public void testNamedSetUsedInCrossJoin() { assertQueryReturns( "WITH\n" + " SET [TopMedia] AS 'TopCount([Promotion Media].children, 5, [Measures].[Store Sales])' \n" + "SELECT {[Time].[1997].[Q1], [Time].[1997].[Q2]} ON COLUMNS,\n" + " {CrossJoin([TopMedia], [Product].children)} ON ROWS\n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "Axis #2:\n" + "{[Promotion Media].[No Media], [Product].[Drink]}\n" + "{[Promotion Media].[No Media], [Product].[Food]}\n" + "{[Promotion Media].[No Media], [Product].[Non-Consumable]}\n" + "{[Promotion Media].[Daily Paper, Radio, TV], [Product].[Drink]}\n" + "{[Promotion Media].[Daily Paper, Radio, TV], [Product].[Food]}\n" + "{[Promotion Media].[Daily Paper, Radio, TV], [Product].[Non-Consumable]}\n" + "{[Promotion Media].[Daily Paper], [Product].[Drink]}\n" + "{[Promotion Media].[Daily Paper], [Product].[Food]}\n" + "{[Promotion Media].[Daily Paper], [Product].[Non-Consumable]}\n" + "{[Promotion Media].[Product Attachment], [Product].[Drink]}\n" + "{[Promotion Media].[Product Attachment], [Product].[Food]}\n" + "{[Promotion Media].[Product Attachment], [Product].[Non-Consumable]}\n" + "{[Promotion Media].[Cash Register Handout], [Product].[Drink]}\n" + "{[Promotion Media].[Cash Register Handout], [Product].[Food]}\n" + "{[Promotion Media].[Cash Register Handout], [Product].[Non-Consumable]}\n" + "Row #0: 3,970\n" + "Row #0: 4,287\n" + "Row #1: 32,939\n" + "Row #1: 33,238\n" + "Row #2: 8,650\n" + "Row #2: 9,057\n" + "Row #3: 142\n" + "Row #3: 364\n" + "Row #4: 975\n" + "Row #4: 2,523\n" + "Row #5: 250\n" + "Row #5: 603\n" + "Row #6: 464\n" + "Row #6: 66\n" + "Row #7: 3,173\n" + "Row #7: 464\n" + "Row #8: 862\n" + "Row #8: 121\n" + "Row #9: 171\n" + "Row #9: 106\n" + "Row #10: 1,344\n" + "Row #10: 814\n" + "Row #11: 362\n" + "Row #11: 165\n" + "Row #12: \n" + "Row #12: 92\n" + "Row #13: \n" + "Row #13: 933\n" + "Row #14: \n" + "Row #14: 229\n"); } public void testAggOnCalcMember() { assertQueryReturns( "WITH\n" + " SET [TopMedia] AS 'TopCount([Promotion Media].children, 5, [Measures].[Store Sales])' \n" + " MEMBER [Measures].[California sales for Top Media] AS 'Sum([TopMedia], ([Store].[USA].[CA], [Measures].[Store Sales]))'\n" + "SELECT {[Time].[1997].[Q1], [Time].[1997].[Q2]} ON COLUMNS,\n" + " {[Product].children} ON ROWS\n" + "FROM [Sales]\n" + "WHERE [Measures].[California sales for Top Media]", "Axis #0:\n" + "{[Measures].[California sales for Top Media]}\n" + "Axis #1:\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 2,725.85\n" + "Row #0: 2,715.56\n" + "Row #1: 21,200.84\n" + "Row #1: 23,263.72\n" + "Row #2: 5,598.71\n" + "Row #2: 6,111.74\n"); } public void testContextSensitiveNamedSet() { // For reference. assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + "Order([Promotion Media].Children, [Measures].[Unit Sales], DESC) ON ROWS\n" + "FROM [Sales]\n" + "WHERE [Time].[1997]", "Axis #0:\n" + "{[Time].[1997]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Promotion Media].[No Media]}\n" + "{[Promotion Media].[Daily Paper, Radio, TV]}\n" + "{[Promotion Media].[Daily Paper]}\n" + "{[Promotion Media].[Product Attachment]}\n" + "{[Promotion Media].[Daily Paper, Radio]}\n" + "{[Promotion Media].[Cash Register Handout]}\n" + "{[Promotion Media].[Sunday Paper, Radio]}\n" + "{[Promotion Media].[Street Handout]}\n" + "{[Promotion Media].[Sunday Paper]}\n" + "{[Promotion Media].[Bulk Mail]}\n" + "{[Promotion Media].[In-Store Coupon]}\n" + "{[Promotion Media].[TV]}\n" + "{[Promotion Media].[Sunday Paper, Radio, TV]}\n" + "{[Promotion Media].[Radio]}\n" + "Row #0: 195,448\n" + "Row #1: 9,513\n" + "Row #2: 7,738\n" + "Row #3: 7,544\n" + "Row #4: 6,891\n" + "Row #5: 6,697\n" + "Row #6: 5,945\n" + "Row #7: 5,753\n" + "Row #8: 4,339\n" + "Row #9: 4,320\n" + "Row #10: 3,798\n" + "Row #11: 3,607\n" + "Row #12: 2,726\n" + "Row #13: 2,454\n"); // For reference. assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + "Order([Promotion Media].Children, [Measures].[Unit Sales], DESC) ON ROWS\n" + "FROM [Sales]\n" + "WHERE [Time].[1997].[Q2]", "Axis #0:\n" + "{[Time].[1997].[Q2]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Promotion Media].[No Media]}\n" + "{[Promotion Media].[Daily Paper, Radio, TV]}\n" + "{[Promotion Media].[Daily Paper, Radio]}\n" + "{[Promotion Media].[Sunday Paper, Radio]}\n" + "{[Promotion Media].[TV]}\n" + "{[Promotion Media].[Cash Register Handout]}\n" + "{[Promotion Media].[Sunday Paper, Radio, TV]}\n" + "{[Promotion Media].[Product Attachment]}\n" + "{[Promotion Media].[Sunday Paper]}\n" + "{[Promotion Media].[Bulk Mail]}\n" + "{[Promotion Media].[Daily Paper]}\n" + "{[Promotion Media].[Street Handout]}\n" + "{[Promotion Media].[Radio]}\n" + "{[Promotion Media].[In-Store Coupon]}\n" + "Row #0: 46,582\n" + "Row #1: 3,490\n" + "Row #2: 2,704\n" + "Row #3: 2,327\n" + "Row #4: 1,344\n" + "Row #5: 1,254\n" + "Row #6: 1,108\n" + "Row #7: 1,085\n" + "Row #8: 784\n" + "Row #9: 733\n" + "Row #10: 651\n" + "Row #11: 473\n" + "Row #12: 40\n" + "Row #13: 35\n"); // The bottom medium in 1997 is Radio, with $2454 in sales. // The bottom medium in 1997.Q2 is In-Store Coupon, with $35 in sales, // whereas Radio has $40 in sales in 1997.Q2. assertQueryReturns( "WITH\n" + " SET [Bottom Media] AS 'BottomCount([Promotion Media].children, 1, [Measures].[Unit Sales])' \n" + " MEMBER [Measures].[Unit Sales for Bottom Media] AS 'Sum([Bottom Media], [Measures].[Unit Sales])'\n" + "SELECT {[Measures].[Unit Sales for Bottom Media]} ON COLUMNS,\n" + " {[Time].[1997], [Time].[1997].[Q2]} ON ROWS\n" + "FROM [Sales]", // Note that Row #1 gives 40. 35 would be wrong. // [In-Store Coupon], which was bottom for 1997.Q2 but not for // 1997. "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales for Bottom Media]}\n" + "Axis #2:\n" + "{[Time].[1997]}\n" + "{[Time].[1997].[Q2]}\n" + "Row #0: 2,454\n" + "Row #1: 40\n"); assertQueryReturns( "WITH\n" + " SET [TopMedia] AS 'TopCount([Promotion Media].children, 3, [Measures].[Store Sales])' \n" + " MEMBER [Measures].[California sales for Top Media] AS 'Sum([TopMedia], [Measures].[Store Sales])'\n" + "SELECT \n" + " CrossJoin({[Store], [Store].[USA].[CA]},\n" + " {[Time].[1997].[Q1], [Time].[1997].[Q2]}) ON COLUMNS,\n" + " {[Product], [Product].children} ON ROWS\n" + "FROM [Sales]\n" + "WHERE [Measures].[California sales for Top Media]", "Axis #0:\n" + "{[Measures].[California sales for Top Media]}\n" + "Axis #1:\n" + "{[Store].[All Stores], [Time].[1997].[Q1]}\n" + "{[Store].[All Stores], [Time].[1997].[Q2]}\n" + "{[Store].[USA].[CA], [Time].[1997].[Q1]}\n" + "{[Store].[USA].[CA], [Time].[1997].[Q2]}\n" + "Axis #2:\n" + "{[Product].[All Products]}\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 108,249.52\n" + "Row #0: 107,649.93\n" + "Row #0: 29,482.53\n" + "Row #0: 28,953.02\n" + "Row #1: 8,930.95\n" + "Row #1: 9,551.93\n" + "Row #1: 2,721.23\n" + "Row #1: 2,444.78\n" + "Row #2: 78,375.66\n" + "Row #2: 77,219.13\n" + "Row #2: 21,165.50\n" + "Row #2: 20,924.43\n" + "Row #3: 20,942.91\n" + "Row #3: 20,878.87\n" + "Row #3: 5,595.80\n" + "Row #3: 5,583.81\n"); } public void testOrderedNamedSet() { // From http://www.developersdex.com assertQueryReturns( "WITH SET [SET1] AS\n" + "'ORDER ({[Education Level].[Education Level].Members}, [Gender].[All Gender].[F], ASC)'\n" + "MEMBER [Gender].[RANK1] AS 'rank([Education Level].currentmember, [SET1])'\n" + "select\n" + "{[Gender].[All Gender].[F], [Gender].[RANK1]} on columns,\n" + "{[Education Level].[Education Level].Members} on rows\n" + "from Sales\n" + "where ([Measures].[Store Sales])", // MSAS gives results as below, except ranks are displayed as // integers, e.g. '1'. "Axis #0:\n" + "{[Measures].[Store Sales]}\n" + "Axis #1:\n" + "{[Gender].[F]}\n" + "{[Gender].[RANK1]}\n" + "Axis #2:\n" + "{[Education Level].[Bachelors Degree]}\n" + "{[Education Level].[Graduate Degree]}\n" + "{[Education Level].[High School Degree]}\n" + "{[Education Level].[Partial College]}\n" + "{[Education Level].[Partial High School]}\n" + "Row #0: 72,119.26\n" + "Row #0: 3\n" + "Row #1: 17,641.64\n" + "Row #1: 1\n" + "Row #2: 81,112.23\n" + "Row #2: 4\n" + "Row #3: 27,175.97\n" + "Row #3: 2\n" + "Row #4: 82,177.11\n" + "Row #4: 5\n"); assertQueryReturns( "WITH SET [SET1] AS\n" + "'ORDER ({[Education Level].[Education Level].Members}, [Gender].[All Gender].[F], ASC)'\n" + "MEMBER [Gender].[RANK1] AS 'rank([Education Level].currentmember, [SET1])'\n" + "select\n" + "{[Gender].[All Gender].[F], [Gender].[RANK1]} on columns,\n" + "{[Education Level].[Education Level].Members} on rows\n" + "from Sales\n" + "where ([Measures].[Profit])", // MSAS gives results as below. The ranks are (correctly) 0 // because profit is a calc member. "Axis #0:\n" + "{[Measures].[Profit]}\n" + "Axis #1:\n" + "{[Gender].[F]}\n" + "{[Gender].[RANK1]}\n" + "Axis #2:\n" + "{[Education Level].[Bachelors Degree]}\n" + "{[Education Level].[Graduate Degree]}\n" + "{[Education Level].[High School Degree]}\n" + "{[Education Level].[Partial College]}\n" + "{[Education Level].[Partial High School]}\n" + "Row #0: $43,382.33\n" + "Row #0: $0.00\n" + "Row #1: $10,599.59\n" + "Row #1: $0.00\n" + "Row #2: $48,766.50\n" + "Row #2: $0.00\n" + "Row #3: $16,306.05\n" + "Row #3: $0.00\n" + "Row #4: $49,394.27\n" + "Row #4: $0.00\n"); // Solve order fixes the problem. assertQueryReturns( "WITH SET [SET1] AS\n" + "'ORDER ({[Education Level].[Education Level].Members}, [Gender].[F], ASC)'\n" + "MEMBER [Gender].[RANK1] AS 'rank([Education Level].currentmember, [SET1])', \n" + " SOLVE_ORDER = 10\n" + "select\n" + "{[Gender].[F], [Gender].[RANK1]} on columns,\n" + "{[Education Level].[Education Level].Members} on rows\n" + "from Sales\n" + "where ([Measures].[Profit])", // MSAS gives results as below. "Axis #0:\n" + "{[Measures].[Profit]}\n" + "Axis #1:\n" + "{[Gender].[F]}\n" + "{[Gender].[RANK1]}\n" + "Axis #2:\n" + "{[Education Level].[Bachelors Degree]}\n" + "{[Education Level].[Graduate Degree]}\n" + "{[Education Level].[High School Degree]}\n" + "{[Education Level].[Partial College]}\n" + "{[Education Level].[Partial High School]}\n" + "Row #0: $43,382.33\n" + "Row #0: 3\n" + "Row #1: $10,599.59\n" + "Row #1: 1\n" + "Row #2: $48,766.50\n" + "Row #2: 4\n" + "Row #3: $16,306.05\n" + "Row #3: 2\n" + "Row #4: $49,394.27\n" + "Row #4: 5\n"); } public void testGenerate() { assertQueryReturns( "with \n" + " member [Measures].[DateName] as \n" + " 'Generate({[Time].[1997].[Q1], [Time].[1997].[Q2]}, [Time].[Time].CurrentMember.Name) '\n" + "select {[Measures].[DateName]} on columns,\n" + " {[Time].[1997].[Q1], [Time].[1997].[Q2]} on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[DateName]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "Row #0: Q1Q2\n" + "Row #1: Q1Q2\n"); assertQueryReturns( "with \n" + " member [Measures].[DateName] as \n" + " 'Generate({[Time].[1997].[Q1], [Time].[1997].[Q2]}, [Time].[Time].CurrentMember.Name, \" and \") '\n" + "select {[Measures].[DateName]} on columns,\n" + " {[Time].[1997].[Q1], [Time].[1997].[Q2]} on rows\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[DateName]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1]}\n" + "{[Time].[1997].[Q2]}\n" + "Row #0: Q1 and Q2\n" + "Row #1: Q1 and Q2\n"); } public void testNamedSetAgainstCube() { final TestContext tc = getTestContext().withSchemaProcessor( NamedSetsInCubeProcessor.class); // Set defined against cube, using 'formula' attribute. tc.assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[CA Cities]} ON ROWS\n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[Alameda]}\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "Row #0: \n" + "Row #1: 21,333\n" + "Row #2: 25,663\n" + "Row #3: 25,635\n" + "Row #4: 2,117\n"); // Set defined against cube, in terms of another set, and using // '' element. tc.assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Top CA Cities]} ON ROWS\n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "Row #0: 25,663\n" + "Row #1: 25,635\n"); // Override named set in query. tc.assertQueryReturns( "WITH SET [CA Cities] AS '{[Store].[USA].[OR].[Portland]}' " + "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[CA Cities]} ON ROWS\n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[OR].[Portland]}\n" + "Row #0: 26,079\n"); // When [CA Cities] is overridden, does the named set [Top CA Cities], // which is derived from it, use the new definition? No. It stays // bound to the original definition. tc.assertQueryReturns( "WITH SET [CA Cities] AS '{[Store].[USA].[OR].[Portland]}' " + "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Top CA Cities]} ON ROWS\n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "Row #0: 25,663\n" + "Row #1: 25,635\n"); } public void testNamedSetAgainstSchema() { final TestContext tc = getTestContext().withSchemaProcessor( NamedSetsInCubeAndSchemaProcessor.class); tc.assertQueryReturns( "SELECT {[Measures].[Store Sales]} on columns,\n" + " Intersect([Top CA Cities], [Top USA Stores]) on rows\n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "Row #0: 54,545.28\n"); // Use non-existent set. tc.assertQueryThrows( "SELECT {[Measures].[Store Sales]} on columns,\n" + " Intersect([Top CA Cities], [Top Ukrainian Cities]) on rows\n" + "FROM [Sales]", "MDX object '[Top Ukrainian Cities]' not found in cube 'Sales'"); } public void testBadNamedSet() { final TestContext tc = TestContext.instance().create( null, null, null, "", null, null); tc.assertQueryThrows( "SELECT {[Measures].[Store Sales]} on columns,\n" + " {[Bad]} on rows\n" + "FROM [Sales]", "Named set 'Bad' has bad formula"); } public void testNamedSetMustBeSet() { Result result; String queryString; String pattern; // Formula for a named set must not be a member. queryString = "with set [Foo] as ' [Store].CurrentMember '" + "select {[Foo]} on columns from [Sales]"; pattern = "Set expression '[Foo]' must be a set"; assertQueryThrows(queryString, pattern); // Formula for a named set must not be a dimension. queryString = "with set [Foo] as ' [Store] '" + "select {[Foo]} on columns from [Sales]"; assertQueryThrows(queryString, pattern); // Formula for a named set must not be a level. queryString = "with set [Foo] as ' [Store].[Store Country] '" + "select {[Foo]} on columns from [Sales]"; assertQueryThrows(queryString, pattern); // Formula for a named set must not be a cube name. queryString = "with set [Foo] as ' [Sales] '" + "select {[Foo]} on columns from [Sales]"; assertQueryThrows( queryString, "MDX object '[Sales]' not found in cube 'Sales'"); // Formula for a named set must not be a string. queryString = "with set [Foo] as ' \"foobar\" '" + "select {[Foo]} on columns from [Sales]"; assertQueryThrows(queryString, pattern); // Formula for a named set must not be a number. queryString = "with set [Foo] as ' -1.45 '" + "select {[Foo]} on columns from [Sales]"; assertQueryThrows(queryString, pattern); // Formula for a named set must not be a tuple. queryString = "with set [Foo] as ' ([Gender].[M], [Marital Status].[S]) '" + "select {[Foo]} on columns from [Sales]"; assertQueryThrows(queryString, pattern); // Formula for a named set may be a set of tuples. queryString = "with set [Foo] as ' CrossJoin([Gender].members, [Marital Status].members) '" + "select {[Foo]} on columns from [Sales]"; result = executeQuery(queryString); Util.discard(result); // Formula for a named set may be a set of members. queryString = "with set [Foo] as ' [Gender].members '" + "select {[Foo]} on columns from [Sales]"; result = executeQuery(queryString); Util.discard(result); } public void testNamedSetsMixedWithCalcMembers() { final TestContext tc = getTestContext().withSchemaProcessor( MixedNamedSetSchemaProcessor.class); tc.assertQueryReturns( "select {\n" + " [Measures].[Unit Sales],\n" + " [Measures].[CA City Sales]} on columns,\n" + " Crossjoin(\n" + " [Time].[1997].Children,\n" + " [Top Products In CA]) on rows\n" + "from [Sales]\n" + "where [Marital Status].[S]", "Axis #0:\n" + "{[Marital Status].[S]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[CA City Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1], [Product].[Food].[Produce]}\n" + "{[Time].[1997].[Q1], [Product].[Food].[Snack Foods]}\n" + "{[Time].[1997].[Q1], [Product].[Non-Consumable].[Household]}\n" + "{[Time].[1997].[Q2], [Product].[Food].[Produce]}\n" + "{[Time].[1997].[Q2], [Product].[Food].[Snack Foods]}\n" + "{[Time].[1997].[Q2], [Product].[Non-Consumable].[Household]}\n" + "{[Time].[1997].[Q3], [Product].[Food].[Produce]}\n" + "{[Time].[1997].[Q3], [Product].[Food].[Snack Foods]}\n" + "{[Time].[1997].[Q3], [Product].[Non-Consumable].[Household]}\n" + "{[Time].[1997].[Q4], [Product].[Food].[Produce]}\n" + "{[Time].[1997].[Q4], [Product].[Food].[Snack Foods]}\n" + "{[Time].[1997].[Q4], [Product].[Non-Consumable].[Household]}\n" + "Row #0: 4,872\n" + "Row #0: $1,218.0\n" + "Row #1: 3,746\n" + "Row #1: $840.0\n" + "Row #2: 3,425\n" + "Row #2: $817.0\n" + "Row #3: 4,633\n" + "Row #3: $1,320.0\n" + "Row #4: 3,588\n" + "Row #4: $1,058.0\n" + "Row #5: 3,149\n" + "Row #5: $938.0\n" + "Row #6: 4,651\n" + "Row #6: $1,353.0\n" + "Row #7: 3,895\n" + "Row #7: $1,134.0\n" + "Row #8: 3,395\n" + "Row #8: $1,029.0\n" + "Row #9: 5,160\n" + "Row #9: $1,550.0\n" + "Row #10: 4,160\n" + "Row #10: $1,301.0\n" + "Row #11: 3,808\n" + "Row #11: $1,166.0\n"); } public void testNamedSetAndUnion() { assertQueryReturns( "with set [Set Education Level] as\n" + " '{([Education Level].[All Education Levels].[Bachelors Degree]),\n" + " ([Education Level].[All Education Levels].[Graduate Degree])}'\n" + "select\n" + " {[Measures].[Unit Sales],\n" + " [Measures].[Store Cost],\n" + " [Measures].[Store Sales]} ON COLUMNS,\n" + " UNION(\n" + " CROSSJOIN(\n" + " {[Time].[1997].[Q1]},\n" + " [Set Education Level]), \n" + " {([Time].[1997].[Q1],\n" + " [Education Level].[All Education Levels].[Graduate Degree])}) ON ROWS\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Store Cost]}\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1], [Education Level].[Bachelors Degree]}\n" + "{[Time].[1997].[Q1], [Education Level].[Graduate Degree]}\n" + "Row #0: 17,066\n" + "Row #0: 14,234.10\n" + "Row #0: 35,699.43\n" + "Row #1: 3,637\n" + "Row #1: 3,030.82\n" + "Row #1: 7,583.71\n"); } /** * Tests that named sets never depend on anything. */ public void testNamedSetDependencies() { final TestContext tc = getTestContext().withSchemaProcessor( NamedSetsInCubeProcessor.class); tc.assertSetExprDependsOn("[Top CA Cities]", "{}"); } /** * Test csae for bug 1971080, "hierarchize(named set) causes attempt to * sort immutable list". */ public void testHierarchizeNamedSetImmutable() { assertQueryReturns( "with set necj as\n" + "NonEmptyCrossJoin([Customers].[Name].members,[Store].[Store Name].members)\n" + "select\n" + "{[Measures].[Unit Sales]} on columns,\n" + "Tail(hierarchize(necj),5) on rows\n" + "from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Customers].[USA].[WA].[Yakima].[Tracy Meyer], [Store].[USA].[WA].[Yakima].[Store 23]}\n" + "{[Customers].[USA].[WA].[Yakima].[Vanessa Thompson], [Store].[USA].[WA].[Yakima].[Store 23]}\n" + "{[Customers].[USA].[WA].[Yakima].[Velma Lykes], [Store].[USA].[WA].[Yakima].[Store 23]}\n" + "{[Customers].[USA].[WA].[Yakima].[William Battaglia], [Store].[USA].[WA].[Yakima].[Store 23]}\n" + "{[Customers].[USA].[WA].[Yakima].[Wilma Fink], [Store].[USA].[WA].[Yakima].[Store 23]}\n" + "Row #0: 44\n" + "Row #1: 128\n" + "Row #2: 55\n" + "Row #3: 149\n" + "Row #4: 89\n"); } public void testCurrentAndCurrentOrdinal() { assertQueryReturns( "with set [Gender Marital Status] as\n" + " [Gender].members * [Marital Status].members\n" + "member [Measures].[GMS Ordinal] as\n" + " [Gender Marital Status].CurrentOrdinal\n" + "member [Measures].[GMS Name]\n" + " as TupleToStr([Gender Marital Status].Current)\n" + "select {\n" + " [Measures].[Unit Sales],\n" + " [Measures].[GMS Ordinal],\n" + " [Measures].[GMS Name]} on 0,\n" + " {[Gender Marital Status]} on 1\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[GMS Ordinal]}\n" + "{[Measures].[GMS Name]}\n" + "Axis #2:\n" + "{[Gender].[All Gender], [Marital Status].[All Marital Status]}\n" + "{[Gender].[All Gender], [Marital Status].[M]}\n" + "{[Gender].[All Gender], [Marital Status].[S]}\n" + "{[Gender].[F], [Marital Status].[All Marital Status]}\n" + "{[Gender].[F], [Marital Status].[M]}\n" + "{[Gender].[F], [Marital Status].[S]}\n" + "{[Gender].[M], [Marital Status].[All Marital Status]}\n" + "{[Gender].[M], [Marital Status].[M]}\n" + "{[Gender].[M], [Marital Status].[S]}\n" + "Row #0: 266,773\n" + "Row #0: 0\n" + "Row #0: ([Gender].[All Gender], [Marital Status].[All Marital Status])\n" + "Row #1: 131,796\n" + "Row #1: 1\n" + "Row #1: ([Gender].[All Gender], [Marital Status].[M])\n" + "Row #2: 134,977\n" + "Row #2: 2\n" + "Row #2: ([Gender].[All Gender], [Marital Status].[S])\n" + "Row #3: 131,558\n" + "Row #3: 3\n" + "Row #3: ([Gender].[F], [Marital Status].[All Marital Status])\n" + "Row #4: 65,336\n" + "Row #4: 4\n" + "Row #4: ([Gender].[F], [Marital Status].[M])\n" + "Row #5: 66,222\n" + "Row #5: 5\n" + "Row #5: ([Gender].[F], [Marital Status].[S])\n" + "Row #6: 135,215\n" + "Row #6: 6\n" + "Row #6: ([Gender].[M], [Marital Status].[All Marital Status])\n" + "Row #7: 66,460\n" + "Row #7: 7\n" + "Row #7: ([Gender].[M], [Marital Status].[M])\n" + "Row #8: 68,755\n" + "Row #8: 8\n" + "Row #8: ([Gender].[M], [Marital Status].[S])\n"); } /** * Dynamic schema processor which adds two named sets to a the first cube * in a schema. */ public static class NamedSetsInCubeProcessor extends FilterDynamicSchemaProcessor { public String filter( String schemaUrl, Util.PropertyList connectInfo, InputStream stream) throws Exception { String s = super.filter(schemaUrl, connectInfo, stream); int i = s.indexOf(""); return s.substring(0, i) + "\n" + "\n" + "\n" + " TopCount([CA Cities], 2, [Measures].[Unit Sales])\n" + "\n" + s.substring(i); } } /** * Dynamic schema processor which adds two named sets to a the first cube * in a schema. */ public static class NamedSetsInCubeAndSchemaProcessor extends FilterDynamicSchemaProcessor { protected String filter( String schemaUrl, Util.PropertyList connectInfo, InputStream stream) throws Exception { String s = super.filter(schemaUrl, connectInfo, stream); int i = s.indexOf(""); s = s.substring(0, i) + "\n" + "\n" + "\n" + " TopCount([CA Cities], 2, [Measures].[Unit Sales])\n" + "\n" + s.substring(i); // Schema-level named sets occur after and and // before elements. i = s.indexOf(""); } s = s.substring(0, i) + "\n" + "\n" + "\n" + " TopCount(Descendants([Store].[USA]), 7)\n" + "\n" + s.substring(i); return s; } } /** * Dynamic schema processor which adds a named set which has a syntax * error. */ public static class MixedNamedSetSchemaProcessor extends FilterDynamicSchemaProcessor { protected String filter( String schemaUrl, Util.PropertyList connectInfo, InputStream stream) throws Exception { String s = super.filter(schemaUrl, connectInfo, stream); // Declare mutually dependent named sets and calculated members // at the end of a cube: // m2 references s1 // s1 references s0 and m1 and m0 int i = s.indexOf(""); s = s.substring(0, i) + "\n" // member [CA City Sales] references set [CA Cities] + " \n" + " \n" + " \n" // set [Top Products In CA] references member [CA City Sales] + "\n" + " TopCount([Product].[Product Department].MEMBERS, 3, ([Time].[1997].[Q3], [Measures].[CA City Sales]))\n" + "\n" + "\n" + s.substring(i); return s; } } } // End NamedSetTest.java mondrian-3.4.1/testsrc/main/mondrian/test/DynamicSchemaProcessorTest.java0000644000175000017500000001664211735330606026550 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2011 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.olap.Connection; import mondrian.olap.Util; import mondrian.spi.DynamicSchemaProcessor; import junit.framework.Assert; import junit.framework.TestCase; /** * Unit test DynamicSchemaProcessor. Tests availability of properties that DSP's * are called, and used to modify the resulting Mondrian schema * * @author ngoodman */ public class DynamicSchemaProcessorTest extends TestCase { public static final String FRAGMENT_ONE = "\n" + "\n" + "\n" + "

" + " " + " " + " " + " " + " " + " " + " " + " " + "
" + " " + " " + " " + " " + "\n" + "\n"; public static final String TEMPLATE_SCHEMA = FRAGMENT_ONE + "REPLACEME" + FRAGMENT_TWO; /** * Tests to make sure that our base DynamicSchemaProcessor works, with no * replacement. Does not test Mondrian is able to connect with the schema * definition. */ public void testDSPBasics() { DynamicSchemaProcessor dsp = new BaseDsp(); Util.PropertyList dummy = new Util.PropertyList(); String processedSchema = ""; try { processedSchema = dsp.processSchema("", dummy); } catch (Exception e) { // TODO some other assert failure message assertEquals(0, 1); } Assert.assertEquals(TEMPLATE_SCHEMA, processedSchema); } /** * Tests to make sure that our base DynamicSchemaProcessor works, and * Mondrian is able to parse and connect to FoodMart with it */ public void testFoodmartDsp() { final Connection monConnection = TestContext.instance() .withSchemaProcessor(BaseDsp.class) .getConnection(); assertEquals(monConnection.getSchema().getName(), "REPLACEME"); } /** * Our base, token replacing schema processor. * * @author ngoodman */ public static class BaseDsp implements DynamicSchemaProcessor { // Determines the "cubeName" protected String replaceToken = "REPLACEME"; public BaseDsp() {} public String processSchema( String schemaUrl, Util.PropertyList connectInfo) throws Exception { return getSchema(); } public String getSchema() throws Exception { return DynamicSchemaProcessorTest.TEMPLATE_SCHEMA.replaceAll( "REPLACEME", this.replaceToken); } } /** * Tests to ensure we have access to Connect properies in a DSP */ public void testProviderTestDSP() { Connection monConnection = TestContext.instance() .withSchemaProcessor(ProviderTestDSP.class) .getConnection(); assertEquals(monConnection.getSchema().getName(), "mondrian"); } /** * DSP that checks that replaces the Schema Name with the name of the * Provider property * * @author ngoodman * */ public static class ProviderTestDSP extends BaseDsp { public String processSchema( String schemaUrl, Util.PropertyList connectInfo) throws Exception { this.replaceToken = connectInfo.get("Provider"); return getSchema(); } } /** * Tests to ensure we have access to Connect properies in a DSP */ public void testDBInfoDSP() { Connection monConnection = TestContext.instance() .withSchemaProcessor(FoodMartCatalogDsp.class) .getConnection(); assertEquals( monConnection.getSchema().getName(), "FoodmartFoundInCatalogProperty"); } /** * Checks to make sure our Catalog property contains our FoodMart.xml VFS * URL * * @author ngoodman * */ public static class FoodMartCatalogDsp extends BaseDsp { public String processSchema( String schemaUrl, Util.PropertyList connectInfo) throws Exception { if (connectInfo.get("Catalog").indexOf("FoodMart.xml") <= 0) { this.replaceToken = "NoFoodmartFoundInCatalogProperty"; } else { this.replaceToken = "FoodmartFoundInCatalogProperty"; } return getSchema(); } } /** * Tests to ensure we have access to Connect properties in a DSP */ public void testCheckJdbcPropertyDsp() { Connection monConnection = TestContext.instance() .withSchemaProcessor(CheckJdbcPropertyDsp.class) .getConnection(); assertEquals( monConnection.getSchema().getName(), CheckJdbcPropertyDsp.RETURNTRUESTRING); } /** * Ensures we have access to the JDBC URL. Note, since Foodmart can run on * multiple databases all we check in schema name is the first four * characters (JDBC) * * @author ngoodman * */ public static class CheckJdbcPropertyDsp extends BaseDsp { public static String RETURNTRUESTRING = "true"; public static String RETURNFALSESTRING = "false"; public String processSchema( String schemaUrl, Util.PropertyList connectInfo) throws Exception { String dataSource = null; String jdbc = null; dataSource = connectInfo.get("DataSource"); jdbc = connectInfo.get("Jdbc"); // If we're using a DataSource we might not get a Jdbc= property // trivially return true. if (dataSource != null && dataSource.length() > 0) { this.replaceToken = RETURNTRUESTRING; return getSchema(); } // IF we're here, we don't have a DataSource and // our JDBC property should have jdbc: in the URL if (jdbc == null || !jdbc.startsWith("jdbc")) { this.replaceToken = RETURNFALSESTRING; return getSchema(); } else { // If we're here, we have a JDBC url this.replaceToken = RETURNTRUESTRING; return getSchema(); } } } } // End DynamicSchemaProcessorTest.java mondrian-3.4.1/testsrc/main/mondrian/test/MondrianResultPrinter.java0000644000175000017500000001100211735330606025576 0ustar drazzibdrazzib/* // Modified from junit's ResultPrinter class. Original code is covered by // the junit license and modifications are covered as follows: // // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 SAS Institute, Inc. // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. // // sasebb, 14 December, 2004 */ package mondrian.test; import junit.framework.*; import junit.runner.BaseTestRunner; import java.io.PrintStream; import java.text.NumberFormat; import java.util.Enumeration; public class MondrianResultPrinter implements TestListener { PrintStream fWriter; int fStarted = 0; public MondrianResultPrinter(PrintStream writer) { fWriter = writer; } /* API for use by textui.TestRunner */ synchronized void print(TestResult result, long runTime) { printHeader(); printErrors(result); printFailures(result); printFooter(runTime, result); } void printWaitPrompt() { getWriter().println(); getWriter().println(" to continue"); } /* Internal methods */ protected void printHeader() { getWriter().println(); } protected void printErrors(TestResult result) { printDefects(result.errors(), result.errorCount(), "error"); } protected void printFailures(TestResult result) { printDefects(result.failures(), result.failureCount(), "failure"); } protected void printDefects(Enumeration booBoos, int count, String type) { if (count == 0) { return; } if (count == 1) { getWriter().println("There was " + count + " " + type + ":"); } else { getWriter().println("There were " + count + " " + type + "s:"); } for (int i = 1; booBoos.hasMoreElements(); i++) { printDefect((TestFailure) booBoos.nextElement(), i); } } // only public for testing purposes public void printDefect(TestFailure booBoo, int count) { printDefectHeader(booBoo, count); printDefectTrace(booBoo); } protected void printDefectHeader(TestFailure booBoo, int count) { // I feel like making this a println, then adding a line // giving the throwable a chance to print something before we // get to the stack trace. getWriter().print(count + ") " + booBoo.failedTest()); } protected void printDefectTrace(TestFailure booBoo) { getWriter().print(BaseTestRunner.getFilteredTrace(booBoo.trace())); } protected void printFooter(long runTime, TestResult result) { if (result.wasSuccessful()) { getWriter().println(); getWriter().print("OK"); getWriter().println( " (" + result.runCount() + " test" + (result.runCount() == 1 ? "" : "s") + ")"); } else { getWriter().println(); getWriter().println("FAILURES!!!"); getWriter().println( "Tests run: " + result.runCount() + ", Failures: " + result.failureCount() + ", Errors: " + result.errorCount()); } getWriter().println(); getWriter().println("Time: " + elapsedTimeAsString(runTime)); } /** * Returns the formatted string of the elapsed time. * Duplicated from BaseTestRunner. Fix it. */ protected String elapsedTimeAsString(long runTime) { return NumberFormat.getInstance().format((double)runTime / 1000); } public PrintStream getWriter() { return fWriter; } /** * @see junit.framework.TestListener#addError(Test, Throwable) */ public void addError(Test test, Throwable t) { getWriter().print("E"); } /** * @see junit.framework.TestListener#addFailure(Test, AssertionFailedError) */ public void addFailure(Test test, AssertionFailedError t) { getWriter().print("F"); } /** * @see junit.framework.TestListener#endTest(Test) */ public void endTest(Test test) { } /** * @see junit.framework.TestListener#startTest(Test) */ public void startTest(Test test) { if (fStarted % 40 == 0) { getWriter().print("\n[" + fStarted + "] "); } getWriter().print("."); fStarted++; } } // End MondrianResultPrinter.java mondrian-3.4.1/testsrc/main/mondrian/test/RaggedHierarchyTest.java0000644000175000017500000004176611735330606025200 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.spi.Dialect; /** * RaggedHierarchyTest tests ragged hierarchies. *

* I have disabled some tests by prefixing the tests name with "dont_". * * @author jhyde * @since Apr 19, 2004 */ public class RaggedHierarchyTest extends FoodMartTestCase { private void assertRaggedReturns(String expression, String expected) { getTestContext().withCube("[Sales Ragged]") .assertAxisReturns(expression, expected); } // ~ The tests ------------------------------------------------------------ public void testChildrenOfRoot() { assertRaggedReturns( "[Store].children", "[Store].[Canada]\n" + "[Store].[Israel]\n" + "[Store].[Mexico]\n" + "[Store].[USA]\n" + "[Store].[Vatican]"); } public void testChildrenOfUSA() { assertRaggedReturns( "[Store].[USA].children", "[Store].[USA].[CA]\n" + "[Store].[USA].[OR]\n" + "[Store].[USA].[USA].[Washington]\n" + "[Store].[USA].[WA]"); } // Israel has one real child, which is hidden, and which has children // Haifa and Tel Aviv public void testChildrenOfIsrael() { assertRaggedReturns( "[Store].[Israel].children", "[Store].[Israel].[Israel].[Haifa]\n" + "[Store].[Israel].[Israel].[Tel Aviv]"); } // disabled: (1) does not work with SmartMemberReader and // (2) test returns [null] member // Vatican's descendants at the province and city level are hidden public void dont_testChildrenOfVatican() { assertRaggedReturns( "[Store].[Vatican].children", "[Store].[Vatican].[Vatican].[null].[Store 17]"); } public void testParentOfHaifa() { assertRaggedReturns( "[Store].[Israel].[Haifa].Parent", "[Store].[Israel]"); } public void testParentOfVatican() { assertRaggedReturns( "[Store].[Vatican].Parent", "[Store].[All Stores]"); } // PrevMember must return something at the same level -- a city public void testPrevMemberOfHaifa() { assertRaggedReturns( "[Store].[Israel].[Haifa].PrevMember", "[Store].[Canada].[BC].[Victoria]"); } // PrevMember must return something at the same level -- a city public void testNextMemberOfTelAviv() { assertRaggedReturns( "[Store].[Israel].[Tel Aviv].NextMember", "[Store].[Mexico].[DF].[Mexico City]"); } public void testNextMemberOfBC() { // The next state after BC is Israel, but it's hidden assertRaggedReturns( "[Store].[Canada].[BC].NextMember", "[Store].[Mexico].[DF]"); } public void testLead() { assertRaggedReturns( "[Store].[Mexico].[DF].Lead(1)", "[Store].[Mexico].[Guerrero]"); assertRaggedReturns( "[Store].[Mexico].[DF].Lead(0)", "[Store].[Mexico].[DF]"); // Israel is immediately before Mexico, but is hidden assertRaggedReturns( "[Store].[Mexico].[DF].Lead(-1)", "[Store].[Canada].[BC]"); assertRaggedReturns( "[Store].[Mexico].[DF].Lag(1)", "[Store].[Canada].[BC]"); // Fall off the edge of the world assertRaggedReturns( "[Store].[Mexico].[DF].Lead(-2)", ""); assertRaggedReturns( "[Store].[Mexico].[DF].Lead(-543)", ""); } // disabled: (1) does not work with SmartMemberReader and (2) test returns // [null] member public void dont_testDescendantsOfVatican() { assertRaggedReturns( "Descendants([Store].[Vatican])", "[Store].[Vatican]\n" + "[Store].[Vatican].[Vatican].[null].[Store 17]"); } // The only child of Vatican at state level is hidden public void testDescendantsOfVaticanAtStateLevel() { assertRaggedReturns( "Descendants([Store].[Vatican], [Store].[Store State])", ""); } public void testDescendantsOfRootAtCity() { assertRaggedReturns( "Descendants([Store], [Store City])", "[Store].[Canada].[BC].[Vancouver]\n" + "[Store].[Canada].[BC].[Victoria]\n" + "[Store].[Israel].[Israel].[Haifa]\n" + "[Store].[Israel].[Israel].[Tel Aviv]\n" + "[Store].[Mexico].[DF].[Mexico City]\n" + "[Store].[Mexico].[DF].[San Andres]\n" + "[Store].[Mexico].[Guerrero].[Acapulco]\n" + "[Store].[Mexico].[Jalisco].[Guadalajara]\n" + "[Store].[Mexico].[Veracruz].[Orizaba]\n" + "[Store].[Mexico].[Yucatan].[Merida]\n" + "[Store].[Mexico].[Zacatecas].[Camacho]\n" + "[Store].[Mexico].[Zacatecas].[Hidalgo]\n" + "[Store].[USA].[CA].[Alameda]\n" + "[Store].[USA].[CA].[Beverly Hills]\n" + "[Store].[USA].[CA].[Los Angeles]\n" + "[Store].[USA].[CA].[San Francisco]\n" + "[Store].[USA].[OR].[Portland]\n" + "[Store].[USA].[OR].[Salem]\n" + "[Store].[USA].[USA].[Washington]\n" + "[Store].[USA].[WA].[Bellingham]\n" + "[Store].[USA].[WA].[Bremerton]\n" + "[Store].[USA].[WA].[Seattle]\n" + "[Store].[USA].[WA].[Spokane]"); } // no ancestor at the State level public void testAncestorOfHaifa() { assertRaggedReturns( "Ancestor([Store].[Israel].[Haifa], [Store].[Store State])", ""); } public void testHierarchize() { // Haifa and Tel Aviv should appear directly after Israel // Vatican should have no children // Washington should appear after WA assertRaggedReturns( "Hierarchize(Descendants([Store], [Store].[Store City], SELF_AND_BEFORE))", "[Store].[All Stores]\n" + "[Store].[Canada]\n" + "[Store].[Canada].[BC]\n" + "[Store].[Canada].[BC].[Vancouver]\n" + "[Store].[Canada].[BC].[Victoria]\n" + "[Store].[Israel]\n" + "[Store].[Israel].[Israel].[Haifa]\n" + "[Store].[Israel].[Israel].[Tel Aviv]\n" + "[Store].[Mexico]\n" + "[Store].[Mexico].[DF]\n" + "[Store].[Mexico].[DF].[Mexico City]\n" + "[Store].[Mexico].[DF].[San Andres]\n" + "[Store].[Mexico].[Guerrero]\n" + "[Store].[Mexico].[Guerrero].[Acapulco]\n" + "[Store].[Mexico].[Jalisco]\n" + "[Store].[Mexico].[Jalisco].[Guadalajara]\n" + "[Store].[Mexico].[Veracruz]\n" + "[Store].[Mexico].[Veracruz].[Orizaba]\n" + "[Store].[Mexico].[Yucatan]\n" + "[Store].[Mexico].[Yucatan].[Merida]\n" + "[Store].[Mexico].[Zacatecas]\n" + "[Store].[Mexico].[Zacatecas].[Camacho]\n" + "[Store].[Mexico].[Zacatecas].[Hidalgo]\n" + "[Store].[USA]\n" + "[Store].[USA].[CA]\n" + "[Store].[USA].[CA].[Alameda]\n" + "[Store].[USA].[CA].[Beverly Hills]\n" + "[Store].[USA].[CA].[Los Angeles]\n" + "[Store].[USA].[CA].[San Francisco]\n" + "[Store].[USA].[OR]\n" + "[Store].[USA].[OR].[Portland]\n" + "[Store].[USA].[OR].[Salem]\n" + "[Store].[USA].[USA].[Washington]\n" + "[Store].[USA].[WA]\n" + "[Store].[USA].[WA].[Bellingham]\n" + "[Store].[USA].[WA].[Bremerton]\n" + "[Store].[USA].[WA].[Seattle]\n" + "[Store].[USA].[WA].[Spokane]\n" + "[Store].[Vatican]"); } /** * Make sure that the numbers are right! * *

The Vatican is the tricky case, * because one of the columns is null, so the SQL generator might get * confused. */ // disabled: (1) does not work with SmartMemberReader and (2) test returns // [null] member public void dont_testMeasuresVatican() { assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {Descendants([Store].[Vatican])} ON ROWS\n" + "FROM [Sales Ragged]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[Vatican]}\n" + "{[Store].[Vatican].[Vatican].[null].[Store 17]}\n" + "Row #0: 35,257\n" + "Row #1: 35,257\n"); } // Make sure that the numbers are right! /** * disabled: (1) does not work with SmartMemberReader and (2) test returns * [null] member? */ public void dont_testMeasures() { assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " NON EMPTY {Descendants([Store])} ON ROWS\n" + "FROM [Sales Ragged]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[All Stores]}\n" + "{[Store].[Israel]}\n" + "{[Store].[Israel].[Israel].[Haifa]}\n" + "{[Store].[Israel].[Israel].[Haifa].[Store 22]}\n" + "{[Store].[Israel].[Israel].[Tel Aviv]}\n" + "{[Store].[Israel].[Israel].[Tel Aviv].[Store 23]}\n" + "{[Store].[USA]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[OR].[Portland]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Store].[USA].[OR].[Salem]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Store].[USA].[USA].[Washington]}\n" + "{[Store].[USA].[USA].[Washington].[Store 24]}\n" + "{[Store].[USA].[WA]}\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "{[Store].[USA].[WA].[Bellingham].[Store 2]}\n" + "{[Store].[USA].[WA].[Bremerton]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Seattle]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15]}\n" + "{[Store].[USA].[WA].[Spokane]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16]}\n" + "{[Store].[Vatican]}\n" + "{[Store].[Vatican].[Vatican].[null].[Store 17]}\n" + "Row #0: 266,773\n" + "Row #1: 13,694\n" + "Row #2: 2,203\n" + "Row #3: 2,203\n" + "Row #4: 11,491\n" + "Row #5: 11,491\n" + "Row #6: 217,822\n" + "Row #7: 49,113\n" + "Row #8: 21,333\n" + "Row #9: 21,333\n" + "Row #10: 25,663\n" + "Row #11: 25,663\n" + "Row #12: 2,117\n" + "Row #13: 2,117\n" + "Row #14: 67,659\n" + "Row #15: 26,079\n" + "Row #16: 26,079\n" + "Row #17: 41,580\n" + "Row #18: 41,580\n" + "Row #19: 25,635\n" + "Row #20: 25,635\n" + "Row #21: 75,415\n" + "Row #22: 2,237\n" + "Row #23: 2,237\n" + "Row #24: 24,576\n" + "Row #25: 24,576\n" + "Row #26: 25,011\n" + "Row #27: 25,011\n" + "Row #28: 23,591\n" + "Row #29: 23,591\n" + "Row #30: 35,257\n" + "Row #31: 35,257\n"); } /** * Test case for bug * MONDRIAN-628, * "ClassCastException in Mondrian for query using Sales Ragged cube". * *

Cause was that ancestor yielded a null member, which was a RolapMember * but Order required it to be a RolapCubeMember. */ public void testNullMember() { assertQueryReturns( "With \n" + " Set [*NATIVE_CJ_SET] as '[*BASE_MEMBERS_Geography]' \n" + " Set [*SORTED_ROW_AXIS] as 'Order([*CJ_ROW_AXIS],Ancestor([Geography].CurrentMember, [Geography].[Country]).OrderKey,BASC,Ancestor([Geography].CurrentMember, [Geography].[State]).OrderKey,BASC,[Geography].CurrentMember.OrderKey,BASC)' \n" + " Set [*BASE_MEMBERS_Geography] as '[Geography].[City].Members' \n" + " Set [*NATIVE_MEMBERS_Geography] as 'Generate([*NATIVE_CJ_SET], {[Geography].CurrentMember})' \n" + " Set [*BASE_MEMBERS_Measures] as '{[Measures].[*ZERO]}' \n" + " Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Geography].currentMember)})' \n" + " Set [*CJ_COL_AXIS] as '[*NATIVE_CJ_SET]' \n" + " Member [Measures].[*ZERO] as '0', SOLVE_ORDER=0 \n" + " Select \n" + " [*BASE_MEMBERS_Measures] on columns, \n" + " [*SORTED_ROW_AXIS] on rows \n" + " From [Sales Ragged]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[*ZERO]}\n" + "Axis #2:\n" + "{[Geography].[Canada].[BC].[Vancouver]}\n" + "{[Geography].[Canada].[BC].[Victoria]}\n" + "{[Geography].[Israel].[Israel].[Haifa]}\n" + "{[Geography].[Israel].[Israel].[Tel Aviv]}\n" + "{[Geography].[Mexico].[DF].[Mexico City]}\n" + "{[Geography].[Mexico].[DF].[San Andres]}\n" + "{[Geography].[Mexico].[Guerrero].[Acapulco]}\n" + "{[Geography].[Mexico].[Jalisco].[Guadalajara]}\n" + "{[Geography].[Mexico].[Veracruz].[Orizaba]}\n" + "{[Geography].[Mexico].[Yucatan].[Merida]}\n" + "{[Geography].[Mexico].[Zacatecas].[Camacho]}\n" + "{[Geography].[Mexico].[Zacatecas].[Hidalgo]}\n" + "{[Geography].[USA].[USA].[Washington]}\n" + "{[Geography].[USA].[CA].[Alameda]}\n" + "{[Geography].[USA].[CA].[Beverly Hills]}\n" + "{[Geography].[USA].[CA].[Los Angeles]}\n" + "{[Geography].[USA].[CA].[San Francisco]}\n" + "{[Geography].[USA].[OR].[Portland]}\n" + "{[Geography].[USA].[OR].[Salem]}\n" + "{[Geography].[USA].[WA].[Bellingham]}\n" + "{[Geography].[USA].[WA].[Bremerton]}\n" + "{[Geography].[USA].[WA].[Seattle]}\n" + "{[Geography].[USA].[WA].[Spokane]}\n" + "Row #0: 0\n" + "Row #1: 0\n" + "Row #2: 0\n" + "Row #3: 0\n" + "Row #4: 0\n" + "Row #5: 0\n" + "Row #6: 0\n" + "Row #7: 0\n" + "Row #8: 0\n" + "Row #9: 0\n" + "Row #10: 0\n" + "Row #11: 0\n" + "Row #12: 0\n" + "Row #13: 0\n" + "Row #14: 0\n" + "Row #15: 0\n" + "Row #16: 0\n" + "Row #17: 0\n" + "Row #18: 0\n" + "Row #19: 0\n" + "Row #20: 0\n" + "Row #21: 0\n" + "Row #22: 0\n"); } public void testHideIfBlankHidesWhitespace() { if (TestContext.instance().getDialect().getDatabaseProduct() != Dialect.DatabaseProduct.ORACLE) { return; } final TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + " \n" + " \n" + " " + " " + "case \"gender\" " + "when 'F' then ' ' " + "when 'M' then 'M' " + " end " + " " + " " + " " + " \n" + " "); testContext.assertQueryReturns( " select {[Gender4].[Gender].members} " + "on COLUMNS " + "from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender4].[M]}\n" + "Row #0: 135,215\n"); } } // End RaggedHierarchyTest.java mondrian-3.4.1/testsrc/main/mondrian/test/CacheHitTest.java0000644000175000017500000001712711735330606023612 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1998-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; import mondrian.server.monitor.ServerInfo; import mondrian.test.clearview.*; import junit.framework.TestResult; import junit.framework.TestSuite; /** * The CacheHitTest class contains test suites that return * hit ratio of aggregation cache for various sequences of MDX queries. * *

This is not run as part of Main test suite as it only reports * ratios for further investigations.

* * @author kvu */ public class CacheHitTest extends FoodMartTestCase { /** * Runs a set of small MDX queries that targets a small region * of aggregation cache sequentially. All queries reference * the relational Sales cube. * * @throws Exception on error */ public void testSmallSetSequential() throws Exception { TestSuite suite = new TestSuite(); suite.addTest(PartialCacheTest.suite()); suite.addTest(MultiLevelTest.suite()); suite.addTest(MultiDimTest.suite()); suite.addTest(QueryAllTest.suite()); System.out.println("== " + this.getName() + " =="); runTestSuiteInOrder(suite, 50); clearCache("Sales"); } /** * Runs a set of small MDX queries that targets a small region * of aggregation cache in random order. All queries reference * the relational Sales cube. * * @throws Exception on error */ public void testSmallSetRandom() throws Exception { TestSuite suite = new TestSuite(); suite.addTest(PartialCacheTest.suite()); suite.addTest(MultiLevelTest.suite()); suite.addTest(MultiDimTest.suite()); suite.addTest(QueryAllTest.suite()); System.out.println("== " + this.getName() + " =="); runRandomSuite(suite, 200); clearCache("Sales"); } /** * Runs a set of small MDX queries that targets a small region * of aggregation cache sequentially. All queries reference * the virtual Warehouse and Sales cube. * * @throws Exception on error */ public void testSmallSetVCSequential() throws Exception { TestSuite suite = new TestSuite(); suite.addTest(PartialCacheVCTest.suite()); suite.addTest(MultiLevelVCTest.suite()); suite.addTest(MultiDimVCTest.suite()); suite.addTest(QueryAllVCTest.suite()); System.out.println("== " + this.getName() + " =="); runTestSuiteInOrder(suite, 50); clearCache("Warehouse and Sales"); } /** * Runs a set of small MDX queries that targets a small region * of aggregation cache in random order. All queries reference * the virtual Warehouse and Sales cube. * * @throws Exception on error */ public void testSmallSetVCRandom() throws Exception { TestSuite suite = new TestSuite(); suite.addTest(PartialCacheVCTest.suite()); suite.addTest(MultiLevelVCTest.suite()); suite.addTest(MultiDimVCTest.suite()); suite.addTest(QueryAllVCTest.suite()); System.out.println("== " + this.getName() + " =="); runRandomSuite(suite, 200); clearCache("Warehouse and Sales"); } /** * Runs a set of bigger MDX queries that requires more memory * and targets a bigger region of cache in random order. * Queries reference to Sales cube as well as * Warehouse and Sales cube. * * @throws Exception on error */ public void testBigSetRandom() throws Exception { TestSuite suite = new TestSuite(); suite.addTest(MemHungryTest.suite()); suite.addTest(PartialCacheTest.suite()); suite.addTest(MultiLevelTest.suite()); suite.addTest(MultiDimTest.suite()); suite.addTest(QueryAllTest.suite()); suite.addTest(PartialCacheVCTest.suite()); suite.addTest(MultiLevelVCTest.suite()); suite.addTest(MultiDimVCTest.suite()); suite.addTest(QueryAllVCTest.suite()); suite.addTest(CVBasicTest.suite()); suite.addTest(GrandTotalTest.suite()); suite.addTest(MetricFilterTest.suite()); suite.addTest(MiscTest.suite()); suite.addTest(PredicateFilterTest.suite()); suite.addTest(SubTotalTest.suite()); suite.addTest(SummaryMetricPercentTest.suite()); suite.addTest(SummaryTest.suite()); suite.addTest(TopBottomTest.suite()); System.out.println("== " + this.getName() + " =="); runRandomSuite(suite, 200); clearCache("Sales"); clearCache("Warehouse and Sales"); } /** * Loops n times, each time run a random test case * in the test suite * * @param suite the suite of test cases * @param n number of times * @throws Exception on error */ public void runRandomSuite(TestSuite suite, int n) throws Exception { final TestResult tres = new TestResult(); final MondrianServer server = MondrianServer.forConnection(getTestContext().getConnection()); for (int i = 0; i < n; i++) { int suiteIdx = (int) (Math.random() * suite.testCount()); TestSuite test = (TestSuite) suite.testAt(suiteIdx); int testIdx = (int) (Math.random() * test.testCount()); test.testAt(testIdx).run(tres); } report(server.getMonitor().getServer()); } /** * Loops numIte times, each time run all child test * suite in the suite * * @param suite the suite of test suites * @param numIter number of iterations * @throws Exception on error */ public void runTestSuiteInOrder(TestSuite suite, int numIter) throws Exception { final TestResult tres = new TestResult(); final MondrianServer server = MondrianServer.forConnection(getTestContext().getConnection()); for (int i = 0; i < numIter; i++) { TestSuite test = (TestSuite) suite.testAt(i % suite.testCount()); for (int j = 0; j < test.testCount(); j++) { test.testAt(j).run(tres); } } report(server.getMonitor().getServer()); } /** * Prints cache hit ratio. * * @param serverInfo Server statistics */ public void report(ServerInfo serverInfo) { System.out.println( "Number of requests: " + serverInfo.cellCacheRequestCount); System.out.println( "Number of misses: " + serverInfo.cellCacheMissCount); System.out.println( "Hit ratio ---> " + ((float) serverInfo.cellCacheHitCount / (float) serverInfo.cellCacheRequestCount)); } /** * Clears aggregation cache * * @param cube Cube name */ public void clearCache(String cube) { final TestContext testContext = getTestContext(); final CacheControl cacheControl = testContext.getConnection().getCacheControl(null); // Flush the entire cache. final Connection connection = testContext.getConnection(); final Cube salesCube = connection.getSchema().lookupCube(cube, true); final CacheControl.CellRegion measuresRegion = cacheControl.createMeasuresRegion(salesCube); cacheControl.flush(measuresRegion); } } // End CacheHitTest.java mondrian-3.4.1/testsrc/main/mondrian/test/DrillThroughTest.java0000644000175000017500000016322511735330606024552 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. // // jhyde, Feb 14, 2003 */ package mondrian.test; import mondrian.olap.*; import mondrian.resource.MondrianResource; import mondrian.rolap.*; import mondrian.spi.Dialect; import java.sql.*; import javax.sql.DataSource; /** * Test generation of SQL to access the fact table data underlying an MDX * result set. * * @author jhyde * @since May 10, 2006 */ public class DrillThroughTest extends FoodMartTestCase { public DrillThroughTest() { super(); } public DrillThroughTest(String name) { super(name); } // ~ Tests ================================================================ public void testTrivialCalcMemberDrillThrough() { Result result = executeQuery( "WITH MEMBER [Measures].[Formatted Unit Sales]" + " AS '[Measures].[Unit Sales]', FORMAT_STRING='$#,###.000'\n" + "MEMBER [Measures].[Twice Unit Sales]" + " AS '[Measures].[Unit Sales] * 2'\n" + "MEMBER [Measures].[Twice Unit Sales Plus Store Sales] " + " AS '[Measures].[Twice Unit Sales] + [Measures].[Store Sales]'," + " FOMRAT_STRING='#'\n" + "MEMBER [Measures].[Foo] " + " AS '[Measures].[Unit Sales] + ([Measures].[Unit Sales], [Time].[Time].PrevMember)'\n" + "MEMBER [Measures].[Unit Sales Percentage] " + " AS '[Measures].[Unit Sales] / [Measures].[Twice Unit Sales]'\n" + "SELECT {[Measures].[Unit Sales],\n" + " [Measures].[Formatted Unit Sales],\n" + " [Measures].[Twice Unit Sales],\n" + " [Measures].[Twice Unit Sales Plus Store Sales],\n" + " [Measures].[Foo],\n" + " [Measures].[Unit Sales Percentage]} on columns,\n" + " {[Product].Children} on rows\n" + "from Sales"); // can drill through [Formatted Unit Sales] final Cell cell = result.getCell(new int[]{0, 0}); assertTrue(cell.canDrillThrough()); // can drill through [Unit Sales] assertTrue(result.getCell(new int[]{1, 0}).canDrillThrough()); // can drill through [Twice Unit Sales] assertTrue(result.getCell(new int[]{2, 0}).canDrillThrough()); // can drill through [Twice Unit Sales Plus Store Sales] assertTrue(result.getCell(new int[]{3, 0}).canDrillThrough()); // can not drill through [Foo] assertFalse(result.getCell(new int[]{4, 0}).canDrillThrough()); // can drill through [Unit Sales Percentage] assertTrue(result.getCell(new int[]{5, 0}).canDrillThrough()); assertNotNull( result.getCell( new int[]{ 5, 0 }).getDrillThroughSQL(false)); String sql = cell.getDrillThroughSQL(false); String expectedSql = "select `time_by_day`.`the_year` as `Year`," + " `product_class`.`product_family` as `Product Family`," + " `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` = 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' " + "order by `time_by_day`.`the_year` ASC," + " `product_class`.`product_family` ASC"; getTestContext().assertSqlEquals(expectedSql, sql, 7978); // Can drill through a trivial calc member. final Cell calcCell = result.getCell(new int[]{1, 0}); assertTrue(calcCell.canDrillThrough()); sql = calcCell.getDrillThroughSQL(false); assertNotNull(sql); expectedSql = "select `time_by_day`.`the_year` as `Year`," + " `product_class`.`product_family` as `Product Family`," + " `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` = 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' " + "order by `time_by_day`.`the_year` ASC," + " `product_class`.`product_family` ASC"; getTestContext().assertSqlEquals(expectedSql, sql, 7978); assertEquals(calcCell.getDrillThroughCount(), 7978); } public void testTrivialCalcMemberNotMeasure() { // [Product].[My Food] is trivial because it maps to a single member. // First, on ROWS axis. Result result = executeQuery( "with member [Product].[My Food]\n" + " AS [Product].[Food], FORMAT_STRING = '#,###'\n" + "SELECT [Measures].[Unit Sales] * [Gender].[M] on 0,\n" + " [Marital Status].[S] * [Product].[My Food] on 1\n" + "from [Sales]"); Cell cell = result.getCell(new int[] {0, 0}); assertTrue(cell.canDrillThrough()); assertEquals(16129, cell.getDrillThroughCount()); // Next, on filter axis. result = executeQuery( "with member [Product].[My Food]\n" + " AS [Product].[Food], FORMAT_STRING = '#,###'\n" + "SELECT [Measures].[Unit Sales] * [Gender].[M] on 0,\n" + " [Marital Status].[S] on 1\n" + "from [Sales]\n" + "where [Product].[My Food]"); cell = result.getCell(new int[] {0, 0}); assertTrue(cell.canDrillThrough()); assertEquals(16129, cell.getDrillThroughCount()); // Trivial member with Aggregate. result = executeQuery( "with member [Product].[My Food]\n" + " AS Aggregate({[Product].[Food]}), FORMAT_STRING = '#,###'\n" + "SELECT [Measures].[Unit Sales] * [Gender].[M] on 0,\n" + " [Marital Status].[S] * [Product].[My Food] on 1\n" + "from [Sales]"); cell = result.getCell(new int[] {0, 0}); assertTrue(cell.canDrillThrough()); assertEquals(16129, cell.getDrillThroughCount()); // Non-trivial member on rows. result = executeQuery( "with member [Product].[My Food Drink]\n" + " AS Aggregate({[Product].[Food], [Product].[Drink]}),\n" + " FORMAT_STRING = '#,###'\n" + "SELECT [Measures].[Unit Sales] * [Gender].[M] on 0,\n" + " [Marital Status].[S] * [Product].[My Food Drink] on 1\n" + "from [Sales]"); cell = result.getCell(new int[] {0, 0}); assertFalse(cell.canDrillThrough()); // drop the constraint when we drill through assertEquals(22479, cell.getDrillThroughCount()); // Non-trivial member on filter axis. result = executeQuery( "with member [Product].[My Food Drink]\n" + " AS Aggregate({[Product].[Food], [Product].[Drink]}),\n" + " FORMAT_STRING = '#,###'\n" + "SELECT [Measures].[Unit Sales] * [Gender].[M] on 0,\n" + " [Marital Status].[S] on 1\n" + "from [Sales]\n" + "where [Product].[My Food Drink]"); cell = result.getCell(new int[] {0, 0}); assertFalse(cell.canDrillThrough()); } public void testDrillThrough() { Result result = executeQuery( "WITH MEMBER [Measures].[Price] AS '[Measures].[Store Sales] / ([Measures].[Store Sales], [Time].[Time].PrevMember)'\n" + "SELECT {[Measures].[Unit Sales], [Measures].[Price]} on columns,\n" + " {[Product].Children} on rows\n" + "from Sales"); final Cell cell = result.getCell(new int[]{0, 0}); assertTrue(cell.canDrillThrough()); String sql = cell.getDrillThroughSQL(false); String expectedSql = "select `time_by_day`.`the_year` as `Year`," + " `product_class`.`product_family` as `Product Family`," + " `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` = 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' " + "order by `time_by_day`.`the_year` ASC," + " `product_class`.`product_family` ASC"; getTestContext().assertSqlEquals(expectedSql, sql, 7978); // Cannot drill through a calc member. final Cell calcCell = result.getCell(new int[]{1, 1}); assertFalse(calcCell.canDrillThrough()); sql = calcCell.getDrillThroughSQL(false); assertNull(sql); } private String getNameExp( Result result, String hierName, String levelName) { final Cube cube = result.getQuery().getCube(); RolapStar star = ((RolapCube) cube).getStar(); Hierarchy h = cube.lookupHierarchy( new Id.Segment(hierName, Id.Quoting.UNQUOTED), false); if (h == null) { return null; } for (Level l : h.getLevels()) { if (l.getName().equals(levelName)) { MondrianDef.Expression exp = ((RolapLevel) l).getNameExp(); String nameExpStr = exp.getExpression(star.getSqlQuery()); nameExpStr = nameExpStr.replace('"', '`') ; return nameExpStr; } } return null; } public void testDrillThrough2() { Result result = executeQuery( "WITH MEMBER [Measures].[Price] AS '[Measures].[Store Sales] / ([Measures].[Unit Sales], [Time].[Time].PrevMember)'\n" + "SELECT {[Measures].[Unit Sales], [Measures].[Price]} on columns,\n" + " {[Product].Children} on rows\n" + "from Sales"); String sql = result.getCell(new int[]{0, 0}).getDrillThroughSQL(true); String nameExpStr = getNameExp(result, "Customers", "Name"); String expectedSql = "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`," + " `time_by_day`.`quarter` as `Quarter`," + " `time_by_day`.`month_of_year` as `Month`," + " `time_by_day`.`week_of_year` as `Week`," + " `time_by_day`.`day_of_month` as `Day`," + " `product_class`.`product_family` as `Product Family`," + " `product_class`.`product_department` as `Product Department`," + " `product_class`.`product_category` as `Product Category`," + " `product_class`.`product_subcategory` as `Product Subcategory`," + " `product`.`brand_name` as `Brand Name`," + " `product`.`product_name` as `Product Name`," + " `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`, " + nameExpStr + " as `Name`," + " `customer`.`customer_id` as `Name (Key)`," + " `customer`.`education` as `Education Level`," + " `customer`.`gender` as `Gender`," + " `customer`.`marital_status` as `Marital Status`," + " `customer`.`yearly_income` as `Yearly Income`," + " `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`.`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'" + " and `sales_fact_1997`.`promotion_id` = `promotion`.`promotion_id`" + " and `sales_fact_1997`.`customer_id` = `customer`.`customer_id` " + "order by `store`.`store_country` ASC," + " `store`.`store_state` ASC," + " `store`.`store_city` ASC," + " `store`.`store_name` ASC," + " `store`.`store_sqft` ASC," + " `store`.`store_type` ASC," + " `time_by_day`.`the_year` ASC," + " `time_by_day`.`quarter` ASC," + " `time_by_day`.`month_of_year` ASC," + " `time_by_day`.`week_of_year` ASC," + " `time_by_day`.`day_of_month` ASC," + " `product_class`.`product_family` ASC," + " `product_class`.`product_department` ASC," + " `product_class`.`product_category` ASC," + " `product_class`.`product_subcategory` ASC," + " `product`.`brand_name` ASC," + " `product`.`product_name` ASC," + " `promotion`.`media_type` ASC," + " `promotion`.`promotion_name` ASC," + " `customer`.`country` ASC," + " `customer`.`state_province` ASC," + " `customer`.`city` ASC, " + nameExpStr + " ASC," + " `customer`.`customer_id` ASC," + " `customer`.`education` ASC," + " `customer`.`gender` ASC," + " `customer`.`marital_status` ASC," + " `customer`.`yearly_income` ASC"; getTestContext().assertSqlEquals(expectedSql, sql, 7978); // Drillthrough SQL is null for cell based on calc member sql = result.getCell(new int[]{1, 1}).getDrillThroughSQL(true); assertNull(sql); } public void testDrillThrough3() { Result result = executeQuery( "select {[Measures].[Unit Sales], [Measures].[Store Cost], [Measures].[Store Sales]} ON COLUMNS, \n" + "Hierarchize(Union(Union(Crossjoin({[Promotion Media].[All Media]}, {[Product].[All Products]}), \n" + "Crossjoin({[Promotion Media].[All Media]}, [Product].[All Products].Children)), Crossjoin({[Promotion Media].[All Media]}, [Product].[All Products].[Drink].Children))) ON ROWS \n" + "from [Sales] where [Time].[1997].[Q4].[12]"); // [Promotion Media].[All Media], [Product].[All // Products].[Drink].[Dairy], [Measures].[Store Cost] Cell cell = result.getCell(new int[]{0, 4}); String sql = cell.getDrillThroughSQL(true); String nameExpStr = getNameExp(result, "Customers", "Name"); String expectedSql = "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`," + " `time_by_day`.`quarter` as `Quarter`," + " `time_by_day`.`month_of_year` as `Month`," + " `time_by_day`.`week_of_year` as `Week`," + " `time_by_day`.`day_of_month` as `Day`," + " `product_class`.`product_family` as `Product Family`," + " `product_class`.`product_department` as `Product Department`," + " `product_class`.`product_category` as `Product Category`," + " `product_class`.`product_subcategory` as `Product Subcategory`," + " `product`.`brand_name` as `Brand Name`," + " `product`.`product_name` as `Product Name`," + " `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`," + " " + nameExpStr + " as `Name`," + " `customer`.`customer_id` as `Name (Key)`," + " `customer`.`education` as `Education Level`," + " `customer`.`gender` as `Gender`," + " `customer`.`marital_status` as `Marital Status`," + " `customer`.`yearly_income` as `Yearly Income`," + " `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`.`time_id` = `time_by_day`.`time_id` and " + "`time_by_day`.`the_year` = 1997 and " + "`time_by_day`.`quarter` = 'Q4' and " + "`time_by_day`.`month_of_year` = 12 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' and " + "`product_class`.`product_department` = 'Dairy' and " + "`sales_fact_1997`.`promotion_id` = `promotion`.`promotion_id` and " + "`sales_fact_1997`.`customer_id` = `customer`.`customer_id` " + "order by `store`.`store_country` ASC," + " `store`.`store_state` ASC," + " `store`.`store_city` ASC," + " `store`.`store_name` ASC," + " `store`.`store_sqft` ASC," + " `store`.`store_type` ASC," + " `time_by_day`.`the_year` ASC," + " `time_by_day`.`quarter` ASC," + " `time_by_day`.`month_of_year` ASC," + " `time_by_day`.`week_of_year` ASC," + " `time_by_day`.`day_of_month` ASC," + " `product_class`.`product_family` ASC," + " `product_class`.`product_department` ASC," + " `product_class`.`product_category` ASC," + " `product_class`.`product_subcategory` ASC," + " `product`.`brand_name` ASC," + " `product`.`product_name` ASC," + " `promotion.media_type` ASC," + " `promotion`.`promotion_name` ASC," + " `customer`.`country` ASC," + " `customer`.`state_province` ASC," + " `customer`.`city` ASC," + " " + nameExpStr + " ASC," + " `customer`.`customer_id` ASC," + " `customer`.`education` ASC," + " `customer`.gender` ASC," + " `customer`.`marital_status` ASC," + " `customer`.`yearly_income` ASC"; getTestContext().assertSqlEquals(expectedSql, sql, 141); } /** * Test case for bug * MONDRIAN-180, "Drillthrough fails, if Aggregate in * MDX-query". The problem actually occurs with any calculated member, * not just Aggregate. The bug was causing a syntactically invalid * constraint to be added to the WHERE clause; after the fix, we do * not constrain on the member at all. */ public void testDrillThroughBugMondrian180() { Result result = executeQuery( "with set [Date Range] as '{[Time].[1997].[Q1], [Time].[1997].[Q2]}'\n" + "member [Time].[Time].[Date Range] as 'Aggregate([Date Range])'\n" + "select {[Measures].[Unit Sales]} ON COLUMNS,\n" + "Hierarchize(Union(Union(Union({[Store].[All Stores]}, [Store].[All Stores].Children), [Store].[All Stores].[USA].Children), [Store].[All Stores].[USA].[CA].Children)) ON ROWS\n" + "from [Sales]\n" + "where [Time].[Date Range]"); final Cell cell = result.getCell(new int[]{0, 6}); // It is not valid to drill through this cell, because it contains a // non-trivial calculated member. assertFalse(cell.canDrillThrough()); // For backwards compatibility, generate drill-through SQL (ignoring // calculated members) even though we said we could not drill through. String sql = cell.getDrillThroughSQL(true); String nameExpStr = getNameExp(result, "Customers", "Name"); final String expectedSql = "select" + " `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`," + " `time_by_day`.`quarter` as `Quarter`," + " `time_by_day`.`month_of_year` as `Month`," + " `time_by_day`.`week_of_year` as `Week`," + " `time_by_day`.`day_of_month` as `Day`," + " `product_class`.`product_family` as `Product Family`," + " `product_class`.`product_department` as `Product Department`," + " `product_class`.`product_category` as `Product Category`," + " `product_class`.`product_subcategory` as `Product Subcategory`," + " `product`.`brand_name` as `Brand Name`," + " `product`.`product_name` as `Product Name`," + " `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`, " + nameExpStr + " as `Name`," + " `customer`.`customer_id` as `Name (Key)`," + " `customer`.`education` as `Education Level`," + " `customer`.`gender` as `Gender`," + " `customer`.`marital_status` as `Marital Status`," + " `customer`.`yearly_income` as `Yearly Income`," + " `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" + " `store`.`store_state` = 'CA' and" + " `store`.`store_city` = 'Beverly Hills' and" + " `sales_fact_1997`.time_id` = `time_by_day`.`time_id` and" + " `sales_fact_1997`.`product_id` = `product`.`product_id` and" + " `product`.`product_class_id` = `product_class`.`product_class_id` and" + " `sales_fact_1997`.`promotion_id` = `promotion`.`promotion_id` and" + " `sales_fact_1997`.`customer_id` = `customer`.`customer_id`" + " order by" + " `store`.`store_state` ASC," + " `store`.`store_city` ASC," + " `store`.`store_name` ASC," + " `store`.`store_sqft` ASC," + " `store`.`store_type` ASC," + " `time_by_day`.`the_year` ASC," + " `time_by_day`.`quarter` ASC," + " `time_by_day`.`month_of_year` ASC," + " `time_by_day`.`week_of_year` ASC," + " `time_by_day`.`day_of_month` ASC," + " `product_class`.`product_family` ASC," + " `product_class`.`product_department` ASC," + " `product_class`.`product_category` ASC," + " `product_class`.`product_subcategory` ASC," + " `product`.`brand_name` ASC," + " `product`.`product_name` ASC," + " `promotion`.`media_type` ASC," + " `promotion`.`promotion_name` ASC," + " `customer`.`country` ASC," + " `customer`.`state_province` ASC," + " `customer`.`city` ASC, " + nameExpStr + " ASC," + " `customer`.`customer_id` ASC," + " `customer`.`education` ASC," + " `customer`.`gender` ASC," + " `customer`.`marital_status` ASC," + " `customer`.`yearly_income` ASC"; getTestContext().assertSqlEquals(expectedSql, sql, 6815); } /** * Tests 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,\n" + " {[Product].Children} on rows\n" + "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' " + "order by `time_by_day`.`the_year` ASC, `product_class`.`product_family` ASC"; final Cube cube = result.getQuery().getCube(); RolapStar star = ((RolapCube) cube).getStar(); // Adjust expected SQL for dialect differences in FoodMart.xml. Dialect dialect = star.getSqlQueryDialect(); final String caseStmt = " \\(case when `sales_fact_1997`.`promotion_id` = 0 then 0" + " else `sales_fact_1997`.`store_sales` end\\)"; switch (dialect.getDatabaseProduct()) { case ACCESS: expectedSql = expectedSql.replaceAll( caseStmt, " Iif(`sales_fact_1997`.`promotion_id` = 0, 0," + " `sales_fact_1997`.`store_sales`)"); break; case INFOBRIGHT: expectedSql = expectedSql.replaceAll( caseStmt, " `sales_fact_1997`.`store_sales`"); break; } getTestContext().assertSqlEquals(expectedSql, sql, 7978); } /** * Tests that drill-through works if two dimension tables have primary key * columns with the same name. Related to bug 1592556, "XMLA Drill through * bug". */ public void testDrillThroughDupKeys() { /* * Note here that the type on the Store Id level is Integer or * Numeric. The default, of course, would be String. * * For DB2 and Derby, we need the Integer type, otherwise the * generated SQL will be something like: * * `store_ragged`.`store_id` = '19' * * and DB2 and Derby don't like converting from CHAR to INTEGER */ TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n"); Result result = testContext.executeQuery( "SELECT {[Store2].[Store Id].Members} on columns,\n" + " NON EMPTY([Store3].[Store Id].Members) on rows\n" + "from Sales"); String sql = result.getCell(new int[] {0, 0}).getDrillThroughSQL(false); String expectedSql = "select `time_by_day`.`the_year` as `Year`," + " `store_ragged`.`store_id` as `Store Id`," + " `store`.`store_id` as `Store Id_0`," + " `sales_fact_1997`.`unit_sales` as `Unit Sales` " + "from `time_by_day` =as= `time_by_day`," + " `sales_fact_1997` =as= `sales_fact_1997`," + " `store_ragged` =as= `store_ragged`," + " `store` =as= `store` " + "where `sales_fact_1997`.`time_id` = `time_by_day`.`time_id`" + " and `time_by_day`.`the_year` = 1997" + " and `sales_fact_1997`.`store_id` = `store_ragged`.`store_id`" + " and `store_ragged`.`store_id` = 19" + " and `sales_fact_1997`.`store_id` = `store`.`store_id`" + " and `store`.`store_id` = 2 " + "order by `time_by_day`.`the_year` ASC, `store_ragged`.`store_id` ASC, `store`.`store_id` ASC"; getTestContext().assertSqlEquals(expectedSql, sql, 0); } /** * Tests that cells in a virtual cube say they can be drilled through. */ public void testDrillThroughVirtualCube() { Result result = executeQuery( "select Crossjoin([Customers].[All Customers].[USA].[OR].Children, {[Measures].[Unit Sales]}) ON COLUMNS, " + " [Gender].[All Gender].Children ON ROWS" + " from [Warehouse and Sales]" + " where [Time].[1997].[Q4].[12]"); String sql = result.getCell(new int[]{0, 0}).getDrillThroughSQL(false); String expectedSql = "select `time_by_day`.`the_year` as `Year`," + " `time_by_day`.`quarter` as `Quarter`," + " `time_by_day`.month_of_year` as `Month`," + " `customer`.`state_province` as `State Province`," + " `customer`.`city` as `City`," + " `customer`.`gender` as `Gender`," + " `sales_fact_1997`.`unit_sales` as `Unit Sales`" + " from `time_by_day` =as= `time_by_day`," + " `sales_fact_1997` =as= `sales_fact_1997`," + " `customer` =as= `customer`" + " where `sales_fact_1997`.`time_id` = `time_by_day`.`time_id` and" + " `time_by_day`.`the_year` = 1997 and" + " `time_by_day`.`quarter` = 'Q4' and" + " `time_by_day`.`month_of_year` = 12 and" + " `sales_fact_1997`.`customer_id` = `customer`.customer_id` and" + " `customer`.`state_province` = 'OR' and" + " `customer`.`city` = 'Albany' and" + " `customer`.`gender` = 'F'" + " order by `time_by_day`.`the_year` ASC," + " `time_by_day`.`quarter` ASC," + " `time_by_day`.`month_of_year` ASC," + " `customer`.`state_province` ASC," + " `customer`.`city` ASC," + " `customer`.`gender` ASC"; getTestContext().assertSqlEquals(expectedSql, sql, 73); } /** * This tests for bug 1438285, "nameColumn cannot be column in level * definition". */ public void testBug1438285() { final Dialect dialect = getTestContext().getDialect(); if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.TERADATA) { // On default Teradata express instance there isn't enough spool // space to run this query. return; } // Specify the column and nameColumn to be the same // in order to reproduce the problem final TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "
\n" + " \n" + " " + " \n" + " \n"); Result result = testContext.executeQuery( "SELECT {[Measures].[Unit Sales]} on columns, " + "{[Store2].members} on rows FROM [Sales]"); // Prior to fix the request for the drill through SQL would result in // an assertion error String sql = result.getCell(new int[] {0, 0}).getDrillThroughSQL(true); String nameExpStr = getNameExp(result, "Customers", "Name"); String expectedSql = "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`," + " `time_by_day`.`quarter` as `Quarter`," + " `time_by_day`.`month_of_year` as `Month`," + " `time_by_day`.`week_of_year` as `Week`," + " `time_by_day`.`day_of_month` as `Day`," + " `product_class`.`product_family` as `Product Family`," + " `product_class`.`product_department` as `Product Department`," + " `product_class`.`product_category` as `Product Category`," + " `product_class`.`product_subcategory` as `Product Subcategory`," + " `product`.`brand_name` as `Brand Name`," + " `product`.`product_name` as `Product Name`," + " `store_ragged`.`store_id` as `Store Id`," + " `store_ragged`.`store_id` as `Store Id (Key)`," + " `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`," + " " + nameExpStr + " as `Name`," + " `customer`.`customer_id` as `Name (Key)`," + " `customer`.`education` as `Education Level`," + " `customer`.`gender` as `Gender`," + " `customer`.`marital_status` as `Marital Status`," + " `customer`.`yearly_income` as `Yearly Income`," + " `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`," + " `store_ragged` =as= `store_ragged`," + " `promotion` =as= `promotion`," + " `customer` =as= `customer`" + " where `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` = 1997" + " and `sales_fact_1997`.`product_id` = `product`.`product_id`" + " and `product`.`product_class_id` = `product_class`.`product_class_id`" + " and `sales_fact_1997`.`store_id` = `store_ragged`.`store_id`" + " and `sales_fact_1997`.`promotion_id` = `promotion`.`promotion_id`" + " and `sales_fact_1997`.`customer_id` = `customer`.`customer_id`" + " order by `store`.`store_country` ASC," + " `store`.`store_state` ASC," + " `store`.`store_city` ASC," + " `store`.`store_name` ASC," + " `store`.`store_sqft` ASC," + " `store`.`store_type` ASC," + " `time_by_day`.`the_year` ASC," + " `time_by_day`.`quarter` ASC," + " `time_by_day`.`month_of_year` ASC," + " `time_by_day`.`week_of_year` ASC," + " `time_by_day`.`day_of_month` ASC," + " `product_class`.`product_family` ASC," + " `product_class`.`product_department` ASC," + " `product_class`.`product_category` ASC," + " `product_class`.`product_subcategory` ASC," + " `product`.`brand_name` ASC," + " `product`.`product_name` ASC," + " `store_ragged`.`store_id` ASC," + " `promotion`.`media_type` ASC," + " `promotion`.`promotion_name` ASC," + " `customer`.`country` ASC," + " `customer`.`state_province` ASC," + " `customer`.`city` ASC," + " " + nameExpStr + " ASC," + " `customer`.`customer_id` ASC," + " `customer`.`education` ASC," + " `customer`.`gender` ASC," + " `customer`.`marital_status` ASC," + " `customer`.`yearly_income` ASC"; getTestContext().assertSqlEquals(expectedSql, sql, 86837); } /** * Tests that long levels do not result in column aliases larger than the * database can handle. For example, Access allows maximum of 64; Oracle * allows 30. * *

Testcase for bug 1893959, "Generated drill-through columns too long * for DBMS". * * @throws Exception on error */ public void testTruncateLevelName() throws Exception { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "

\n" + " \n" + " \n" + " ", null); Result result = testContext.executeQuery( "SELECT {[Measures].[Unit Sales]} on columns,\n" + "{[Education Level2].Children} on rows\n" + "FROM [Sales]\n" + "WHERE ([Time].[1997].[Q1].[1], [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses].[ADJ].[ADJ Rosy Sunglasses]) "); String sql = result.getCell(new int[] {0, 0}).getDrillThroughSQL(false); // Check that SQL is valid. java.sql.Connection connection = null; try { DataSource dataSource = getConnection().getDataSource(); connection = dataSource.getConnection(); final Statement statement = connection.createStatement(); final ResultSet resultSet = statement.executeQuery(sql); final int columnCount = resultSet.getMetaData().getColumnCount(); final Dialect dialect = testContext.getDialect(); if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.DERBY) { // derby counts ORDER BY columns as columns. insane! assertEquals(11, columnCount); } else { assertEquals(6, columnCount); } final String columnName = resultSet.getMetaData().getColumnLabel(5); assertTrue( columnName, columnName.startsWith("Education Level but with a")); int n = 0; while (resultSet.next()) { ++n; } assertEquals(2, n); } finally { if (connection != null) { connection.close(); } } } public void testDrillThroughExprs() { assertCanDrillThrough( true, "Sales", "CoalesceEmpty([Measures].[Unit Sales], [Measures].[Store Sales])"); assertCanDrillThrough( true, "Sales", "[Measures].[Unit Sales] + [Measures].[Unit Sales]"); assertCanDrillThrough( true, "Sales", "[Measures].[Unit Sales] / ([Measures].[Unit Sales] - 5.0)"); assertCanDrillThrough( true, "Sales", "[Measures].[Unit Sales] * [Measures].[Unit Sales]"); // constants are drillable - in a virtual cube it means take the first // cube assertCanDrillThrough( true, "Warehouse and Sales", "2.0"); assertCanDrillThrough( true, "Warehouse and Sales", "[Measures].[Unit Sales] * 2.0"); // in virtual cube, mixture of measures from two cubes is not drillable assertCanDrillThrough( false, "Warehouse and Sales", "[Measures].[Unit Sales] + [Measures].[Units Ordered]"); // expr with measures both from [Sales] is drillable assertCanDrillThrough( true, "Warehouse and Sales", "[Measures].[Unit Sales] + [Measures].[Store Sales]"); // expr with measures both from [Warehouse] is drillable assertCanDrillThrough( true, "Warehouse and Sales", "[Measures].[Warehouse Cost] + [Measures].[Units Ordered]"); // .Children not drillable assertCanDrillThrough( false, "Sales", "Sum([Product].Children)"); // Sets of members not drillable assertCanDrillThrough( false, "Sales", "Sum({[Store].[USA], [Store].[Canada].[BC]})"); // Tuples not drillable assertCanDrillThrough( false, "Sales", "([Time].[1997].[Q1], [Measures].[Unit Sales])"); } public void testDrillthroughMaxRows() throws SQLException { assertMaxRows("", 29); assertMaxRows("maxrows 1000", 29); assertMaxRows("maxrows 0", 29); assertMaxRows("maxrows 3", 3); assertMaxRows("maxrows 10 firstrowset 6", 4); assertMaxRows("firstrowset 20", 9); assertMaxRows("firstrowset 30", 0); } private void assertMaxRows(String firstMaxRow, int expectedCount) throws SQLException { final ResultSet resultSet = getTestContext().executeStatement( "drillthrough\n" + firstMaxRow + " select\n" + "non empty{[Customers].[USA].[CA]} on 0,\n" + "non empty {[Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice]} on 1\n" + "from\n" + "[Sales]\n" + "where([Measures].[Sales Count], [Time].[1997].[Q3].[8])"); int actualCount = 0; while (resultSet.next()) { ++actualCount; } assertEquals(expectedCount, actualCount); resultSet.close(); } public void testDrillthroughNegativeMaxRowsFails() throws SQLException { try { final ResultSet resultSet = getTestContext().executeStatement( "DRILLTHROUGH MAXROWS -3\n" + "SELECT {[Customers].[USA].[CA].[Berkeley]} ON 0,\n" + "{[Time].[1997]} ON 1\n" + "FROM Sales"); fail("expected error, got " + resultSet); } catch (SQLException e) { TestContext.checkThrowable( e, "Syntax error at line 1, column 22, token '-'"); } } public void testDrillThroughNotDrillableFails() { try { final ResultSet resultSet = getTestContext().executeStatement( "DRILLTHROUGH\n" + "WITH MEMBER [Measures].[Foo] " + " AS [Measures].[Unit Sales]\n" + " + ([Measures].[Unit Sales], [Time].[Time].PrevMember)\n" + "SELECT {[Customers].[USA].[CA].[Berkeley]} ON 0,\n" + "{[Time].[1997]} ON 1\n" + "FROM Sales\n" + "WHERE [Measures].[Foo]"); fail("expected error, got " + resultSet); } catch (Exception e) { TestContext.checkThrowable( e, "Cannot do DrillThrough operation on the cell"); } } /** * Asserts that a cell based on the given measure expression has a given * drillability. * * @param canDrillThrough Whether we expect the cell to be drillable * @param cubeName Cube name * @param expr Scalar expression */ private void assertCanDrillThrough( boolean canDrillThrough, String cubeName, String expr) { Result result = executeQuery( "WITH MEMBER [Measures].[Foo] AS '" + expr + "'\n" + "SELECT {[Measures].[Foo]} on columns,\n" + " {[Product].Children} on rows\n" + "from [" + cubeName + "]"); final Cell cell = result.getCell(new int[] {0, 0}); assertEquals(canDrillThrough, cell.canDrillThrough()); final String sql = cell.getDrillThroughSQL(false); if (canDrillThrough) { assertNotNull(sql); } else { assertNull(sql); } } /** * Test case for bug * MONDRIAN-752, "cell.getDrillCount returns 0". */ public void testDrillThroughOneAxis() { Result result = executeQuery( "SELECT [Measures].[Unit Sales] on 0\n" + "from Sales"); final Cell cell = result.getCell(new int[]{0}); assertTrue(cell.canDrillThrough()); assertEquals(86837, cell.getDrillThroughCount()); } /** * Test case for bug * MONDRIAN-751, "Drill SQL does not include slicer members in WHERE * clause". */ public void testDrillThroughCalcMemberInSlicer() { Result result = executeQuery( "WITH MEMBER [Product].[Aggregate Food Drink] AS \n" + " Aggregate({[Product].[Food], [Product].[Drink]})\n" + "SELECT [Measures].[Unit Sales] on 0\n" + "from Sales\n" + "WHERE [Product].[Aggregate Food Drink]"); final Cell cell = result.getCell(new int[]{0}); assertFalse(cell.canDrillThrough()); } /** * Test case for MONDRIAN-791. */ public void testDrillThroughMultiPositionCompoundSlicer() { // A query with a simple multi-position compound slicer Result result = executeQuery( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Product].[All Products]} ON ROWS\n" + "FROM [Sales]\n" + "WHERE {[Time].[1997].[Q1], [Time].[1997].[Q2]}"); Cell cell = result.getCell(new int[]{0, 0}); assertTrue(cell.canDrillThrough()); String sql = cell.getDrillThroughSQL(false); String expectedSql; switch (getTestContext().getDialect().getDatabaseProduct()) { case MYSQL: expectedSql = "select sales_fact_1997.unit_sales as Unit Sales from time_by_day as time_by_day, sales_fact_1997 as sales_fact_1997 where sales_fact_1997.time_id = time_by_day.time_id and (((time_by_day.the_year, time_by_day.quarter) in ((1997, 'Q1'), (1997, 'Q2'))))"; break; case ORACLE: expectedSql = "select sales_fact_1997.unit_sales as Unit Sales from time_by_day time_by_day, sales_fact_1997 sales_fact_1997 where sales_fact_1997.time_id = time_by_day.time_id and ((time_by_day.quarter = 'Q1' and time_by_day.the_year = 1997) or (time_by_day.quarter = 'Q2' and time_by_day.the_year = 1997))"; break; default: return; } getTestContext().assertSqlEquals(expectedSql, sql, 41956); // A query with a slightly more complex multi-position compound slicer result = executeQuery( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Product].[All Products]} ON ROWS\n" + "FROM [Sales]\n" + "WHERE Crossjoin(Crossjoin({[Gender].[F]}, {[Marital Status].[M]})," + " {[Time].[1997].[Q1], [Time].[1997].[Q2]})"); cell = result.getCell(new int[]{0, 0}); assertTrue(cell.canDrillThrough()); sql = cell.getDrillThroughSQL(false); // Note that gender and marital status get their own predicates, // independent of the time portion of the slicer switch (getTestContext().getDialect().getDatabaseProduct()) { case MYSQL: expectedSql = "select customer.gender as Gender, customer.marital_status as Marital Status, sales_fact_1997.unit_sales as Unit Sales from customer as customer, sales_fact_1997 as sales_fact_1997, time_by_day as time_by_day where sales_fact_1997.customer_id = customer.customer_id and customer.gender = 'F' and customer.marital_status = 'M' and sales_fact_1997.time_id = time_by_day.time_id and (((time_by_day.the_year, time_by_day.quarter) in ((1997, 'Q1'), (1997, 'Q2')))) order by customer.gender ASC, customer.marital_status ASC"; break; case ORACLE: expectedSql = "select customer.gender as Gender, customer.marital_status as Marital Status, sales_fact_1997.unit_sales as Unit Sales from customer customer, sales_fact_1997 sales_fact_1997, time_by_day time_by_day where sales_fact_1997.customer_id = customer.customer_id and customer.gender = 'F' and customer.marital_status = 'M' and sales_fact_1997.time_id = time_by_day.time_id and ((time_by_day.quarter = 'Q1' and time_by_day.the_year = 1997) or (time_by_day.quarter = 'Q2' and time_by_day.the_year = 1997)) order by customer.gender ASC, customer.marital_status ASC"; break; default: return; } getTestContext().assertSqlEquals(expectedSql, sql, 10430); // A query with an even more complex multi-position compound slicer // (gender must be in the slicer predicate along with time) result = executeQuery( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Product].[All Products]} ON ROWS\n" + "FROM [Sales]\n" + "WHERE Union(Crossjoin({[Gender].[F]}, {[Time].[1997].[Q1]})," + " Crossjoin({[Gender].[M]}, {[Time].[1997].[Q2]}))"); cell = result.getCell(new int[]{0, 0}); assertTrue(cell.canDrillThrough()); sql = cell.getDrillThroughSQL(false); // Note that gender and marital status get their own predicates, // independent of the time portion of the slicer switch (getTestContext().getDialect().getDatabaseProduct()) { case MYSQL: expectedSql = "select sales_fact_1997.unit_sales as Unit Sales from time_by_day as time_by_day, sales_fact_1997 as sales_fact_1997, customer as customer where sales_fact_1997.time_id = time_by_day.time_id and sales_fact_1997.customer_id = customer.customer_id and (((time_by_day.the_year, time_by_day.quarter, customer.gender) in ((1997, 'Q1', 'F'), (1997, 'Q2', 'M'))))"; break; case ORACLE: expectedSql = "select sales_fact_1997.unit_sales as Unit Sales from time_by_day time_by_day, sales_fact_1997 sales_fact_1997, customer customer where sales_fact_1997.time_id = time_by_day.time_id and sales_fact_1997.customer_id = customer.customer_id and ((customer.gender = 'F' and time_by_day.quarter = 'Q1' and time_by_day.the_year = 1997) or (customer.gender = 'M' and time_by_day.quarter = 'Q2' and time_by_day.the_year = 1997))"; break; default: return; } getTestContext().assertSqlEquals(expectedSql, sql, 20971); // A query with a simple multi-position compound slicer with // different levels (overlapping) result = executeQuery( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Product].[All Products]} ON ROWS\n" + "FROM [Sales]\n" + "WHERE {[Time].[1997].[Q1], [Time].[1997].[Q1].[1]}"); cell = result.getCell(new int[]{0, 0}); assertTrue(cell.canDrillThrough()); sql = cell.getDrillThroughSQL(false); // With overlapping slicer members, the first slicer predicate is // redundant, but does not affect the query's results switch (getTestContext().getDialect().getDatabaseProduct()) { case MYSQL: expectedSql = "select sales_fact_1997.unit_sales as Unit Sales from time_by_day as time_by_day, sales_fact_1997 as sales_fact_1997 where sales_fact_1997.time_id = time_by_day.time_id and ((time_by_day.quarter = 'Q1' and time_by_day.the_year = 1997) or (time_by_day.month_of_year = 1 and time_by_day.quarter = 'Q1' and time_by_day.the_year = 1997))"; break; case ORACLE: expectedSql = "select sales_fact_1997.unit_sales as Unit Sales from time_by_day time_by_day, sales_fact_1997 sales_fact_1997 where sales_fact_1997.time_id = time_by_day.time_id and ((time_by_day.quarter = 'Q1' and time_by_day.the_year = 1997) or (time_by_day.month_of_year = 1 and time_by_day.quarter = 'Q1' and time_by_day.the_year = 1997))"; break; default: return; } getTestContext().assertSqlEquals(expectedSql, sql, 21588); // A query with a simple multi-position compound slicer with // different levels (non-overlapping) result = executeQuery( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Product].[All Products]} ON ROWS\n" + "FROM [Sales]\n" + "WHERE {[Time].[1997].[Q1].[1], [Time].[1997].[Q2]}"); cell = result.getCell(new int[]{0, 0}); assertTrue(cell.canDrillThrough()); sql = cell.getDrillThroughSQL(false); switch (getTestContext().getDialect().getDatabaseProduct()) { case MYSQL: expectedSql = "select sales_fact_1997.unit_sales as Unit Sales from time_by_day as time_by_day, sales_fact_1997 as sales_fact_1997 where sales_fact_1997.time_id = time_by_day.time_id and ((time_by_day.month_of_year = 1 and time_by_day.quarter = 'Q1' and time_by_day.the_year = 1997) or (time_by_day.quarter = 'Q2' and time_by_day.the_year = 1997))"; break; case ORACLE: expectedSql = "select sales_fact_1997.unit_sales as Unit Sales from time_by_day time_by_day, sales_fact_1997 sales_fact_1997 where sales_fact_1997.time_id = time_by_day.time_id and ((time_by_day.month_of_year = 1 and time_by_day.quarter = 'Q1' and time_by_day.the_year = 1997) or (time_by_day.quarter = 'Q2' and time_by_day.the_year = 1997))"; break; default: return; } getTestContext().assertSqlEquals(expectedSql, sql, 27402); } public void testDrillthroughDisable() { propSaver.set( MondrianProperties.instance().EnableDrillThrough, true); Result result = executeQuery( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Product].[All Products]} ON ROWS\n" + "FROM [Sales]\n" + "WHERE {[Time].[1997].[Q1], [Time].[1997].[Q2]}"); Cell cell = result.getCell(new int[]{0, 0}); assertTrue(cell.canDrillThrough()); propSaver.set( MondrianProperties.instance().EnableDrillThrough, false); result = executeQuery( "SELECT {[Measures].[Unit Sales]} ON COLUMNS,\n" + " {[Product].[All Products]} ON ROWS\n" + "FROM [Sales]\n" + "WHERE {[Time].[1997].[Q1], [Time].[1997].[Q2]}"); cell = result.getCell(new int[]{0, 0}); assertFalse(cell.canDrillThrough()); try { cell.getDrillThroughSQL(false); fail(); } catch (MondrianException e) { assertTrue( e.getMessage().contains( "Can't perform drillthrough operations because")); } } } // End DrillThroughTest.java mondrian-3.4.1/testsrc/main/mondrian/test/SqlPattern.java0000644000175000017500000000714311735330606023374 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.spi.Dialect; import org.olap4j.impl.Olap4jUtil; import java.util.Set; /** * Pattern for a SQL statement (or fragment thereof) expected to be produced * during the course of running a test. * *

A pattern contains a dialect. This allows a test to run against different * dialects. * * @see mondrian.spi.Dialect * * @author jhyde */ public class SqlPattern { private final String sql; private final String triggerSql; private final Set databaseProducts; /** * Creates a pattern which applies to a collection of dialects * and is triggered by the first N characters of the expected statement. * * @param databaseProducts Set of dialects * @param sql SQL statement * @param startsWithLen Length of prefix of statement to consider */ public SqlPattern( Set databaseProducts, String sql, int startsWithLen) { this(databaseProducts, sql, sql.substring(0, startsWithLen)); } /** * Creates a pattern which applies to one or more dialects * and is triggered by the first N characters of the expected statement. * * @param databaseProduct Dialect * @param sql SQL statement * @param startsWithLen Length of prefix of statement to consider */ public SqlPattern( Dialect.DatabaseProduct databaseProduct, final String sql, final int startsWithLen) { this(databaseProduct, sql, sql.substring(0, startsWithLen)); } /** * Creates a pattern which applies to one or more dialects. * * @param databaseProduct Dialect * @param sql SQL statement * @param triggerSql Prefix of SQL statement which triggers a match; null * means whole statement */ public SqlPattern( Dialect.DatabaseProduct databaseProduct, final String sql, final String triggerSql) { this(Olap4jUtil.enumSetOf(databaseProduct), sql, triggerSql); } /** * Creates a pattern which applies a collection of dialects. * * @param databaseProducts Set of dialects * @param sql SQL statement * @param triggerSql Prefix of SQL statement which triggers a match; null * means whole statement */ public SqlPattern( Set databaseProducts, String sql, String triggerSql) { this.databaseProducts = databaseProducts; this.sql = sql; this.triggerSql = triggerSql != null ? triggerSql : sql; } public static SqlPattern getPattern( Dialect.DatabaseProduct d, SqlPattern[] patterns) { if (d == Dialect.DatabaseProduct.UNKNOWN) { return null; } for (SqlPattern pattern : patterns) { if (pattern.hasDatabaseProduct(d)) { return pattern; } } return null; } public boolean hasDatabaseProduct(Dialect.DatabaseProduct databaseProduct) { return databaseProducts.contains(databaseProduct); } public String getSql() { return sql; } public String getTriggerSql() { return triggerSql; } } // End SqlPattern.java mondrian-3.4.1/testsrc/main/mondrian/test/UdfTest.java0000644000175000017500000020054611742752424022663 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; import mondrian.olap.type.*; import mondrian.spi.CellFormatter; import mondrian.spi.MemberFormatter; import mondrian.spi.PropertyFormatter; import mondrian.spi.*; import org.olap4j.CellSet; import org.olap4j.metadata.Property; import java.sql.SQLException; import java.util.*; import java.util.regex.Pattern; /** * Unit-test for {@link UserDefinedFunction user-defined functions}. * Also tests {@link mondrian.spi.CellFormatter cell formatters} * and {@link mondrian.spi.MemberFormatter member formatters}. * *

TODO: * 1. test that function which does not return a name, description etc. * gets a sensible error * 2. document UDFs * * @author jhyde * @since Apr 29, 2005 */ public class UdfTest extends FoodMartTestCase { public UdfTest() { } public UdfTest(String name) { super(name); } /** * Test context which uses the local FoodMart schema, and adds a "PlusOne" * user-defined function. */ private TestContext tc; @Override protected void setUp() throws Exception { super.setUp(); tc = udfTestContext( "\n"); } @Override protected void tearDown() throws Exception { super.tearDown(); tc = null; // allow gc } public TestContext getTestContext() { return tc; } /** * Shorthand for containing a test context that consists of the standard * FoodMart schema plus a UDF. * * @param xmlUdf UDF definition * @return Test context */ private TestContext udfTestContext(String xmlUdf) { return TestContext.instance().create( null, null, null, null, xmlUdf, null); } /** * Shorthand for containing a test context that consists of the standard * FoodMart Sales cube plus one measure. * * @param xmlMeasure Measure definition * @return Test context */ private TestContext measureTestContext(String xmlMeasure) { return TestContext.instance().createSubstitutingCube( "Sales", null, xmlMeasure, null, null); } /** * Shorthand for containing a test context that consists of the standard * FoodMart Sales cube plus one calculated member. * * @param xmlCalcMember Calculated member definition * @return Test context */ private TestContext calcMemberTestContext(String xmlCalcMember) { return TestContext.instance().createSubstitutingCube( "Sales", null, null, xmlCalcMember, null); } // ~ Tests follow ---------------------------------------------------------- public void testSanity() { // sanity check, make sure the schema is loading correctly assertQueryReturns( "SELECT {[Measures].[Store Sqft]} ON COLUMNS, {[Store Type]} ON ROWS FROM [Store]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sqft]}\n" + "Axis #2:\n" + "{[Store Type].[All Store Types]}\n" + "Row #0: 571,596\n"); } public void testFun() { assertQueryReturns( "WITH MEMBER [Measures].[Sqft Plus One] AS 'PlusOne([Measures].[Store Sqft])'\n" + "SELECT {[Measures].[Sqft Plus One]} ON COLUMNS, \n" + " {[Store Type].children} ON ROWS \n" + "FROM [Store]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Sqft Plus One]}\n" + "Axis #2:\n" + "{[Store Type].[Deluxe Supermarket]}\n" + "{[Store Type].[Gourmet Supermarket]}\n" + "{[Store Type].[HeadQuarters]}\n" + "{[Store Type].[Mid-Size Grocery]}\n" + "{[Store Type].[Small Grocery]}\n" + "{[Store Type].[Supermarket]}\n" + "Row #0: 146,046\n" + "Row #1: 47,448\n" + "Row #2: \n" + "Row #3: 109,344\n" + "Row #4: 75,282\n" + "Row #5: 193,481\n"); } public void testLastNonEmpty() { assertQueryReturns( "WITH MEMBER [Measures].[Last Unit Sales] AS \n" + " '([Measures].[Unit Sales], \n" + " LastNonEmpty(Descendants([Time].[Time]), [Measures].[Unit Sales]))'\n" + "SELECT {[Measures].[Last Unit Sales]} ON COLUMNS,\n" + " CrossJoin(\n" + " {[Time].[1997], [Time].[1997].[Q1], [Time].[1997].[Q1].Children},\n" + " {[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].children}) ON ROWS\n" + "FROM [Sales]\n" + "WHERE ([Store].[All Stores].[USA].[OR].[Portland].[Store 11])", "Axis #0:\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "Axis #1:\n" + "{[Measures].[Last Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1997], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good]}\n" + "{[Time].[1997], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Pearl]}\n" + "{[Time].[1997], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth]}\n" + "{[Time].[1997], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Top Measure]}\n" + "{[Time].[1997], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Walrus]}\n" + "{[Time].[1997].[Q1], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good]}\n" + "{[Time].[1997].[Q1], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Pearl]}\n" + "{[Time].[1997].[Q1], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth]}\n" + "{[Time].[1997].[Q1], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Top Measure]}\n" + "{[Time].[1997].[Q1], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Walrus]}\n" + "{[Time].[1997].[Q1].[1], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good]}\n" + "{[Time].[1997].[Q1].[1], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Pearl]}\n" + "{[Time].[1997].[Q1].[1], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth]}\n" + "{[Time].[1997].[Q1].[1], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Top Measure]}\n" + "{[Time].[1997].[Q1].[1], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Walrus]}\n" + "{[Time].[1997].[Q1].[2], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good]}\n" + "{[Time].[1997].[Q1].[2], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Pearl]}\n" + "{[Time].[1997].[Q1].[2], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth]}\n" + "{[Time].[1997].[Q1].[2], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Top Measure]}\n" + "{[Time].[1997].[Q1].[2], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Walrus]}\n" + "{[Time].[1997].[Q1].[3], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good]}\n" + "{[Time].[1997].[Q1].[3], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Pearl]}\n" + "{[Time].[1997].[Q1].[3], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth]}\n" + "{[Time].[1997].[Q1].[3], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Top Measure]}\n" + "{[Time].[1997].[Q1].[3], [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Walrus]}\n" + "Row #0: 2\n" + "Row #1: 7\n" + "Row #2: 6\n" + "Row #3: 7\n" + "Row #4: 4\n" + "Row #5: 3\n" + "Row #6: 4\n" + "Row #7: 3\n" + "Row #8: 4\n" + "Row #9: 2\n" + "Row #10: \n" + "Row #11: 4\n" + "Row #12: \n" + "Row #13: 2\n" + "Row #14: \n" + "Row #15: \n" + "Row #16: 2\n" + "Row #17: \n" + "Row #18: 4\n" + "Row #19: \n" + "Row #20: 3\n" + "Row #21: 4\n" + "Row #22: 3\n" + "Row #23: 4\n" + "Row #24: 2\n"); } /** * Tests a performance issue with LastNonEmpty (bug 1533677). The naive * implementation of LastNonEmpty crawls backward one period at a time, * generates a cache miss, and the next iteration reads precisely one cell. * So the query soon exceeds the {@link MondrianProperties#MaxEvalDepth} * property. */ public void testLastNonEmptyBig() { assertQueryReturns( "with\n" + " member\n" + " [Measures].[Last Sale] as ([Measures].[Unit Sales],\n" + " LastNonEmpty(Descendants([Time].[Time].CurrentMember, [Time].[Month]),\n" + " [Measures].[Unit Sales]))\n" + "select\n" + " NON EMPTY {[Measures].[Last Sale]} ON columns,\n" + " NON EMPTY Order([Store].[All Stores].Children,\n" + " [Measures].[Last Sale], DESC) ON rows\n" + "from [Sales]\n" + "where [Time].[Time].LastSibling", "Axis #0:\n" + "{[Time].[1998]}\n" + "Axis #1:\n" + "Axis #2:\n"); } public void testBadFun() { final TestContext tc = udfTestContext( "\n"); try { tc.executeQuery("SELECT {} ON COLUMNS FROM [Sales]"); fail("Expected exception"); } catch (Exception e) { final String s = e.getMessage(); assertEquals( "Mondrian Error:Internal error: Invalid " + "user-defined function 'BadPlusOne': return type is null", s); } } public void testGenericFun() { final TestContext tc = udfTestContext( "\n" + "\n"); tc.assertExprReturns("GenericPlusOne(3)", "4"); tc.assertExprReturns("GenericMinusOne(3)", "2"); } public void testComplexFun() { assertQueryReturns( "WITH MEMBER [Measures].[InverseNormal] AS 'InverseNormal([Measures].[Grocery Sqft] / [Measures].[Store Sqft])', FORMAT_STRING = \"0.000\"\n" + "SELECT {[Measures].[InverseNormal]} ON COLUMNS, \n" + " {[Store Type].children} ON ROWS \n" + "FROM [Store]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[InverseNormal]}\n" + "Axis #2:\n" + "{[Store Type].[Deluxe Supermarket]}\n" + "{[Store Type].[Gourmet Supermarket]}\n" + "{[Store Type].[HeadQuarters]}\n" + "{[Store Type].[Mid-Size Grocery]}\n" + "{[Store Type].[Small Grocery]}\n" + "{[Store Type].[Supermarket]}\n" + "Row #0: 0.467\n" + "Row #1: 0.463\n" + "Row #2: \n" + "Row #3: 0.625\n" + "Row #4: 0.521\n" + "Row #5: 0.504\n"); } public void testException() { Result result = executeQuery( "WITH MEMBER [Measures].[InverseNormal] " + " AS 'InverseNormal([Measures].[Store Sqft] / [Measures].[Grocery Sqft])'," + " FORMAT_STRING = \"0.000000\"\n" + "SELECT {[Measures].[InverseNormal]} ON COLUMNS, \n" + " {[Store Type].children} ON ROWS \n" + "FROM [Store]"); Axis rowAxis = result.getAxes()[0]; assertTrue(rowAxis.getPositions().size() == 1); Axis colAxis = result.getAxes()[1]; assertTrue(colAxis.getPositions().size() == 6); Cell cell = result.getCell(new int[]{0, 0}); assertTrue(cell.isError()); getTestContext().assertMatchesVerbose( Pattern.compile( "(?s).*Invalid value for inverse normal distribution: 1.4708.*"), cell.getValue().toString()); cell = result.getCell(new int[]{0, 5}); assertTrue(cell.isError()); getTestContext().assertMatchesVerbose( Pattern.compile( "(?s).*Invalid value for inverse normal distribution: 1.4435.*"), cell.getValue().toString()); } public void testCurrentDateString() { String actual = executeExpr("CurrentDateString(\"Ddd mmm dd yyyy\")"); Date currDate = new Date(); String dateString = currDate.toString(); String expected = dateString.substring(0, 11) + dateString.substring(dateString.length() - 4); assertEquals(expected, actual); } public void testCurrentDateMemberBefore() { assertQueryReturns( "SELECT { CurrentDateMember([Time].[Time], " + "\"[Ti\\me]\\.[yyyy]\\.[Qq]\\.[m]\", BEFORE)} " + "ON COLUMNS FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1998].[Q4].[12]}\n" + "Row #0: \n"); } public void testCurrentDateMemberBeforeUsingQuotes() { assertAxisReturns( MondrianProperties.instance().SsasCompatibleNaming.get() ? "CurrentDateMember([Time].[Time], " + "'\"[Time].[Time].[\"yyyy\"].[Q\"q\"].[\"m\"]\"', BEFORE)" : "CurrentDateMember([Time], " + "'\"[Time].[\"yyyy\"].[Q\"q\"].[\"m\"]\"', BEFORE)", "[Time].[1998].[Q4].[12]"); } public void testCurrentDateMemberAfter() { // CurrentDateMember will return null member since the latest date in // FoodMart is from '98 assertQueryReturns( "SELECT { CurrentDateMember([Time].[Time], " + "\"[Ti\\me]\\.[yyyy]\\.[Qq]\\.[m]\", AFTER)} " + "ON COLUMNS FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n"); } public void testCurrentDateMemberExact() { // CurrentDateMember will return null member since the latest date in // FoodMart is from '98; apply a function on the return value to // ensure null member instead of null is returned assertQueryReturns( "SELECT { CurrentDateMember([Time].[Time], " + "\"[Ti\\me]\\.[yyyy]\\.[Qq]\\.[m]\", EXACT).lag(1)} " + "ON COLUMNS FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n"); } public void testCurrentDateMemberNoFindArg() { // CurrentDateMember will return null member since the latest date in // FoodMart is from '98 assertQueryReturns( "SELECT { CurrentDateMember([Time].[Time], " + "\"[Ti\\me]\\.[yyyy]\\.[Qq]\\.[m]\")} " + "ON COLUMNS FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n"); } public void testCurrentDateMemberHierarchy() { final String query = MondrianProperties.instance().SsasCompatibleNaming.get() ? "SELECT { CurrentDateMember([Time.Weekly], " + "\"[Ti\\me\\.Weekl\\y]\\.[All Weekl\\y\\s]\\.[yyyy]\\.[ww]\", BEFORE)} " + "ON COLUMNS FROM [Sales]" : "SELECT { CurrentDateMember([Time.Weekly], " + "\"[Ti\\me\\.Weekl\\y]\\.[All Ti\\me\\.Weekl\\y\\s]\\.[yyyy]\\.[ww]\", BEFORE)} " + "ON COLUMNS FROM [Sales]"; assertQueryReturns( query, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[Weekly].[1998].[52]}\n" + "Row #0: \n"); } public void testCurrentDateMemberHierarchyNullReturn() { // CurrentDateMember will return null member since the latest date in // FoodMart is from '98; note that first arg is a hierarchy rather // than a dimension assertQueryReturns( "SELECT { CurrentDateMember([Time.Weekly], " + "\"[Ti\\me]\\.[yyyy]\\.[Qq]\\.[m]\")} " + "ON COLUMNS FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n"); } public void testCurrentDateMemberRealAfter() { // omit formatting characters from the format so the current date // is hard-coded to actual value in the database so we can test the // after logic assertQueryReturns( "SELECT { CurrentDateMember([Time].[Time], " + "\"[Ti\\me]\\.[1996]\\.[Q4]\", after)} " + "ON COLUMNS FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997].[Q1]}\n" + "Row #0: 66,291\n"); } public void testCurrentDateMemberRealExact1() { // omit formatting characters from the format so the current date // is hard-coded to actual value in the database so we can test the // exact logic assertQueryReturns( "SELECT { CurrentDateMember([Time].[Time], " + "\"[Ti\\me]\\.[1997]\")} " + "ON COLUMNS FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "Row #0: 266,773\n"); } public void testCurrentDateMemberRealExact2() { // omit formatting characters from the format so the current date // is hard-coded to actual value in the database so we can test the // exact logic assertQueryReturns( "SELECT { CurrentDateMember([Time].[Time], " + "\"[Ti\\me]\\.[1997]\\.[Q2]\\.[5]\")} " + "ON COLUMNS FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1997].[Q2].[5]}\n" + "Row #0: 21,081\n"); } public void testCurrentDateMemberPrev() { // apply a function on the result of the UDF assertQueryReturns( "SELECT { CurrentDateMember([Time].[Time], " + "\"[Ti\\me]\\.[yyyy]\\.[Qq]\\.[m]\", BEFORE).PrevMember} " + "ON COLUMNS FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1998].[Q4].[11]}\n" + "Row #0: \n"); } public void testCurrentDateLag() { // Also, try a different style of quoting, because single quote followed // by double quote (used in other examples) is difficult to read. assertQueryReturns( "SELECT\n" + " { [Measures].[Unit Sales] } ON COLUMNS,\n" + " { CurrentDateMember([Time].[Time], '[\"Time\"]\\.[yyyy]\\.[\"Q\"q]\\.[m]', BEFORE).Lag(3) : " + " CurrentDateMember([Time].[Time], '[\"Time\"]\\.[yyyy]\\.[\"Q\"q]\\.[m]', BEFORE) } ON ROWS\n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Time].[1998].[Q3].[9]}\n" + "{[Time].[1998].[Q4].[10]}\n" + "{[Time].[1998].[Q4].[11]}\n" + "{[Time].[1998].[Q4].[12]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: \n" + "Row #3: \n"); } public void testMatches() { assertQueryReturns( "SELECT {[Measures].[Org Salary]} ON COLUMNS, " + "Filter({[Employees].MEMBERS}, " + "[Employees].CurrentMember.Name MATCHES '(?i)sam.*') ON ROWS " + "FROM [HR]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Org Salary]}\n" + "Axis #2:\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Beverly Baker].[Jacqueline Wyllie].[Ralph Mccoy].[Anne Tuck].[Samuel Johnson]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Jose Bernard].[Mary Hunt].[Bonnie Bruno].[Sam Warren]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Charles Macaluso].[Barbara Wallin].[Michael Suggs].[Sam Adair]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Pedro Castillo].[Lois Wood].[Dell Gras].[Kristine Aldred].[Sam Zeller]}\n" + "{[Employees].[Sheri Nowmer].[Derrick Whelply].[Laurie Borges].[Cody Goldey].[Shanay Steelman].[Neal Hasty].[Sam Wheeler]}\n" + "{[Employees].[Sheri Nowmer].[Maya Gutierrez].[Brenda Blumberg].[Wayne Banack].[Samuel Agcaoili]}\n" + "{[Employees].[Sheri Nowmer].[Maya Gutierrez].[Jonathan Murraiin].[James Thompson].[Samantha Weller]}\n" + "Row #0: $40.62\n" + "Row #1: $40.31\n" + "Row #2: $75.60\n" + "Row #3: $40.35\n" + "Row #4: $47.52\n" + "Row #5: \n" + "Row #6: \n"); } public void testNotMatches() { assertQueryReturns( "SELECT {[Measures].[Store Sales]} ON COLUMNS, " + "Filter({[Store Type].MEMBERS}, " + "[Store Type].CurrentMember.Name NOT MATCHES " + "'.*Grocery.*') ON ROWS " + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Store Type].[All Store Types]}\n" + "{[Store Type].[Deluxe Supermarket]}\n" + "{[Store Type].[Gourmet Supermarket]}\n" + "{[Store Type].[HeadQuarters]}\n" + "{[Store Type].[Supermarket]}\n" + "Row #0: 565,238.13\n" + "Row #1: 162,062.24\n" + "Row #2: 45,750.24\n" + "Row #3: \n" + "Row #4: 319,210.04\n"); } public void testIn() { assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS, " + "FILTER([Product].[Product Family].MEMBERS, " + "[Product].[Product Family].CurrentMember IN " + "{[Product].[All Products].firstChild, " + "[Product].[All Products].lastChild}) ON ROWS " + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 24,597\n" + "Row #1: 50,236\n"); } public void testNotIn() { assertQueryReturns( "SELECT {[Measures].[Unit Sales]} ON COLUMNS, " + "FILTER([Product].[Product Family].MEMBERS, " + "[Product].[Product Family].CurrentMember NOT IN " + "{[Product].[All Products].firstChild, " + "[Product].[All Products].lastChild}) ON ROWS " + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Food]}\n" + "Row #0: 191,940\n"); } public void testChildMemberIn() { assertQueryReturns( "SELECT {[Measures].[Store Sales]} ON COLUMNS, " + "{[Store].[Store Name].MEMBERS} ON ROWS " + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n" + "{[Store].[Canada].[BC].[Vancouver].[Store 19]}\n" + "{[Store].[Canada].[BC].[Victoria].[Store 20]}\n" + "{[Store].[Mexico].[DF].[Mexico City].[Store 9]}\n" + "{[Store].[Mexico].[DF].[San Andres].[Store 21]}\n" + "{[Store].[Mexico].[Guerrero].[Acapulco].[Store 1]}\n" + "{[Store].[Mexico].[Jalisco].[Guadalajara].[Store 5]}\n" + "{[Store].[Mexico].[Veracruz].[Orizaba].[Store 10]}\n" + "{[Store].[Mexico].[Yucatan].[Merida].[Store 8]}\n" + "{[Store].[Mexico].[Zacatecas].[Camacho].[Store 4]}\n" + "{[Store].[Mexico].[Zacatecas].[Hidalgo].[Store 12]}\n" + "{[Store].[Mexico].[Zacatecas].[Hidalgo].[Store 18]}\n" + "{[Store].[USA].[CA].[Alameda].[HQ]}\n" + "{[Store].[USA].[CA].[Beverly Hills].[Store 6]}\n" + "{[Store].[USA].[CA].[Los Angeles].[Store 7]}\n" + "{[Store].[USA].[CA].[San Diego].[Store 24]}\n" + "{[Store].[USA].[CA].[San Francisco].[Store 14]}\n" + "{[Store].[USA].[OR].[Portland].[Store 11]}\n" + "{[Store].[USA].[OR].[Salem].[Store 13]}\n" + "{[Store].[USA].[WA].[Bellingham].[Store 2]}\n" + "{[Store].[USA].[WA].[Bremerton].[Store 3]}\n" + "{[Store].[USA].[WA].[Seattle].[Store 15]}\n" + "{[Store].[USA].[WA].[Spokane].[Store 16]}\n" + "{[Store].[USA].[WA].[Tacoma].[Store 17]}\n" + "{[Store].[USA].[WA].[Walla Walla].[Store 22]}\n" + "{[Store].[USA].[WA].[Yakima].[Store 23]}\n" + "Row #0: \n" + "Row #1: \n" + "Row #2: \n" + "Row #3: \n" + "Row #4: \n" + "Row #5: \n" + "Row #6: \n" + "Row #7: \n" + "Row #8: \n" + "Row #9: \n" + "Row #10: \n" + "Row #11: \n" + "Row #12: 45,750.24\n" + "Row #13: 54,545.28\n" + "Row #14: 54,431.14\n" + "Row #15: 4,441.18\n" + "Row #16: 55,058.79\n" + "Row #17: 87,218.28\n" + "Row #18: 4,739.23\n" + "Row #19: 52,896.30\n" + "Row #20: 52,644.07\n" + "Row #21: 49,634.46\n" + "Row #22: 74,843.96\n" + "Row #23: 4,705.97\n" + "Row #24: 24,329.23\n"); // test when the member arg is at a different level // from the set argument assertQueryReturns( "SELECT {[Measures].[Store Sales]} ON COLUMNS, " + "Filter({[Store].[Store Name].MEMBERS}, " + "[Store].[Store Name].CurrentMember IN " + "{[Store].[All Stores].[Mexico], " + "[Store].[All Stores].[USA]}) ON ROWS " + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "Axis #2:\n"); } /** * Tests that the inferred return type is correct for a UDF whose return * type is not the same as would be guessed by the default implementation * of {@link mondrian.olap.fun.FunDefBase#getResultType}, which simply * guesses based on the type of the first argument. */ public void testNonGuessableReturnType() { TestContext tc = udfTestContext( "\n"); // The default implementation of getResultType would assume that // StringMult(int, string) returns an int, whereas it returns a string. tc.assertExprReturns( "StringMult(5, 'foo') || 'bar'", "foofoofoofoofoobar"); } /** * Test case for the problem where a string expression gave a * ClassCastException because it was evaluating to a member, whereas the * member should have been evaluated to a scalar. */ public void testUdfToString() { TestContext tc = udfTestContext( "\n"); tc.assertQueryReturns( "with member [Measures].[ABC] as StringMult(1, 'A')\n" + "member [Measures].[Unit Sales Formatted] as\n" + " [Measures].[Unit Sales],\n" + " FORMAT_STRING = '#,###|color=' ||\n" + " Iif([Measures].[ABC] = 'A', 'red', 'green')\n" + "select [Measures].[Unit Sales Formatted] on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales Formatted]}\n" + "Row #0: 266,773|color=red\n"); } /** * Tests a UDF whose return type is not the same as its first * parameter. The return type needs to have full dimensional information; * in this case, HierarchyType(dimension=Time, hierarchy=unknown). * *

Also tests applying a UDF to arguments of coercible type. In this * case, applies f(member,dimension) to args(member,hierarchy). */ public void testAnotherMemberFun() { final TestContext tc = udfTestContext( "\n" + ""); tc.assertQueryReturns( "WITH MEMBER [Measures].[Test] AS " + "'([Measures].[Store Sales],[Product].[Food],AnotherMemberError([Product].[Drink],[Time].[Time]))'\n" + "SELECT {[Measures].[Test]} ON COLUMNS, \n" + " {[Customers].DefaultMember} ON ROWS \n" + "FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Test]}\n" + "Axis #2:\n" + "{[Customers].[All Customers]}\n" + "Row #0: 409,035.59\n"); } public void testCachingCurrentDate() { assertQueryReturns( "SELECT {filter([Time].[Month].Members, " + "[Time].[Time].CurrentMember in {CurrentDateMember([Time]" + ".[Time], '[\"Time\"]\\.[yyyy]\\.[\"Q\"q]\\.[m]', " + "BEFORE)})} ON COLUMNS " + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time].[1998].[Q4].[12]}\n" + "Row #0: \n"); } /** * Test case for a UDF that returns a list. * *

Test case for bug * MONDRIAN-588, * "UDF returning List works under 2.4, fails under 3.1.1". * *

Also test case for bug * MONDRIAN-589, * "UDF expecting List gets anonymous * mondrian.rolap.RolapNamedSetEvaluator$1 instead". */ public void testListUdf() { checkListUdf(ReverseFunction.class); checkListUdf(ReverseIterableFunction.class); } /** * Helper for {@link #testListUdf()}. * * @param functionClass Class that implements the "Reverse" function. */ private void checkListUdf( final Class functionClass) { TestContext tc = udfTestContext( "\n"); final String expectedResult = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender].[M]}\n" + "{[Gender].[F]}\n" + "{[Gender].[All Gender]}\n" + "Row #0: 135,215\n" + "Row #0: 131,558\n" + "Row #0: 266,773\n"; // UDF called directly in axis expression. tc.assertQueryReturns( "select Reverse([Gender].Members) on 0\n" + "from [Sales]", expectedResult); // UDF as calc set definition tc.assertQueryReturns( "with set [Foo] as Reverse([Gender].Members)\n" + "select [Foo] on 0\n" + "from [Sales]", expectedResult); // UDF applied to calc set -- exhibited MONDRIAN-589 tc.assertQueryReturns( "with set [Foo] as [Gender].Members\n" + "select Reverse([Foo]) on 0\n" + "from [Sales]", expectedResult); } /** * Tests that a non-static function gives an error. */ public void testNonStaticUdfFails() { if (Util.PreJdk15) { // Cannot detect non-static inner classes in JDK 1.4, because // such things are not supposed to exist. return; } TestContext tc = udfTestContext( "\n"); tc.assertQueryThrows( "select Reverse2([Gender].Members) on 0\n" + "from [Sales]", "Failed to load user-defined function 'Reverse2': class " + "'mondrian.test.UdfTest$ReverseFunctionNotStatic' must be " + "public " + "and static"); } /** * Tests a function that takes a member as argument. Want to make sure that * Mondrian leaves it as a member, does not try to evaluate it to a scalar * value. */ public void testMemberUdfDoesNotEvaluateToScalar() { TestContext tc = udfTestContext( "\n"); tc.assertExprReturns( "MemberName([Gender].[F])", "F"); } /** * Unit test that ensures that a UDF has either a script or a className. */ public void testUdfNeitherScriptNorClassname() { TestContext tc = udfTestContext( "\n"); tc.assertQueryThrows( "select from [Sales]", "Must specify either className attribute or Script element"); } /** * Unit test that ensures that a UDF does not have both a script * and a className. */ public void testUdfBothScriptAndClassname() { TestContext tc = udfTestContext( "\n" + " \n" + ""); tc.assertQueryThrows( "select from [Sales]", "Must not specify both className attribute and Script element"); } /** * Unit test that ensures that a UDF has either a script or a className. */ public void testUdfScriptBadLanguage() { TestContext tc = udfTestContext( "\n" + " \n" + ""); tc.assertQueryThrows( "select from [Sales]", "Invalid script language 'bad'"); } /** * Unit test that ensures that script UDFs fail before JDK 1.6. */ public void testUdfScriptBadJdk() { if (!Util.PreJdk16) { return; } TestContext tc = udfTestContext( "\n" + " \n" + ""); tc.assertQueryThrows( "select from [Sales]", "Scripting not supported until Java 1.6"); } /** * Unit test for a UDF defined in JavaScript. */ public void testScriptUdf() { if (Util.PreJdk16) { return; } TestContext tc = udfTestContext( "\n" + " \n" + "\n"); tc.assertQueryReturns( "with member [Measures].[ABC] as StringMult(1, 'A')\n" + "member [Measures].[Unit Sales Formatted] as\n" + " [Measures].[Unit Sales],\n" + " FORMAT_STRING = '#,###|color=' ||\n" + " Iif([Measures].[ABC] = 'A', 'red', 'green')\n" + "select [Measures].[Unit Sales Formatted] on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales Formatted]}\n" + "Row #0: 266,773|color=red\n"); } /** * Unit test for a UDF defined in JavaScript, this time the factorial * function. We also use 'CDATA' section to mask the '<' symbol. */ public void testScriptUdfFactorial() { if (Util.PreJdk16) { return; } TestContext tc = udfTestContext( "\n" + " \n" + "\n"); tc.assertExprReturns( "Factorial(4 + 2)", "720"); } /** * Unit test that we get a nice error if a script UDF contains an error. */ public void testScriptUdfInvalid() { if (Util.PreJdk16) { return; } TestContext tc = udfTestContext( "\n" + " \n" + "\n"); final Cell cell = tc.executeExprRaw("Factorial(4 + 2)"); getTestContext().assertMatchesVerbose( Pattern.compile( "(?s).*ReferenceError: \"factorial_xx\" is not defined..*"), cell.getValue().toString()); } /** * Unit test for a cell formatter defined in the old way -- a 'formatter' * attribute of a Measure element. */ public void testCellFormatter() { // Note that // formatString="Standard" // is ignored. TestContext tc = measureTestContext( ""); tc.assertQueryReturns( "select {[Measures].[Unit Sales],\n" + " [Measures].[Unit Sales Foo Bar]} on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Unit Sales Foo Bar]}\n" + "Row #0: 266,773\n" + "Row #0: foo266773.0bar\n"); } /** * As {@link #testCellFormatter()}, but using new-style nested * CellFormatter element. */ public void testCellFormatterNested() { // Note that // formatString="Standard" // is ignored. TestContext tc = measureTestContext( "\n" + " \n" + ""); tc.assertQueryReturns( "select {[Measures].[Unit Sales],\n" + " [Measures].[Unit Sales Foo Bar]} on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Unit Sales Foo Bar]}\n" + "Row #0: 266,773\n" + "Row #0: foo266773.0bar\n"); } /** * As {@link #testCellFormatterNested()}, but using a script. */ public void testCellFormatterScript() { if (Util.PreJdk16) { return; } TestContext tc = measureTestContext( "\n" + " \n" + " \n" + " \n" + ""); // Note that the result is slightly different to above (a missing ".0"). // Not a great concern -- in fact it proves that the scripted UDF is // being used. tc.assertQueryReturns( "select {[Measures].[Unit Sales],\n" + " [Measures].[Unit Sales Foo Bar]} on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Unit Sales Foo Bar]}\n" + "Row #0: 266,773\n" + "Row #0: foo266773bar\n"); } /** * Unit test for a cell formatter defined against a calculated member, * using the old syntax (a member property called "CELL_FORMATTER"). */ public void testCellFormatterOnCalcMember() { TestContext tc = calcMemberTestContext( "\n" + " [Measures].[Unit Sales]\n" + " \n" + ""); tc.assertQueryReturns( "select {[Measures].[Unit Sales],\n" + " [Measures].[Unit Sales Foo Bar]} on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Unit Sales Foo Bar]}\n" + "Row #0: 266,773\n" + "Row #0: foo266773.0bar\n"); } /** * Unit test for a cell formatter defined against a calculated member, * using the new syntax (a nested CellFormatter element). */ public void testCellFormatterOnCalcMemberNested() { TestContext tc = calcMemberTestContext( "\n" + " [Measures].[Unit Sales]\n" + " \n" + ""); tc.assertQueryReturns( "select {[Measures].[Unit Sales],\n" + " [Measures].[Unit Sales Foo Bar]} on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Unit Sales Foo Bar]}\n" + "Row #0: 266,773\n" + "Row #0: foo266773.0bar\n"); } /** * Unit test for a cell formatter defined against a calculated member, * using a script. */ public void testCellFormatterOnCalcMemberScript() { if (Util.PreJdk16) { return; } TestContext tc = calcMemberTestContext( "\n" + " [Measures].[Unit Sales]\n" + " \n" + " \n" + " \n" + ""); tc.assertQueryReturns( "select {[Measures].[Unit Sales],\n" + " [Measures].[Unit Sales Foo Bar]} on 0\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Unit Sales Foo Bar]}\n" + "Row #0: 266,773\n" + "Row #0: foo266773bar\n"); } /** * Unit test for a member formatter defined in the old way -- a 'formatter' * attribute of a Measure element. */ public void testMemberFormatter() { TestContext tc = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "

\n" + " \n" + " \n" + " "); tc.assertExprReturns( "[Promotion Media2].FirstChild.Caption", "fooBulk Mailbar"); } /** * As {@link #testMemberFormatter()}, but using new-style nested * memberFormatter element. */ public void testMemberFormatterNested() { TestContext tc = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " "); tc.assertExprReturns( "[Promotion Media2].FirstChild.Caption", "fooBulk Mailbar"); } /** * As {@link #testMemberFormatterNested()}, but using a script. */ public void testMemberFormatterScript() { if (Util.PreJdk16) { return; } TestContext tc = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " "); tc.assertExprReturns( "[Promotion Media2].FirstChild.Caption", "fooBulk Mailbar"); } /** * Unit test for a property formatter defined in the old way -- a * 'formatter' attribute of a Property element. * * @throws java.sql.SQLException on error */ public void testPropertyFormatter() throws SQLException { TestContext tc = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + ""); final CellSet result = tc.executeOlap4jQuery( "select [Promotions2].Children on 0\n" + "from [Sales]"); final org.olap4j.metadata.Member member = result.getAxes().get(0).getPositions().get(0).getMembers().get(0); final Property property = member.getProperties().get("Medium"); assertEquals( "foo0/Medium/No Mediabar", member.getPropertyFormattedValue(property)); } /** * As {@link #testPropertyFormatter()}, but using new-style nested * PropertyFormatter element. * * @throws java.sql.SQLException on error */ public void testPropertyFormatterNested() throws SQLException { TestContext tc = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""); final CellSet result = tc.executeOlap4jQuery( "select [Promotions2].Children on 0\n" + "from [Sales]"); final org.olap4j.metadata.Member member = result.getAxes().get(0).getPositions().get(0).getMembers().get(0); final Property property = member.getProperties().get("Medium"); assertEquals( "foo0/Medium/No Mediabar", member.getPropertyFormattedValue(property)); } /** * As {@link #testPropertyFormatterNested()}, but using a script. * * @throws java.sql.SQLException on error */ public void testPropertyFormatterScript() throws SQLException { if (Util.PreJdk16) { return; } TestContext tc = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""); final CellSet result = tc.executeOlap4jQuery( "select [Promotions2].Children on 0\n" + "from [Sales]"); final org.olap4j.metadata.Member member = result.getAxes().get(0).getPositions().get(0).getMembers().get(0); final Property property = member.getProperties().get("Medium"); assertEquals( "foo0/Medium/No Mediabar", member.getPropertyFormattedValue(property)); } // ~ Inner classes -------------------------------------------------------- /** * A simple user-defined function which adds one to its argument. */ public static class PlusOneUdf implements UserDefinedFunction { public String getName() { return "PlusOne"; } public String getDescription() { return "Returns its argument plus one"; } public Syntax getSyntax() { return Syntax.Function; } public Type getReturnType(Type[] parameterTypes) { return new NumericType(); } public Type[] getParameterTypes() { return new Type[] {new NumericType()}; } public Object execute(Evaluator evaluator, Argument[] arguments) { final Object argValue = arguments[0].evaluateScalar(evaluator); if (argValue instanceof Number) { return ((Number) argValue).doubleValue() + 1.0; } else { // Argument might be a RuntimeException indicating that // the cache does not yet have the required cell value. The // function will be called again when the cache is loaded. return null; } } public String[] getReservedWords() { return null; } } /** * A simple user-defined function which adds one to its argument. */ public static class BadPlusOneUdf extends PlusOneUdf { private final String name; public BadPlusOneUdf(String name) { this.name = name; } public String getName() { return name; } public Type getReturnType(Type[] parameterTypes) { // Will cause error. return null; } } /** * A user-defined function which, depending on its given name, either adds * one to, or subtracts one from, its argument. */ public static class PlusOrMinusOneUdf implements UserDefinedFunction { private final String name; public PlusOrMinusOneUdf(String name) { if (!(name.equals("GenericPlusOne") || name.equals("GenericMinusOne"))) { throw new IllegalArgumentException(); } this.name = name; } public String getName() { return name; } public String getDescription() { return "A user-defined function which, depending on its given name, " + "either addsone to, or subtracts one from, its argument"; } public Syntax getSyntax() { return Syntax.Function; } public Type getReturnType(Type[] parameterTypes) { return new NumericType(); } public String[] getReservedWords() { return null; } public Type[] getParameterTypes() { return new Type[] {new NumericType()}; } public Object execute(Evaluator evaluator, Argument[] arguments) { final Object argValue = arguments[0].evaluateScalar(evaluator); if (argValue instanceof Number) { return ((Number) argValue).doubleValue() + (name.equals("GenericPlusOne") ? 1.0 : -1.0); } else { // Argument might be a RuntimeException indicating that // the cache does not yet have the required cell value. The // function will be called again when the cache is loaded. return null; } } } /** * The "TimesString" user-defined function. We wanted a function whose * actual return type (string) is not the same as the guessed return type * (integer). */ public static class StringMultUdf implements UserDefinedFunction { public String getName() { return "StringMult"; } public String getDescription() { return "Returns N copies of its string argument"; } public Syntax getSyntax() { return Syntax.Function; } public Type getReturnType(Type[] parameterTypes) { return new StringType(); } public Type[] getParameterTypes() { return new Type[] { new NumericType(), new StringType() }; } public Object execute(Evaluator evaluator, Argument[] arguments) { final Object argValue = arguments[0].evaluateScalar(evaluator); int n; if (argValue instanceof Number) { n = ((Number) argValue).intValue(); } else { // Argument might be a RuntimeException indicating that // the cache does not yet have the required cell value. The // function will be called again when the cache is loaded. return null; } String s; final Object argValue2 = arguments[1].evaluateScalar(evaluator); if (argValue2 instanceof String) { s = (String) argValue2; } else { return null; } if (n < 0) { return null; } StringBuilder buf = new StringBuilder(s.length() * n); for (int i = 0; i < n; i++) { buf.append(s); } return buf.toString(); } public String[] getReservedWords() { return null; } } /** * A user-defined function which returns ignores its first parameter (a * member) and returns the default member from the second parameter (a * hierarchy). */ public static class AnotherMemberErrorUdf implements UserDefinedFunction { public String getName() { return "AnotherMemberError"; } public String getDescription() { return "Returns default member from hierarchy, " + "specified as a second parameter. " + "First parameter - any member from any hierarchy"; } public Syntax getSyntax() { return Syntax.Function; } public Type getReturnType(Type[] parameterTypes) { HierarchyType hierType = (HierarchyType) parameterTypes[1]; return MemberType.forType(hierType); } public Type[] getParameterTypes() { return new Type[] { // The first argument must be a member. MemberType.Unknown, // The second argument must be a hierarchy. HierarchyType.Unknown }; } public Object execute(Evaluator evaluator, Argument[] arguments) { // Simply ignore first parameter Member member = (Member)arguments[0].evaluate(evaluator); Util.discard(member); Hierarchy hierarchy = (Hierarchy)arguments[1].evaluate(evaluator); return hierarchy.getDefaultMember(); } public String[] getReservedWords() { return null; } } /** * Function that reverses a list of members. */ public static class ReverseFunction implements UserDefinedFunction { public Object execute(Evaluator eval, Argument[] args) { // Note: must call Argument.evaluateList. If we call // Argument.evaluate we may get an Iterable. List list = args[0].evaluateList(eval); // We do need to copy before we reverse. The list is not guaranteed // to be mutable. list = new ArrayList(list); Collections.reverse(list); return list; } public String getDescription() { return "Reverses the order of a set"; } public String getName() { return "Reverse"; } public Type[] getParameterTypes() { return new Type[] {new SetType(MemberType.Unknown)}; } public String[] getReservedWords() { return null; } public Type getReturnType(Type[] arg0) { return arg0[0]; } public Syntax getSyntax() { return Syntax.Function; } } /** * Function that is non-static. */ public class ReverseFunctionNotStatic extends ReverseFunction { } /** * Function that takes a set of members as argument, and returns a set of * members. */ public static class ReverseIterableFunction extends ReverseFunction { public Object execute(Evaluator eval, Argument[] args) { // Note: must call Argument.evaluateList. If we call // Argument.evaluate we may get an Iterable. Iterable iterable = args[0].evaluateIterable(eval); List list = new ArrayList(); for (Object o : iterable) { list.add(o); } Collections.reverse(list); return list; } } /** * Function that takes a member and returns a name. */ public static class MemberNameFunction implements UserDefinedFunction { public Object execute(Evaluator eval, Argument[] args) { Member member = (Member) args[0].evaluate(eval); return member.getName(); } public String getDescription() { return "Returns the name of a member"; } public String getName() { return "MemberName"; } public Type[] getParameterTypes() { return new Type[] {MemberType.Unknown}; } public String[] getReservedWords() { return null; } public Type getReturnType(Type[] arg0) { return new StringType(); } public Syntax getSyntax() { return Syntax.Function; } } /** * Member formatter for test purposes. Returns name of the member prefixed * with "foo" and suffixed with "bar". */ public static class FooBarMemberFormatter implements MemberFormatter { public String formatMember(Member member) { return "foo" + member.getName() + "bar"; } } /** * Cell formatter for test purposes. Returns value of the cell prefixed * with "foo" and suffixed with "bar". */ public static class FooBarCellFormatter implements CellFormatter { public String formatCell(Object value) { return "foo" + value + "bar"; } } /** * Property formatter for test purposes. Returns name of the member and * property, then the value, prefixed with "foo" and suffixed with "bar". */ public static class FooBarPropertyFormatter implements PropertyFormatter { public String formatProperty( Member member, String propertyName, Object propertyValue) { return "foo" + member.getName() + "/" + propertyName + "/" + propertyValue + "bar"; } } } // End UdfTest.java mondrian-3.4.1/testsrc/main/mondrian/test/SchemaTest.java0000644000175000017500000066056511735330606023354 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; import mondrian.olap.Hierarchy; import mondrian.rolap.aggmatcher.AggTableManager; import mondrian.spi.Dialect; import mondrian.spi.PropertyFormatter; import mondrian.util.Bug; import org.apache.log4j.*; import org.apache.log4j.Level; import org.apache.log4j.varia.LevelRangeFilter; import org.olap4j.metadata.NamedList; import java.io.StringWriter; import java.sql.SQLException; import java.util.List; import java.util.Map; /** * Unit tests for various schema features. * * @author jhyde * @since August 7, 2006 */ public class SchemaTest extends FoodMartTestCase { public SchemaTest(String name) { super(name); } /** * Asserts that a list of exceptions (probably from * {@link mondrian.olap.Schema#getWarnings()}) contains the expected * exception. * * @param exceptionList List of exceptions * @param expected Expected message */ private void assertContains( List exceptionList, String expected) { StringBuilder buf = new StringBuilder(); for (Exception exception : exceptionList) { if (exception.getMessage().matches(expected)) { return; } if (buf.length() > 0) { buf.append(Util.nl); } buf.append(exception.getMessage()); } fail( "Exception list did not contain expected exception '" + expected + "'. Exception list is:" + buf.toString()); } // Tests follow... public void testSolveOrderInCalculatedMember() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", null, "\n" + " [Measures].[Store Sales] / [Measures].[Store Cost]\n" + " \n" + " , \n" + " Sum(Gender.Members)\n" + " \n" + " \n" + " "); testContext.assertQueryReturns( "select {[Measures].[QuantumProfit]} on 0, {(Gender.foo)} on 1 from sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[QuantumProfit]}\n" + "Axis #2:\n" + "{[Gender].[foo]}\n" + "Row #0: $7.52\n"); } public void testHierarchyDefaultMember() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "
\n" + " \n" + " \n" + " "); testContext.assertQueryReturns( "select {[Gender with default]} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Gender with default].[M]}\n" + "Row #0: 135,215\n"); } /** * Test case for the issue described in * Pentaho * forum post 'wrong unique name for default member when hasAll=false'. */ public void testDefaultMemberName() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n"); // note that default member name has no 'all' and has a name not an id testContext.assertQueryReturns( "select {[Product with no all]} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product with no all].[Nuts]}\n" + "Row #0: 4,400\n"); } public void testHierarchyAbbreviatedDefaultMember() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "
\n" + " \n" + " \n" + " "); testContext.assertQueryReturns( "select {[Gender with default]} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" // Note that the 'all' member is named according to the rule // '[].[All s]'. + "{[Gender with default].[F]}\n" + "Row #0: 131,558\n"); } public void testHierarchyNoLevelsFails() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "
\n" + " \n" + " "); testContext.assertQueryThrows( "select {[Gender no levels]} on columns from [Sales]", "Hierarchy '[Gender no levels]' must have at least one level."); } public void testHierarchyNonUniqueLevelsFails() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " "); testContext.assertQueryThrows( "select {[Gender dup levels]} on columns from [Sales]", "Level names within hierarchy '[Gender dup levels]' are not unique; there is more than one level with name 'Gender'."); } /** * Tests a measure based on 'count'. */ public void testCountMeasure() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", null, "\n"); testContext.assertQueryReturns( "select {[Measures].[Fact Count], [Measures].[Unit Sales]} on 0,\n" + "[Gender].members on 1\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Fact Count]}\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Gender].[All Gender]}\n" + "{[Gender].[F]}\n" + "{[Gender].[M]}\n" + "Row #0: 86,837\n" + "Row #0: 266,773\n" + "Row #1: 42,831\n" + "Row #1: 131,558\n" + "Row #2: 44,006\n" + "Row #2: 135,215\n"); } /** * Tests that an error occurs if a hierarchy is based on a non-existent * table. */ public void testHierarchyTableNotFound() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + ""); // FIXME: This should validate the schema, and fail. testContext.assertSimpleQuery(); // FIXME: Should give better error. testContext.assertQueryThrows( "select [Yearly Income3].Children on 0 from [Sales]", "Internal error: while building member cache"); } public void testPrimaryKeyTableNotFound() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + ""); testContext.assertQueryThrows( "select from [Sales]", "no table 'customer_not_found' found in hierarchy [Yearly Income4]"); } public void testLevelTableNotFound() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + ""); testContext.assertQueryThrows( "select from [Sales]", "Table 'customer_not_found' not found"); } public void testHierarchyBadDefaultMember() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "
\n" + " \n" + " \n" + " "); testContext.assertQueryThrows( "select {[Gender with default]} on columns from [Sales]", "Can not find Default Member with name \"[Gender with default].[Non].[Existent]\" in Hierarchy \"Gender with default\""); } /** * WG: Note, this no longer throws an exception with the new RolapCubeMember * functionality. * *

Tests that an error is issued if two dimensions use the same table via * different drill-paths and do not use a different alias. If this error is * not issued, the generated SQL can be missing a join condition, as in * * Bug MONDRIAN-236, "Mondrian generates invalid SQL". */ public void testDuplicateTableAlias() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "

\n" + " \n" + " \n" + ""); testContext.assertQueryReturns( "select {[Yearly Income2]} on columns, {[Measures].[Unit Sales]} on rows from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Yearly Income2].[All Yearly Income2s]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 266,773\n"); } /** * This result is somewhat peculiar. If two dimensions share a foreign key, * what is the expected result? Also, in this case, they share the same * table without an alias, and the system doesn't complain. */ public void testDuplicateTableAliasSameForeignKey() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + ""); testContext.assertQueryReturns( "select from [Sales]", "Axis #0:\n" + "{}\n" + "266,773"); // NonEmptyCrossJoin Fails if (false) { testContext.assertQueryReturns( "select NonEmptyCrossJoin({[Yearly Income2].[All Yearly Income2s]},{[Customers].[All Customers]}) on rows," + "NON EMPTY {[Measures].[Unit Sales]} on columns" + " from [Sales]", "Axis #0:\n" + "{}\n" + "266,773"); } } /** * Tests two dimensions using same table (via different join paths). * Without the table alias, generates SQL which is missing a join condition. * See {@link #testDuplicateTableAlias()}. */ public void testDimensionsShareTable() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + ""); testContext.assertQueryReturns( "select {[Yearly Income].[$10K - $30K]} on columns," + "{[Yearly Income2].[$150K +]} on rows from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Yearly Income].[$10K - $30K]}\n" + "Axis #2:\n" + "{[Yearly Income2].[$150K +]}\n" + "Row #0: 918\n"); testContext.assertQueryReturns( "select NON EMPTY {[Measures].[Unit Sales]} ON COLUMNS,\n" + "NON EMPTY Crossjoin({[Yearly Income].[All Yearly Incomes].Children},\n" + " [Yearly Income2].[All Yearly Income2s].Children) ON ROWS\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Yearly Income].[$10K - $30K], [Yearly Income2].[$10K - $30K]}\n" + "{[Yearly Income].[$10K - $30K], [Yearly Income2].[$110K - $130K]}\n" + "{[Yearly Income].[$10K - $30K], [Yearly Income2].[$130K - $150K]}\n" + "{[Yearly Income].[$10K - $30K], [Yearly Income2].[$150K +]}\n" + "{[Yearly Income].[$10K - $30K], [Yearly Income2].[$30K - $50K]}\n" + "{[Yearly Income].[$10K - $30K], [Yearly Income2].[$50K - $70K]}\n" + "{[Yearly Income].[$10K - $30K], [Yearly Income2].[$70K - $90K]}\n" + "{[Yearly Income].[$10K - $30K], [Yearly Income2].[$90K - $110K]}\n" + "{[Yearly Income].[$110K - $130K], [Yearly Income2].[$10K - $30K]}\n" + "{[Yearly Income].[$110K - $130K], [Yearly Income2].[$110K - $130K]}\n" + "{[Yearly Income].[$110K - $130K], [Yearly Income2].[$130K - $150K]}\n" + "{[Yearly Income].[$110K - $130K], [Yearly Income2].[$150K +]}\n" + "{[Yearly Income].[$110K - $130K], [Yearly Income2].[$30K - $50K]}\n" + "{[Yearly Income].[$110K - $130K], [Yearly Income2].[$50K - $70K]}\n" + "{[Yearly Income].[$110K - $130K], [Yearly Income2].[$70K - $90K]}\n" + "{[Yearly Income].[$110K - $130K], [Yearly Income2].[$90K - $110K]}\n" + "{[Yearly Income].[$130K - $150K], [Yearly Income2].[$10K - $30K]}\n" + "{[Yearly Income].[$130K - $150K], [Yearly Income2].[$110K - $130K]}\n" + "{[Yearly Income].[$130K - $150K], [Yearly Income2].[$130K - $150K]}\n" + "{[Yearly Income].[$130K - $150K], [Yearly Income2].[$150K +]}\n" + "{[Yearly Income].[$130K - $150K], [Yearly Income2].[$30K - $50K]}\n" + "{[Yearly Income].[$130K - $150K], [Yearly Income2].[$50K - $70K]}\n" + "{[Yearly Income].[$130K - $150K], [Yearly Income2].[$70K - $90K]}\n" + "{[Yearly Income].[$130K - $150K], [Yearly Income2].[$90K - $110K]}\n" + "{[Yearly Income].[$150K +], [Yearly Income2].[$10K - $30K]}\n" + "{[Yearly Income].[$150K +], [Yearly Income2].[$110K - $130K]}\n" + "{[Yearly Income].[$150K +], [Yearly Income2].[$130K - $150K]}\n" + "{[Yearly Income].[$150K +], [Yearly Income2].[$150K +]}\n" + "{[Yearly Income].[$150K +], [Yearly Income2].[$30K - $50K]}\n" + "{[Yearly Income].[$150K +], [Yearly Income2].[$50K - $70K]}\n" + "{[Yearly Income].[$150K +], [Yearly Income2].[$70K - $90K]}\n" + "{[Yearly Income].[$150K +], [Yearly Income2].[$90K - $110K]}\n" + "{[Yearly Income].[$30K - $50K], [Yearly Income2].[$10K - $30K]}\n" + "{[Yearly Income].[$30K - $50K], [Yearly Income2].[$110K - $130K]}\n" + "{[Yearly Income].[$30K - $50K], [Yearly Income2].[$130K - $150K]}\n" + "{[Yearly Income].[$30K - $50K], [Yearly Income2].[$150K +]}\n" + "{[Yearly Income].[$30K - $50K], [Yearly Income2].[$30K - $50K]}\n" + "{[Yearly Income].[$30K - $50K], [Yearly Income2].[$50K - $70K]}\n" + "{[Yearly Income].[$30K - $50K], [Yearly Income2].[$70K - $90K]}\n" + "{[Yearly Income].[$30K - $50K], [Yearly Income2].[$90K - $110K]}\n" + "{[Yearly Income].[$50K - $70K], [Yearly Income2].[$10K - $30K]}\n" + "{[Yearly Income].[$50K - $70K], [Yearly Income2].[$110K - $130K]}\n" + "{[Yearly Income].[$50K - $70K], [Yearly Income2].[$130K - $150K]}\n" + "{[Yearly Income].[$50K - $70K], [Yearly Income2].[$150K +]}\n" + "{[Yearly Income].[$50K - $70K], [Yearly Income2].[$30K - $50K]}\n" + "{[Yearly Income].[$50K - $70K], [Yearly Income2].[$50K - $70K]}\n" + "{[Yearly Income].[$50K - $70K], [Yearly Income2].[$70K - $90K]}\n" + "{[Yearly Income].[$50K - $70K], [Yearly Income2].[$90K - $110K]}\n" + "{[Yearly Income].[$70K - $90K], [Yearly Income2].[$10K - $30K]}\n" + "{[Yearly Income].[$70K - $90K], [Yearly Income2].[$110K - $130K]}\n" + "{[Yearly Income].[$70K - $90K], [Yearly Income2].[$130K - $150K]}\n" + "{[Yearly Income].[$70K - $90K], [Yearly Income2].[$150K +]}\n" + "{[Yearly Income].[$70K - $90K], [Yearly Income2].[$30K - $50K]}\n" + "{[Yearly Income].[$70K - $90K], [Yearly Income2].[$50K - $70K]}\n" + "{[Yearly Income].[$70K - $90K], [Yearly Income2].[$70K - $90K]}\n" + "{[Yearly Income].[$70K - $90K], [Yearly Income2].[$90K - $110K]}\n" + "{[Yearly Income].[$90K - $110K], [Yearly Income2].[$10K - $30K]}\n" + "{[Yearly Income].[$90K - $110K], [Yearly Income2].[$110K - $130K]}\n" + "{[Yearly Income].[$90K - $110K], [Yearly Income2].[$130K - $150K]}\n" + "{[Yearly Income].[$90K - $110K], [Yearly Income2].[$150K +]}\n" + "{[Yearly Income].[$90K - $110K], [Yearly Income2].[$30K - $50K]}\n" + "{[Yearly Income].[$90K - $110K], [Yearly Income2].[$50K - $70K]}\n" + "{[Yearly Income].[$90K - $110K], [Yearly Income2].[$70K - $90K]}\n" + "{[Yearly Income].[$90K - $110K], [Yearly Income2].[$90K - $110K]}\n" + "Row #0: 12,824\n" + "Row #1: 2,822\n" + "Row #2: 2,933\n" + "Row #3: 918\n" + "Row #4: 18,381\n" + "Row #5: 10,436\n" + "Row #6: 6,777\n" + "Row #7: 2,859\n" + "Row #8: 2,432\n" + "Row #9: 532\n" + "Row #10: 566\n" + "Row #11: 177\n" + "Row #12: 3,877\n" + "Row #13: 2,131\n" + "Row #14: 1,319\n" + "Row #15: 527\n" + "Row #16: 3,331\n" + "Row #17: 643\n" + "Row #18: 703\n" + "Row #19: 187\n" + "Row #20: 4,497\n" + "Row #21: 2,629\n" + "Row #22: 1,681\n" + "Row #23: 721\n" + "Row #24: 1,123\n" + "Row #25: 224\n" + "Row #26: 257\n" + "Row #27: 109\n" + "Row #28: 1,924\n" + "Row #29: 1,026\n" + "Row #30: 675\n" + "Row #31: 291\n" + "Row #32: 19,067\n" + "Row #33: 4,078\n" + "Row #34: 4,235\n" + "Row #35: 1,569\n" + "Row #36: 28,160\n" + "Row #37: 15,368\n" + "Row #38: 10,329\n" + "Row #39: 4,504\n" + "Row #40: 9,708\n" + "Row #41: 2,353\n" + "Row #42: 2,243\n" + "Row #43: 748\n" + "Row #44: 14,469\n" + "Row #45: 7,966\n" + "Row #46: 5,272\n" + "Row #47: 2,208\n" + "Row #48: 7,320\n" + "Row #49: 1,630\n" + "Row #50: 1,602\n" + "Row #51: 541\n" + "Row #52: 10,550\n" + "Row #53: 5,843\n" + "Row #54: 3,997\n" + "Row #55: 1,562\n" + "Row #56: 2,722\n" + "Row #57: 597\n" + "Row #58: 568\n" + "Row #59: 193\n" + "Row #60: 3,800\n" + "Row #61: 2,192\n" + "Row #62: 1,324\n" + "Row #63: 523\n"); } /** * Tests two dimensions using same table (via different join paths). * native non empty cross join sql generation returns empty query. * note that this works when native cross join is disabled */ public void testDimensionsShareTableNativeNonEmptyCrossJoin() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + ""); testContext.assertQueryReturns( "select NonEmptyCrossJoin({[Yearly Income2].[All Yearly Income2s]},{[Customers].[All Customers]}) on rows," + "NON EMPTY {[Measures].[Unit Sales]} on columns" + " from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Yearly Income2].[All Yearly Income2s], [Customers].[All Customers]}\n" + "Row #0: 266,773\n"); } /** * Tests two dimensions using same table with same foreign key * one table uses an alias. */ public void testDimensionsShareTableSameForeignKeys() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + "
\n" + " \n" + " \n" + ""); testContext.assertQueryReturns( "select {[Yearly Income].[$10K - $30K]} on columns," + "{[Yearly Income2].[$150K +]} on rows from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Yearly Income].[$10K - $30K]}\n" + "Axis #2:\n" + "{[Yearly Income2].[$150K +]}\n" + "Row #0: \n"); testContext.assertQueryReturns( "select NON EMPTY {[Measures].[Unit Sales]} ON COLUMNS,\n" + "NON EMPTY Crossjoin({[Yearly Income].[All Yearly Incomes].Children},\n" + " [Yearly Income2].[All Yearly Income2s].Children) ON ROWS\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Yearly Income].[$10K - $30K], [Yearly Income2].[$10K - $30K]}\n" + "{[Yearly Income].[$110K - $130K], [Yearly Income2].[$110K - $130K]}\n" + "{[Yearly Income].[$130K - $150K], [Yearly Income2].[$130K - $150K]}\n" + "{[Yearly Income].[$150K +], [Yearly Income2].[$150K +]}\n" + "{[Yearly Income].[$30K - $50K], [Yearly Income2].[$30K - $50K]}\n" + "{[Yearly Income].[$50K - $70K], [Yearly Income2].[$50K - $70K]}\n" + "{[Yearly Income].[$70K - $90K], [Yearly Income2].[$70K - $90K]}\n" + "{[Yearly Income].[$90K - $110K], [Yearly Income2].[$90K - $110K]}\n" + "Row #0: 57,950\n" + "Row #1: 11,561\n" + "Row #2: 14,392\n" + "Row #3: 5,629\n" + "Row #4: 87,310\n" + "Row #5: 44,967\n" + "Row #6: 33,045\n" + "Row #7: 11,919\n"); } /** * test hierarchy with completely different join path to fact table than * first hierarchy. tables are auto-aliased as necessary to guarantee * unique joins to the fact table. */ public void testSnowflakeHierarchyValidationNotNeeded() { // this test breaks when using aggregates at the moment // due to a known limitation if ((MondrianProperties.instance().ReadAggregates.get() || MondrianProperties.instance().UseAggregates.get()) && !Bug.BugMondrian361Fixed) { return; } final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + "
\n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "", null, null, null, null); testContext.assertQueryReturns( "select {[Store.MyHierarchy].[Mexico]} on rows," + "{[Customers].[USA].[South West]} on columns" + " from " + "AliasedDimensionsTesting", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[South West]}\n" + "Axis #2:\n" + "{[Store].[MyHierarchy].[Mexico]}\n" + "Row #0: 51,298\n"); } /** * test hierarchy with slightly different join path to fact table than * first hierarchy. tables from first and second hierarchy should contain * the same join aliases to the fact table. */ public void testSnowflakeHierarchyValidationNotNeeded2() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + "
" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "", null, null, null, null); testContext.assertQueryReturns( "select {[Store.MyHierarchy].[USA].[South West]} on rows," + "{[Customers].[USA].[South West]} on columns" + " from " + "AliasedDimensionsTesting", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[South West]}\n" + "Axis #2:\n" + "{[Store].[MyHierarchy].[USA].[South West]}\n" + "Row #0: 72,631\n"); } /** * WG: This no longer throws an exception, it is now possible * * Tests two dimensions using same table (via different join paths). * both using a table alias. */ public void testDimensionsShareJoinTable() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + "
" + "\n" + "\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "\n" + "\n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "\n" + "\n" + "", null, null, null, null); testContext.assertQueryReturns( "select {[Store].[USA].[South West]} on rows," + "{[Customers].[USA].[South West]} on columns" + " from " + "AliasedDimensionsTesting", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[South West]}\n" + "Axis #2:\n" + "{[Store].[USA].[South West]}\n" + "Row #0: 72,631\n"); } /** * Tests two dimensions using same table (via different join paths). * both using a table alias. */ public void testDimensionsShareJoinTableOneAlias() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + "
" + "\n" + "\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "\n" + "\n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "\n" + "\n" + "", null, null, null, null); testContext.assertQueryReturns( "select {[Store].[USA].[South West]} on rows," + "{[Customers].[USA].[South West]} on columns" + " from " + "AliasedDimensionsTesting", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[South West]}\n" + "Axis #2:\n" + "{[Store].[USA].[South West]}\n" + "Row #0: 72,631\n"); } /** * Tests two dimensions using same table (via different join paths). * both using a table alias. */ public void testDimensionsShareJoinTableTwoAliases() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + "
" + "\n" + "\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "\n" + "\n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "\n" + "\n" + "", null, null, null, null); testContext.assertQueryReturns( "select {[Store].[USA].[South West]} on rows," + "{[Customers].[USA].[South West]} on columns" + " from " + "AliasedDimensionsTesting", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Customers].[USA].[South West]}\n" + "Axis #2:\n" + "{[Store].[USA].[South West]}\n" + "Row #0: 72,631\n"); } /** * Tests two dimensions using same table (via different join paths). * both using a table alias. */ public void testTwoAliasesDimensionsShareTable() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " " + " " + "
" + " " + " " + " " + " " + " " + " " + "
" + " " + " " + " " + " " + " \n" + " \n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select {[StoreA].[USA]} on rows," + "{[StoreB].[USA]} on columns" + " from " + "AliasedDimensionsTesting", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[StoreB].[USA]}\n" + "Axis #2:\n" + "{[StoreA].[USA]}\n" + "Row #0: 10,425\n"); } /** * Tests two dimensions using same table with same foreign key. * both using a table alias. */ public void testTwoAliasesDimensionsShareTableSameForeignKeys() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " " + " " + "
" + " " + " " + " " + " " + " " + " " + "
" + " " + " " + " " + " " + " \n" + " \n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select {[StoreA].[USA]} on rows," + "{[StoreB].[USA]} on columns" + " from " + "AliasedDimensionsTesting", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[StoreB].[USA]}\n" + "Axis #2:\n" + "{[StoreA].[USA]}\n" + "Row #0: 10,425\n"); } /** * Test Multiple DimensionUsages on same Dimension. * Alias the fact table to avoid issues with aggregation rules * and multiple column names */ public void testMultipleDimensionUsages() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select\n" + " {[Time2].[1997]} on columns,\n" + " {[Time].[1997].[Q3]} on rows\n" + "From [Sales Two Dimensions]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + (MondrianProperties.instance().SsasCompatibleNaming.get() ? "{[Time2].[Time].[1997]}\n" : "{[Time2].[1997]}\n") + "Axis #2:\n" + "{[Time].[1997].[Q3]}\n" + "Row #0: 16,266\n"); } /** * Test Multiple DimensionUsages on same Dimension. * Alias the fact table to avoid issues with aggregation rules * and multiple column names */ public void testMultipleDimensionHierarchyCaptionUsages() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); String query = "select\n" + " {[Time2].[1997]} on columns,\n" + " {[Time].[1997].[Q3]} on rows\n" + "From [Sales Two Dimensions]"; Result result = testContext.executeQuery(query); // Time2.1997 Member Member member1 = result.getAxes()[0].getPositions().iterator().next().iterator() .next(); // NOTE: The caption is modified at the dimension, not the hierarchy assertEquals("TimeTwo", member1.getLevel().getDimension().getCaption()); Member member2 = result.getAxes()[1].getPositions().iterator().next().iterator() .next(); assertEquals("TimeOne", member2.getLevel().getDimension().getCaption()); } /** * This test verifies that the createDimension() API call is working * correctly. */ public void testDimensionCreation() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + "", null, null, null, null); Cube cube = testContext.getConnection().getSchema().lookupCube( "Sales Create Dimension", true); testContext.assertQueryReturns( "select\n" + "NON EMPTY {[Store].[All Stores].children} on columns \n" + "From [Sales Create Dimension]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA]}\n" + "Row #0: 266,773\n"); String dimension = ""; testContext.getConnection().getSchema().createDimension( cube, dimension); testContext.assertQueryReturns( "select\n" + "NON EMPTY {[Store].[All Stores].children} on columns, \n" + "{[Time].[1997].[Q1]} on rows \n" + "From [Sales Create Dimension]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q1]}\n" + "Row #0: 66,291\n"); } /** * Test DimensionUsage level attribute */ public void testDimensionUsageLevel() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" // + alias=\"sales_fact_1997_multi\"/>\n" + " \n" + " \n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select\n" + " {[Store].[Store State].members} on columns \n" + "From [Customer Usage Level]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[Canada].[BC]}\n" + "{[Store].[Mexico].[DF]}\n" + "{[Store].[Mexico].[Guerrero]}\n" + "{[Store].[Mexico].[Jalisco]}\n" + "{[Store].[Mexico].[Veracruz]}\n" + "{[Store].[Mexico].[Yucatan]}\n" + "{[Store].[Mexico].[Zacatecas]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 7,700\n" + "Row #0: 1,492\n" + "Row #0: 228\n" + "Row #0: 206\n" + "Row #0: 195\n" + "Row #0: 229\n" + "Row #0: 1,209\n" + "Row #0: 46,965\n" + "Row #0: 4,686\n" + "Row #0: 32,767\n"); // BC.children should return an empty list, considering that we've // joined Store at the State level. if (false) { testContext.assertQueryReturns( "select\n" + " {[Store].[All Stores].[Canada].[BC].children} on columns \n" + "From [Customer Usage Level]", "Axis #0:\n" + "{}\n" + "Axis #1:\n"); } } /** * Test to verify naming of all member with * dimension usage name is different then source name */ public void testAllMemberMultipleDimensionUsages() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); // If SsasCompatibleNaming (the new behavior), the usages of the // [Store] dimension create dimensions called [Store] // and [Store2], each with a hierarchy called [Store]. // Therefore Store2's all member is [Store2].[Store].[All Stores], // or [Store2].[All Stores] for short. // // Under the old behavior, the member is called [Store2].[All Store2s]. final String store2AllMember = MondrianProperties.instance().SsasCompatibleNaming.get() ? "[Store2].[All Stores]" : "[Store2].[All Store2s]"; testContext.assertQueryReturns( "select\n" + " {[Store].[Store].[All Stores]} on columns,\n" + " {" + store2AllMember + "} on rows\n" + "From [Sales Two Sales Dimensions]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[All Stores]}\n" + "Axis #2:\n" + "{[Store2].[Store].[All Stores]}\n" + "Row #0: 266,773\n"); final Result result = testContext.executeQuery( "select ([Store].[All Stores], " + store2AllMember + ") on 0\n" + "from [Sales Two Sales Dimensions]"); final Axis axis = result.getAxes()[0]; final Position position = axis.getPositions().get(0); assertEquals( "First Store", position.get(0).getDimension().getCaption()); assertEquals( "Second Store", position.get(1).getDimension().getCaption()); } /** * This test displays an informative error message if someone uses * an unaliased name instead of an aliased name */ public void testNonAliasedDimensionUsage() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); final String query = "select\n" + " {[Time].[1997]} on columns \n" + "From [Sales Two Dimensions]"; if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { testContext.assertQueryThrows( query, "In cube \"Sales Two Dimensions\" use of unaliased Dimension name \"[Time]\" rather than the alias name \"Time2\""); } else { // In new behavior, resolves to the hierarchy name [Time] even if // not qualified by dimension name [Time2]. testContext.assertQueryReturns( query, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time2].[Time].[1997]}\n" + "Row #0: 266,773\n"); } } /** * Tests a cube whose fact table is a <View> element as well as a * degenerate dimension. */ public void testViewDegenerateDims() { final TestContext testContext = TestContext.instance().create( null, // Warehouse cube where the default member in the Warehouse // dimension is USA. "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select\n" + " NON EMPTY {[Time].[1997], [Time].[1997].[Q3]} on columns,\n" + " NON EMPTY {[Store].[USA].Children} on rows\n" + "From [Warehouse (based on view)]\n" + "where [Warehouse].[2]", "Axis #0:\n" + "{[Warehouse].[2]}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "Axis #2:\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 917.554\n"); } /** * Tests a cube whose fact table is a <View> element. */ public void testViewFactTable() { final TestContext testContext = TestContext.instance().create( null, // Warehouse cube where the default member in the Warehouse // dimension is USA. "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select\n" + " {[Time].[1997], [Time].[1997].[Q3]} on columns,\n" + " {[Store].[USA].Children} on rows\n" + "From [Warehouse (based on view)]\n" + "where [Warehouse].[USA]", "Axis #0:\n" + "{[Warehouse].[USA]}\n" + "Axis #1:\n" + "{[Time].[1997]}\n" + "{[Time].[1997].[Q3]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 25,789.086\n" + "Row #0: 8,624.791\n" + "Row #1: 17,606.904\n" + "Row #1: 3,812.023\n" + "Row #2: 45,647.262\n" + "Row #2: 12,664.162\n"); } /** * Tests a cube whose fact table is a <View> element, and which * has dimensions based on the fact table. */ public void testViewFactTable2() { final TestContext testContext = TestContext.instance().create( null, // Similar to "Store" cube in FoodMart.xml. "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + " \n" + " \n" + "\n" + "", null, null, null, null); testContext.assertQueryReturns( "select {[Store Type].Children} on columns from [Store2]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store Type].[Deluxe Supermarket]}\n" + "{[Store Type].[Gourmet Supermarket]}\n" + "{[Store Type].[HeadQuarters]}\n" + "{[Store Type].[Mid-Size Grocery]}\n" + "{[Store Type].[Small Grocery]}\n" + "{[Store Type].[Supermarket]}\n" + "Row #0: 146,045\n" + "Row #0: 47,447\n" + "Row #0: \n" + "Row #0: 109,343\n" + "Row #0: 75,281\n" + "Row #0: 193,480\n"); } /** * Tests that the deprecated "distinct count" value for the * Measure@aggregator attribute still works. The preferred value these days * is "distinct-count". */ public void testDeprecatedDistinctCountAggregator() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", null, " \n" + " \n" + " "); testContext.assertQueryReturns( "select {[Measures].[Unit Sales]," + " [Measures].[Customer Count], " + " [Measures].[Customer Count2], " + " [Measures].[Half Customer Count]} on 0,\n" + " {[Store].[USA].Children} ON 1\n" + "FROM [Sales]\n" + "WHERE ([Gender].[M])", "Axis #0:\n" + "{[Gender].[M]}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "{[Measures].[Customer Count]}\n" + "{[Measures].[Customer Count2]}\n" + "{[Measures].[Half Customer Count]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 37,989\n" + "Row #0: 1,389\n" + "Row #0: 1,389\n" + "Row #0: 695\n" + "Row #1: 34,623\n" + "Row #1: 536\n" + "Row #1: 536\n" + "Row #1: 268\n" + "Row #2: 62,603\n" + "Row #2: 901\n" + "Row #2: 901\n" + "Row #2: 451\n"); } /** * Tests that an invalid aggregator causes an error. */ public void testInvalidAggregator() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", null, " \n" + " \n" + " "); testContext.assertQueryThrows( "select from [Sales]", "Unknown aggregator 'invalidAggregator'; valid aggregators are: 'sum', 'count', 'min', 'max', 'avg', 'distinct-count'"); } /** * Testcase for * * Bug MONDRIAN-291, "'unknown usage' messages". */ public void testUnknownUsages() { if (!MondrianProperties.instance().ReadAggregates.get()) { return; } final Logger logger = Logger.getLogger(AggTableManager.class); propSaver.setAtLeast(logger, Level.WARN); final StringWriter sw = new StringWriter(); final Appender appender = new WriterAppender(new SimpleLayout(), sw); final LevelRangeFilter filter = new LevelRangeFilter(); filter.setLevelMin(Level.WARN); appender.addFilter(filter); logger.addAppender(appender); try { final TestContext testContext = TestContext.instance().withSchema( "\n" + "\n" + "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " " + " " + " \n" + " \n" + "\n" + ""); testContext.assertQueryReturns( "select from [Sales Degen]", "Axis #0:\n" + "{}\n" + "225,627.23"); } finally { logger.removeAppender(appender); } // Note that 'product_id' is NOT one of the columns with unknown usage. // It is used as a level in the degenerate dimension [Time Degenerate]. TestContext.assertEqualsVerbose( "WARN - Recognizer.checkUnusedColumns: Candidate aggregate table 'agg_c_10_sales_fact_1997' for fact table 'sales_fact_1997' has a column 'customer_count' with unknown usage.\n" + "WARN - Recognizer.checkUnusedColumns: Candidate aggregate table 'agg_c_10_sales_fact_1997' for fact table 'sales_fact_1997' has a column 'month_of_year' with unknown usage.\n" + "WARN - Recognizer.checkUnusedColumns: Candidate aggregate table 'agg_c_10_sales_fact_1997' for fact table 'sales_fact_1997' has a column 'quarter' with unknown usage.\n" + "WARN - Recognizer.checkUnusedColumns: Candidate aggregate table 'agg_c_10_sales_fact_1997' for fact table 'sales_fact_1997' has a column 'the_year' with unknown usage.\n" + "WARN - Recognizer.checkUnusedColumns: Candidate aggregate table 'agg_c_10_sales_fact_1997' for fact table 'sales_fact_1997' has a column 'unit_sales' with unknown usage.\n", sw.toString()); } public void testUnknownUsages1() { if (!MondrianProperties.instance().ReadAggregates.get()) { return; } final Logger logger = Logger.getLogger(AggTableManager.class); propSaver.setAtLeast(logger, Level.WARN); final StringWriter sw = new StringWriter(); final Appender appender = new WriterAppender(new SimpleLayout(), sw); final LevelRangeFilter filter = new LevelRangeFilter(); filter.setLevelMin(Level.WARN); appender.addFilter(filter); logger.addAppender(appender); try { final TestContext testContext = TestContext.instance().withSchema( "\n" + "\n" + "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " " + " " + " \n" + " \n" + " \n" + " " + " " + " \n" + " \n" + " \n" + "\n" + ""); testContext.assertQueryReturns( "select from [Denormalized Sales]", "Axis #0:\n" + "{}\n" + "225,627.23"); } finally { logger.removeAppender(appender); } TestContext.assertEqualsVerbose( "WARN - Recognizer.checkUnusedColumns: Candidate aggregate table 'agg_l_03_sales_fact_1997' for fact table 'sales_fact_1997' has a column 'time_id' with unknown usage.\n", sw.toString()); } public void testPropertyFormatter() { final TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "
\n" + " \n" + " " + " " + " " + " \n" + " \n"); try { testContext.assertSimpleQuery(); fail("expected exception"); } catch (RuntimeException e) { TestContext.checkThrowable( e, "Failed to load formatter class 'mondrian.test.SchemaTest$DummyPropertyFormatter' for property 'Store Type'."); } } /** * Bug MONDRIAN-233, * "ClassCastException in AggQuerySpec" occurs when two cubes * have the same fact table, distinct aggregate tables, and measures with * the same name. * *

This test case attempts to reproduce this issue by creating that * environment, but it found a different issue: a measure came back with a * cell value which was from a different measure. The root cause is * probably the same: when measures are registered in a star, they should * be qualified by cube name. */ public void testBugMondrian233() { final TestContext testContext = TestContext.instance().create( null, "" + "

\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); // With bug, and with aggregates enabled, query against Sales returns // 565,238, which is actually the total for [Store Sales]. I think the // aggregate tables are getting crossed. final String expected = "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 266,773\n"; testContext.assertQueryReturns( "select {[Measures]} on 0 from [Sales2]", expected); testContext.assertQueryReturns( "select {[Measures]} on 0 from [Sales]", expected); } /** * Test case for bug * MONDRIAN-303, "Property column shifting when use captionColumn". */ public void testBugMondrian303() { // In order to reproduce the problem a dimension specifying // captionColumn and Properties were required. TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + " \n" + " \n" + " " + " " + " " + " \n" + " \n"); // In the query below Mondrian (prior to the fix) would // return the store name instead of the store type. testContext.assertQueryReturns( "WITH\n" + " MEMBER [Measures].[StoreType] AS \n" + " '[Store2].CurrentMember.Properties(\"Store Type\")'\n" + "SELECT\n" + " NonEmptyCrossJoin({[Store2].[All Stores].children}, {[Product].[All Products]}) ON ROWS,\n" + " { [Measures].[Store Sales], [Measures].[StoreType]} ON COLUMNS\n" + "FROM Sales", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[StoreType]}\n" + "Axis #2:\n" + "{[Store2].[2], [Product].[All Products]}\n" + "{[Store2].[3], [Product].[All Products]}\n" + "{[Store2].[6], [Product].[All Products]}\n" + "{[Store2].[7], [Product].[All Products]}\n" + "{[Store2].[11], [Product].[All Products]}\n" + "{[Store2].[13], [Product].[All Products]}\n" + "{[Store2].[14], [Product].[All Products]}\n" + "{[Store2].[15], [Product].[All Products]}\n" + "{[Store2].[16], [Product].[All Products]}\n" + "{[Store2].[17], [Product].[All Products]}\n" + "{[Store2].[22], [Product].[All Products]}\n" + "{[Store2].[23], [Product].[All Products]}\n" + "{[Store2].[24], [Product].[All Products]}\n" + "Row #0: 4,739.23\n" + "Row #0: Small Grocery\n" + "Row #1: 52,896.30\n" + "Row #1: Supermarket\n" + "Row #2: 45,750.24\n" + "Row #2: Gourmet Supermarket\n" + "Row #3: 54,545.28\n" + "Row #3: Supermarket\n" + "Row #4: 55,058.79\n" + "Row #4: Supermarket\n" + "Row #5: 87,218.28\n" + "Row #5: Deluxe Supermarket\n" + "Row #6: 4,441.18\n" + "Row #6: Small Grocery\n" + "Row #7: 52,644.07\n" + "Row #7: Supermarket\n" + "Row #8: 49,634.46\n" + "Row #8: Supermarket\n" + "Row #9: 74,843.96\n" + "Row #9: Deluxe Supermarket\n" + "Row #10: 4,705.97\n" + "Row #10: Small Grocery\n" + "Row #11: 24,329.23\n" + "Row #11: Mid-Size Grocery\n" + "Row #12: 54,431.14\n" + "Row #12: Supermarket\n"); } public void testCubeWithOneDimensionOneMeasure() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select {[Promotion Media]} on columns from [OneDim]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Promotion Media].[All Media]}\n" + "Row #0: 266,773\n"); } public void testCubeWithOneDimensionUsageOneMeasure() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select {[Product].Children} on columns from [OneDimUsage]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 24,597\n" + "Row #0: 191,940\n" + "Row #0: 50,236\n"); } public void testCubeHasFact() { final TestContext testContext = TestContext.instance().create( null, "\n", null, null, null, null); Throwable throwable = null; try { testContext.assertSimpleQuery(); } catch (Throwable e) { throwable = e; } TestContext.checkThrowable( throwable, "Must specify fact table of cube 'Cube with caption'"); } public void testCubeCaption() throws SQLException { final TestContext testContext = TestContext.instance().create( null, "" + "
" + "\n", "\n" + " \n" + "", null, null, null); final NamedList cubes = testContext.getOlap4jConnection().getOlapSchema().getCubes(); final org.olap4j.metadata.Cube cube = cubes.get("Cube with caption"); assertEquals("Cube with name", cube.getCaption()); final org.olap4j.metadata.Cube cube2 = cubes.get("Warehouse and Sales with caption"); assertEquals("Warehouse and Sales with name", cube2.getCaption()); } public void testCubeWithNoDimensions() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select {[Measures].[Unit Sales]} on columns from [NoDim]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 266,773\n"); } public void testCubeWithNoMeasuresFails() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + "", null, null, null, null); // Does not fail with // "Hierarchy '[Measures]' is invalid (has no members)" // because of the implicit [Fact Count] measure. testContext.assertSimpleQuery(); } public void testCubeWithOneCalcMeasure() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); // Because there are no explicit stored measures, the default measure is // the implicit stored measure, [Fact Count]. Stored measures, even // non-visible ones, come before calculated measures. testContext.assertQueryReturns( "select {[Measures]} on columns from [OneCalcMeasure]\n" + "where [Promotion Media].[TV]", "Axis #0:\n" + "{[Promotion Media].[TV]}\n" + "Axis #1:\n" + "{[Measures].[Fact Count]}\n" + "Row #0: 1,171\n"); } /** * Test case for feature * MONDRIAN-960, * "Ability to define non-measure calculated members in a cube under a * specifc parent". */ public void testCalcMemberInCube() { final TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", null, null, "\n" + " \n" + " [Store].[USA].[CA].[San Francisco]\n" + " + [Store].[USA].[CA].[Los Angeles]\n" + " \n" + "", null); // Because there are no explicit stored measures, the default measure is // the implicit stored measure, [Fact Count]. Stored measures, even // non-visible ones, come before calculated measures. testContext.assertQueryReturns( "select {[Store].[USA].[CA].[SF and LA]} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA].[SF and LA]}\n" + "Row #0: 27,780\n"); // Now access the same member using a path that is not its unique name. // Only works with new name resolver (if ssas = true). if (MondrianProperties.instance().SsasCompatibleNaming.get()) { testContext.assertQueryReturns( "select {[Store].[All Stores].[USA].[CA].[SF and LA]} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA].[SF and LA]}\n" + "Row #0: 27,780\n"); } // Test where hierarchy & dimension both specified. should fail try { final TestContext testContextFail1 = TestContext.instance().createSubstitutingCube( "Sales", null, null, "\n" + " \n" + " [Store].[USA].[CA].[San Francisco]\n" + " + [Store].[USA].[CA].[Los Angeles]\n" + " \n" + "", null); testContextFail1.assertQueryReturns( "select {[Store].[All Stores].[USA].[CA].[SF and LA]} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA].[SF and LA]}\n" + "Row #0: 27,780\n"); fail(); } catch (MondrianException e) { assertTrue( e.getMessage().contains( "Cannot specify both a dimension and hierarchy" + " for calculated member 'SF and LA' in cube 'Sales'")); } // test where hierarchy is not uname of valid hierarchy. should fail try { final TestContext testContextFail1 = TestContext.instance().createSubstitutingCube( "Sales", null, null, "\n" + " \n" + " [Store].[USA].[CA].[San Francisco]\n" + " + [Store].[USA].[CA].[Los Angeles]\n" + " \n" + "", null); testContextFail1.assertQueryReturns( "select {[Store].[All Stores].[USA].[CA].[SF and LA]} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA].[SF and LA]}\n" + "Row #0: 27,780\n"); fail(); } catch (MondrianException e) { assertTrue( e.getMessage().contains( "Unknown dimension '[Bacon]' for calculated member" + " 'SF and LA' in cube 'Sales'")); } // test where formula is invalid. should fail try { final TestContext testContextFail1 = TestContext.instance().createSubstitutingCube( "Sales", null, null, "\n" + " \n" + " Baconating!\n" + " \n" + "", null); testContextFail1.assertQueryReturns( "select {[Store].[All Stores].[USA].[CA].[SF and LA]} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA].[SF and LA]}\n" + "Row #0: 27,780\n"); fail(); } catch (MondrianException e) { assertTrue( e.getMessage().contains( "Named set in cube 'Sales' has bad formula")); } // Test where parent is invalid. should fail try { final TestContext testContextFail1 = TestContext.instance().createSubstitutingCube( "Sales", null, null, "\n" + " \n" + " [Store].[USA].[CA].[San Francisco]\n" + " + [Store].[USA].[CA].[Los Angeles]\n" + " \n" + "", null); testContextFail1.assertQueryReturns( "select {[Store].[All Stores].[USA].[CA].[SF and LA]} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA].[SF and LA]}\n" + "Row #0: 27,780\n"); fail(); } catch (MondrianException e) { assertTrue( e.getMessage().contains( "Cannot find a parent with name '[Store].[USA].[CA]" + ".[Baconville]' for calculated member 'SF and LA'" + " in cube 'Sales'")); } // test where parent is not in same hierarchy as hierarchy. should fail try { final TestContext testContextFail1 = TestContext.instance().createSubstitutingCube( "Sales", null, null, "\n" + " \n" + " [Store].[USA].[CA].[San Francisco]\n" + " + [Store].[USA].[CA].[Los Angeles]\n" + " \n" + "", null); testContextFail1.assertQueryReturns( "select {[Store].[All Stores].[USA].[CA].[SF and LA]} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA].[SF and LA]}\n" + "Row #0: 27,780\n"); fail(); } catch (MondrianException e) { assertTrue( e.getMessage().contains( "The calculated member 'SF and LA' in cube 'Sales'" + " is defined for hierarchy '[Store Type]' but its" + " parent member is not part of that hierarchy")); } // test where calc member has no formula (formula attribute or // embedded element); should fail try { final TestContext testContextFail1 = TestContext.instance().createSubstitutingCube( "Sales", null, null, "\n" + " \n" + " \n" + "", null); testContextFail1.assertQueryReturns( "select {[Store].[All Stores].[USA].[CA].[SF and LA]} on columns from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA].[CA].[SF and LA]}\n" + "Row #0: 27,780\n"); fail(); } catch (MondrianException e) { assertTrue( e.getMessage().contains( "Named set in cube 'Sales' has bad formula")); } } /** * this test triggers an exception out of the aggregate table manager */ public void testAggTableSupportOfSharedDims() { if (Bug.BugMondrian361Fixed) { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select\n" + " {[Time2].[1997]} on columns,\n" + " {[Time].[1997].[Q3]} on rows\n" + "From [Sales Two Dimensions]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time2].[1997]}\n" + "Axis #2:\n" + "{[Time].[1997].[Q3]}\n" + "Row #0: 16,266\n"); MondrianProperties props = MondrianProperties.instance(); // turn off caching propSaver.set(props.DisableCaching, true); // re-read aggregates propSaver.set(props.UseAggregates, true); propSaver.set(props.ReadAggregates, false); propSaver.set(props.ReadAggregates, true); // force reloading of aggregates, which currently throws an // exception } } /** * Verifies that RolapHierarchy.tableExists() supports views. */ public void testLevelTableAttributeAsView() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "" + " \n" + "", null, null, null, null); if (!testContext.getDialect().allowsFromQuery()) { return; } Result result = testContext.executeQuery( "select {[Gender2].members} on columns from [GenderCube]"); TestContext.assertEqualsVerbose( "[Gender2].[All Gender]\n" + "[Gender2].[F]\n" + "[Gender2].[M]", TestContext.toString( result.getAxes()[0].getPositions())); } public void testInvalidSchemaAccess() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + "") .withRole("Role1"); testContext.assertQueryThrows( "select from [Sales]", "In Schema: In Role: In SchemaGrant: " + "Value 'invalid' of attribute 'access' has illegal value 'invalid'. " + "Legal values: {all, custom, none, all_dimensions}"); } public void testAllMemberNoStringReplace() { final TestContext testContext = TestContext.instance().create( null, "\n" + "
\n" + "" + "" + "
" + " " + " " + " " + "" + "" + " \n" + " \n" + " \n" + "", null, null, null, null); testContext.assertQueryReturns( "select [TIME.CALENDAR].[All TIME(CALENDAR)] on columns\n" + "from [Sales Special Time]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[TIME].[CALENDAR].[All TIME(CALENDAR)]}\n" + "Row #0: 266,773\n"); } public void testUnionRole() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + "\n" + "\n" + " \n" + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + "\n").withRole("Role1Plus2Plus1"); testContext.assertQueryReturns( "select from [Sales]", "Axis #0:\n" + "{}\n" + "266,773"); } public void testUnionRoleContainsGrants() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n").withRole("Role1Plus2"); testContext.assertQueryThrows( "select from [Sales]", "Union role must not contain grants"); } public void testUnionRoleIllegalForwardRef() { final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + "").withRole("Role1Plus2"); testContext.assertQueryThrows( "select from [Sales]", "Unknown role 'Role2'"); } public void testVirtualCubeNamedSetSupportInSchema() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Warehouse and Sales", null, null, null, ""); testContext.assertQueryReturns( "WITH " + "SET [Non CA State Stores] AS 'EXCEPT({[Store].[Store Country].[USA].children}," + "{[Store].[Store Country].[USA].[CA]})'\n" + "MEMBER " + "[Store].[Total Non CA State] AS \n" + "'SUM({[Non CA State Stores]})'\n" + "SELECT {[Store].[Store Country].[USA],[Store].[Total Non CA State]} ON 0," + "{[Measures].[Unit Sales]} ON 1 FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA]}\n" + "{[Store].[Total Non CA State]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 266,773\n" + "Row #0: 192,025\n"); testContext.assertQueryReturns( "WITH " + "MEMBER " + "[Store].[Total Non CA State] AS \n" + "'SUM({[Non CA State Stores]})'\n" + "SELECT {[Store].[Store Country].[USA],[Store].[Total Non CA State]} ON 0," + "{[Measures].[Unit Sales]} ON 1 FROM [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA]}\n" + "{[Store].[Total Non CA State]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 266,773\n" + "Row #0: 192,025\n"); } public void testVirtualCubeNamedSetSupportInSchemaError() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Warehouse and Sales", null, null, null, ""); try { testContext.assertQueryReturns( "WITH " + "SET [Non CA State Stores] AS 'EXCEPT({[Store].[Store Country].[USA].children}," + "{[Store].[Store Country].[USA].[CA]})'\n" + "MEMBER " + "[Store].[Total Non CA State] AS \n" + "'SUM({[Non CA State Stores]})'\n" + "SELECT {[Store].[Store Country].[USA],[Store].[Total Non CA State]} ON 0," + "{[Measures].[Unit Sales]} ON 1 FROM [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Store].[USA]}\n" + "{[Store].[Total Non CA State]}\n" + "Axis #2:\n" + "{[Measures].[Unit Sales]}\n" + "Row #0: 266,773\n" + "Row #0: 192,025\n"); fail(); } catch (MondrianException e) { assertTrue(e.getMessage().indexOf("bad formula") >= 0); } } public void _testValidatorFindsNumericLevel() { // In the real foodmart, the level has type="Numeric" final TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "
\n" + " \n" + " \n" + " "); final List exceptionList = testContext.getSchemaWarnings(); assertContains(exceptionList, "todo xxxxx"); } public void testInvalidRoleError() { String schema = TestContext.getRawFoodMartSchema(); schema = schema.replaceFirst( " exceptionList = testContext.getSchemaWarnings(); assertContains(exceptionList, "Role 'Unknown' not found"); } /** * Test case for bug * MONDRIAN-413, "RolapMember causes ClassCastException in compare()", * caused by binary column value. */ public void testBinaryLevelKey() { switch (TestContext.instance().getDialect().getDatabaseProduct()) { case DERBY: case MYSQL: break; default: // Not all databases support binary literals (e.g. X'AB01'). Only // Derby returns them as byte[] values from its JDBC driver and // therefore experiences bug MONDRIAN-413. return; } TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 2\n" + " X'4546'\n" + " Ben\n" + " \n" + " \n" + " 3\n" + " X'424344'\n" + " Bill\n" + " \n" + " \n" + " 4\n" + " X'424344'\n" + " Bill\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n"); testContext.assertQueryReturns( "select {[Binary].members} on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Binary].[Ben]}\n" + "{[Binary].[Ben].[2]}\n" + "{[Binary].[Bill]}\n" + "{[Binary].[Bill].[3]}\n" + "{[Binary].[Bill].[4]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n"); testContext.assertQueryReturns( "select hierarchize({[Binary].members}) on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Binary].[Ben]}\n" + "{[Binary].[Ben].[2]}\n" + "{[Binary].[Bill]}\n" + "{[Binary].[Bill].[3]}\n" + "{[Binary].[Bill].[4]}\n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n" + "Row #0: \n"); } /** * Test case for the Level@internalType attribute. * *

See bug * MONDRIAN-896, "Oracle integer columns overflow if value >>2^31". */ public void testLevelInternalType() { // One of the keys is larger than Integer.MAX_VALUE (2 billion), so // will only work if we use long values. TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 0\n" + " 1234\n" + " Ben\n" + " \n" + " \n" + " 519\n" + " 1234567890123\n" + " Bill\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n"); testContext.assertQueryReturns( "select {[Big numbers].members} on 0 from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Big numbers].[1234]}\n" + "{[Big numbers].[1234].[0]}\n" + "{[Big numbers].[1234567890123]}\n" + "{[Big numbers].[1234567890123].[519]}\n" + "Row #0: 195,448\n" + "Row #0: 195,448\n" + "Row #0: 739\n" + "Row #0: 739\n"); } /** * Negative test for Level@internalType attribute. */ public void testLevelInternalTypeErr() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 0\n" + " 1234\n" + " Ben\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n"); testContext.assertQueryThrows( "select {[Big numbers].members} on 0 from [Sales]", "In Schema: In Cube: In Dimension: In Hierarchy: In Level: Value 'char' of attribute 'internalType' has illegal value 'char'. Legal values: {int, long, Object, String}"); } public void _testAttributeHierarchy() { // from email from peter tran dated 2008/9/8 // TODO: schema syntax to create attribute hierarchy assertQueryReturns( "WITH \n" + " MEMBER\n" + " Measures.SalesPerWorkingDay AS \n" + " IIF(\n" + " Count(\n" + " Filter(\n" + " Descendants(\n" + " [Date].[Calendar].CurrentMember\n" + " ,[Date].[Calendar].[Date]\n" + " ,SELF)\n" + " , [Date].[Day of Week].CurrentMember.Name <> \"1\"\n" + " )\n" + " ) = 0\n" + " ,NULL\n" + " ,[Measures].[Internet Sales Amount]\n" + " /\n" + " Count(\n" + " Filter(\n" + " Descendants(\n" + " [Date].[Calendar].CurrentMember\n" + " ,[Date].[Calendar].[Date]\n" + " ,SELF)\n" + " , [Date].[Day of Week].CurrentMember.Name <> \"1\"\n" + " )\n" + " )\n" + " )\n" + " '\n" + "SELECT [Measures].[SalesPerWorkingDay] ON 0\n" + ", [Date].[Calendar].[Month].MEMBERS ON 1\n" + "FROM [Adventure Works]", "x"); } /** * Testcase for a problem which involved a slowly changing dimension. * Not actually a slowly-changing dimension - we don't have such a thing in * the foodmart schema - but the same structure. The dimension is a two * table snowflake, and the table nearer to the fact table is not used by * any level. */ public void testScdJoin() { final TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + " \n" + "

\n" + "
\n" + " \n" + " \n" + " \n" + " \n", null, null, null); testContext.assertQueryReturns( "select non empty {[Measures].[Unit Sales]} on 0,\n" + " non empty Filter({[Product truncated].Members}, [Measures].[Unit Sales] > 10000) on 1\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product truncated].[All Product truncateds]}\n" + "{[Product truncated].[Fresh Vegetables]}\n" + "{[Product truncated].[Fresh Fruit]}\n" + "Row #0: 266,773\n" + "Row #1: 20,739\n" + "Row #2: 11,767\n"); } // TODO: enable this test as part of PhysicalSchema work // TODO: also add a test that Table.alias, Join.leftAlias and // Join.rightAlias cannot be the empty string. public void _testNonUniqueAlias() { final TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n", null, null, null); Throwable throwable = null; try { testContext.assertSimpleQuery(); } catch (Throwable e) { throwable = e; } // neither a source column or source expression specified TestContext.checkThrowable( throwable, "Alias not unique"); } /** * Test case for bug * MONDRIAN-482, "ClassCastException when obtaining RolapCubeLevel". */ public void testBugMondrian482() { // until bug MONDRIAN-495, "Table filter concept does not support // dialects." is fixed, this test case only works on MySQL if (!Bug.BugMondrian495Fixed && TestContext.instance().getDialect().getDatabaseProduct() != Dialect.DatabaseProduct.MYSQL) { return; } // skip this test if using aggregates, the agg tables do not // enforce the SQL element in the fact table if (MondrianProperties.instance().UseAggregates.booleanValue()) { return; } // In order to reproduce the problem it was necessary to only have one // non empty member under USA. In the cube definition below we create a // cube with only CA data to achieve this. String salesCube1 = "\n" + "
\n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + "\n"; final TestContext testContext = TestContext.instance().create( null, salesCube1, null, null, null, null); // First query all children of the USA. This should only return CA since // all the other states were filtered out. CA will be put in the member // cache String query1 = "WITH SET [#DataSet#] as " + "'NonEmptyCrossjoin({[Product].[All Products]}, {[Store].[All Stores].[USA].Children})' " + "SELECT {[Measures].[Unit Sales]} on columns, " + "NON EMPTY Hierarchize({[#DataSet#]}) on rows FROM [Sales2]"; testContext.assertQueryReturns( query1, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[All Products], [Store].[USA].[CA]}\n" + "Row #0: 74,748\n"); // Now query the children of CA using the descendants function // This is where the ClassCastException occurs String query2 = "WITH SET [#DataSet#] as " + "'{Descendants([Store].[All Stores], 3)}' " + "SELECT {[Measures].[Unit Sales]} on columns, " + "NON EMPTY Hierarchize({[#DataSet#]}) on rows FROM [Sales2]"; testContext.assertQueryReturns( query2, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "Row #0: 21,333\n" + "Row #1: 25,663\n" + "Row #2: 25,635\n" + "Row #3: 2,117\n"); } /** * Test case for * Bug MONDRIAN-355, * "adding hours/mins as levelType for level of type Dimension". */ public void testBugMondrian355() { checkBugMondrian355("TimeHalfYears"); // make sure that the deprecated name still works checkBugMondrian355("TimeHalfYear"); } public void checkBugMondrian355(String timeHalfYear) { final String xml = "\n" + "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + ""; TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", xml); testContext.assertQueryReturns( "select Head([Time2].[Quarter hours].Members, 3) on columns\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Time2].[1997].[Q1].[1].[367]}\n" + "{[Time2].[1997].[Q1].[1].[368]}\n" + "{[Time2].[1997].[Q1].[1].[369]}\n" + "Row #0: 348\n" + "Row #0: 635\n" + "Row #0: 589\n"); // Check that can apply ParallelPeriod to a TimeUndefined level. testContext.assertAxisReturns( "PeriodsToDate([Time2].[Quarter hours], [Time2].[1997].[Q1].[1].[368])", "[Time2].[1997].[Q1].[1].[368]"); testContext.assertAxisReturns( "PeriodsToDate([Time2].[Half year], [Time2].[1997].[Q1].[1].[368])", "[Time2].[1997].[Q1].[1].[367]\n" + "[Time2].[1997].[Q1].[1].[368]"); // Check that get an error if give invalid level type try { TestContext.instance() .createSubstitutingCube( "Sales", Util.replace(xml, "TimeUndefined", "TimeUnspecified")) .assertSimpleQuery(); fail("expected error"); } catch (Throwable e) { TestContext.checkThrowable( e, "Value 'TimeUnspecified' of attribute 'levelType' has illegal value 'TimeUnspecified'. Legal values: {Regular, TimeYears, "); } } /** * Test for descriptions, captions and annotations of various schema * elements. */ public void testCaptionDescriptionAndAnnotation() { final String schemaName = "Description schema"; final String salesCubeName = "DescSales"; final String virtualCubeName = "DescWarehouseAndSales"; final String warehouseCubeName = "Warehouse"; final TestContext testContext = TestContext.instance().withSchema( "\n" + " \n" + " Schema\n" + " Xyz\n" + " \n" + " \n" + " Time shared\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " Cube\n" + "
\n" + " \n" + " Dimension\n" + " \n" + " Hierarchy\n" + " \n" + "
\n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " Level\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " Time usage\n" + " \n" + " \n" + "\n" + " Measure\n" + "\n" + "\n" + " Calc member\n" + " [Measures].[Unit Sales] + 1\n" + " \n" + " \n" + " \n" + " Named set\n" + " TopCount([Time1].MEMBERS, 5, [Measures].[Foo])\n" + " \n" + "\n" + "\n" + "
\n" + "\n" + " \n" + " \n" + "\n" + " \n" + "\n" + "\n" + " Virtual cube\n" + " \n" + " \n" + " \n" + " Virtual cube measure\n" + " \n" + " \n" + " \n" + " 1 / [Measures].[Units Shipped]\n" + " \n" + "" + ""); final Result result = testContext.executeQuery("select from [" + salesCubeName + "]"); final Cube cube = result.getQuery().getCube(); assertEquals("Cube description", cube.getDescription()); checkAnnotations(cube.getAnnotationMap(), "a", "Cube"); final Schema schema = cube.getSchema(); checkAnnotations(schema.getAnnotationMap(), "a", "Schema", "b", "Xyz"); final Dimension dimension = cube.getDimensions()[1]; assertEquals("Dimension description", dimension.getDescription()); assertEquals("Dimension caption", dimension.getCaption()); checkAnnotations(dimension.getAnnotationMap(), "a", "Dimension"); final Hierarchy hierarchy = dimension.getHierarchies()[0]; assertEquals("Hierarchy description", hierarchy.getDescription()); assertEquals("Hierarchy caption", hierarchy.getCaption()); checkAnnotations(hierarchy.getAnnotationMap(), "a", "Hierarchy"); final mondrian.olap.Level level = hierarchy.getLevels()[1]; assertEquals("Level description", level.getDescription()); assertEquals("Level caption", level.getCaption()); checkAnnotations(level.getAnnotationMap(), "a", "Level"); // Caption comes from the CAPTION member property, defaults to name. // Description comes from the DESCRIPTION member property. // Annotations are always empty for regular members. final List memberList = cube.getSchemaReader(null).withLocus() .getLevelMembers(level, false); final Member member = memberList.get(0); assertEquals("Canada", member.getName()); assertEquals("Canada", member.getCaption()); assertNull(member.getDescription()); checkAnnotations(member.getAnnotationMap()); // All member. Caption defaults to name; description is null. final Member allMember = member.getParentMember(); assertEquals("All Stores", allMember.getName()); assertEquals("All Stores", allMember.getCaption()); assertNull(allMember.getDescription()); // All level. final mondrian.olap.Level allLevel = hierarchy.getLevels()[0]; assertEquals("(All)", allLevel.getName()); assertNull(allLevel.getDescription()); assertEquals(allLevel.getName(), allLevel.getCaption()); checkAnnotations(allLevel.getAnnotationMap()); // the first time dimension overrides the caption and description of the // shared time dimension final Dimension timeDimension = cube.getDimensions()[2]; assertEquals("Time1", timeDimension.getName()); assertEquals("Time usage description", timeDimension.getDescription()); assertEquals("Time usage caption", timeDimension.getCaption()); checkAnnotations(timeDimension.getAnnotationMap(), "a", "Time usage"); // Time1 is a usage of a shared dimension Time. // Now look at the hierarchy usage within that dimension usage. // Because the dimension usage has a name, use that as a prefix for // name, caption and description of the hierarchy usage. final Hierarchy timeHierarchy = timeDimension.getHierarchies()[0]; // The hierarchy in the shared dimension does not have a name, so the // hierarchy usage inherits the name of the dimension usage, Time1. final boolean ssasCompatibleNaming = MondrianProperties.instance().SsasCompatibleNaming.get(); if (ssasCompatibleNaming) { assertEquals("Time", timeHierarchy.getName()); assertEquals("Time1", timeHierarchy.getDimension().getName()); } else { assertEquals("Time1", timeHierarchy.getName()); } // The description is prefixed by the dimension usage name. assertEquals( "Time usage caption.Time shared hierarchy description", timeHierarchy.getDescription()); // The hierarchy caption is prefixed by the caption of the dimension // usage. assertEquals( "Time usage caption.Time shared hierarchy caption", timeHierarchy.getCaption()); // No annotations. checkAnnotations(timeHierarchy.getAnnotationMap()); // the second time dimension does not overrides caption and description final Dimension time2Dimension = cube.getDimensions()[3]; assertEquals("Time2", time2Dimension.getName()); assertEquals( "Time shared description", time2Dimension.getDescription()); assertEquals("Time shared caption", time2Dimension.getCaption()); checkAnnotations(time2Dimension.getAnnotationMap()); final Hierarchy time2Hierarchy = time2Dimension.getHierarchies()[0]; // The hierarchy in the shared dimension does not have a name, so the // hierarchy usage inherits the name of the dimension usage, Time2. if (ssasCompatibleNaming) { assertEquals("Time", time2Hierarchy.getName()); assertEquals("Time2", time2Hierarchy.getDimension().getName()); } else { assertEquals("Time2", time2Hierarchy.getName()); } // The description is prefixed by the dimension usage name (because // dimension usage has no caption). assertEquals( "Time2.Time shared hierarchy description", time2Hierarchy.getDescription()); // The hierarchy caption is prefixed by the dimension usage name // (because the dimension usage has no caption. assertEquals( "Time2.Time shared hierarchy caption", time2Hierarchy.getCaption()); // No annotations. checkAnnotations(time2Hierarchy.getAnnotationMap()); final Dimension measuresDimension = cube.getDimensions()[0]; final Hierarchy measuresHierarchy = measuresDimension.getHierarchies()[0]; final mondrian.olap.Level measuresLevel = measuresHierarchy.getLevels()[0]; final SchemaReader schemaReader = cube.getSchemaReader(null); final List measures = schemaReader.getLevelMembers(measuresLevel, true); final Member measure = measures.get(0); assertEquals("Unit Sales", measure.getName()); assertEquals("Measure caption", measure.getCaption()); assertEquals("Measure description", measure.getDescription()); assertEquals( measure.getDescription(), measure.getPropertyValue(Property.DESCRIPTION.name)); assertEquals( measure.getCaption(), measure.getPropertyValue(Property.CAPTION.name)); assertEquals( measure.getCaption(), measure.getPropertyValue(Property.MEMBER_CAPTION.name)); checkAnnotations(measure.getAnnotationMap(), "a", "Measure"); // The implicitly created [Fact Count] measure final Member factCountMeasure = measures.get(1); assertEquals("Fact Count", factCountMeasure.getName()); assertEquals( false, factCountMeasure.getPropertyValue(Property.VISIBLE.name)); final Member calcMeasure = measures.get(2); assertEquals("Foo", calcMeasure.getName()); assertEquals("Calc member caption", calcMeasure.getCaption()); assertEquals("Calc member description", calcMeasure.getDescription()); assertEquals( calcMeasure.getDescription(), calcMeasure.getPropertyValue(Property.DESCRIPTION.name)); assertEquals( calcMeasure.getCaption(), calcMeasure.getPropertyValue(Property.CAPTION.name)); assertEquals( calcMeasure.getCaption(), calcMeasure.getPropertyValue(Property.MEMBER_CAPTION.name)); checkAnnotations(calcMeasure.getAnnotationMap(), "a", "Calc member"); final NamedSet namedSet = cube.getNamedSets()[0]; assertEquals("Top Periods", namedSet.getName()); assertEquals("Named set caption", namedSet.getCaption()); assertEquals("Named set description", namedSet.getDescription()); checkAnnotations(namedSet.getAnnotationMap(), "a", "Named set"); final Result result2 = testContext.executeQuery("select from [" + virtualCubeName + "]"); final Cube cube2 = result2.getQuery().getCube(); assertEquals("Virtual cube description", cube2.getDescription()); checkAnnotations(cube2.getAnnotationMap(), "a", "Virtual cube"); final SchemaReader schemaReader2 = cube2.getSchemaReader(null); final Dimension measuresDimension2 = cube2.getDimensions()[0]; final Hierarchy measuresHierarchy2 = measuresDimension2.getHierarchies()[0]; final mondrian.olap.Level measuresLevel2 = measuresHierarchy2.getLevels()[0]; final List measures2 = schemaReader2.getLevelMembers(measuresLevel2, true); final Member measure2 = measures2.get(0); assertEquals("Unit Sales", measure2.getName()); assertEquals("Measure caption", measure2.getCaption()); assertEquals("Measure description", measure2.getDescription()); assertEquals( measure2.getDescription(), measure2.getPropertyValue(Property.DESCRIPTION.name)); assertEquals( measure2.getCaption(), measure2.getPropertyValue(Property.CAPTION.name)); assertEquals( measure2.getCaption(), measure2.getPropertyValue(Property.MEMBER_CAPTION.name)); checkAnnotations( measure2.getAnnotationMap(), "a", "Virtual cube measure"); } private static void checkAnnotations( Map annotationMap, String... nameVal) { assertNotNull(annotationMap); assertEquals(0, nameVal.length % 2); assertEquals(nameVal.length / 2, annotationMap.size()); int i = 0; for (Map.Entry entry : annotationMap.entrySet()) { assertEquals(nameVal[i++], entry.getKey()); assertEquals(nameVal[i++], entry.getValue().getValue()); } } public void testCaption() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "
\n" + " \n" + " \n" + " 'foobar'\n" + " \n" + " \n" + " \n" + " "); Result result = testContext.executeQuery( "select {[Gender2].Children} on columns from [Sales]"); assertEquals( "foobar", result.getAxes()[0].getPositions().get(0).get(0).getCaption()); } /** * Implementation of {@link PropertyFormatter} that throws. */ public static class DummyPropertyFormatter implements PropertyFormatter { public DummyPropertyFormatter() { throw new RuntimeException("oops"); } public String formatProperty( Member member, String propertyName, Object propertyValue) { return null; } } /** * Unit test for bug * * MONDRIAN-747, "When joining a shared dimension into a cube at a level * other than its leaf level, Mondrian gives wrong results". */ public void testBugMondrian747() { // Test case requires a pecular inline view, and it works on dialects // that scalar subqery, viz oracle. I believe that the mondrian code // being works in all dialects. switch (TestContext.instance().getDialect().getDatabaseProduct()) { case ORACLE: break; default: return; } final TestContext testContext = TestContext.instance().withSchema( " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" // + " \n" + " \n" + " select \"product_id\", \"time_id\", \"customer_id\", \"promotion_id\", \"store_id\", \"store_sales\", \"store_cost\", \"unit_sales\", (select \"store_state\" from \"store\" where \"store_id\" = \"sales_fact_1997\".\"store_id\") as \"sales_state_province\" from \"sales_fact_1997\"\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""); if (!Bug.BugMondrian747Fixed && MondrianProperties.instance().EnableGroupingSets.get()) { // With grouping sets enabled, MONDRIAN-747 behavior is even worse. return; } // [Store].[All Stores] and [Store].[USA] should be 266,773. A higher // value would indicate that there is a cartesian product going on -- // because "store_state" is not unique in "store" table. final String x = !Bug.BugMondrian747Fixed ? "1,379,620" : "266,773"; testContext.assertQueryReturns( "select non empty {[Measures].[unitsales2]} on 0,\n" + " non empty [Store].members on 1\n" + "from [cube2]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[unitsales2]}\n" + "Axis #2:\n" + "{[Store].[All Stores]}\n" + "{[Store].[USA]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[WA]}\n" + "Row #0: 266,773\n" + "Row #1: " + x + "\n" + "Row #2: 373,740\n" + "Row #3: 135,318\n" + "Row #4: 870,562\n"); // No idea why, but this value comes out TOO LOW. FIXME. final String y = !Bug.BugMondrian747Fixed && MondrianProperties.instance().ReadAggregates.get() && MondrianProperties.instance().UseAggregates.get() ? "20,957" : "266,773"; testContext.assertQueryReturns( "select non empty {[Measures].[unitsales1]} on 0,\n" + " non empty [Store].members on 1\n" + "from [cube1]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[unitsales1]}\n" + "Axis #2:\n" + "{[Store].[All Stores]}\n" + "{[Store].[USA]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[OR].[Portland]}\n" + "{[Store].[USA].[OR].[Salem]}\n" + "{[Store].[USA].[WA]}\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "{[Store].[USA].[WA].[Bremerton]}\n" + "{[Store].[USA].[WA].[Seattle]}\n" + "{[Store].[USA].[WA].[Spokane]}\n" + "{[Store].[USA].[WA].[Tacoma]}\n" + "{[Store].[USA].[WA].[Walla Walla]}\n" + "{[Store].[USA].[WA].[Yakima]}\n" + "Row #0: " + y + "\n" + "Row #1: 266,773\n" + "Row #2: 74,748\n" + "Row #3: 21,333\n" + "Row #4: 25,663\n" + "Row #5: 25,635\n" + "Row #6: 2,117\n" + "Row #7: 67,659\n" + "Row #8: 26,079\n" + "Row #9: 41,580\n" + "Row #10: 124,366\n" + "Row #11: 2,237\n" + "Row #12: 24,576\n" + "Row #13: 25,011\n" + "Row #14: 23,591\n" + "Row #15: 35,257\n" + "Row #16: 2,203\n" + "Row #17: 11,491\n"); testContext.assertQueryReturns( "select non empty {[Measures].[unitsales2], [Measures].[unitsales1]} on 0,\n" + " non empty [Store].members on 1\n" + "from [virtual_cube]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[unitsales2]}\n" + "{[Measures].[unitsales1]}\n" + "Axis #2:\n" + "{[Store].[All Stores]}\n" + "{[Store].[USA]}\n" + "{[Store].[USA].[CA]}\n" + "{[Store].[USA].[CA].[Beverly Hills]}\n" + "{[Store].[USA].[CA].[Los Angeles]}\n" + "{[Store].[USA].[CA].[San Diego]}\n" + "{[Store].[USA].[CA].[San Francisco]}\n" + "{[Store].[USA].[OR]}\n" + "{[Store].[USA].[OR].[Portland]}\n" + "{[Store].[USA].[OR].[Salem]}\n" + "{[Store].[USA].[WA]}\n" + "{[Store].[USA].[WA].[Bellingham]}\n" + "{[Store].[USA].[WA].[Bremerton]}\n" + "{[Store].[USA].[WA].[Seattle]}\n" + "{[Store].[USA].[WA].[Spokane]}\n" + "{[Store].[USA].[WA].[Tacoma]}\n" + "{[Store].[USA].[WA].[Walla Walla]}\n" + "{[Store].[USA].[WA].[Yakima]}\n" + "Row #0: 266,773\n" + "Row #0: " + y + "\n" + "Row #1: 1,379,620\n" + "Row #1: 266,773\n" + "Row #2: 373,740\n" + "Row #2: 74,748\n" + "Row #3: \n" + "Row #3: 21,333\n" + "Row #4: \n" + "Row #4: 25,663\n" + "Row #5: \n" + "Row #5: 25,635\n" + "Row #6: \n" + "Row #6: 2,117\n" + "Row #7: 135,318\n" + "Row #7: 67,659\n" + "Row #8: \n" + "Row #8: 26,079\n" + "Row #9: \n" + "Row #9: 41,580\n" + "Row #10: 870,562\n" + "Row #10: 124,366\n" + "Row #11: \n" + "Row #11: 2,237\n" + "Row #12: \n" + "Row #12: 24,576\n" + "Row #13: \n" + "Row #13: 25,011\n" + "Row #14: \n" + "Row #14: 23,591\n" + "Row #15: \n" + "Row #15: 35,257\n" + "Row #16: \n" + "Row #16: 2,203\n" + "Row #17: \n" + "Row #17: 11,491\n"); } /** * Unit test for bug * * MONDRIAN-463, "Snowflake dimension with 3-way join.". */ public void testBugMondrian463() { if (!MondrianProperties.instance().FilterChildlessSnowflakeMembers .get()) { // Similar to aggregates. If we turn off filtering, // we get wild stuff because of referential integrity. return; } // To build a dimension that is a 3-way snowflake, take the 2-way // product -> product_class join and convert to product -> store -> // product_class. // // It works because product_class_id covers the range 1 .. 110; // store_id covers every value in 0 .. 24; // region_id has 24 distinct values in the range 0 .. 106 (region_id 25 // occurs twice). // Therefore in store, store_id -> region_id is a 25 to 24 mapping. checkBugMondrian463( TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + " \n" + "
\n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "")); // As above, but using shared dimension. if (MondrianProperties.instance().ReadAggregates.get() && MondrianProperties.instance().UseAggregates.get()) { // With aggregates enabled, query gives different answer. This is // expected because some of the foreign keys have referential // integrity problems. return; } checkBugMondrian463( TestContext.instance().withSchema( "\n" + "\n" + "\n" + " \n" + " \n" + "
\n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "
\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "")); } private void checkBugMondrian463(TestContext testContext) { testContext.assertQueryReturns( "select [Measures] on 0,\n" + " head([Product3].members, 10) on 1\n" + "from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product3].[All Product3s]}\n" + "{[Product3].[Drink]}\n" + "{[Product3].[Drink].[Baking Goods]}\n" + "{[Product3].[Drink].[Baking Goods].[Dry Goods]}\n" + "{[Product3].[Drink].[Baking Goods].[Dry Goods].[Coffee]}\n" + "{[Product3].[Drink].[Baking Goods].[Dry Goods].[Coffee].[24]}\n" + "{[Product3].[Drink].[Baking Goods].[Dry Goods].[Coffee].[24].[Amigo]}\n" + "{[Product3].[Drink].[Baking Goods].[Dry Goods].[Coffee].[24].[Amigo].[Amigo Lox]}\n" + "{[Product3].[Drink].[Baking Goods].[Dry Goods].[Coffee].[24].[Curlew]}\n" + "{[Product3].[Drink].[Baking Goods].[Dry Goods].[Coffee].[24].[Curlew].[Curlew Lox]}\n" + "Row #0: 266,773\n" + "Row #1: 2,647\n" + "Row #2: 835\n" + "Row #3: 835\n" + "Row #4: 835\n" + "Row #5: 835\n" + "Row #6: 175\n" + "Row #7: 175\n" + "Row #8: 186\n" + "Row #9: 186\n"); } /** * Tests that a join nested left-deep, that is (Join (Join A B) C), fails. * The correct way to use a join is right-deep, that is (Join A (Join B C)). * Same schema as {@link #testBugMondrian463}, except left-deep. */ public void testLeftDeepJoinFails() { TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", "\n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + ""); try { testContext.assertSimpleQuery(); fail("expected error"); } catch (MondrianException e) { assertEquals( "Mondrian Error:Left side of join must not be a join; mondrian only supports right-deep joins.", e.getMessage()); } } /** * Test for MONDRIAN-943 and MONDRIAN-465. */ public void testCaptionWithOrdinalColumn() { final TestContext tc = TestContext.instance().createSubstitutingCube( "HR", "\n" + " \n" + "
\n" + " \n" + " \n" + " \n" + "\n"); String mdxQuery = "WITH SET [#DataSet#] as '{Descendants([Position].[All Position], 2)}' " + "SELECT {[Measures].[Org Salary]} on columns, " + "NON EMPTY Hierarchize({[#DataSet#]}) on rows FROM [HR]"; Result result = tc.executeQuery(mdxQuery); Axis[] axes = result.getAxes(); List positions = axes[1].getPositions(); Member mall = positions.get(0).get(0); String caption = mall.getHierarchy().getCaption(); assertEquals("Position", caption); String captionValue = mall.getCaption(); assertEquals("HQ Information Systems", captionValue); mall = positions.get(14).get(0); captionValue = mall.getCaption(); assertEquals("Store Manager", captionValue); mall = positions.get(15).get(0); captionValue = mall.getCaption(); assertEquals("Store Assistant Manager", captionValue); } /** * This is a test case for bug Mondrian-923. When a virtual cube included * calculated members in its schema, they were not included in the list of * existing measures because of an override of the hierarchy schema reader * which was done at cube init time when resolving the calculated members * of the base cubes. */ public void testBugMondrian923() throws Exception { TestContext context = TestContext.instance().createSubstitutingCube( "Warehouse and Sales", null, null, "[Measures].[Unit Sales]" + "[Measures].[Unit Sales] 10000,'|#,###|arrow=up',IIf([Measures].[Unit Sales] > 5000,'|#,###|arrow=down','|#,###|arrow=none'))\"/>" + "[Measures].[Unit Sales] 100000,'|#,###|style=green',IIf([Measures].[Unit Sales] > 50000,'|#,###|style=yellow','|#,###|style=red'))\"/>", null); for (Cube cube : context.getConnection().getSchemaReader().getCubes()) { if (cube.getName().equals("Warehouse and Sales")) { for (Dimension dim : cube.getDimensions()) { if (dim.isMeasures()) { List members = context.getConnection() .getSchemaReader().getLevelMembers( dim.getHierarchy().getLevels()[0], true); assertTrue( members.toString().contains( "[Measures].[Profit Per Unit Shipped]")); assertTrue( members.toString().contains( "[Measures].[Image Unit Sales]")); assertTrue( members.toString().contains( "[Measures].[Arrow Unit Sales]")); assertTrue( members.toString().contains( "[Measures].[Style Unit Sales]")); assertTrue( members.toString().contains( "[Measures].[Average Warehouse Sale]")); return; } } } } fail("Didn't find measures in sales cube."); } public void testCubesVisibility() throws Exception { for (Boolean testValue : new Boolean[] {true, false}) { String cubeDef = "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; cubeDef = cubeDef.replace( "@REPLACE_ME@", String.valueOf(testValue)); final TestContext context = TestContext.instance().create( null, cubeDef, null, null, null, null); final Cube cube = context.getConnection().getSchema() .lookupCube("Foo", true); assertTrue(testValue.equals(cube.isVisible())); } } public void testVirtualCubesVisibility() throws Exception { for (Boolean testValue : new Boolean[] {true, false}) { String cubeDef = "\n" + " \n" + " \n" + "\n"; cubeDef = cubeDef.replace( "@REPLACE_ME@", String.valueOf(testValue)); final TestContext context = TestContext.instance().create( null, null, cubeDef, null, null, null); final Cube cube = context.getConnection().getSchema() .lookupCube("Foo", true); assertTrue(testValue.equals(cube.isVisible())); } } public void testDimensionVisibility() throws Exception { for (Boolean testValue : new Boolean[] {true, false}) { String cubeDef = "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; cubeDef = cubeDef.replace( "@REPLACE_ME@", String.valueOf(testValue)); final TestContext context = TestContext.instance().create( null, cubeDef, null, null, null, null); final Cube cube = context.getConnection().getSchema() .lookupCube("Foo", true); Dimension dim = null; for (Dimension dimCheck : cube.getDimensions()) { if (dimCheck.getName().equals("Bar")) { dim = dimCheck; } } assertNotNull(dim); assertTrue(testValue.equals(dim.isVisible())); } } public void testVirtualDimensionVisibility() throws Exception { for (Boolean testValue : new Boolean[] {true, false}) { String cubeDef = "\n" + " \n" + " \n" + "\n"; cubeDef = cubeDef.replace( "@REPLACE_ME@", String.valueOf(testValue)); final TestContext context = TestContext.instance().create( null, null, cubeDef, null, null, null); final Cube cube = context.getConnection().getSchema() .lookupCube("Foo", true); Dimension dim = null; for (Dimension dimCheck : cube.getDimensions()) { if (dimCheck.getName().equals("Customers")) { dim = dimCheck; } } assertNotNull(dim); assertTrue(testValue.equals(dim.isVisible())); } } public void testDimensionUsageVisibility() throws Exception { for (Boolean testValue : new Boolean[] {true, false}) { String cubeDef = "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; final TestContext context = TestContext.instance().create( null, cubeDef, null, null, null, null); final Cube cube = context.getConnection().getSchema() .lookupCube("Foo", true); String dimensionDef = ""; dimensionDef = dimensionDef.replace( "@REPLACE_ME@", String.valueOf(testValue)); context.getConnection().getSchema().createDimension( cube, dimensionDef); Dimension dim = null; for (Dimension dimCheck : cube.getDimensions()) { if (dimCheck.getName().equals("Bar")) { dim = dimCheck; } } assertNotNull(dim); assertTrue(testValue.equals(dim.isVisible())); } } public void testHierarchyVisibility() throws Exception { for (Boolean testValue : new Boolean[] {true, false}) { String cubeDef = "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; cubeDef = cubeDef.replace( "@REPLACE_ME@", String.valueOf(testValue)); final TestContext context = TestContext.instance().create( null, cubeDef, null, null, null, null); final Cube cube = context.getConnection().getSchema() .lookupCube("Foo", true); Dimension dim = null; for (Dimension dimCheck : cube.getDimensions()) { if (dimCheck.getName().equals("Bar")) { dim = dimCheck; } } assertNotNull(dim); final Hierarchy hier = dim.getHierarchy(); assertNotNull(hier); assertEquals( MondrianProperties.instance().SsasCompatibleNaming.get() ? "Bacon" : "Bar.Bacon", hier.getName()); assertTrue(testValue.equals(hier.isVisible())); } } public void testLevelVisibility() throws Exception { for (Boolean testValue : new Boolean[] {true, false}) { String cubeDef = "\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"; cubeDef = cubeDef.replace( "@REPLACE_ME@", String.valueOf(testValue)); final TestContext context = TestContext.instance().create( null, cubeDef, null, null, null, null); final Cube cube = context.getConnection().getSchema() .lookupCube("Foo", true); Dimension dim = null; for (Dimension dimCheck : cube.getDimensions()) { if (dimCheck.getName().equals("Bar")) { dim = dimCheck; } } assertNotNull(dim); final Hierarchy hier = dim.getHierarchy(); assertNotNull(hier); assertEquals( MondrianProperties.instance().SsasCompatibleNaming.get() ? "Bacon" : "Bar.Bacon", hier.getName()); final mondrian.olap.Level level = hier.getLevels()[0]; assertEquals("Samosa", level.getName()); assertTrue(testValue.equals(level.isVisible())); } } public void testNonCollapsedAggregate() throws Exception { if (MondrianProperties.instance().UseAggregates.get() == false && MondrianProperties.instance().ReadAggregates.get() == false) { return; } final String cube = "\n" + "
\n" + " " + " " + " " + " " + " " + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "\n" + "\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "\n" + "\n"; final TestContext context = TestContext.instance().create( null, cube, null, null, null, null); context.assertQueryReturns( "select {[Product].[Product Family].Members} on rows, {[Measures].[Unit Sales]} on columns from [Foo]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 24,597\n" + "Row #1: 191,940\n" + "Row #2: 50,236\n"); } public void testNonCollapsedAggregateOnNonUniqueLevelFails() throws Exception { if (MondrianProperties.instance().UseAggregates.get() == false && MondrianProperties.instance().ReadAggregates.get() == false) { return; } final String cube = "\n" + "
\n" + " " + " " + " " + " " + " " + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "\n" + "\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "\n" + "\n"; final TestContext context = TestContext.instance().create( null, cube, null, null, null, null); context.assertQueryThrows( "select {[Product].[Product Family].Members} on rows, {[Measures].[Unit Sales]} on columns from [Foo]", "mondrian.olap.MondrianException: Mondrian Error:Too many errors, '1', while loading/reloading aggregates."); } public void testTwoNonCollapsedAggregate() throws Exception { if (MondrianProperties.instance().UseAggregates.get() == false && MondrianProperties.instance().ReadAggregates.get() == false) { return; } final String cube = "\n" + "
\n" + " " + " " + " " + " " + " " + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "\n" + "\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + " \n" + " \n" + " \n" + "
\n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n"; final TestContext context = TestContext.instance().create( null, cube, null, null, null, null); context.assertQueryReturns( "select {Crossjoin([Product].[Product Family].Members, [Store].[Store Id].Members)} on rows, {[Measures].[Unit Sales]} on columns from [Foo]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Unit Sales]}\n" + "Axis #2:\n" + "{[Product].[Drink], [Store].[Acapulco].[1]}\n" + "{[Product].[Drink], [Store].[Bellingham].[2]}\n" + "{[Product].[Drink], [Store].[Beverly Hills].[6]}\n" + "{[Product].[Drink], [Store].[Bremerton].[3]}\n" + "{[Product].[Drink], [Store].[Camacho].[4]}\n" + "{[Product].[Drink], [Store].[Guadalajara].[5]}\n" + "{[Product].[Drink], [Store].[Hidalgo].[12]}\n" + "{[Product].[Drink], [Store].[Hidalgo].[18]}\n" + "{[Product].[Drink], [Store].[Los Angeles].[7]}\n" + "{[Product].[Drink], [Store].[Merida].[8]}\n" + "{[Product].[Drink], [Store].[Mexico City].[9]}\n" + "{[Product].[Drink], [Store].[None].[0]}\n" + "{[Product].[Drink], [Store].[Orizaba].[10]}\n" + "{[Product].[Drink], [Store].[Portland].[11]}\n" + "{[Product].[Drink], [Store].[Salem].[13]}\n" + "{[Product].[Drink], [Store].[San Andres].[21]}\n" + "{[Product].[Drink], [Store].[San Diego].[24]}\n" + "{[Product].[Drink], [Store].[San Francisco].[14]}\n" + "{[Product].[Drink], [Store].[Seattle].[15]}\n" + "{[Product].[Drink], [Store].[Spokane].[16]}\n" + "{[Product].[Drink], [Store].[Tacoma].[17]}\n" + "{[Product].[Drink], [Store].[Vancouver].[19]}\n" + "{[Product].[Drink], [Store].[Victoria].[20]}\n" + "{[Product].[Drink], [Store].[Walla Walla].[22]}\n" + "{[Product].[Drink], [Store].[Yakima].[23]}\n" + "{[Product].[Food], [Store].[Acapulco].[1]}\n" + "{[Product].[Food], [Store].[Bellingham].[2]}\n" + "{[Product].[Food], [Store].[Beverly Hills].[6]}\n" + "{[Product].[Food], [Store].[Bremerton].[3]}\n" + "{[Product].[Food], [Store].[Camacho].[4]}\n" + "{[Product].[Food], [Store].[Guadalajara].[5]}\n" + "{[Product].[Food], [Store].[Hidalgo].[12]}\n" + "{[Product].[Food], [Store].[Hidalgo].[18]}\n" + "{[Product].[Food], [Store].[Los Angeles].[7]}\n" + "{[Product].[Food], [Store].[Merida].[8]}\n" + "{[Product].[Food], [Store].[Mexico City].[9]}\n" + "{[Product].[Food], [Store].[None].[0]}\n" + "{[Product].[Food], [Store].[Orizaba].[10]}\n" + "{[Product].[Food], [Store].[Portland].[11]}\n" + "{[Product].[Food], [Store].[Salem].[13]}\n" + "{[Product].[Food], [Store].[San Andres].[21]}\n" + "{[Product].[Food], [Store].[San Diego].[24]}\n" + "{[Product].[Food], [Store].[San Francisco].[14]}\n" + "{[Product].[Food], [Store].[Seattle].[15]}\n" + "{[Product].[Food], [Store].[Spokane].[16]}\n" + "{[Product].[Food], [Store].[Tacoma].[17]}\n" + "{[Product].[Food], [Store].[Vancouver].[19]}\n" + "{[Product].[Food], [Store].[Victoria].[20]}\n" + "{[Product].[Food], [Store].[Walla Walla].[22]}\n" + "{[Product].[Food], [Store].[Yakima].[23]}\n" + "{[Product].[Non-Consumable], [Store].[Acapulco].[1]}\n" + "{[Product].[Non-Consumable], [Store].[Bellingham].[2]}\n" + "{[Product].[Non-Consumable], [Store].[Beverly Hills].[6]}\n" + "{[Product].[Non-Consumable], [Store].[Bremerton].[3]}\n" + "{[Product].[Non-Consumable], [Store].[Camacho].[4]}\n" + "{[Product].[Non-Consumable], [Store].[Guadalajara].[5]}\n" + "{[Product].[Non-Consumable], [Store].[Hidalgo].[12]}\n" + "{[Product].[Non-Consumable], [Store].[Hidalgo].[18]}\n" + "{[Product].[Non-Consumable], [Store].[Los Angeles].[7]}\n" + "{[Product].[Non-Consumable], [Store].[Merida].[8]}\n" + "{[Product].[Non-Consumable], [Store].[Mexico City].[9]}\n" + "{[Product].[Non-Consumable], [Store].[None].[0]}\n" + "{[Product].[Non-Consumable], [Store].[Orizaba].[10]}\n" + "{[Product].[Non-Consumable], [Store].[Portland].[11]}\n" + "{[Product].[Non-Consumable], [Store].[Salem].[13]}\n" + "{[Product].[Non-Consumable], [Store].[San Andres].[21]}\n" + "{[Product].[Non-Consumable], [Store].[San Diego].[24]}\n" + "{[Product].[Non-Consumable], [Store].[San Francisco].[14]}\n" + "{[Product].[Non-Consumable], [Store].[Seattle].[15]}\n" + "{[Product].[Non-Consumable], [Store].[Spokane].[16]}\n" + "{[Product].[Non-Consumable], [Store].[Tacoma].[17]}\n" + "{[Product].[Non-Consumable], [Store].[Vancouver].[19]}\n" + "{[Product].[Non-Consumable], [Store].[Victoria].[20]}\n" + "{[Product].[Non-Consumable], [Store].[Walla Walla].[22]}\n" + "{[Product].[Non-Consumable], [Store].[Yakima].[23]}\n" + "Row #0: \n" + "Row #1: 208\n" + "Row #2: 1,945\n" + "Row #3: 2,288\n" + "Row #4: \n" + "Row #5: \n" + "Row #6: \n" + "Row #7: \n" + "Row #8: 2,422\n" + "Row #9: \n" + "Row #10: \n" + "Row #11: \n" + "Row #12: \n" + "Row #13: 2,371\n" + "Row #14: 3,735\n" + "Row #15: \n" + "Row #16: 2,560\n" + "Row #17: 175\n" + "Row #18: 2,213\n" + "Row #19: 2,238\n" + "Row #20: 3,092\n" + "Row #21: \n" + "Row #22: \n" + "Row #23: 191\n" + "Row #24: 1,159\n" + "Row #25: \n" + "Row #26: 1,587\n" + "Row #27: 15,438\n" + "Row #28: 17,809\n" + "Row #29: \n" + "Row #30: \n" + "Row #31: \n" + "Row #32: \n" + "Row #33: 18,294\n" + "Row #34: \n" + "Row #35: \n" + "Row #36: \n" + "Row #37: \n" + "Row #38: 18,632\n" + "Row #39: 29,905\n" + "Row #40: \n" + "Row #41: 18,369\n" + "Row #42: 1,555\n" + "Row #43: 18,159\n" + "Row #44: 16,925\n" + "Row #45: 25,453\n" + "Row #46: \n" + "Row #47: \n" + "Row #48: 1,622\n" + "Row #49: 8,192\n" + "Row #50: \n" + "Row #51: 442\n" + "Row #52: 3,950\n" + "Row #53: 4,479\n" + "Row #54: \n" + "Row #55: \n" + "Row #56: \n" + "Row #57: \n" + "Row #58: 4,947\n" + "Row #59: \n" + "Row #60: \n" + "Row #61: \n" + "Row #62: \n" + "Row #63: 5,076\n" + "Row #64: 7,940\n" + "Row #65: \n" + "Row #66: 4,706\n" + "Row #67: 387\n" + "Row #68: 4,639\n" + "Row #69: 4,428\n" + "Row #70: 6,712\n" + "Row #71: \n" + "Row #72: \n" + "Row #73: 390\n" + "Row #74: 2,140\n"); } public void testCollapsedError() throws Exception { if (MondrianProperties.instance().UseAggregates.get() == false && MondrianProperties.instance().ReadAggregates.get() == false) { return; } final String cube = "\n" + "
\n" + " " + " " + " " + " " + " " + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + "\n" + "\n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "\n" + "\n" + "\n"; final TestContext context = TestContext.instance().create( null, cube, null, null, null, null); context.assertQueryThrows( "select {[Product].[Product Family].Members} on rows, {[Measures].[Unit Sales]} on columns from [Foo]", "Too many errors, '1', while loading/reloading aggregates."); } /** * Test case for bug * MONDRIAN-1047, * "IllegalArgumentException when cube has closure tables and many * levels". */ public void testBugMondrian1047() { // Test case only works under MySQL, due to how columns are quoted. switch (TestContext.instance().getDialect().getDatabaseProduct()) { case MYSQL: break; default: return; } TestContext testContext = TestContext.instance().createSubstitutingCube( "HR", TestContext.repeatString( 100, "\n" + " \n" + "
\n" + " \n" + " `position_title` + %1$d\n" + " \n" + " \n" + ""), null); testContext.assertQueryReturns( "select from [HR]", "Axis #0:\n" + "{}\n" + "$39,431.67"); } /** * Test case for bug * MONDRIAN-1065, * Incorrect data column is used in the WHERE clause of the SQL when * using Oracle DB. */ public void testBugMondrian1065() { // Test case only works under Oracle switch (TestContext.instance().getDialect().getDatabaseProduct()) { case ORACLE: break; default: return; } TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " 1\n" + " level 1\n" + " 1\n" + " level 2 - 1\n" + " 112\n" + " level 3 - 1\n" + " \n" + " \n" + " 1\n" + " level 1\n" + " 1\n" + " level 2 - 1\n" + " 114\n" + " level 3 - 2\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n"); testContext.assertQueryReturns( "select non empty crossjoin({[PandaSteak].[Level3].[level 3 - 1], [PandaSteak].[Level3].[level 3 - 2]}, {[Measures].[Unit Sales], [Measures].[Store Cost]}) on columns, {[Product].[Product Family].Members} on rows from [Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[PandaSteak].[level 1].[level 2 - 1].[level 3 - 1], [Measures].[Unit Sales]}\n" + "{[PandaSteak].[level 1].[level 2 - 1].[level 3 - 1], [Measures].[Store Cost]}\n" + "{[PandaSteak].[level 1].[level 2 - 1].[level 3 - 2], [Measures].[Unit Sales]}\n" + "{[PandaSteak].[level 1].[level 2 - 1].[level 3 - 2], [Measures].[Store Cost]}\n" + "Axis #2:\n" + "{[Product].[Drink]}\n" + "{[Product].[Food]}\n" + "{[Product].[Non-Consumable]}\n" + "Row #0: 5\n" + "Row #0: 3.50\n" + "Row #0: 9\n" + "Row #0: 7.70\n" + "Row #1: 27\n" + "Row #1: 20.77\n" + "Row #1: 46\n" + "Row #1: 39.88\n" + "Row #2: 10\n" + "Row #2: 9.63\n" + "Row #2: 17\n" + "Row #2: 16.21\n"); } } // End SchemaTest.java mondrian-3.4.1/testsrc/main/mondrian/test/CmdRunnerTest.java0000644000175000017500000000353711735330606024037 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2006 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.olap.Connection; import mondrian.tui.CmdRunner; import java.io.*; /** * Unit test for {@link mondrian.tui.CmdRunner}. * * @author jhyde * @since Jun 2, 2006 */ public class CmdRunnerTest extends FoodMartTestCase { protected DiffRepository getDiffRepos() { return DiffRepository.lookup(CmdRunnerTest.class); } public CmdRunnerTest() { } public CmdRunnerTest(String name) { super(name); } public void testQuery() throws IOException { doTest(); } public void test7731() throws IOException { doTest(); } protected void doTest() { final DiffRepository diffRepos = getDiffRepos(); String input = diffRepos.expand("input", "${input}"); final StringWriter sw = new StringWriter(); final PrintWriter pw = new PrintWriter(sw); final CmdRunnerTrojan cmdRunner = new CmdRunnerTrojan(null, pw); cmdRunner.commandLoop(new StringReader(input), false); pw.flush(); String output = sw.toString(); diffRepos.assertEquals("output", "${output}", output); } private class CmdRunnerTrojan extends CmdRunner { public CmdRunnerTrojan(CmdRunner.Options options, PrintWriter out) { super(options, out); } public void commandLoop(Reader in, boolean interactive) { super.commandLoop(in, interactive); } public Connection getConnection() { return CmdRunnerTest.this.getConnection(); } } } // End CmdRunnerTest.java mondrian-3.4.1/testsrc/main/mondrian/test/IgnoreMeasureForNonJoiningDimensionInAggregationTest.java0000644000175000017500000003177111735330606033655 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.test; import mondrian.olap.MondrianProperties; import mondrian.rolap.agg.AggregationManager; /** * Test ignoring of measure when unrelated Dimension is in * aggregation list when IgnoreMeasureForNonJoiningDimension property is * set to true. * * @author ajoglekar * @since Dec 12, 2007 */ public class IgnoreMeasureForNonJoiningDimensionInAggregationTest extends FoodMartTestCase { protected void setUp() throws Exception { super.setUp(); propSaver.set( MondrianProperties.instance().EnableNonEmptyOnAllAxis, true); propSaver.set( MondrianProperties.instance().IgnoreMeasureForNonJoiningDimension, true); getTestContext().getConnection().getCacheControl(null) .flushSchemaCache(); } public void testNoTotalsForCompdMeasureWithComponentsHavingNonJoiningDims() { assertQueryReturns( "with member [Measures].[Total Sales] as " + "'[Measures].[Store Sales] + [Measures].[Warehouse Sales]'" + "member [Product].x as 'sum({Product.members * Gender.members})' " + "select {[Measures].[Total Sales]} on 0, " + "{Product.x} on 1 from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Total Sales]}\n" + "Axis #2:\n" + "{[Product].[x]}\n" + "Row #0: 7,913,333.82\n"); } public void testNonJoiningDimsWhenAggFunctionIsUsedOrNotUsed() { final String query = "WITH\n" + "MEMBER [Measures].[Total Sales] AS " + "'[Measures].[Store Sales] + [Measures].[Warehouse Sales]'\n" + "MEMBER [Warehouse].[AggSP1] AS\n" + "'IIF([Measures].CURRENTMEMBER IS [Measures].[Total Sales],\n" + "([Warehouse].[All Warehouses], [Measures].[Total Sales]),\n" + "([Product].[All Products], [Warehouse].[All Warehouses]))'\n" + "MEMBER [Warehouse].[AggPreSP] AS\n" + "'IIF([Measures].CURRENTMEMBER IS [Measures].[Total Sales],\n" + "([Warehouse].[All Warehouses], [Measures].[Total Sales]),\n" + "Aggregate({([Product].[All Products], [Warehouse].[All Warehouses])}))'\n" + "\n" + "SELECT\n" + "{[Measures].[Total Sales]} ON AXIS(0),\n" + "{{([Warehouse].[AggPreSP])},\n" + "{([Warehouse].[AggSP1])}} ON AXIS(1)\n" + "FROM\n" + "[Warehouse and Sales]"; assertQueryReturns( query, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Total Sales]}\n" + "Axis #2:\n" + "{[Warehouse].[AggPreSP]}\n" + "{[Warehouse].[AggSP1]}\n" + "Row #0: 196,770.89\n" + "Row #1: 196,770.89\n"); propSaver.set( MondrianProperties.instance().IgnoreMeasureForNonJoiningDimension, false); assertQueryReturns( query, "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Total Sales]}\n" + "Axis #2:\n" + "{[Warehouse].[AggPreSP]}\n" + "{[Warehouse].[AggSP1]}\n" + "Row #0: 762,009.02\n" + "Row #1: 762,009.02\n"); } public void testNonJoiningDimForAMemberDefinedOnJoiningDim() { assertQueryReturns( "WITH\n" + "MEMBER [Measures].[Total Sales] AS '[Measures].[Store Sales] + " + "[Measures].[Warehouse Sales]'\n" + "MEMBER [Product].[AggSP1] AS\n" + "'IIF([Measures].CURRENTMEMBER IS [Measures].[Total Sales],\n" + "([Warehouse].[All Warehouses], [Measures].[Total Sales]),\n" + "([Warehouse].[All Warehouses]))'\n" + "\n" + "SELECT\n" + "{[Measures].[Total Sales]} ON AXIS(0),\n" + "{[Product].[AggSP1]} ON AXIS(1)\n" + "FROM\n" + "[Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Total Sales]}\n" + "Axis #2:\n" + "{[Product].[AggSP1]}\n" + "Row #0: 196,770.89\n"); } public void testNonJoiningDimWithNumericIif() { assertQueryReturns( "WITH\n" + "MEMBER [Measures].[Total Sales] AS " + "'[Measures].[Store Sales] + [Measures].[Warehouse Sales]'\n" + "MEMBER [Warehouse].[AggSP1_1] AS\n" + "'IIF(1=0,\n" + "([Warehouse].[All Warehouses], [Measures].[Total Sales]),\n" + "([Warehouse].[All Warehouses]))'\n" + "MEMBER [Warehouse].[AggSP1_2] AS\n" + "'IIF(1=0,\n" + "111,\n" + "([Warehouse].[All Warehouses], [Store].[All Stores]))'\n" + "\n" + "SELECT\n" + "{[Measures].[Total Sales]} ON AXIS(0),\n" + "{([Warehouse].[AggSP1_1]), ([Warehouse].[AggSP1_2])} ON AXIS(1)\n" + "FROM\n" + "[Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Total Sales]}\n" + "Axis #2:\n" + "{[Warehouse].[AggSP1_1]}\n" + "{[Warehouse].[AggSP1_2]}\n" + "Row #0: 196,770.89\n" + "Row #1: 196,770.89\n"); } public void testNonJoiningDimAtMemberValueCalcMultipleScenarios() { assertQueryReturns( "WITH\n" + "MEMBER [Measures].[Total Sales] AS " + "'[Measures].[Store Sales] + [Measures].[Warehouse Sales]'\n" + "MEMBER [Warehouse].[AggSP1_1] AS\n" + "'IIF(1=0,\n" + "([Warehouse].[All Warehouses]),\n" + "([Warehouse].[All Warehouses]))'\n" + "MEMBER [Warehouse].[AggSP1_2] AS\n" + "'IIF(1=0,\n" + "[Warehouse].[All Warehouses],\n" + "([Warehouse].[All Warehouses]))'\n" + "MEMBER [Warehouse].[AggSP1_3] AS\n" + "'IIF(1=0,\n" + "([Warehouse].[All Warehouses]),\n" + "[Warehouse].[All Warehouses])'\n" + "MEMBER [Warehouse].[AggSP1_4] AS\n" + "'IIF(1=0,\n" + "StrToMember(\"[Warehouse].[All Warehouses]\"),\n" + "[Warehouse].[All Warehouses])'\n" + "\n" + "SELECT\n" + "{[Measures].[Total Sales]} ON AXIS(0),\n" + "{([Warehouse].[AggSP1_1]),([Warehouse].[AggSP1_2])," + "([Warehouse].[AggSP1_3]),([Warehouse].[AggSP1_4])} ON AXIS(1)\n" + "FROM\n" + "[Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Total Sales]}\n" + "Axis #2:\n" + "{[Warehouse].[AggSP1_1]}\n" + "{[Warehouse].[AggSP1_2]}\n" + "{[Warehouse].[AggSP1_3]}\n" + "{[Warehouse].[AggSP1_4]}\n" + "Row #0: 196,770.89\n" + "Row #1: 196,770.89\n" + "Row #2: 196,770.89\n" + "Row #3: 196,770.89\n"); } public void testNonJoiningDimAtTupleValueCalcMultipleScenarios() { assertQueryReturns( "WITH\n" + "MEMBER [Measures].[Total Sales] AS " + "'[Measures].[Store Sales] + [Measures].[Warehouse Sales]'\n" + "MEMBER [Warehouse].[AggSP1_1] AS\n" + "'IIF(1=0,\n" + "([Warehouse].[All Warehouses], [Store].[All Stores]),\n" + "([Warehouse].[All Warehouses], [Store].[All Stores]))'\n" + "MEMBER [Warehouse].[AggSP1_2] AS\n" + "'IIF(1=0,\n" + "([Warehouse].[All Warehouses]),\n" + "([Warehouse].[All Warehouses], [Store].[All Stores]))'\n" + "MEMBER [Warehouse].[AggSP1_3] AS\n" + "'IIF(1=0,\n" + "([Warehouse].[All Warehouses], [Store].[All Stores]),\n" + "([Warehouse].[All Warehouses]))'\n" + "MEMBER [Warehouse].[AggSP1_4] AS\n" + "'IIF(1=0,\n" + "StrToTuple(\"([Warehouse].[All Warehouses])\", [Warehouse]),\n" + "([Warehouse].[All Warehouses], [Store].[All Stores]))'\n" + "MEMBER [Warehouse].[AggSP1_5] AS\n" + "'IIF(1=0,\n" + "([Warehouse].[All Warehouses], [Store].[All Stores]),\n" + "[Warehouse].[All Warehouses])'\n" + "MEMBER [Warehouse].[AggSP1_6] AS\n" + "'IIF(1=0,\n" + "[Warehouse].[All Warehouses],\n" + "([Warehouse].[All Warehouses], [Store].[All Stores]))'\n" + "\n" + "SELECT\n" + "{[Measures].[Total Sales]} ON AXIS(0),\n" + "{[Warehouse].[AggSP1_1],[Warehouse].[AggSP1_2],[Warehouse].[AggSP1_3]," + "[Warehouse].[AggSP1_4],[Warehouse].[AggSP1_5],[Warehouse].[AggSP1_6]} " + "ON AXIS(1)\n" + "FROM\n" + "[Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Total Sales]}\n" + "Axis #2:\n" + "{[Warehouse].[AggSP1_1]}\n" + "{[Warehouse].[AggSP1_2]}\n" + "{[Warehouse].[AggSP1_3]}\n" + "{[Warehouse].[AggSP1_4]}\n" + "{[Warehouse].[AggSP1_5]}\n" + "{[Warehouse].[AggSP1_6]}\n" + "Row #0: 196,770.89\n" + "Row #1: 196,770.89\n" + "Row #2: 196,770.89\n" + "Row #3: 196,770.89\n" + "Row #4: 196,770.89\n" + "Row #5: 196,770.89\n"); } public void testNoTotalsForCompoundMeasureWithNonJoiningDimAtAllLevel() { assertQueryReturns( "with member [Measures].[Total Sales] as " + "'[Measures].[Store Sales]'" + "member [Product].x as 'sum({Product.members * " + "Gender.[All Gender]})' " + "select {[Measures].[Total Sales]} on 0, " + "{Product.x} on 1 from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Total Sales]}\n" + "Axis #2:\n" + "{[Product].[x]}\n" + "Row #0: 3,956,666.91\n"); } public void testNoTotalForMeasureWithCrossJoinOfJoiningAndNonJoiningDims() { assertQueryReturns( "with member [Product].x as " + "'sum({Product.members} * {Gender.members})' " + "select {[Measures].[Warehouse Sales]} on 0, " + "{Product.x} on 1 from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "Axis #2:\n"); } public void testShouldTotalAMeasureWithAllJoiningDimensions() { assertQueryReturns( "with member [Product].x as " + "'sum({Product.members})' " + "select " + "{[Measures].[Warehouse Sales]} on 0, " + "{Product.x} on 1 " + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Warehouse Sales]}\n" + "Axis #2:\n" + "{[Product].[x]}\n" + "Row #0: 1,377,396.213\n"); } public void testShouldNotTotalAMeasureWithANonJoiningDimension() { assertQueryReturns( "with member [Gender].x as 'sum({Gender.members})'" + "select " + "{[Measures].[Warehouse Sales]} on 0, " + "{Gender.x} on 1 " + "from [Warehouse and Sales]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "Axis #2:\n"); } // base cube is null for calc measure public void testGetMeasureCubeForCalcMeasureDoesNotThrowCastException() { getTestContext().assertQueryReturns( "WITH MEMBER [Measures].[My Profit] AS " + "'Measures.[Profit]', SOLVE_ORDER = 3000 " + "MEMBER Gender.G AS " + "'sum(CROSSJOIN({GENDER.[M]},{[Product].[All Products].[Drink]}))'," + "SOLVE_ORDER = 4 " + "SELECT {[Measures].[My Profit]} ON 0, {Gender.G} ON 1 FROM [SALES]", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[My Profit]}\n" + "Axis #2:\n" + "{[Gender].[G]}\n" + "Row #0: $14,652.70\n"); } } // End IgnoreMeasureForNonJoiningDimensionInAggregationTest.java mondrian-3.4.1/testsrc/main/mondrian/test/SteelWheelsPerformanceTest.java0000644000175000017500000002217211735330606026544 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2011 Pentaho // All Rights Reserved. */ package mondrian.test; import junit.framework.TestCase; import org.apache.log4j.Logger; /** * Those performance tests use the steel wheels schema * and are not meant to be run as part of the CI test suite. * They must be deactivated by default. * @author LBoudreau */ public class SteelWheelsPerformanceTest extends TestCase { /** * Certain tests are enabled only if logging is enabled. */ private static final Logger LOGGER = Logger.getLogger(SteelWheelsPerformanceTest.class); public SteelWheelsPerformanceTest(String name) { super(name); } /** * Returns the test context. Override this method if you wish to use a * different source for your FoodMart connection. */ public TestContext getTestContext() { return SteelWheelsTestCase.createContext(TestContext.instance(), null); } /** * This test execute a specially crafted query with * tons of filters and sort to test the performance * of some bug fixes before/after. */ public void testComplexFilters() throws Exception { if (!LOGGER.isDebugEnabled()) { return; } final String query = "with set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Product], NonEmptyCrossJoin([*BASE_MEMBERS_Markets], NonEmptyCrossJoin([*BASE_MEMBERS_Customers], NonEmptyCrossJoin([*BASE_MEMBERS_Time], [*BASE_MEMBERS_Order Status]))))'\n" + " set [*METRIC_CJ_SET] as 'Filter(Filter([*NATIVE_CJ_SET], (([Measures].[*Sales_SEL~AGG] > 0.0) AND ([Measures].[*Quantity_SEL~AGG] > 0.0))), (NOT IsEmpty([Measures].[Quantity])))'\n" + " set [*SORTED_ROW_AXIS] as 'Order([*CJ_ROW_AXIS], Ancestor([Markets].CurrentMember, [Markets].[Territory]).OrderKey, BASC, ([Product].[*TOTAL_MEMBER_SEL~AGG], [Measures].[*FORMATTED_MEASURE_0]), BDESC)'\n" + " set [*SORTED_COL_AXIS] as 'Order([*CJ_COL_AXIS], Ancestor([Product].CurrentMember, [Product].[Line]).OrderKey, BDESC)'\n" + " set [*METRIC_MEMBERS_Order Status] as 'Generate([*METRIC_CJ_SET], {[Order Status].CurrentMember})'\n" + " set [*METRIC_MEMBERS_Markets] as 'Generate([*METRIC_CJ_SET], {[Markets].CurrentMember})'\n" + " set [*NATIVE_MEMBERS_Order Status] as 'Generate([*NATIVE_CJ_SET], {[Order Status].CurrentMember})'\n" + " set [*BASE_MEMBERS_Product] as '{[Product].[Vintage Cars].[Motor City Art Classics].[1911 Ford Town Car], [Product].[Planes].[Motor City Art Classics].[America West Airlines B757-200], [Product].[Ships].[Unimax Art Galleries].[HMS Bounty], [Product].[Planes].[Gearbox Collectibles].[P-51-D Mustang]}'\n" + " set [*BASE_MEMBERS_Customers] as '[Customers].[Customer].Members'\n" + " set [*NATIVE_MEMBERS_Customers] as 'Generate([*NATIVE_CJ_SET], {[Customers].CurrentMember})'\n" + " set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}'\n" + " set [*BASE_MEMBERS_Time] as 'Filter([Time].[Months].Members, (NOT ([Time].CurrentMember IN {[Time].[2004].[QTR1].[Mar]})))'\n" + " set [*METRIC_MEMBERS_Time] as 'Generate([*METRIC_CJ_SET], {[Time].CurrentMember})'\n" + " set [*CJ_COL_AXIS] as 'Generate([*METRIC_CJ_SET], {Ancestor([Product].CurrentMember, [Product].[Line]).CalculatedChild(\"*DISPLAY_MEMBER\")})'\n" + " set [*NATIVE_MEMBERS_Product] as 'Generate([*NATIVE_CJ_SET], {[Product].CurrentMember})'\n" + " set [*BASE_MEMBERS_Markets] as 'Filter([Markets].[Country].Members, (([Markets].CurrentMember IN {[Markets].[EMEA].[Denmark], [Markets].[#null].[Germany], [Markets].[Japan].[Japan], [Markets].[Japan].[Philippines], [Markets].[#null].[South Africa], [Markets].[EMEA].[Spain], [Markets].[NA].[USA]}) AND (NOT (Ancestor([Markets].CurrentMember, [Markets].[Territory]) IN {[Markets].[#null]}))))'\n" + " set [*NATIVE_MEMBERS_Markets] as 'Generate([*NATIVE_CJ_SET], {[Markets].CurrentMember})'\n" + " set [*BASE_MEMBERS_Order Status] as '{[Order Status].[In Process], [Order Status].[Resolved], [Order Status].[Shipped]}'\n" + " set [*NATIVE_MEMBERS_Time] as 'Generate([*NATIVE_CJ_SET], {[Time].CurrentMember})'\n" + " set [*METRIC_MEMBERS_Product] as 'Generate([*METRIC_CJ_SET], {[Product].CurrentMember})'\n" + " set [*CJ_ROW_AXIS] as 'Generate([*METRIC_CJ_SET], {(Ancestor([Markets].CurrentMember, [Markets].[Territory]).CalculatedChild(\"*DISPLAY_MEMBER\"), [Customers].CurrentMember)})'\n" + " member [Product].[Ships].[*DISPLAY_MEMBER] as 'Aggregate(Filter([*METRIC_MEMBERS_Product], (Ancestor([Product].CurrentMember, [Product].[Line]) IS [Product].[Ships])))', SOLVE_ORDER = (- 102.0)\n" + " member [Product].[Vintage Cars].[*CTX_MEMBER_SEL~AGG] as 'Aggregate(Filter([*NATIVE_MEMBERS_Product], (Ancestor([Product].CurrentMember, [Product].[Line]) IS [Product].[Vintage Cars])))', SOLVE_ORDER = (- 102.0)\n" + " member [Markets].[Japan].[*DISPLAY_MEMBER] as 'Aggregate(Filter([*METRIC_MEMBERS_Markets], (Ancestor([Markets].CurrentMember, [Markets].[Territory]) IS [Markets].[Japan])))', SOLVE_ORDER = (- 100.0)\n" + " member [Order Status].[*SLICER_MEMBER] as 'Aggregate([*METRIC_MEMBERS_Order Status])', SOLVE_ORDER = (- 400.0)\n" + " member [Markets].[*CTX_MEMBER_SEL~AGG] as 'Aggregate([*NATIVE_MEMBERS_Markets])', SOLVE_ORDER = (- 100.0)\n" + " member [Order Status].[*CTX_MEMBER_SEL~AGG] as 'Aggregate([*NATIVE_MEMBERS_Order Status])', SOLVE_ORDER = (- 403.0)\n" + " member [Product].[Planes].[*DISPLAY_MEMBER] as 'Aggregate(Filter([*METRIC_MEMBERS_Product], (Ancestor([Product].CurrentMember, [Product].[Line]) IS [Product].[Planes])))', SOLVE_ORDER = (- 102.0)\n" + " member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Quantity]', FORMAT_STRING = \"#,###\", SOLVE_ORDER = 400.0\n" + " member [Customers].[*CTX_MEMBER_SEL~AGG] as 'Aggregate([*NATIVE_MEMBERS_Customers])', SOLVE_ORDER = (- 101.0)\n" + " member [Measures].[*Sales_SEL~AGG] as '([Measures].[Sales], Ancestor([Product].CurrentMember, [Product].[Line]).CalculatedChild(\"*CTX_MEMBER_SEL~AGG\"), [Markets].[*CTX_MEMBER_SEL~AGG], [Customers].[*CTX_MEMBER_SEL~AGG], [Time].[*CTX_MEMBER_SEL~AGG], [Order Status].[*CTX_MEMBER_SEL~AGG])', SOLVE_ORDER = 300.0\n" + " member [Product].[Vintage Cars].[*DISPLAY_MEMBER] as 'Aggregate(Filter([*METRIC_MEMBERS_Product], (Ancestor([Product].CurrentMember, [Product].[Line]) IS [Product].[Vintage Cars])))', SOLVE_ORDER = (- 102.0)\n" + " member [Time].[Time].[*SLICER_MEMBER] as 'Aggregate([*METRIC_MEMBERS_Time])', SOLVE_ORDER = (- 400.0)\n" + " member [Markets].[EMEA].[*DISPLAY_MEMBER] as 'Aggregate(Filter([*METRIC_MEMBERS_Markets], (Ancestor([Markets].CurrentMember, [Markets].[Territory]) IS [Markets].[EMEA])))', SOLVE_ORDER = (- 100.0)\n" + " member [Product].[*TOTAL_MEMBER_SEL~AGG] as 'Aggregate(Generate([*METRIC_CJ_SET], {[Product].CurrentMember}))', SOLVE_ORDER = (- 102.0)\n" + " member [Measures].[*Quantity_SEL~AGG] as '([Measures].[Quantity], Ancestor([Product].CurrentMember, [Product].[Line]).CalculatedChild(\"*CTX_MEMBER_SEL~AGG\"), [Markets].[*CTX_MEMBER_SEL~AGG], [Customers].[*CTX_MEMBER_SEL~AGG], [Time].[*CTX_MEMBER_SEL~AGG], [Order Status].[*CTX_MEMBER_SEL~AGG])', SOLVE_ORDER = 300.0\n" + " member [Markets].[NA].[*DISPLAY_MEMBER] as 'Aggregate(Filter([*METRIC_MEMBERS_Markets], (Ancestor([Markets].CurrentMember, [Markets].[Territory]) IS [Markets].[NA])))', SOLVE_ORDER = (- 100.0)\n" + " member [Time].[Time].[*CTX_MEMBER_SEL~AGG] as 'Aggregate([*NATIVE_MEMBERS_Time])', SOLVE_ORDER = (- 402.0)\n" + " member [Product].[Ships].[*CTX_MEMBER_SEL~AGG] as 'Aggregate(Filter([*NATIVE_MEMBERS_Product], (Ancestor([Product].CurrentMember, [Product].[Line]) IS [Product].[Ships])))', SOLVE_ORDER = (- 102.0)\n" + " member [Product].[Planes].[*CTX_MEMBER_SEL~AGG] as 'Aggregate(Filter([*NATIVE_MEMBERS_Product], (Ancestor([Product].CurrentMember, [Product].[Line]) IS [Product].[Planes])))', SOLVE_ORDER = (- 102.0)\n" + "select Union(Crossjoin({[Product].[*TOTAL_MEMBER_SEL~AGG]}, [*BASE_MEMBERS_Measures]), Crossjoin([*SORTED_COL_AXIS], [*BASE_MEMBERS_Measures])) ON COLUMNS,\n" + " [*SORTED_ROW_AXIS] ON ROWS\n" + "from [SteelWheelsSales]\n" + "where ([Time].[*SLICER_MEMBER], [Order Status].[*SLICER_MEMBER])\n"; long start = System.currentTimeMillis(); getTestContext().executeQuery(query); printDuration("Complex filters query performance", start); } private void printDuration(String desc, long t0) { final long t1 = System.currentTimeMillis(); final long duration = t1 - t0; LOGGER.debug(desc + " took " + duration + " millis"); } } // End SteelWheelsPerformanceTest.java mondrian-3.4.1/testsrc/main/mondrian/test/CVConcurrentMdxTest.java0000644000175000017500000001635211735330606025165 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho and others // All Rights Reserved. // */ package mondrian.test; import mondrian.olap.MondrianProperties; import mondrian.olap.Util; import mondrian.test.clearview.*; import junit.framework.*; import java.lang.reflect.Constructor; import java.util.*; /** * A copy of {@link ConcurrentMdxTest} with modifications to take * as input ref.xml files. This does not fully use {@link DiffRepository} * and does not generate log files. * This Class is not added to the Main test suite. * Purpose of this test is to simulate Concurrent access to Aggregation and data * load. Simulation will be more effective if we run this single test again and * again with a fresh connection. * * @author Khanh Vu */ public class CVConcurrentMdxTest extends FoodMartTestCase { private MondrianProperties props; public CVConcurrentMdxTest() { super(); props = MondrianProperties.instance(); } public CVConcurrentMdxTest(String name) { super(name); props = MondrianProperties.instance(); } public void testConcurrentQueriesInRandomOrder() { propSaver.set(props.UseAggregates, false); propSaver.set(props.ReadAggregates, false); propSaver.set(props.DisableCaching, false); // test partially filled aggregation cache // add test classes List testList = new ArrayList(); List suiteList = new ArrayList(); testList.add(PartialCacheTest.class); suiteList.add(PartialCacheTest.suite()); testList.add(MultiLevelTest.class); suiteList.add(MultiLevelTest.suite()); testList.add(QueryAllTest.class); suiteList.add(QueryAllTest.suite()); testList.add(MultiDimTest.class); suiteList.add(MultiDimTest.suite()); // sanity check assertTrue(sanityCheck(suiteList)); // generate list of queries and results QueryAndResult[] queryList = generateQueryArray(testList); assertTrue(ConcurrentValidatingQueryRunner.runTest( 3, 100, true, true, true, queryList).size() == 0); } public void testConcurrentQueriesInRandomOrderOnVirtualCube() { propSaver.set(props.UseAggregates, false); propSaver.set(props.ReadAggregates, false); propSaver.set(props.DisableCaching, false); // test partially filled aggregation cache // add test classes List testList = new ArrayList(); List suiteList = new ArrayList(); testList.add(PartialCacheVCTest.class); suiteList.add(PartialCacheVCTest.suite()); testList.add(MultiLevelTest.class); suiteList.add(MultiLevelTest.suite()); testList.add(QueryAllVCTest.class); suiteList.add(QueryAllVCTest.suite()); testList.add(MultiDimVCTest.class); suiteList.add(MultiDimVCTest.suite()); // sanity check assertTrue(sanityCheck(suiteList)); // generate list of queries and results QueryAndResult[] queryList = generateQueryArray(testList); assertTrue(ConcurrentValidatingQueryRunner.runTest( 3, 100, true, true, true, queryList).size() == 0); } public void testConcurrentCVQueriesInRandomOrder() { propSaver.set(props.UseAggregates, false); propSaver.set(props.ReadAggregates, false); propSaver.set(props.DisableCaching, false); // test partially filled aggregation cache // add test classes List testList = new ArrayList(); testList.add(CVBasicTest.class); testList.add(GrandTotalTest.class); testList.add(MetricFilterTest.class); testList.add(MiscTest.class); testList.add(PredicateFilterTest.class); testList.add(SubTotalTest.class); testList.add(SummaryMetricPercentTest.class); testList.add(SummaryTest.class); testList.add(TopBottomTest.class); // generate list of queries and results QueryAndResult[] queryList = generateQueryArray(testList); assertEquals( Collections.emptyList(), ConcurrentValidatingQueryRunner.runTest( 3, 100, true, true, true, queryList)); } protected void tearDown() throws Exception { super.tearDown(); } protected void setUp() throws Exception { super.setUp(); } /** * Runs one pass of all tests single-threaded using * {@link mondrian.test.clearview.ClearViewBase} mechanism * @param suiteList list of tests to be checked * @return true if all tests pass */ private boolean sanityCheck(List suiteList) { TestSuite suite = new TestSuite(); for (int i = 0; i < suiteList.size(); i++) { suite.addTest(suiteList.get(i)); } TestResult tres = new TestResult(); suite.run(tres); return tres.wasSuccessful(); } /** * Generates an array of QueryAndResult objects from the list of * test classes * @param testList list of test classes * @return array of QueryAndResult */ private QueryAndResult[] generateQueryArray(List testList) { List queryList = new ArrayList(); for (int i = 0; i < testList.size(); i++) { Class testClass = testList.get(i); Class[] types = new Class[] { String.class }; try { Constructor cons = testClass.getConstructor(types); Object[] args = new Object[] { "" }; Test newCon = (Test) cons.newInstance(args); DiffRepository diffRepos = ((ClearViewBase) newCon).getDiffRepos(); List testCaseNames = diffRepos.getTestCaseNames(); for (int j = 0; j < testCaseNames.size(); j++) { String testCaseName = testCaseNames.get(j); String query = diffRepos.get(testCaseName, "mdx"); String result = diffRepos.get(testCaseName, "result"); // current limitation: only run queries if // calculated members are not specified if (diffRepos.get(testCaseName, "calculatedMembers") == null) { // trim the starting newline char only if (result.startsWith(Util.nl)) { result = result.replaceFirst(Util.nl, ""); } QueryAndResult queryResult = new QueryAndResult(query, result); queryList.add(queryResult); } } } catch (Exception e) { throw new Error(e.getMessage()); } } QueryAndResult[] queryArray = new QueryAndResult[queryList.size()]; return queryList.toArray(queryArray); } } // End CVConcurrentMdxTest.java mondrian-3.4.1/testsrc/main/mondrian/test/SimpleTestRunner.java0000644000175000017500000001372011735330606024560 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.test; import junit.framework.*; import java.lang.reflect.Constructor; import java.util.Enumeration; /** * Simple test runner. */ public class SimpleTestRunner { protected static void usage(String msg) { StringBuilder buf = new StringBuilder(64); if (msg != null) { buf.append(msg); buf.append('\n'); } buf.append("Usage: mondrian.test.SimpleTestRunner options tests*"); buf.append('\n'); buf.append(" options:"); buf.append('\n'); buf.append(" -h (print this text)"); buf.append('\n'); buf.append(" -q (error output quiet)"); buf.append('\n'); buf.append(" tests:"); buf.append('\n'); buf.append(" -c testcaseclassname methodnames*"); buf.append('\n'); buf.append("If no method names are given, then all are tested"); buf.append('\n'); System.out.println(buf.toString()); System.exit(0); } protected static TestCase makeTestCase(String classname) throws Exception { Class cls = Class.forName(classname); return (TestCase) cls.newInstance(); } protected static TestCase makeTestCase(String classname, String methodname) throws Exception { Class cls = Class.forName(classname); Constructor cons = cls.getConstructor(new Class[] { String.class}); return (TestCase) cons.newInstance(new Object[] { methodname }); } protected static void outputErrorInfo(Enumeration e, boolean quiet) { while (e.hasMoreElements()) { TestFailure tf = (TestFailure) e.nextElement(); if (! quiet) { System.out.println(tf.trace()); Throwable t = tf.thrownException().getCause(); while (t != null) { t.printStackTrace(); t = t.getCause(); } } else { System.out.println(tf.toString()); System.out.println("run without -quiet for more information"); } } } public static void main(String[] args) throws Throwable { String classname = null; TestCase testcase = null; boolean quiet = false; boolean explicitMethods = false; for (int i = 0; i < args.length; i++) { String arg = args[i]; if (arg.equals("-h")) { usage(null); } else if (arg.equals("-q")) { quiet = true; } else if (arg.equals("-quiet")) { quiet = true; } else if (arg.equals("-c")) { i++; if (i == args.length) { usage("Must supply TestCase classname after -c"); } classname = args[i]; } else { explicitMethods = true; String methodname = arg; if (testcase == null) { if (classname == null) { usage( "Must supply TestCase classname before methodname"); } testcase = makeTestCase(classname, methodname); } else { testcase.setName(methodname); } //testcase.runBare(); junit.framework.TestResult tr = testcase.run(); System.out.println("Test Class: " + classname); System.out.println(" Method : " + methodname); System.out.println(" Error Count : " + tr.errorCount()); if (tr.errorCount() != 0) { Enumeration e = tr.errors(); outputErrorInfo(e, quiet); } System.out.println(" Failure Count : " + tr.failureCount()); if (tr.failureCount() != 0) { Enumeration e = tr.failures(); outputErrorInfo(e, quiet); } testcase = null; } } if (! explicitMethods) { if (classname == null) { usage("Must supply TestCase classname"); } if (testcase == null) { try { // maybe it has a no argument constructor testcase = makeTestCase(classname); } catch (InstantiationException ex) { String msg = "InstantiationException: " + "most likely the test class does not have a " + "zero-parameter, public constructor."; System.out.println(msg); System.exit(1); } catch (Exception ex) { testcase = null; // ignore } } if (testcase != null) { TestSuite suite = new TestSuite(testcase.getClass()); TestResult tr = new TestResult(); suite.run(tr); System.out.println("Test Class: " + classname); System.out.println(" Method Count : " + tr.runCount()); System.out.println(" Error Count : " + tr.errorCount()); if (tr.errorCount() != 0) { Enumeration e = tr.errors(); outputErrorInfo(e, quiet); } System.out.println(" Failure Count : " + tr.failureCount()); if (tr.failureCount() != 0) { Enumeration e = tr.failures(); outputErrorInfo(e, quiet); } } } } } // End SimpleTestRunner.java mondrian-3.4.1/testsrc/main/mondrian/test/PerformanceTest.java0000644000175000017500000006566711735330606024417 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2012 Pentaho // All Rights Reserved. */ package mondrian.test; import mondrian.olap.*; import mondrian.util.Bug; import org.apache.log4j.Logger; import java.util.*; /** * Various unit tests concerned with performance. * * @author jhyde * @since August 7, 2006 */ public class PerformanceTest extends FoodMartTestCase { /** * Certain tests are enabled only if logging is enabled at debug level or * higher. */ public static final Logger LOGGER = Logger.getLogger(PerformanceTest.class); public PerformanceTest(String name) { super(name); } /** * Test case for * * Bug MONDRIAN-550, "Performance bug with NON EMPTY and large axes". */ public void testBugMondrian550() { final TestContext testContext = getBugMondrian550Schema(); final Statistician statistician = new Statistician("testBugMondrian550"); for (int i = 0; i < 10; i++) { checkBugMondrian550(testContext, statistician); } statistician.printDurations(); } private void checkBugMondrian550( TestContext testContext, Statistician statistician) { long start = System.currentTimeMillis(); // On my Latitude D630: // Takes 137 seconds before bug fixed. // Takes 13 seconds after bug fixed. // jdk1.6 marmalade 3.2 14036 17,899 12,889 ms // jdk1.6 marmalade main 14036 15,845 15,180 ms // jdk1.6 marmalade main 14037 TODO ms // jdk1.6 marmalade main 14052 14,284 ms first, 1419 +- 12 ms // jdk1.7 marmite main 14770 3,527 ms first, 1325 +- 18 ms // jdk1.7 marmite main 14771 3,319 ms first, 1305 +- 26 ms // jdk1.7 marmite main 14772 3,721 ms first, 1321 +- 28 ms // jdk1.7 marmite main 14773 3,421 ms first, 1298 +- 11 ms final Result result = testContext.executeQuery( "select NON EMPTY {[Store Name sans All].Members} ON COLUMNS,\n" + " NON EMPTY Hierarchize(Union({[ACC].[All]}, [ACC].[All].Children)) ON ROWS\n" + "from [Sales]\n" + "where ([Time].[1997].[Q4], [Measures].[EXP2])"); statistician.record(start); assertEquals(13, result.getAxes()[0].getPositions().size()); assertEquals(3262, result.getAxes()[1].getPositions().size()); } /** * As {@link #testBugMondrian550()} but with tuples on the rows axis. */ public void testBugMondrian550Tuple() { final TestContext testContext = getBugMondrian550Schema(); final Statistician statistician = new Statistician("testBugMondrian550Tuple"); int n = LOGGER.isDebugEnabled() ? 10 : 2; for (int i = 0; i < n; i++) { checkBugMondrian550Tuple(testContext, statistician); } statistician.printDurations(); } private void checkBugMondrian550Tuple( TestContext testContext, Statistician statistician) { long start = System.currentTimeMillis(); // On my Latitude D630: // Takes 252 seconds before bug fixed. // Takes 45 seconds after bug fixed. // jdk1.6 marmalade 3.2 14036 14,799 14,986 ms // jdk1.6 marmalade main 14036 20,839 20,331 ms // jdk1.6 marmalade main 14037 TODO ms // jdk1.6 marmalade main 14052 9,664 +- 49 // jdk1.7 marmite main 14770 10,228 +- 60 ms // jdk1.7 marmite main 14771 9,742 +- 111 ms // jdk1.7 marmite main 14772 10,512 +- 38 ms // jdk1.7 marmite main 14773 9,544 +- 118 ms final Result result2 = testContext.executeQuery( "select NON EMPTY {[Store Name sans All].Members} ON COLUMNS,\n" + " NON EMPTY Hierarchize(Union({[ACC].[All]}, [ACC].[All].Children))\n" + " * [Gender].Children ON ROWS\n" + "from [Sales]\n" + "where ([Time].[1997].[Q4], [Measures].[EXP2])"); statistician.record(start); assertEquals(13, result2.getAxes()[0].getPositions().size()); assertEquals(3263, result2.getAxes()[1].getPositions().size()); } private TestContext getBugMondrian550Schema() { return TestContext.instance().createSubstitutingCube( "Sales", " \n" + " \n" + "
\n" + " \n" + " \n" + " \n" + " \n" + " \n" + "
\n" + " \n" + " \n" + " \n", " \n" + " \n"); } /** * Test case for * * Bug MONDRIAN-641, "Large NON EMPTY result performs poorly with * ResultStyle.ITERABLE". Runs in ~10 seconds with ResultStyle.LIST, * 99+ seconds with ITERABLE (on DELL Latitude D630). */ public void testMondrianBug641() { if (!Bug.BugMondrian641Fixed) { return; } long start = System.currentTimeMillis(); Result result = executeQuery( "select non empty { crossjoin( customers.[city].members, " + "crossjoin( [store type].[store type].members, " + "product.[product name].members)) }" + " on 0 from sales"); // jdk1.6 marmalade main 14036 287,940 518,349 ms printDuration("testBugMondrian641", start); assertEquals(51148, result.getAxes()[0].getPositions().size()); } /** * Tests performance when an MDX query contains a very large explicit set. */ public void testVeryLargeExplicitSet() { final TestContext testContext = getTestContext(); final Statistician[] statisticians = { // jdk1.6 mackerel access main old 5,000 ms // jdk1.6 marmalade 3.2 14036 4,376 4,055 ms // jdk1.6 marmalade main 14036 4,471 3,589 ms // jdk1.6 marmalade main 14037 4,400 ms // jdk1.6 marmalade main 14052 5,280 ms // jdk1.7 marmite main 14770 1,189 ms first, 27 +- 4 ms // jdk1.7 marmite main 14771 1,369 ms first, 24 +- 2 ms // jdk1.7 marmite main 14773 1,003 ms first, 23 +- 1 ms new Statistician("testVeryLargeExplicitSet: Execute axis"), // Execute: // first: // jdk1.6 mackerel access old 75,000 ms // jdk1.6 marmalade main 14036 19,262 18,493 ms // jdk1.6 marmalade main 14037 19,000 ms // jdk1.6 marmalade 3.2 14036 18,710 19,077 ms // jdk1.6 marmalade main 14052 21,739 ms // // second: // jdk1.6 mackerel access old 65,000 ms // jdk1.6 marmalade main 14036 526 429 ms // jdk1.6 marmalade main 14037 800 400 ms // jdk1.6 marmalade 3.2 14036 313 406 ms // jdk1.6 marmalade main 14052 577 ms // // jdk1.7 marmite main 14770 520 ms first, 88 +- 22 ms // jdk1.7 marmite main 14771 555 ms first, 84 +- 20 ms // jdk1.7 marmite main 14773 654 ms first, 76 +- 19 ms new Statistician("testVeryLargeExplicitSet: Execute"), // Param query: // first: // unknown revision mackerel 2,424 ms // jdk1.6 marmalade 3.2 14036 34 115 72 ms // jdk1.6 marmalade main 14036 66 107 ms // jdk1.6 marmalade main 14037 117 ms // jdk1.6 marmalade main 14052 47 ms // // second: // unknown revision mackerel 51 ms // jdk1.6 marmalade 3.2 14036 18 102 ms // jdk1.6 marmalade main 14036 86 105 95 ms // jdk1.6 marmalade main 14037 106 ms // jdk1.6 marmalade main 14052 21 ms // // jdk1.7 marmite main 14770 46 ms first, 12 +- 2 ms // jdk1.7 marmite main 14771 26 ms first, 10 +- 2 ms // jdk1.7 marmite main 14773 43 ms first, 10 +- 2 ms new Statistician("testVeryLargeExplicitSet: Param query"), }; for (int i = 0; i < 10; i++) { checkVeryLargeExplicitSet(statisticians, testContext); } for (Statistician statistician : statisticians) { statistician.printDurations(); } } private void checkVeryLargeExplicitSet( Statistician[] statisticians, TestContext testContext) { Result result; long start = System.currentTimeMillis(); final Axis axis = testContext.executeAxis("Customers.Members"); statisticians[0].record(start); final List positionList = axis.getPositions(); assertEquals(10407, positionList.size()); // Take customers 0-2000 and 5000-7000. Using contiguous bursts, // Mondrian has a chance to optimize how it reads cells from the // database. List memberList = new ArrayList(); for (int i = 0; i < positionList.size(); i++) { Position position = positionList.get(i); ++i; if (i < 2000 || i >= 5000 && i < 7000) { memberList.add(position.get(0)); } } // Build a query with an explcit member list. if (LOGGER.isDebugEnabled()) { StringBuilder buf = new StringBuilder(); for (Member member : memberList) { if (buf.length() > 0) { buf.append(", "); } buf.append(member); } final String mdx = "WITH SET [Selected Customers] AS {" + buf + "}\n" + "SELECT {[Measures].[Unit Sales],\n" + " [Measures].[Store Sales]} on 0,\n" + " [Selected Customers] on 1\n" + "FROM [Sales]"; start = System.currentTimeMillis(); result = testContext.executeQuery(mdx); statisticians[1].record(start); assertEquals( memberList.size(), result.getAxes()[1].getPositions().size()); } // Much more efficient technique. Use a parameter, and bind to array. // Cuts out a lot of parsing, so takes 2.4s as opposed to 65s. Query query = testContext.getConnection().parseQuery( "WITH SET [Selected Customers]\n" + " AS Parameter('Foo', [Customers], {}, 'Description')\n" + "SELECT {[Measures].[Unit Sales],\n" + " [Measures].[Store Sales]} on 0,\n" + " [Selected Customers] on 1\n" + "FROM [Sales]"); query.setParameter("Foo", memberList); start = System.currentTimeMillis(); result = testContext.getConnection().execute(query); statisticians[2].record(start); assertEquals( memberList.size(), result.getAxes()[1].getPositions().size()); } /** * Test case for * * Bug MONDRIAN-639, "RolapNamedSetEvaluator anon classes implement * Iterable, causing performance regression from 2.4 in * FunUtil.count()". */ public void testBugMondrian639() { // unknown revision before fix mac-mini 233,000 ms // unknown revision after fix mac-mini 4,500 ms // jdk1.6 marmalade 3.2 14036 1,821 1,702 ms // jdk1.6 marmalade main 14036 2,185 3,208 1,431 ms // jdk1.6 marmalade main 14037 1,801 ms // jdk1.6 marmalade main 14052 396 +- 28 ms // jdk1.7 marmite main 14770 478 ms first, 150 +- 8 ms // jdk1.7 marmite main 14771 513 ms first, 152 +- 14 ms // jdk1.7 marmite main 14773 523 ms first, 150 +- 5 ms final Statistician statistician = new Statistician("testBugMondrian639"); for (int i = 0; i < 20; i++) { checkBugMondrian639(statistician); } statistician.printDurations(); } private void checkBugMondrian639(Statistician statistician) { long start = System.currentTimeMillis(); Result result = executeQuery( "WITH SET [cjoin] AS " + "crossjoin(customers.members, " + TestContext.hierarchyName("store type", "store type") + ".[store type].members) " + "MEMBER [Measures].[total_available_count] " + "AS Format(COUNT([cjoin]), \"#####\") " + "SELECT" + "{[cjoin]} ON COLUMNS, " + "{[Measures].[total_available_count]} ON ROWS " + "FROM sales"); statistician.record(start); assertEquals(62442, result.getAxes()[0].getPositions().size()); assertEquals(1, result.getAxes()[1].getPositions().size()); } /** * Tests performance of a larger schema with a large number of result cells. * Runs in 186 seconds without nonAllPositions array in RolapEvaluator. * Runs in 14 seconds when RolapEvaluator.getProperty uses getNonAllMembers. * The performance boost gets more significant as the schema size grows. */ public void testBigResultsWithBigSchemaPerforms() { if (!LOGGER.isDebugEnabled()) { return; } TestContext testContext = TestContext.instance().createSubstitutingCube( "Sales", TestContext.repeatString( 1000, "" + " " + "
" + " " + " " + ""), null); String mdx = "with " + " member [Measures].[one] as '1'" + " member [Measures].[two] as '2'" + " member [Measures].[three] as '3'" + " member [Measures].[four] as '4'" + " member [Measures].[five] as '5'" + " select " + "{[Measures].[one],[Measures].[two],[Measures].[three],[Measures].[four],[Measures].[five]}" + " on 0, " + "Crossjoin([Customers].[name].members,[Store].[Store Name].members)" + " on 1 from sales"; long start = System.currentTimeMillis(); testContext.executeQuery(mdx); // jdk1.6 marmalade 3.2 14036 23,588 23,426 ms // jdk1.6 marmalade main 14036 26,430 27,045 25,497 ms // jdk1.6 marmalade main 14037 26,893 ms // jdk1.6 marmalade main 14052 29,870 ms // jdk1.7 marmite main 14770 2,877 ms // jdk1.7 marmite main 14773 3,353 ms printDuration("testBigResultsWithBigSchemaPerforms", start); } /** * Runs a query that performs a lot of in-memory calculation. * *

Timings (branch / change / host / DBMS / jdk / timings (s) / mean): *

    *
  • mondrian-3.2 13366 marmalade oracle jdk1.6 592 588 581 571 avg 583 *
  • mondrian-3.2 13367 marmalade oracle jdk1.6 643 620 631 671 avg 641 *
  • mondrian-3.2 13397 marmalade oracle jdk1.6 604 626 *
  • mondrian-3.2 13467 marmalade oracle jdk1.6 610 574 *
  • mondrian-3.2 13489 marmalade oracle jdk1.6 565 561 579 596 avg 575 *
  • mondrian-3.2 13490 marmalade oracle jdk1.6 607 611 581 605 avg 601 *
  • mondrian-3.2 xxxxx marmalade oracle jdk1.6 562 583 541 522 avg 552 *
  • mondrian-3.2 14036 marmalade oracle jdk1.6 451 433 *
  • mondrian 14036 marmalade oracle jdk1.6 598 552 *
  • mondrian 14037 marmalade oracle jdk1.6 626 596 *
  • mondrian 14052 marmalade oracle jdk1.6 454 *
  • mondrian 14770 marmite mysql jdk1.7 > 30 minutes *
*/ public void testInMemoryCalc() { if (!LOGGER.isDebugEnabled()) { // Test is too expensive to run as part of standard regress. // Take 10h on hudson (MySQL)!!! return; } final String result = "Axis #0:\n" + "{[Time].[1997].[Q3]}\n" + "Axis #1:\n" + "{[Measures].[Store Sales]}\n" + "{[Measures].[Typical Store Sales]}\n" + "{[Measures].[Ratio]}\n" + "Axis #2:\n" + "{[Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Modell].[Modell Rye Bread], [Customers].[USA].[OR].[Salem].[Joan Johnson]}\n" + "{[Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Denny].[Denny Plastic Knives], [Customers].[USA].[OR].[Lebanon].[Pat Pinkston]}\n" + "{[Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Shady Lake].[Shady Lake Thai Rice], [Customers].[USA].[CA].[Grossmont].[Anne Silva]}\n" + "{[Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Better].[Better Regular Ramen Soup], [Customers].[USA].[CA].[Coronado].[Robert Brink]}\n" + "{[Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Bird Call].[Bird Call Laundry Detergent], [Customers].[USA].[CA].[Downey].[Eric Renn]}\n" + "Row #0: 19.65\n" + "Row #0: 3.12\n" + "Row #0: 6.30\n" + "Row #1: 15.56\n" + "Row #1: 2.80\n" + "Row #1: 5.56\n" + "Row #2: 11.24\n" + "Row #2: 2.10\n" + "Row #2: 5.35\n" + "Row #3: 11.22\n" + "Row #3: 2.46\n" + "Row #3: 4.56\n" + "Row #4: 6.33\n" + "Row #4: 1.71\n" + "Row #4: 3.70\n"; final String mdx = "with member [Measures].[Typical Store Sales] as\n" + " Max(\n" + " [Customers].Siblings,\n" + " Min(\n" + " [Product].Siblings,\n" + " Avg(\n" + " [Time].Siblings,\n" + " [Measures].[Store Sales])))\n" + "member [Measures].[Ratio] as\n" + " [Measures].[Store Sales]\n" + " / [Measures].[Typical Store Sales]\n" + "select\n" + " {\n" + " [Measures].[Store Sales],\n" + " [Measures].[Typical Store Sales],\n" + " [Measures].[Ratio]\n" + " } on 0,\n" + " TopCount(\n" + " Filter(\n" + " NonEmptyCrossJoin(" + " [Product].[Product Name].Members,\n" + " [Customers].[Name].Members),\n" + " [Measures].[Ratio] > 1.1\n" + " and [Measures].[Store Sales] > 5),\n" + " 5,\n" + " [Measures].[Ratio]) on 1\n" + "from [Sales]\n" + "where [Time].[1997].[Q3]"; final long start = System.currentTimeMillis(); assertQueryReturns(mdx, result); printDuration("in-memory calc", start); } /** * Test case for * * Bug MONDRIAN-843, where Filter is inefficient. */ public void testBugMondrian843() { // On my core i7 laptop: // takes 2.5 seconds before bug fixed // takes 0.4 seconds after bug fixed // jdk1.6 marmalade 3.2 14036 826 ms // jdk1.6 marmalade main 14036 4,427 3,894 ms // jdk1.6 marmalade main 14037 TODO ms // jdk1.6 marmalade main 14052 800 ms // jdk1.7 marmite main 14770 2159 ms (standalone) // jdk1.7 marmite main 14771 266 ms (as part of suite) // jdk1.7 marmite main 14773 181 ms long start = System.currentTimeMillis(); executeQuery( "WITH SET [filtered] AS " + "FILTER({customers.members, customers.members, customers.members, customers.members, customers.members}, [Measures].[Unit Sales] > 100) " + "SELECT" + "{[Measures].[Unit Sales]} ON COLUMNS, " + "{[filtered]} ON ROWS " + "FROM sales"); printDuration("testBugMondrian843", start); } /** * Testcase for bug * MONDRIAN-981, * "Poor performance when >=2 hierarchies are access-controlled with * rollupPolicy=partial". */ public void testBugMondrian981() { if (!LOGGER.isDebugEnabled()) { // Too slow to run as part of standard regress until bug is fixed. return; } // To see the cartesian-product nature of this bug, try commenting out // various of the following HierarchyGrants. // The query runs in about 2s with no access-controlled hierarchies, // then appromixately doubles as each is added (48s with 5 hierarchies). // // jdk1.7 marmite main 14770 30,857 ms // jdk1.7 marmite main 14771 29,083 ms final TestContext testContext = TestContext.instance().create( null, null, null, null, null, "\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n"); testContext.withRole("Role1").assertQueryReturns( "with member [Measures].[Foo] as\n" + "Aggregate([Gender].Members * [Marital Status].Members * [Time].Members)\n" + "select from [Sales] where [Measures].[Foo]", "Axis #0:\n" + "{[Measures].[Foo]}\n" + "1,184,028"); } private static long printDuration(String desc, long t0) { final long t1 = System.currentTimeMillis(); final long duration = t1 - t0; LOGGER.debug(desc + " took " + duration + " millis"); return duration; } /** * Collects statistics for a test that is run multiple times. */ static class Statistician { private final String desc; private final List durations = new ArrayList(); public Statistician(String desc) { super(); this.desc = desc; } private void record(long start) { durations.add( printDuration( desc + " iteration #" + (durations.size() + 1), start)); } private void printDurations() { if (!LOGGER.isDebugEnabled()) { return; } List coreDurations = durations; String durationsString = durations.toString(); // save before sort // Ignore the first 3 readings. (JIT compilation takes a while to // kick in.) if (coreDurations.size() > 3) { coreDurations = durations.subList(3, durations.size()); } Collections.sort(coreDurations); // Further ignore the max and min. List coreCoreDurations = coreDurations; if (coreDurations.size() > 4) { coreCoreDurations = coreDurations.subList(1, coreDurations.size() - 1); } long sum = 0; int count = coreCoreDurations.size(); for (long duration : coreCoreDurations) { sum += duration; } final double avg = ((double) sum) / count; double y = 0; for (long duration : coreCoreDurations) { double x = duration - avg; y += x * x; } final double stddev = Math.sqrt(y / count); LOGGER.debug( desc + ": " + durations.get(0) + " first; " + avg + " +- " + stddev + "; " + coreDurations.get(0) + " min; " + coreDurations.get(coreDurations.size() - 1) + " max; " + durationsString + " millis"); } } } // End PerformanceTest.java mondrian-3.4.1/testsrc/main/mondrian/test/Main.java0000644000175000017500000004465511735330606022174 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1998-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. // // jhyde, 21 January, 1999 */ package mondrian.test; import mondrian.calc.impl.ConstantCalcTest; import mondrian.olap.*; import mondrian.olap.fun.*; import mondrian.olap.fun.vba.ExcelTest; import mondrian.olap.fun.vba.VbaTest; import mondrian.olap.type.TypeTest; import mondrian.rolap.*; import mondrian.rolap.agg.*; import mondrian.rolap.aggmatcher.*; import mondrian.rolap.sql.SelectNotInGroupByTest; import mondrian.rolap.sql.SqlQueryTest; import mondrian.test.build.CodeComplianceTest; import mondrian.test.clearview.*; import mondrian.test.comp.ResultComparatorTest; import mondrian.udf.CurrentDateMemberUdfTest; import mondrian.udf.NullValueTest; import mondrian.util.*; import mondrian.xmla.*; import mondrian.xmla.impl.DynamicDatasourceXmlaServletTest; import mondrian.xmla.test.XmlaTest; import junit.framework.Test; import junit.framework.*; import org.apache.log4j.Logger; import java.io.PrintWriter; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Enumeration; import java.util.*; import java.util.regex.Pattern; /** * Main test suite for Mondrian. * *

The {@link #suite()} method returns a suite which contains all other * Mondrian tests. * * @author jhyde */ public class Main extends TestSuite { private static final Logger logger = Logger.getLogger(Main.class); /* * Scratch area to store information on the emerging test suite. */ private static Map testSuiteInfo = new HashMap(); private static final boolean RUN_OPTIONAL_TESTS = false; /** * Entry point to run test suite from the command line. * * @param args Command-line parameters. */ public static void main(String[] args) { new Main().runSafe(args); } /** * Runs the suite, catching any exceptions and printing their stack trace. * * @param args Command-line arguments */ private void runSafe(String[] args) { try { run(args); } catch (Exception e) { PrintWriter pw = new PrintWriter(System.out); pw.println("mondrian.test.Main received exception:"); String[] errors = Util.convertStackToString(e); for (String error : errors) { pw.println(error); } pw.flush(); System.exit(1); } } /** * Creates and runs the root test suite. * * @param args Command-line arguments * @throws Exception on error */ private void run(String[] args) throws Exception { final MondrianProperties properties = MondrianProperties.instance(); Test test = suite(); if (args.length == 1 && args[0].equals("-l")) { /* * Only lists the tests to run if invoking ant test-nobuild next. */ return; } if (properties.Warmup.get()) { System.out.println("Starting warmup run..."); MondrianTestRunner runner = new MondrianTestRunner(); TestResult tres = runner.doRun(test); if (!tres.wasSuccessful()) { System.out.println( "Warmup run failed. Regular tests will not be run."); System.exit(1); } System.out.println("Warmup run complete. Starting regular run..."); } MondrianTestRunner runner = new MondrianTestRunner(); runner.setIterations(properties.Iterations.get()); System.out.println("Iterations=" + properties.Iterations.get()); runner.setVUsers(properties.VUsers.get()); runner.setTimeLimit(properties.TimeLimit.get()); TestResult tres = runner.doRun(test); if (!tres.wasSuccessful()) { System.exit(1); } } /** * Creates a TestSuite to test the whole of mondrian. Methods with the * signature public static Test suite() are recognized * automatically by JUnit test-harnesses; see {@link TestSuite}. * * @return test suite * @throws Exception on error */ public static Test suite() throws Exception { MondrianProperties properties = MondrianProperties.instance(); String testName = properties.TestName.get(); String testClass = properties.TestClass.get(); System.out.println("testName: " + testName); System.out.println("testClass: " + testClass); System.out.println( "java.version: " + System.getProperty("java.version")); TestSuite suite = new TestSuite(); if (testClass != null && !testClass.equals("")) { //noinspection unchecked Class clazz = (Class) Class.forName(testClass); // use addTestSuite only if the class has test methods // Allows you to run individual queries with ResultComparatorTest boolean matchTestMethods = false; if (Test.class.isAssignableFrom(clazz)) { Method[] methods = clazz.getMethods(); for (int i = 0; i < methods.length && !matchTestMethods; i++) { matchTestMethods = methods[i].getName().startsWith("test"); } } if (matchTestMethods) { // e.g. testClass = "mondrian.test.FoodMartTestCase", // the name of a class which extends TestCase. We will invoke // every method which starts with 'test'. (If "testName" is set, // we'll filter this list later.) addTest(suite, clazz); } else { // e.g. testClass = "mondrian.olap.fun.BuiltinFunTable". Class // does not implement Test, so look for a 'public [static] // Test suite()' method. Method method = clazz.getMethod("suite", new Class[0]); TestCase target; if (Modifier.isStatic(method.getModifiers())) { target = null; } else { target = clazz.newInstance(); } Object o = method.invoke(target); addTest(suite, (Test) o, clazz.getName() + method.getName()); } } else { if (RUN_OPTIONAL_TESTS) { addTest(suite, SegmentLoaderTest.class); // 2f, 1e as of 13571 addTest(suite, AggGenTest.class); // passes addTest(suite, DefaultRuleTest.class); // passes addTest(suite, SelectNotInGroupByTest.class); addTest(suite, CVConcurrentMdxTest.class); addTest(suite, CacheHitTest.class); addTest(suite, ConcurrentMdxTest.class); addTest(suite, MemHungryTest.class, "suite"); addTest(suite, MultiDimTest.class, "suite"); addTest(suite, MultiDimVCTest.class, "suite"); addTest(suite, MultiLevelTest.class, "suite"); addTest(suite, MultiLevelVCTest.class, "suite"); addTest(suite, PartialCacheTest.class, "suite"); addTest(suite, PartialCacheVCTest.class, "suite"); addTest(suite, QueryAllTest.class, "suite"); addTest(suite, QueryAllVCTest.class, "suite"); addTest(suite, Base64Test.class); return suite; } addTest(suite, NativeFilterMatchingTest.class); addTest(suite, RolapConnectionTest.class); addTest(suite, FilteredIterableTest.class); addTest(suite, HighDimensionsTest.class); addTest(suite, IndexedValuesTest.class); addTest(suite, MemoryMonitorTest.class); addTest(suite, ObjectPoolTest.class); addTest(suite, Ssas2005CompatibilityTest.OldBehaviorTest.class); addTest(suite, DialectTest.class); addTest(suite, ResultComparatorTest.class, "suite"); addTest(suite, DrillThroughTest.class); addTest(suite, ScenarioTest.class); addTest(suite, BasicQueryTest.class); addTest(suite, SegmentCacheTest.class); addTest(suite, CVBasicTest.class, "suite"); addTest(suite, GrandTotalTest.class, "suite"); addTest(suite, HangerDimensionTest.class, "suite"); addTest(suite, MetricFilterTest.class, "suite"); addTest(suite, MiscTest.class, "suite"); addTest(suite, PredicateFilterTest.class, "suite"); addTest(suite, SubTotalTest.class, "suite"); addTest(suite, SummaryMetricPercentTest.class, "suite"); addTest(suite, SummaryTest.class, "suite"); addTest(suite, TopBottomTest.class, "suite"); addTest(suite, OrderTest.class, "suite"); addTest(suite, CacheControlTest.class); addTest(suite, MemberCacheControlTest.class); addTest(suite, FunctionTest.class); addTest(suite, CurrentDateMemberUdfTest.class); addTest(suite, PartialSortTest.class); addTest(suite, VbaTest.class); addTest(suite, ExcelTest.class); addTest(suite, HierarchyBugTest.class); addTest(suite, ScheduleTest.class); addTest(suite, UtilTestCase.class); addTest(suite, PartiallyOrderedSetTest.class); addTest(suite, Olap4jTest.class); addTest(suite, SortTest.class); if (isRunOnce()) { addTest(suite, TestAggregationManager.class); } addTest(suite, VirtualCubeTest.class); addTest(suite, ParameterTest.class); addTest(suite, AccessControlTest.class); addTest(suite, ParserTest.class); addTest(suite, CustomizedParserTest.class); addTest(suite, SolveOrderScopeIsolationTest.class); addTest(suite, ParentChildHierarchyTest.class); addTest(suite, Olap4jTckTest.class, "suite"); addTest(suite, MondrianServerTest.class); addTest(suite, XmlaBasicTest.class); addTest(suite, XmlaErrorTest.class); addTest(suite, XmlaExcel2000Test.class); addTest(suite, XmlaExcelXPTest.class); addTest(suite, XmlaExcel2007Test.class); addTest(suite, XmlaCognosTest.class); addTest(suite, XmlaTabularTest.class); addTest(suite, XmlaTests.class); addTest(suite, DynamicDatasourceXmlaServletTest.class); addTest(suite, XmlaTest.class, "suite"); if (isRunOnce()) { addTest(suite, TestCalculatedMembers.class); } addTest(suite, CompoundSlicerTest.class); addTest(suite, RaggedHierarchyTest.class); addTest(suite, NonEmptyPropertyForAllAxisTest.class); addTest(suite, InlineTableTest.class); addTest(suite, CompatibilityTest.class); addTest(suite, CaptionTest.class); addTest(suite, UdfTest.class); addTest(suite, NullValueTest.class); addTest(suite, NamedSetTest.class); addTest(suite, PropertiesTest.class); addTest(suite, MultipleHierarchyTest.class); addTest(suite, I18nTest.class); addTest(suite, FormatTest.class); addTest(suite, ParallelTest.class); addTest(suite, SchemaTest.class); addTest(suite, PerformanceTest.class); // GroupingSetQueryTest must be run before any test derived from // CsvDBTestCase addTest(suite, GroupingSetQueryTest.class); addTest(suite, CmdRunnerTest.class); addTest(suite, DataSourceChangeListenerTest.class); addTest(suite, ModulosTest.class); addTest(suite, PrimeFinderTest.class); addTest(suite, CellKeyTest.class); addTest(suite, RolapAxisTest.class); addTest(suite, CrossJoinTest.class); if (Bug.BugMondrian503Fixed) { addTest(suite, RolapResultTest.class); } addTest(suite, ConstantCalcTest.class); addTest(suite, SharedDimensionTest.class); addTest(suite, CellPropertyTest.class); addTest(suite, QueryTest.class); addTest(suite, RolapSchemaReaderTest.class); addTest(suite, RolapCubeTest.class); addTest(suite, NullMemberRepresentationTest.class); addTest(suite, IgnoreUnrelatedDimensionsTest.class); addTest( suite, IgnoreMeasureForNonJoiningDimensionInAggregationTest.class); addTest(suite, SetFunDefTest.class); addTest(suite, VisualTotalsTest.class); addTest(suite, AggregationOnDistinctCountMeasuresTest.class); addTest(suite, NonCollapsedAggTest.class); addTest(suite, BitKeyTest.class); addTest(suite, TypeTest.class); addTest(suite, SteelWheelsSchemaTest.class); addTest(suite, MultipleColsInTupleAggTest.class); addTest(suite, DynamicSchemaProcessorTest.class); addTest(suite, MonitorTest.class); addTest(suite, CodeComplianceTest.class); boolean testNonEmpty = isRunOnce(); if (!MondrianProperties.instance().EnableNativeNonEmpty.get()) { testNonEmpty = false; } if (!MondrianProperties.instance().EnableNativeCrossJoin.get()) { testNonEmpty = false; } if (testNonEmpty) { addTest(suite, NonEmptyTest.class); addTest(suite, FilterTest.class); addTest(suite, NativizeSetFunDefTest.class); } else { logger.warn("skipping NonEmptyTests"); } addTest(suite, FastBatchingCellReaderTest.class); addTest(suite, SqlQueryTest.class); if (MondrianProperties.instance().EnableNativeCrossJoin.get()) { addTest(suite, BatchedFillTest.class, "suite"); } else { logger.warn("skipping BatchedFillTests"); } } if (testName != null && !testName.equals("")) { // Filter the suite, so that only tests whose names match // "testName" (in its entirety) will be run. Pattern testPattern = Pattern.compile(testName); suite = copySuite(suite, testPattern); } String testInfo = testSuiteInfo.get(suite); if (testInfo != null && testInfo.length() > 0) { System.out.println(testInfo); } else { System.out.println( "No tests to run. Check mondrian.properties setting."); } System.out.flush(); return suite; } /** * Checks to see if the tests are running one user, one iteration. * Some tests are not thread safe so have to be skipped if this is not true. * * @return whether the tests are run with one user, one iteration */ private static boolean isRunOnce() { final MondrianProperties properties = MondrianProperties.instance(); return !properties.Warmup.get() && properties.VUsers.get() == 1 && properties.Iterations.get() == 1; } /** * Makes a copy of a suite, filtering certain tests. * * @param suite Test suite * @param testPattern Regular expression of name of tests to include * @return copy of test suite * @throws Exception on error */ private static TestSuite copySuite(TestSuite suite, Pattern testPattern) throws Exception { TestSuite newSuite = new TestSuite(suite.getName()); Enumeration tests = suite.tests(); while (tests.hasMoreElements()) { Test test = (Test) tests.nextElement(); if (test instanceof TestCase) { TestCase testCase = (TestCase) test; final String testName = testCase.getName(); if (testPattern == null || testPattern.matcher(testName).matches()) { addTest(newSuite, test, suite.getName() + testName); } } else if (test instanceof TestSuite) { TestSuite subSuite = copySuite((TestSuite) test, testPattern); if (subSuite.countTestCases() > 0) { addTest(newSuite, subSuite, subSuite.getName()); } } else { // some other kind of test addTest(newSuite, test, " "); } } return newSuite; } private static void addTest( TestSuite suite, Class testClass) throws Exception { int startTestCount = suite.countTestCases(); suite.addTestSuite(testClass); int endTestCount = suite.countTestCases(); printTestInfo(suite, testClass.getName(), startTestCount, endTestCount); } private static void addTest( TestSuite suite, Class testClass, String testMethod) throws Exception { Method method = testClass.getMethod(testMethod); Object o = method.invoke(null); int startTestCount = suite.countTestCases(); suite.addTest((Test) o); int endTestCount = suite.countTestCases(); printTestInfo(suite, testClass.getName(), startTestCount, endTestCount); } private static void addTest( TestSuite suite, Test tests, String testClassName) throws Exception { int startTestCount = suite.countTestCases(); suite.addTest(tests); int endTestCount = suite.countTestCases(); printTestInfo(suite, testClassName, startTestCount, endTestCount); } private static void printTestInfo( TestSuite suite, String testClassName, int startCount, int endCount) { String testInfo = testSuiteInfo.get(suite); String newTestInfo = "[" + startCount + " - " + endCount + "] : " + testClassName + "\n"; if (testInfo == null) { testInfo = newTestInfo; } else { testInfo += newTestInfo; } testSuiteInfo.put(suite, testInfo); } } // End Main.java mondrian-3.4.1/testsrc/main/mondrian/calc/0000755000175000017500000000000011735330606020352 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/calc/impl/0000755000175000017500000000000011735330606021313 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/calc/impl/ConstantCalcTest.java0000644000175000017500000000205611735330606025375 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.calc.impl; import mondrian.olap.fun.FunUtil; import mondrian.olap.type.NullType; import junit.framework.TestCase; /** * Test for ConstantCalc * @author Matt */ public class ConstantCalcTest extends TestCase { public void testNullEvaluatesToConstantDoubleNull() { ConstantCalc constantCalc = new ConstantCalc(new NullType(), null); assertEquals(FunUtil.DoubleNull, constantCalc.evaluateDouble(null)); } public void testNullEvaluatesToConstantIntegerNull() { ConstantCalc constantCalc = new ConstantCalc(new NullType(), null); assertEquals(FunUtil.IntegerNull, constantCalc.evaluateInteger(null)); } } // End ConstantCalcTest.java mondrian-3.4.1/testsrc/main/mondrian/udf/0000755000175000017500000000000011735330606020226 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/udf/MockCurrentDateMember.java0000644000175000017500000000155311735330606025257 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2010 Pentaho // All Rights Reserved. */ package mondrian.udf; import mondrian.olap.Evaluator; import java.util.Calendar; import java.util.Date; public class MockCurrentDateMember extends CurrentDateMemberExactUdf { public MockCurrentDateMember() { super(); } @Override Date getDate(Evaluator evaluator, Argument[] arguments) { Calendar cal = Calendar.getInstance(); cal.set(1997, 1, 1); return new Date(cal.getTimeInMillis()); } @Override public String getName() { return "MockCurrentDateMember"; } } // End MockCurrentDateMember.java mondrian-3.4.1/testsrc/main/mondrian/udf/CurrentDateMemberUdfTest.java0000644000175000017500000000273611735330606025750 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2011 Pentaho // All Rights Reserved. */ package mondrian.udf; import mondrian.test.FoodMartTestCase; import mondrian.test.TestContext; /** * Tests the CurrentDateMemberUdf class. * * @author Luc Boudreau */ public class CurrentDateMemberUdfTest extends FoodMartTestCase { public CurrentDateMemberUdfTest() { super(); } public CurrentDateMemberUdfTest(String name) { super(name); } public void testCurrentDateMemberUdf() { TestContext context = TestContext.instance().create( null, null, null, null, " ", null); context.assertQueryReturns( "SELECT NON EMPTY {[Measures].[Org Salary]} ON COLUMNS, " + "NON EMPTY {MockCurrentDateMember([Time].[Time], \"[yyyy]\")} ON ROWS " + "FROM [HR] ", "Axis #0:\n" + "{}\n" + "Axis #1:\n" + "{[Measures].[Org Salary]}\n" + "Axis #2:\n" + "{[Time].[1997]}\n" + "Row #0: $39,431.67\n"); } } // End CurrentDateMemberUdfTest.java mondrian-3.4.1/testsrc/main/mondrian/udf/NullValueTest.java0000644000175000017500000000416011735330606023641 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2007 Pentaho // All Rights Reserved. // // jhyde, Feb 14, 2003 */ package mondrian.udf; import mondrian.test.FoodMartTestCase; /** * NullValueTest is a test case which tests simple queries * expressions. * * @author Richard M. Emberson * @since Mar 01 2007 */ public class NullValueTest extends FoodMartTestCase { public NullValueTest() { super(); } public NullValueTest(String name) { super(name); } public void testNullValue() { String s = executeExpr(" NullValue()/NullValue() "); assertEquals("", s); s = executeExpr(" NullValue()/NullValue() = NULL "); assertEquals("false", s); boolean hasException = false; try { s = executeExpr(" NullValue() IS NULL "); } catch (Exception ex) { hasException = true; } assertTrue(hasException); // I believe that these IsEmpty results are correct. // The NullValue function does not represent a cell. s = executeExpr(" IsEmpty(NullValue()) "); assertEquals("false", s); // NullValue()/NullValue() evaluates to DoubleNull // but DoubleNull evaluates to null, so this seems // to be broken?? // s = executeExpr(" IsEmpty(NullValue()/NullValue()) "); // assertEquals("false", s); s = executeExpr(" 4 + NullValue() "); assertEquals("4", s); s = executeExpr(" NullValue() - 4 "); assertEquals("-4", s); s = executeExpr(" 4*NullValue() "); assertEquals("", s); s = executeExpr(" NullValue()*4 "); assertEquals("", s); s = executeExpr(" 4/NullValue() "); assertEquals("Infinity", s); s = executeExpr(" NullValue()/4 "); assertEquals("", s); /* */ } } // End NullValueTest.java mondrian-3.4.1/testsrc/main/mondrian/util/0000755000175000017500000000000011735330606020425 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/main/mondrian/util/MemoryMonitorTest.java0000644000175000017500000002654711735330606024766 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho and others // All Rights Reserved. */ package mondrian.util; import mondrian.calc.ResultStyle; import mondrian.olap.*; import mondrian.test.FoodMartTestCase; import java.util.ArrayList; import java.util.List; /** * Test case for {@link ObjectPool}. * * @author Richard Emberson */ public class MemoryMonitorTest extends FoodMartTestCase { static final int PERCENT_100 = 100; protected static int convertThresholdToPercentage( final long threshold, final long maxMemory) { return (int) ((PERCENT_100 * threshold) / maxMemory); } /** * Get the difference between the maximum memory and the used memory * and divide that by 1000. This is the size of allocation chunks. * Keep allocating chunks until an OutOfMemoryError is * created. */ public boolean causeGC(MemoryMonitor mm) { final int nosOfChunks = 1000; long maxMemory = mm.getMaxMemory(); long usedMemory = mm.getUsedMemory(); long delta = (maxMemory - usedMemory) / nosOfChunks; if (delta == 0) { // delta has to be greater than zero so pick 1k delta = 1024; } else if (delta > Integer.MAX_VALUE) { // otherwise we could get a negative value after // the cast to int delta = Integer.MAX_VALUE; } final int size = 2 * nosOfChunks; Object[] byteArrayHolder = new Object[size]; for (int i = 0; i < size; i++) { try { byteArrayHolder[i] = new java.lang.ref.SoftReference(new byte[(int) delta]); } catch (java.lang.OutOfMemoryError ex) { return true; } } // If any member is empty, then its been GCed. for (int i = 0; i < size; i++) { java.lang.ref.SoftReference ref = (java.lang.ref.SoftReference) byteArrayHolder[i]; if (ref.get() == null) { return true; } } return false; } protected boolean enabled; public MemoryMonitorTest() { super(); } public MemoryMonitorTest(String name) { super(name); } protected void setUp() throws Exception { super.setUp(); enabled = MondrianProperties.instance().MemoryMonitor.get(); } protected void tearDown() throws Exception { super.tearDown(); } /* Does not work without the notify on add feature. public void testZeroUsage() throws Exception { if (Util.PreJdk15 || !enabled) { return; } class Listener implements MemoryMonitor.Listener { boolean wasNotified = false; Listener() { } public void memoryUsageNotification(long used, long max) { wasNotified = true; } } Listener listener = new Listener(); MemoryMonitor mm = MemoryMonitorFactory.getMemoryMonitor(); try { // We use a percentage of '0' because we know that value is // less than or equal to the lowest JVM memory usage. mm.addListener(listener, 0); if (! listener.wasNotified) { fail("Listener callback not called"); } } finally { mm.removeListener(listener); } } */ public void testDeltaUsage() throws Exception { if (Util.PreJdk15 || !enabled) { return; } class Listener implements MemoryMonitor.Listener { boolean wasNotified = false; Listener() { } public void memoryUsageNotification(long used, long max) { wasNotified = true; } } Listener listener = new Listener(); MemoryMonitor mm = MemoryMonitorFactory.getMemoryMonitor(); // we will set a percentage slightly above the current // used level, and then allocate some objects that will // force a notification. long maxMemory = mm.getMaxMemory(); long usedMemory = mm.getUsedMemory(); int currentPercentage = convertThresholdToPercentage(usedMemory, maxMemory); int delta = (int) (maxMemory - usedMemory) / 10; int percentage = convertThresholdToPercentage(delta, maxMemory); try { byte[][] bytes = new byte[10][]; mm.addListener(listener, percentage + currentPercentage); for (int i = 0; i < bytes.length; i++) { bytes[i] = new byte[delta]; if (listener.wasNotified) { bytes = null; break; } } if (! listener.wasNotified) { fail("Listener callback not called"); } } finally { mm.removeListener(listener); } } /* Does not work without the notify on add feature. public void testUpdatePercent() throws Exception { if (Util.PreJdk15 || !enabled) { return; } class Listener implements MemoryMonitor.Listener { boolean wasNotified = false; Listener() { } public void memoryUsageNotification(long used, long max) { wasNotified = true; } } Listener listener = new Listener(); // we will set a percentage well above the current // used level, and then allocate an object, and then // update percentage to below new usage level. long maxMemory = mm.getMaxMemory(); long usedMemory = mm.getUsedMemory(); int currentPercentage = convertThresholdToPercentage(usedMemory, maxMemory); int delta = (int) (maxMemory - usedMemory)/10; int percentage = convertThresholdToPercentage(delta, maxMemory); try { mm.addListener(listener, 2 * percentage + currentPercentage); byte[] bytes = new byte[(int) (1.5 * delta)]; if (listener.wasNotified) { fail("Listener callback was called"); } mm.updateListenerThreshold(listener, percentage + currentPercentage); if (! listener.wasNotified) { fail("Listener callback was not called"); } } finally { mm.removeListener(listener); } } */ private static int THRESHOLD_PERCENTAGE = 90; public static class TestMM extends NotificationMemoryMonitor { public TestMM() { } public int getDefaultThresholdPercentage() { return THRESHOLD_PERCENTAGE; } } public static class TestMM2 extends NotificationMemoryMonitor { public TestMM2() { } public int getDefaultThresholdPercentage() { return 98; } } /** * Run this by itself and it works across 2 orders of magnitude. * Run it with other tests and its hard to pick the right * values for the percentage and how much to allocate for it * to always work. * * @throws Exception */ public void _testQuery() throws Exception { if (Util.PreJdk15 || !enabled) { return; } class Listener implements MemoryMonitor.Listener { boolean wasNotified = false; Listener() { } public void memoryUsageNotification(long used, long max) { wasNotified = true; } } Listener listener = new Listener(); final String queryString = "select \n" + "{ \n" /* + "[Measures].[Unit Sales], \n" + "[Measures].[Store Cost], \n" */ + "[Measures].[Store Sales], \n" + "[Measures].[Sales Count], \n" + "[Measures].[Customer Count] \n" + "} \n" + "ON COLUMNS, \n" + "Crossjoin(\n" + " Descendants([Store].[All Stores]), \n" + " Descendants([Product].[All Products]) \n" + ") \n" + "ON ROWS \n" + "from [Sales]"; List list = new ArrayList(); MemoryMonitor mm = null; try { MemoryMonitorFactory.setThreadLocalClassName( TestMM.class.getName()); mm = MemoryMonitorFactory.getMemoryMonitor(); boolean b = causeGC(mm); //System.out.println("causeGC="+b); long neededMemory = 5000000; long maxMemory = mm.getMaxMemory(); long usedMemory = mm.getUsedMemory(); //System.out.println("maxMemory ="+maxMemory); //System.out.println("usedMemory="+usedMemory); // the 10% here and 90% below are related: change one, change // the other. long tenPercentMaxMemory = maxMemory / 10; long level = maxMemory - tenPercentMaxMemory; long buf; //System.out.println("level ="+level); if (level > usedMemory) { buf = level - usedMemory - neededMemory; if (buf <= 0) { buf = level - usedMemory; } //int currentPercentage = convertThresholdToPercentage(level, maxMemory); //System.out.println("currentPercentage="+currentPercentage); THRESHOLD_PERCENTAGE = 90; } else { buf = 0; double dp = (100.0 * (maxMemory - usedMemory)) / maxMemory; THRESHOLD_PERCENTAGE = 100 - (int) Math.ceil(dp); } //System.out.println("buf ="+buf); //System.out.println("THRESHOLD_PERCENTAGE="+THRESHOLD_PERCENTAGE); byte[] bytes = new byte[(int) ((buf > 0) ? buf : 0)]; mm.addListener(listener); // Check to see if we have been notified. // We might be notified if memory usage is already above 90%!! if (listener.wasNotified) { //System.out.println("allready notified"); return; } Connection conn = getConnection(); final int MAX = 100; //System.out.println("BEFORE"); for (int i = 0; i < MAX; i++) { //System.out.println("i=" +i); Query query = conn.parseQuery(queryString); query.setResultStyle(ResultStyle.MUTABLE_LIST); Result result = conn.execute(query); list.add(result); if (listener.wasNotified) { // should never happen break; } } fail("Memory Notification Exception did not occur"); } catch (MemoryLimitExceededException ex) { if (! listener.wasNotified) { fail("Listener callback not called"); } // pass //System.out.println("MemoryMonitorTest: PASS"); } finally { if (mm != null) { mm.removeListener(listener); } for (Result result : list) { result.close(); } MemoryMonitorFactory.clearThreadLocalClassName(); //System.out.println("MemoryMonitorTest: BOTTOM"); //System.out.flush(); } } } // End MemoryMonitorTest.java mondrian-3.4.1/testsrc/main/mondrian/util/ObjectPoolTest.java0000644000175000017500000002333011735330606024171 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho and others // All Rights Reserved. */ package mondrian.util; import mondrian.olap.Util; import junit.framework.TestCase; import java.util.*; /** * Test case for {@link ObjectPool}. * * @author Richard Emberson */ public class ObjectPoolTest extends TestCase { public ObjectPoolTest() { super(); } public ObjectPoolTest(String name) { super(name); } static class KeyValue { long key; Object value; KeyValue(long key, Object value) { this.key = key; this.value = value; } public int hashCode() { return (int)(key ^ (key >>> 32)); } public boolean equals(Object o) { return (o instanceof KeyValue) ? (((KeyValue) o).key == this.key) : false; } public String toString() { return value.toString(); } } public void testString() throws Exception { // for reasons unknown this fails with java4 if (Util.PreJdk15) { return; } ObjectPool strings = new ObjectPool(); int nos = 100000; String[] ss1 = genStringsArray(nos); for (int i = 0; i < nos; i++) { strings.add(ss1[i]); } assertEquals("size not equal", nos, strings.size()); // second array of strings, same as the first but different objects String[] ss2 = genStringsArray(nos); for (int i = 0; i < nos; i++) { String s = strings.add(ss2[i]); assertEquals("string not equal: " + s, s, ss2[i]); // REVIEW jvs 16-Jan-2008: This failed for me when // I ran with a 1GB JVM heap size on JDK 1.5, probably // because of interning (I tried changing genStringsList to add a // gratuitous String constructor call, but that did not help). If // there's a reason this test is on strings explicitly, then // this needs to stay disabled; if the datatype can be changed // to something which doesn't have any magic interning, then // it can be re-enabled. This probably explains the // Util.PreJdk15 "unknown reasons" above. /* assertFalse("same object", (s == ss2[i])); */ } strings.clear(); assertEquals("size not equal", 0, strings.size()); nos = 25; ss1 = genStringsArray(nos); for (int i = 0; i < nos; i++) { strings.add(ss1[i]); } assertEquals("size not equal", nos, strings.size()); List l = genStringsList(nos); Iterator it = strings.iterator(); while (it.hasNext()) { String s = it.next(); l.remove(s); } assertTrue("list not empty", l.isEmpty()); } public void testKeyValue() throws Exception { ObjectPool op = new ObjectPool(); int nos = 100000; KeyValue[] kv1 = genKeyValueArray(nos); for (int i = 0; i < nos; i++) { op.add(kv1[i]); } assertEquals("size not equal", nos, op.size()); // second array of KeyValues, same as the first but different objects KeyValue[] kv2 = genKeyValueArray(nos); for (int i = 0; i < nos; i++) { KeyValue kv = op.add(kv2[i]); assertEquals("KeyValue not equal: " + kv, kv, kv2[i]); assertFalse("same object", (kv == kv2[i])); } op.clear(); assertEquals("size not equal", 0, op.size()); nos = 25; kv1 = genKeyValueArray(nos); for (int i = 0; i < nos; i++) { op.add(kv1[i]); } assertEquals("size not equal", nos, op.size()); List l = genKeyValueList(nos); Iterator it = op.iterator(); while (it.hasNext()) { KeyValue kv = it.next(); l.remove(kv); } assertTrue("list not empty", l.isEmpty()); } /** * Tests ObjectPools containing large numbers of integer and string keys, * and makes sure they return the same results as HashSet. Optionally * measures performance. */ public void testLarge() { // Some typical results (2.4 GHz Intel dual-core). // Key type: Integer String // Implementation: ObjectPool HashSet ObjectPool HashSet // ========== ========== ========== ========== // With density=0.01, 298,477 distinct entries, 7,068 hits // 300,000 adds 221 ms 252 ms 293 ms 1013 ms // 700,000 gets 164 ms 148 ms 224 ms 746 ms // // With density=0.5, 236,022 distinct entries, 275,117 hits // 300,000 adds 175 ms 250 ms 116 ms 596 ms // 700,000 gets 147 ms 176 ms 190 ms 757 ms // // With density=0.999, 189,850 distinct entries, 442,618 hits // 300,000 adds 128 ms 185 ms 99 ms 614 ms // 700,000 gets 133 ms 184 ms 130 ms 830 ms checkLargeMulti(300000, 0.01, 700000, 298477, 7068); checkLargeMulti(300000, 0.5, 700000, 236022, 275117); checkLargeMulti(300000, 0.999, 700000, 189850, 442618); } private void checkLargeMulti( int entryCount, double density, int retrieveCount, int expectedDistinct, int expectedHits) { checkLarge( true, true, entryCount, density, retrieveCount, expectedDistinct, expectedHits); checkLarge( false, true, entryCount, density, retrieveCount, expectedDistinct, expectedHits); checkLarge( false, true, entryCount, density, retrieveCount, expectedDistinct, expectedHits); checkLarge( false, false, entryCount, density, retrieveCount, expectedDistinct, expectedHits); } private void checkLarge( boolean usePool, boolean intKey, int entryCount, double density, int retrieveCount, int expectedDistinct, int expectedHits) { final boolean print = false; final long t1 = System.currentTimeMillis(); assert density > 0 && density <= 1; int space = (int) (entryCount / density); ObjectPool objectPool = new ObjectPool(); HashSet set = new HashSet(); Random random = new Random(1234); int distinctCount = 0; final String longString = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyy"; for (int i = 0; i < entryCount; i++) { final Object key = intKey ? random.nextInt(space) : longString + random.nextInt(space); if (usePool) { if (objectPool.add(key) != null) { ++distinctCount; } } else { if (set.add(key)) { ++distinctCount; } } } final long t2 = System.currentTimeMillis(); int hitCount = 0; for (int i = 0; i < retrieveCount; i++) { final Object key = intKey ? random.nextInt(space) : longString + random.nextInt(space); if (usePool) { if (objectPool.contains(key)) { ++hitCount; } } else { if (set.contains(key)) { ++hitCount; } } } final long t3 = System.currentTimeMillis(); if (usePool) { // todo: assertEquals(expectedDistinct, objectPool.size()); distinctCount = objectPool.size(); } else { assertEquals(expectedDistinct, set.size()); } if (print) { System.out.println( "Using " + (usePool ? "ObjectPool" : "HashSet") + ", density=" + density + ", " + distinctCount + " distinct entries, " + hitCount + " hits"); System.out.println( entryCount + " adds took " + (t2 - t1) + " milliseconds"); System.out.println( retrieveCount + " gets took " + (t3 - t2) + " milliseconds"); } assertEquals(expectedDistinct, distinctCount); assertEquals(expectedHits, hitCount); } ///////////////////////////////////////////////////////////////////////// // helpers ///////////////////////////////////////////////////////////////////////// private static String[] genStringsArray(int nos) { List l = genStringsList(nos); return (String[]) l.toArray(new String[l.size()]); } private static List genStringsList(int nos) { List l = new ArrayList(nos); for (int i = 0; i < nos; i++) { l.add(Integer.valueOf(i).toString()); } return l; } private static KeyValue[] genKeyValueArray(int nos) { List l = genKeyValueList(nos); return (KeyValue[]) l.toArray(new KeyValue[l.size()]); } private static List genKeyValueList(int nos) { List l = new ArrayList(nos); for (int i = 0; i < nos; i++) { l.add(new KeyValue((long)i, Integer.valueOf(i))); } return l; } } // End ObjectPoolTest.java mondrian-3.4.1/testsrc/main/mondrian/util/PartiallyOrderedSetTest.java0000644000175000017500000003131211735330606026052 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho and others // All Rights Reserved. */ package mondrian.util; import mondrian.test.TestContext; import junit.framework.TestCase; import java.util.*; /** * Unit test for {@link PartiallyOrderedSet}. */ public class PartiallyOrderedSetTest extends TestCase { private static final boolean debug = false; private final int SCALE = 250; // 100, 1000, 3000 are also reasonable values final long seed = new Random().nextLong(); final Random random = new Random(seed); static final PartiallyOrderedSet.Ordering stringSubsetOrdering = new PartiallyOrderedSet.Ordering() { public boolean lessThan(String e1, String e2) { // e1 < e2 if every char in e1 is also in e2 for (int i = 0; i < e1.length(); i++) { if (e2.indexOf(e1.charAt(i)) < 0) { return false; } } return true; } }; // Integers, ordered by division. Top is 1, its children are primes, // etc. static final PartiallyOrderedSet.Ordering isDivisor = new PartiallyOrderedSet.Ordering() { public boolean lessThan(Integer e1, Integer e2) { return e2 % e1 == 0; } }; // Bottom is 1, parents are primes, etc. static final PartiallyOrderedSet.Ordering isDivisorInverse = new PartiallyOrderedSet.Ordering() { public boolean lessThan(Integer e1, Integer e2) { return e1 % e2 == 0; } }; // Ordered by bit inclusion. E.g. the children of 14 (1110) are // 12 (1100), 10 (1010) and 6 (0110). static final PartiallyOrderedSet.Ordering isBitSubset = new PartiallyOrderedSet.Ordering() { public boolean lessThan(Integer e1, Integer e2) { return (e2 & e1) == e2; } }; // Ordered by bit inclusion. E.g. the children of 14 (1110) are // 12 (1100), 10 (1010) and 6 (0110). static final PartiallyOrderedSet.Ordering isBitSuperset = new PartiallyOrderedSet.Ordering() { public boolean lessThan(Integer e1, Integer e2) { return (e2 & e1) == e1; } }; public PartiallyOrderedSetTest(String s) { super(s); } public void testPoset() { String empty = "''"; String abcd = "'abcd'"; PartiallyOrderedSet poset = new PartiallyOrderedSet(stringSubsetOrdering); assertEquals(0, poset.size()); final StringBuilder buf = new StringBuilder(); poset.out(buf); TestContext.assertEqualsVerbose( "PartiallyOrderedSet size: 0 elements: {\n" + "}", buf.toString()); poset.add("a"); printValidate(poset); poset.add("b"); printValidate(poset); poset.clear(); assertEquals(0, poset.size()); poset.add(empty); printValidate(poset); poset.add(abcd); printValidate(poset); assertEquals(2, poset.size()); assertEquals("['abcd']", poset.getNonChildren().toString()); assertEquals("['']", poset.getNonParents().toString()); final String ab = "'ab'"; poset.add(ab); printValidate(poset); assertEquals(3, poset.size()); assertEquals("[]", poset.getChildren(empty).toString()); assertEquals("['ab']", poset.getParents(empty).toString()); assertEquals("['ab']", poset.getChildren(abcd).toString()); assertEquals("[]", poset.getParents(abcd).toString()); assertEquals("['']", poset.getChildren(ab).toString()); assertEquals("['abcd']", poset.getParents(ab).toString()); // "bcd" is child of "abcd" and parent of "" final String bcd = "'bcd'"; poset.add(bcd); printValidate(poset); assertTrue(poset.isValid(false)); assertEquals("['']", poset.getChildren(bcd).toString()); assertEquals("['abcd']", poset.getParents(bcd).toString()); assertEquals("['ab', 'bcd']", poset.getChildren(abcd).toString()); buf.setLength(0); poset.out(buf); TestContext.assertEqualsVerbose( "PartiallyOrderedSet size: 4 elements: {\n" + " 'abcd' parents: [] children: ['ab', 'bcd']\n" + " 'ab' parents: ['abcd'] children: ['']\n" + " 'bcd' parents: ['abcd'] children: ['']\n" + " '' parents: ['ab', 'bcd'] children: []\n" + "}", buf.toString()); final String b = "'b'"; // ancestors of an element not in the set assertEqualsList("['ab', 'abcd', 'bcd']", poset.getAncestors(b)); poset.add(b); printValidate(poset); assertEquals("['abcd']", poset.getNonChildren().toString()); assertEquals("['']", poset.getNonParents().toString()); assertEquals("['']", poset.getChildren(b).toString()); assertEqualsList("['ab', 'bcd']", poset.getParents(b)); assertEquals("['']", poset.getChildren(b).toString()); assertEquals("['ab', 'bcd']", poset.getChildren(abcd).toString()); assertEquals("['b']", poset.getChildren(bcd).toString()); assertEquals("['b']", poset.getChildren(ab).toString()); assertEqualsList("['ab', 'abcd', 'bcd']", poset.getAncestors(b)); // descendants and ancestors of an element with no descendants assertEquals("[]", poset.getDescendants(empty).toString()); assertEqualsList( "['ab', 'abcd', 'b', 'bcd']", poset.getAncestors(empty)); // some more ancestors of missing elements assertEqualsList("['abcd']", poset.getAncestors("'ac'")); assertEqualsList("[]", poset.getAncestors("'z'")); assertEqualsList("['ab', 'abcd']", poset.getAncestors("'a'")); } public void testPosetTricky() { PartiallyOrderedSet poset = new PartiallyOrderedSet(stringSubsetOrdering); // A tricky little poset with 4 elements: // {a <= ab and ac, b < ab, ab, ac} poset.clear(); poset.add("'a'"); printValidate(poset); poset.add("'b'"); printValidate(poset); poset.add("'ac'"); printValidate(poset); poset.add("'ab'"); printValidate(poset); } public void testPosetBits() { final PartiallyOrderedSet poset = new PartiallyOrderedSet(isBitSuperset); poset.add(2112); // {6, 11} i.e. 64 + 2048 poset.add(2240); // {6, 7, 11} i.e. 64 + 128 + 2048 poset.add(2496); // {6, 7, 8, 11} i.e. 64 + 128 + 256 + 2048 printValidate(poset); poset.remove(2240); printValidate(poset); poset.add(2240); // {6, 7, 11} i.e. 64 + 128 + 2048 printValidate(poset); } public void testPosetBitsRemoveParent() { final PartiallyOrderedSet poset = new PartiallyOrderedSet(isBitSuperset); poset.add(66); // {bit 2, bit 6} poset.add(68); // {bit 3, bit 6} poset.add(72); // {bit 4, bit 6} poset.add(64); // {bit 6} printValidate(poset); poset.remove(64); // {bit 6} printValidate(poset); } public void testDivisorPoset() { PartiallyOrderedSet integers = new PartiallyOrderedSet(isDivisor, range(1, 1000)); assertEquals( "[1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 24, 30, 40, 60]", new TreeSet(integers.getDescendants(120)).toString()); assertEquals( "[240, 360, 480, 600, 720, 840, 960]", new TreeSet(integers.getAncestors(120)).toString()); assertTrue(integers.getDescendants(1).isEmpty()); assertEquals( 998, integers.getAncestors(1).size()); assertTrue(integers.isValid(true)); } public void testDivisorSeries() { checkPoset(isDivisor, debug, range(1, SCALE * 3), false); } public void testDivisorRandom() { boolean ok = false; try { checkPoset( isDivisor, debug, random(random, SCALE, SCALE * 3), false); ok = true; } finally { if (!ok) { System.out.println("Random seed: " + seed); } } } public void testDivisorRandomWithRemoval() { boolean ok = false; try { checkPoset( isDivisor, debug, random(random, SCALE, SCALE * 3), true); ok = true; } finally { if (!ok) { System.out.println("Random seed: " + seed); } } } public void testDivisorInverseSeries() { checkPoset(isDivisorInverse, debug, range(1, SCALE * 3), false); } public void testDivisorInverseRandom() { boolean ok = false; try { checkPoset( isDivisorInverse, debug, random(random, SCALE, SCALE * 3), false); ok = true; } finally { if (!ok) { System.out.println("Random seed: " + seed); } } } public void testDivisorInverseRandomWithRemoval() { boolean ok = false; try { checkPoset( isDivisorInverse, debug, random(random, SCALE, SCALE * 3), true); ok = true; } finally { if (!ok) { System.out.println("Random seed: " + seed); } } } public void testSubsetSeries() { checkPoset(isBitSubset, debug, range(1, SCALE / 2), false); } public void testSubsetRandom() { boolean ok = false; try { checkPoset( isBitSubset, debug, random(random, SCALE / 4, SCALE), false); ok = true; } finally { if (!ok) { System.out.println("Random seed: " + seed); } } } private void printValidate(PartiallyOrderedSet poset) { if (debug) { dump(poset); } assertTrue(poset.isValid(debug)); } public void checkPoset( PartiallyOrderedSet.Ordering ordering, boolean debug, Iterable generator, boolean remove) { final PartiallyOrderedSet poset = new PartiallyOrderedSet(ordering); int n = 0; int z = 0; if (debug) { dump(poset); } for (int i : generator) { if (remove && z++ % 2 == 0) { if (debug) { System.out.println("remove " + i); } poset.remove(i); if (debug) { dump(poset); } continue; } if (debug) { System.out.println("add " + i); } poset.add(i); if (debug) { dump(poset); } assertEquals(++n, poset.size()); if (i < 100) { if (!poset.isValid(false)) { dump(poset); } assertTrue(poset.isValid(true)); } } assertTrue(poset.isValid(true)); final StringBuilder buf = new StringBuilder(); poset.out(buf); assertTrue(buf.length() > 0); } private void dump(PartiallyOrderedSet poset) { final StringBuilder buf = new StringBuilder(); poset.out(buf); System.out.println(buf); } private static Collection range( final int start, final int end) { return new AbstractList() { @Override public Integer get(int index) { return start + index; } @Override public int size() { return end - start; } }; } private static Iterable random( Random random, final int size, final int max) { final Set set = new LinkedHashSet(); while (set.size() < size) { set.add(random.nextInt(max) + 1); } return set; } private static void assertEqualsList(String expected, List ss) { assertEquals( expected, new TreeSet(ss).toString()); } } // End PartiallyOrderedSetTest.java mondrian-3.4.1/testsrc/main/mondrian/util/FormatTest.java0000644000175000017500000007724011735330606023372 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2012 Pentaho // All Rights Reserved. */ package mondrian.util; import mondrian.olap.Util; import mondrian.test.I18nTest; import junit.framework.TestCase; import java.math.BigDecimal; import java.util.*; /** * Unit test for {@link Format}. * * @author jhyde * @since May 26, 2006 */ public class FormatTest extends TestCase { private final Format.FormatLocale localeFra = Format.createLocale( '.', // thousandSeparator = ',' in en ',', // decimalPlaceholder = '.' in en "-", // dateSeparator = "/" in en "#", // timeSeparator = ":" in en "FF", // currencySymbol = "$" in en // "#.##0-00FF", // currencyFormat = "$#,##0.##" in en "#,##0.00FF", // currencyFormat = "$#,##0.##" in en new String[] { "", "Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam"}, new String[] { "", "Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"}, new String[] { "Jan", "Fev", "Mar", "Avr", "Mai", "Jui", "Jui", "Aou", "Sep", "Oct", "Nov", "Dec", ""}, new String[] { "Janvier", "Fevrier", "Mars", "Avril", "Mai", "Juin", "Juillet", "Aout", "Septembre", "Octobre", "Novembre", "Decembre", ""}, Locale.FRENCH); /** Locale gleaned from Java's German locale. */ private final Format.FormatLocale localeDe = Format.createLocale( Locale.GERMANY); final Number d = new BigDecimal("3141592.653589793"); // note that month #3 == April final Date date = makeCalendar(1969, 4, 29, 20, 9, 6); // 06:05:04 am, 7th sep 2010 final Date date2 = makeCalendar(2010, 9, 7, 6, 5, 4); /** * Exhaustive tests on various numbers. */ public void testNumbers() { checkNumbersInLocale(null); } public void testFrenchNumbers() { checkNumbersInLocale(localeFra); } private void checkNumbersInLocale(Format.FormatLocale locale) { // format +6 -6 0 .6 null // ============== =========== ============ =========== =========== ==== checkNumbers( "", "6", "-6", "0", "0.6", "", locale); checkNumbers( "0", "6", "-6", "0", "1", "", locale); checkNumbers( "0.00", "6.00", "-6.00", "0.00", "0.60", "", locale); checkNumbers( "#,##0", "6", "-6", "0", "1", "", locale); checkNumbers( "#,##0.00;;;N", "6.00", "-6.00", "0.00", "0.60", "N", locale); checkNumbers( "$#,##0;($#,##0)", "$6", "($6)", "$0", "$1", "", locale); checkNumbers( "$#,##0.00;($#,##0.00)", "$6.00", "($6.00)", "$0.00", "$0.60", "", locale); checkNumbers( "0%", "600%", "-600%", "0%", "60%", "", locale); checkNumbers( "0.00%", "600.00%", "-600.00%", "0.00%", "60.00%", "", locale); checkNumbers( "0.00E+00", "6.00E+00", "-6.00E+00", "0.00E+00", "6.00E-01", "", locale); checkNumbers( "0.00E-00", "6.00E00", "-6.00E00", "0.00E00", "6.00E-01", "", locale); checkNumbers( "$#,##0;;\\Z\\e\\r\\o", "$6", "-$6", "Zero", "$1", "", locale); checkNumbers( "#,##0.0 USD", "6.0 USD", "-6.0 USD", "0.0 USD", "0.6 USD", "", locale); checkNumbers( "General Number", "6", "-6", "0", "0.6", "", locale); checkNumbers( "Currency", "$6.00", "($6.00)", "$0.00", "$0.60", "", locale); checkNumbers( "Fixed", "6", "-6", "0", "1", "", locale); checkNumbers( "Standard", "6", "-6", "0", "1", "", locale); checkNumbers( "Percent", "600.00%", "-600.00%", "0.00%", "60.00%", "", locale); checkNumbers( "Scientific", "6.00e+00", "-6.00e+00", "0.00e+00", "6.00e-01", "", locale); checkNumbers( "True/False", "True", "True", "False", "True", "False", locale); checkNumbers( "On/Off", "On", "On", "Off", "On", "Off", locale); checkNumbers( "Yes/No", "Yes", "Yes", "No", "Yes", "No", locale); } private void checkNumbers( String format, String result6, String resultNeg6, String result0, String resultPoint6, String resultEmpty, Format.FormatLocale locale) { checkNumber(locale, format, new BigDecimal("6"), result6); checkNumber(locale, format, new BigDecimal("-6"), resultNeg6); checkNumber(locale, format, new BigDecimal("0"), result0); checkNumber(locale, format, new BigDecimal(".6"), resultPoint6); checkNumber(locale, format, null, resultEmpty); checkNumber(locale, format, 6L, result6); checkNumber(locale, format, -6L, resultNeg6); checkNumber(locale, format, 0L, result0); } private void checkNumber( Format.FormatLocale locale, String format, Number number, String expectedResult) { if (locale == localeFra) { expectedResult = convertToFrench(expectedResult, format); } checkFormat(locale, number, format, expectedResult); } private static String convertToFrench(String result, String format) { if (result.startsWith("(") && result.endsWith(")")) { result = result.substring(1, result.length() - 1); return "(" + convertToFrench(result, format) + ")"; } result = result.replace('.', '!'); result = result.replace(',', '.'); result = result.replace('!', ','); if (format.equals("Currency") && result.startsWith("$")) { result = result.substring(1) + "FF"; } return result; } public void testTrickyNumbers() { checkFormat(null, new BigDecimal("40.385"), "##0.0#", "40.39"); checkFormat(null, new BigDecimal("40.386"), "##0.0#", "40.39"); checkFormat(null, new BigDecimal("40.384"), "##0.0#", "40.38"); checkFormat(null, new BigDecimal("40.385"), "##0.#", "40.4"); checkFormat(null, new BigDecimal("40.38"), "##0.0#", "40.38"); checkFormat(null, new BigDecimal("-40.38"), "##0.0#", "-40.38"); checkFormat(null, new BigDecimal("0.040385"), "#0.###", "0.04"); checkFormat(null, new BigDecimal("0.040385"), "#0.000", "0.040"); checkFormat(null, new BigDecimal("0.040385"), "#0.####", "0.0404"); checkFormat(null, new BigDecimal("0.040385"), "00.####", "00.0404"); checkFormat(null, new BigDecimal("0.040385"), ".00#", ".04"); checkFormat(null, new BigDecimal("0.040785"), ".00#", ".041"); checkFormat(null, new BigDecimal("99.9999"), "##.####", "99.9999"); checkFormat(null, new BigDecimal("99.9999"), "##", "100"); checkFormat(null, new BigDecimal("99.9999"), "##.#", "100."); checkFormat(null, new BigDecimal("99.9999"), "##.###", "100."); checkFormat(null, new BigDecimal("99.9999"), "##.00#", "100.00"); checkFormat(null, new BigDecimal(".00099"), "#.00", ".00"); checkFormat(null, new BigDecimal(".00099"), "#.00#", ".001"); checkFormat(null, new BigDecimal("12.34"), "#.000##", "12.340"); // "Standard" must use thousands separator, and round checkFormat( null, new BigDecimal("1234567.89"), "Standard", "1,234,568"); // must use correct alternate for 0 checkFormat(null, new BigDecimal("0"), "$#,##0;;\\Z\\e\\r\\o", "Zero"); // If there is a '.' in the format string SSAS always prints it, even // if there are no digits right to decimal. checkFormat(null, new BigDecimal("23"), "#.#", "23."); checkFormat(null, new BigDecimal("0"), "#.#", "."); // escaped semicolon final String formatString = "$\\;#;(\\;#);\\;\\Z"; checkFormat(null, new BigDecimal("1"), formatString, "$;1"); checkFormat(null, new BigDecimal("-1"), formatString, "(;1)"); checkFormat(null, new BigDecimal("0"), formatString, ";Z"); checkFormat(null, null, formatString, ""); } /** * Test case for bug * MONDRIAN-186, "Small negative numbers are printed as '-0'". */ public void testSmallNegativeNumbers() { checkFormat(null, new BigDecimal("-0.006"), "#.0", ".0"); checkFormat(null, new BigDecimal("-0.006"), "#.00", "-.01"); checkFormat(null, new BigDecimal("-0.0500001"), "#.0", "-.1"); checkFormat(null, new BigDecimal("-0.0499999"), "#.0", ".0"); // Percent checkFormat(null, new BigDecimal("-0.00006"), "#.0%", ".0%"); checkFormat(null, new BigDecimal("-0.0006"), "#.0%", "-.1%"); checkFormat(null, new BigDecimal("-0.0004"), "#.0%", ".0%"); checkFormat(null, new BigDecimal("-0.0005"), "#.0%", "-.1%"); checkFormat(null, new BigDecimal("-0.0005000001"), "#.0%", "-.1%"); checkFormat(null, new BigDecimal("-0.00006"), "#.00%", "-.01%"); checkFormat(null, new BigDecimal("-0.00004"), "#.00%", ".00%"); checkFormat( null, new BigDecimal("-0.00006"), "00000.00%", "-00000.01%"); checkFormat(null, new BigDecimal("-0.00004"), "00000.00%", "00000.00%"); } /** * When there are format strings for positive and negative numbers, and * a number is too small to appear in either format string, it underflows * to 'Nil', and gets to use a third format. */ public void testNil() { // The +ve format gives "-0.01", but the negative format gives "0.0", // so we move onto the "Nil" format. checkFormat(null, new BigDecimal("-0.001"), "0.##;(0.##);Nil", "Nil"); checkFormat(null, new BigDecimal("-0.01"), "0.##;(0.##);Nil", "(0.01)"); checkFormat(null, new BigDecimal("-0.01"), "0.##;(0.#);Nil", "Nil"); // Bug MONDRIAN-434. If there are only two sections, the default third // section is '.'. checkFormat(null, new BigDecimal("0.00001"), "#.##;(#.##)", "."); checkFormat(null, new BigDecimal("0.001"), "0.##;(0.##)", "0."); checkFormat(null, new BigDecimal("-0.001"), "0.##;(0.##)", "0."); // Zero value and varying numbers of format strings. checkFormat( null, BigDecimal.ZERO, "\\P\\o\\s", "Pos"); checkFormat( null, BigDecimal.ZERO, "\\P\\o\\s;\\N\\e\\g", "Pos"); checkFormat( null, BigDecimal.ZERO, "\\P\\o\\s;\\N\\e\\g;\\Z\\e\\r\\o", "Zero"); checkFormat( null, BigDecimal.ZERO, "\\P\\o\\s;\\N\\e\\g;\\Z\\e\\r\\o;\\N\\u\\l\\l", "Zero"); // Small negative value and varying numbers of format strings. checkFormat( null, new BigDecimal("-0.00001"), "\\P\\o\\s", "-Pos"); checkFormat( null, new BigDecimal("-0.00001"), "\\P\\o\\s;\\N\\e\\g", "Neg"); checkFormat( null, new BigDecimal("-0.00001"), "\\P\\o\\s;\\N\\e\\g;\\Z\\e\\r\\o", "Neg"); checkFormat( null, new BigDecimal("-0.00001"), "\\P\\o\\s;\\N\\e\\g;\\Z\\e\\r\\o;\\N\\u\\l\\l", "Neg"); checkFormat( null, new BigDecimal("-0.001"), "\\P\\o\\s;\\N\\e\\g", "Neg"); // In the following two cases, note that a small number uses the 3rd // format string (for zero) if it underflows the 1st or 2nd format // string (for positive or negative numbers). But if underflow is not // possible (as in the case of the 'Neg' format string), checkFormat( null, new BigDecimal("-0.001"), "#.#;(#.#);\\Z\\e\\r\\o", "Zero"); checkFormat( null, new BigDecimal("-0.001"), "\\P\\o\\s;\\N\\e\\g;\\Z\\e\\r\\o", "Neg"); } /** * Null values use the fourth format. */ public void testNull() { // Null value with different numbers of strings checkFormat( null, null, "\\P\\o\\s", ""); checkFormat( null, null, "\\P\\o\\s;\\N\\e\\g", ""); checkFormat( null, null, "\\P\\o\\s;\\N\\e\\g;\\Z\\e\\r\\o", ""); checkFormat( null, null, "\\P\\o\\s;\\N\\e\\g;\\Z\\e\\r\\o;\\N\\u\\l\\l", "Null"); checkFormat( null, null, "\\P\\o\\s;;;\\N\\u\\l\\l", "Null"); checkFormat( null, null, "\\P\\o\\s;;;", ""); } public void testNegativeZero() { checkFormat(null, new BigDecimal("-0.0"), "#0.000", "0.000"); checkFormat(null, new BigDecimal("-0.0"), "#0", "0"); checkFormat(null, new BigDecimal("-0.0"), "#0.0", "0.0"); } /** * Test case for bug * MONDRIAN-686, "Regression: JPivot output invalid - New Variance * Percent column". */ public void testPercentWithStyle() { checkFormat( null, new BigDecimal("0.0364"), "|#.00%|style='green'", "|3.64%|style='green'"); } /** * Test case for bug * MONDRIAN-687, "Format treats negative numbers differently than SSAS". */ public void testNegativePercentWithStyle() { if (Bug.BugMondrian687Fixed) { checkFormat( null, new BigDecimal("-0.0364"), "|#.00%|style=red", "-|3.64%|style=red"); } else { checkFormat( null, new BigDecimal("-0.0364"), "|#.00%|style='red'", "|-3.64%|style='red'"); // confirmed on SSAS 2005 } // exercise code for long (and int) values if (Bug.BugMondrian687Fixed) { checkFormat( null, -364, "|#.00|style=red", "-|364.00|style=red"); } else { checkFormat( null, -364, "|#.00|style=red", "|-364.00|style=red"); // confirmed on SSAS 2005 } // now with multiple alternate formats checkFormat( null, 364, "|#.00|style='green';|-#.000|style='red'", "|364.00|style='green'"); // confirmed on SSAS 2005 checkFormat( null, -364, "|#.00|style='green';|-#.000|style='red'", "|-364.000|style='red'"); // confirmed on SSAS 2005 } /** * Single quotes in format string. SSAS 2005 removes them; Mondrian should * also. */ public void testSingleQuotes() { if (Bug.BugMondrian687Fixed) { checkFormat( null, 3.64, "|#.00|style='deep red'", "-|364.00|style=deep red"); // confirmed on SSAS 2005 checkFormat( null, 3.64, "|#.00|style=\\'deep red\\'", "-|364.00|style='deep red'"); // confirmed on SSAS 2005 } else { checkFormat( null, -364, "|#.00|style='deep red'", "|-364.00|style='deep red'"); } } public void testNegativePercent() { checkFormat(null, new BigDecimal("-0.0364"), "#.00%", "-3.64%"); checkFormat(null, new BigDecimal("0.0364"), "#.00%", "3.64%"); } public void testNumberRoundingBug() { checkFormat(null, new BigDecimal("0.50"), "0", "1"); checkFormat(null, new BigDecimal("-1.5"), "0", "-2"); checkFormat(null, new BigDecimal("-0.50"), "0", "-1"); checkFormat(null, new BigDecimal("-0.99999999"), "0.0", "-1.0"); checkFormat(null, new BigDecimal("-0.45"), "#.0", "-.5"); checkFormat(null, new BigDecimal("-0.45"), "0", "0"); checkFormat(null, new BigDecimal("-0.49999"), "0", "0"); checkFormat(null, new BigDecimal("-0.49999"), "0.0", "-0.5"); checkFormat(null, new BigDecimal("0.49999"), "0", "0"); checkFormat(null, new BigDecimal("0.49999"), "#.0", ".5"); } public void testCurrencyBug() { // The following case illustrates an outstanding bug. // Should be able to override '.' to '-', // so result should be '3.141.592-65 FF', // but it's actually this stupid string where the value appears twice. checkFormat(localeFra, d, "#.##0-00 FF", "3141592,654-3141592,654 FF"); } private static Date makeCalendar( final int year, final int month, final int date, final int hourOfDay, final int minute, final int second) { Calendar calendar = Calendar.getInstance(); calendar.set(year, month - 1, date, hourOfDay, minute, second); return calendar.getTime(); } public void testDates() { checkDate("dd-mmm-yy", "29-Apr-69", "29-Avr-69", "29-Apr-69"); checkDate("h:mm:ss AM/PM", "8:09:06 PM", "8#09#06 PM", "8:09:06 PM"); checkDate("hh:mm", "20:09", "20#09", "20:09"); checkDate( "Long Date", "Tuesday, April 29, 1969", "Mardi, Avril 29, 1969", "Dienstag, April 29, 1969"); checkDate("Medium Date", "29-Apr-69", "29-Avr-69", "29-Apr-69"); checkDate("Short Date", "4/29/69", "4-29-69", "4.29.69"); checkDate("Long Time", "8:09:06 PM", "8#09#06 PM", "8:09:06 PM"); checkDate("Medium Time", "8:09 PM", "8#09 PM", "8:09 PM"); checkDate("Short Time", "20:09", "20#09", "20:09"); } private void checkDate(String format, String en, String fr, String de) { // check date in english checkFormat(null, date, format, en); // check date in french checkFormat(localeFra, date, format, fr); // check date in german checkFormat(localeDe, date, format, de); } public void testAllTokens() { for (Format.Token fe : Format.getTokenList()) { Object o; if (fe.isNumeric()) { o = d; } else if (fe.isDate()) { o = date; } else if (fe.isString()) { o = "mondrian"; } else { o = d; } checkFormat(null, o, fe.token); } } public void testTrickyDates() { // All examples have been checked with Excel2003 and AS2005. checkFormat(null, date2, "y", "250"); checkFormat(null, date2, "yy", "10"); checkFormat(null, date2, "yyy", "10250"); checkFormat(null, date2, "#", "40428"); // days since 1900 checkFormat(null, date2, "x#", "x40428"); checkFormat(null, date2, "x#y", "x40428y"); checkFormat(null, date2, "x/y", "x/250"); // Using a date format (such as 'y') or separator ('/' or ':') forces // into date mode. '#' is no longer recognized as a numeric format // string. checkFormat(null, date2, "x/y/#", "x/250/#"); checkFormat(null, date2, "xy#", "x250#"); checkFormat(null, date2, "x/#", "x/#"); checkFormat(null, date2, "x:#", "x:#"); checkFormat(null, date2, "x-#", "x-40428"); // '-' is not special checkFormat(null, date2, "x #", "x 40428"); // ' ' is not special // must not throw exception checkFormat(null, date2, "mm/##/yy", "09/##/10"); // must recognize lowercase "dd" checkFormat(null, date2, "mm/dd/yy", "09/07/10"); // must print '7' not '07' checkFormat(null, date2, "mm/d/yy", "09/7/10"); // must not decrement month by one (cuz java.util.Calendar is 0-based) checkFormat(null, date2, "mm/dd/yy", "09/07/10"); // must recognize "MMM" checkFormat(null, date2, "MMM/dd/yyyy", "Sep/07/2010"); // "mmm" is a synonym for "MMMM" checkFormat(null, date2, "mmm/dd/yyyy", "Sep/07/2010"); // must recognize "MMMM" checkFormat(null, date2, "MMMM/dd/yyyy", "September/07/2010"); // "mmmm" is a synonym for "MMMM" checkFormat(null, date2, "mmmm/dd/yyyy", "September/07/2010"); // "mm" means minute, not month, when following "hh" checkFormat(null, date2, "hh/mm/ss", "06/05/04"); // must recognize "Long Date" etc. checkFormat(null, date2, "Long Date", "Tuesday, September 07, 2010"); } public void testFrenchLocale() { Format.FormatLocale fr = Format.createLocale(Locale.FRANCE); assertEquals("#,##0.00 " + I18nTest.Euro, fr.currencyFormat); assertEquals(I18nTest.Euro + "", fr.currencySymbol); assertEquals("/", fr.dateSeparator); assertEquals( "[, dimanche, lundi, mardi, mercredi, jeudi, vendredi, samedi]", Arrays.toString(fr.daysOfWeekLong)); assertEquals( "[, dim., lun., mar., mer., jeu., ven., sam.]", Arrays.toString(fr.daysOfWeekShort)); assertEquals( "[janvier, f" + I18nTest.EA + "vrier, mars, avril, mai, juin," + " juillet, ao" + I18nTest.UC + "t, septembre, octobre, novembre, d" + I18nTest.EA + "cembre, ]", Arrays.toString(fr.monthsLong)); assertEquals( "[janv., f" + I18nTest.EA + "vr., mars, avr., mai, juin," + " juil., ao" + I18nTest.UC + "t, sept., oct., nov., d" + I18nTest.EA + "c., ]", Arrays.toString(fr.monthsShort)); assertEquals(',', fr.decimalPlaceholder); assertEquals(I18nTest.Nbsp, fr.thousandSeparator); assertEquals(":", fr.timeSeparator); } private void checkFormat( Format.FormatLocale locale, Object o, String formatString) { Format format = new Format(formatString, locale); String actualResult = format.format(o); Util.discard(actualResult); if (o instanceof BigDecimal) { BigDecimal bigDecimal = (BigDecimal) o; checkFormat(locale, bigDecimal.doubleValue(), formatString); checkFormat(locale, bigDecimal.floatValue(), formatString); checkFormat(locale, bigDecimal.longValue(), formatString); checkFormat(locale, bigDecimal.intValue(), formatString); } } private void checkFormat( Format.FormatLocale locale, Object o, String formatString, String expectedResult) { Format format = new Format(formatString, locale); String actualResult = format.format(o); assertEquals(expectedResult, actualResult); if (o instanceof BigDecimal) { BigDecimal bigDecimal = (BigDecimal) o; checkFormat( locale, bigDecimal.doubleValue(), formatString, expectedResult); // Convert value to various data types and make sure there is no // error. Do not check the result -- it might be different because // of rounding. checkFormat(locale, bigDecimal.doubleValue(), formatString); } } public void testCache() { StringBuilder buf = new StringBuilder(Format.CacheLimit * 2 + 10); buf.append("0."); for (int i = 0; i < Format.CacheLimit * 2; ++i) { final Format format = Format.get(buf.toString(), null); final String s = format.format(i); assertEquals(i + ".", s); buf.append("#"); } } public void testString() { // Excel2003 checkFormat(null, "This Is A Test", ">", "THIS IS A TEST"); // Excel2003 checkFormat(null, "This Is A Test", "<", "this is a test"); // SSAS2005 checkFormat(null, "hello", "\\f\\i\\x\\e\\d", "hello"); // SSAS2005 checkFormat(null, "hello", ">\\f\\i\\x\\e\\d<", "HELLOfixedhello"); final BigDecimal decimal = new BigDecimal("123.45"); final int integer = 123; final String string = "Foo Bar"; // ">" checkFormat(null, decimal, ">", "123.45"); checkFormat(null, integer, ">", "123"); checkFormat(null, string, ">", "FOO BAR"); // SSAS 2005 returns ">" // "<" checkFormat(null, decimal, "<", "123.45"); checkFormat(null, integer, "<", "123"); checkFormat(null, string, "<", "foo bar"); // SSAS 2005 returns "<" // "@" (can't figure out how to use this -- SSAS 2005 always ignores) checkFormat(null, decimal, "@", "@"); // checked on SSAS 2005 checkFormat(null, integer, "@", "@"); // checked on SSAS 2005 checkFormat(null, string, "@", string); // checked on Excel 2003 // combinations checkFormat(null, string, "<@", "foo bar@"); // SSAS 2005 returns "<@" // SSAS 2005 returns "<>"; Excel returns "Foo Bar", i.e. unchanged checkFormat(null, string, "<>", "foo barFOO BAR"); checkFormat(null, string, "E", string); // checked on Excel 2003 // FIXME: SSAS 2005 returns "1.234500E+002" checkFormat(null, decimal, "E", "E"); // SSAS 2005 returns "", "9/7/10 6:05:04 AM"); // numeric value and string format checkFormat(null, 123.45E6, "<", "123,450,000"); // Excel gives 12345600 checkFormat(null, -123.45E6, ">", "-123,450,000"); } public void testFormatThousands() { checkFormat( null, 123456.7, "######.00", "123456.70"); checkFormat( null, 123456, "######", "123456"); checkFormat( null, 123456.7, "#,##,###.00", "1,23,456.70"); checkFormat( null, 123456.7, "#,##,###", "1,23,457"); checkFormat( null, 9123456.7, "#,#.00", "9,123,456.70"); checkFormat( null, 123456.7, "#,#", "123,457"); checkFormat( null, 123456789.1, "#,#", "123,456,789"); checkFormat( null, 123456.7, "##################,#", "123,457"); checkFormat( null, 123456.7, "#################,#", "123,457"); checkFormat( null, 123456.7, "###,################", "123,457"); checkFormat( null, 0.02, "#,###.000", ".020"); checkFormat( null, 0.02, "#,##0.000", "0.020"); checkFormat( null, 123456789123l, "#,##,#,##,#,##,#,##", "1,23,4,56,7,89,1,23"); checkFormat( null, 123456, "#,###;(#,###)", "123,456"); checkFormat( null, 123456, "\\$ #,###;(\\$ #,###) ", "$ 123,456"); } /** * Tests the international currency symbol parsing * in format strings according to different locales. */ public void testCurrency() { checkFormat( localeDe, 123456, "Currency", "123.456,00 \u20AC"); checkFormat( localeDe, 123456, "###,###.00" + Format.intlCurrencySymbol, "123.456,00\u20AC"); checkFormat( localeFra, 123456, "###,###.00" + Format.intlCurrencySymbol, "123.456,00FF"); checkFormat( localeFra, 123456, "Currency", "123.456,00FF"); // Tests whether the format conversion can fallback to // the system default locale to resolve the currency // symbol it must use. checkFormat( Format.createLocale(Locale.JAPANESE), 123456, "Currency", "$ 123,456.00"); // international currency symbol checkFormat( null, new BigDecimal("1.2"), "" + Format.intlCurrencySymbol + "#", "$1"); } /** * Test case for bug * MONDRIAN-1098, "Trying to get formatted value of a cell results in * ArrayIndexOutOfBoundsException". */ public void testBugMondrian1098() { final double v = 0.5616000000000001; assertFormat("$000,000.56", "$000,000.00", v); assertFormat("$0,000,000.56", "$0,000,000.00", v); assertFormat("$00,000,000.56", "$00,000,000.00", v); // was also broken assertFormat("$000,000,000.56", "$000,000,000.00", v); // the actual bug // for various huge format strings Random random = new Random(123); for (int i = 0; i < 200; i++) { check1098(i); check1098(random.nextInt(20000)); } } private void check1098(int i) { final double v = 0.5616000000000001; StringBuilder buf = new StringBuilder("$"); for (int j = 0; j < i; j++) { if ((i - j) % 3 == 0 && j > 0) { buf.append(","); } buf.append("0"); } buf.append(".00"); final String formatString = buf.toString(); final Format format = Format.get(formatString, Locale.US); final String s = format.format(v); assertEquals(s, formatString.length(), s.length()); assertTrue(s, s.endsWith(".56")); } private void assertFormat( String expected, String formatString, Object v) { assertEquals(expected, Format.get(formatString, Locale.US).format(v)); } } // End FormatTest.java mondrian-3.4.1/testsrc/main/mondrian/util/PrimeFinderTest.java0000644000175000017500000000424111735330606024335 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho // All Rights Reserved. */ package mondrian.util; import mondrian.test.TestContext; import junit.framework.TestCase; import java.io.PrintWriter; import java.io.StringWriter; /** * Testcase for {@link mondrian.util.PrimeFinder}. * * @author jhyde * @since Feb 4, 2007 */ public class PrimeFinderTest extends TestCase { private void assertStatistics(int from, int to, String expected) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); PrimeFinder.statistics(from, to, pw); pw.flush(); TestContext.assertEqualsVerbose(expected, sw.toString()); } public void testOne() { assertStatistics( 1000, 1000, "new maxdev @1000@dev=0.039\n" + "Statistics for [1000,1000] are as follows\n" + "meanDeviation = 3.9 %\n" + "maxDeviation = 3.9 %\n"); } public void testTwo() { assertStatistics( 200, 1000, "new maxdev @200@dev=0.385\n" + "Statistics for [200,1000] are as follows\n" + "meanDeviation = 6.589286 %\n" + "maxDeviation = 38.5 %\n"); } public void testThree() { assertStatistics( 16, 1000, "new maxdev @16@dev=0.0625\n" + "new maxdev @18@dev=0.2777777777777778\n" + "new maxdev @24@dev=0.2916666666666667\n" + "new maxdev @48@dev=0.3958333333333333\n" + "new maxdev @98@dev=0.3979591836734694\n" + "new maxdev @198@dev=0.398989898989899\n" + "Statistics for [16,1000] are as follows\n" + "meanDeviation = 7.374975 %\n" + "maxDeviation = 39.898987 %\n"); } // disabled because it takes a LONG time public void _testFour() { assertStatistics(1000, Integer.MAX_VALUE, ""); } } // End PrimeFinderTest.java mondrian-3.4.1/testsrc/main/mondrian/util/ScheduleTest.java0000644000175000017500000004071211735330606023670 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.util; import junit.framework.TestCase; import java.sql.Time; import java.util.*; public class ScheduleTest extends TestCase { public static final Time time0827 = ScheduleUtil.createTime(8, 27, 00); public static final Time time1600 = ScheduleUtil.createTime(16, 00, 0); public static final Time time0000 = ScheduleUtil.createTime(00, 00, 0); public static final Time time0233 = ScheduleUtil.createTime(02, 33, 0); public static final TimeZone gmtTz = TimeZone.getTimeZone("GMT"); public static final TimeZone pstTz = TimeZone.getTimeZone("America/Los_Angeles"); // GMT-8 public static final TimeZone jstTz = TimeZone.getTimeZone("Asia/Tokyo"); public static final TimeZone sgtTz = TimeZone.getTimeZone("Asia/Singapore"); // GMT+8 public static final int weekdays = (1 << Calendar.MONDAY) | (1 << Calendar.TUESDAY) | (1 << Calendar.WEDNESDAY) | (1 << Calendar.THURSDAY) | (1 << Calendar.FRIDAY); private static final String[] daysOfWeek = { null, "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; public ScheduleTest(String name) { super(name); } // helper methods static void assertEquals(Calendar c1, Calendar c2) { if (c1 == null || c2 == null) { assertEquals((Object) c1, (Object) c2); } else { // do the checks on 'smaller' objects -- otherwise the // failure message is too long to see in the debugger assertEquals(c1.getTimeZone(), c2.getTimeZone()); assertEquals(c1.getTime(), c2.getTime()); } } static void assertEquals(Date expected, Calendar actual) { if (expected == null || actual == null) { assertEquals((Object) expected, (Object) actual); } else { assertEquals(expected, actual.getTime()); } } static void assertEquals( int year, int month, int day, String dow, int hour, int minute, Date actual) { assertEquals(toDate(year, month, day, dow, hour, minute), actual); } static void assertEquals(Calendar expected, Date actual) { if (expected == null || actual == null) { assertEquals((Object) expected, (Object) actual); } else { assertEquals(expected.getTime(), actual); } } static void assertScheduleCount( Schedule schedule, Date d, Date last, int expectedCount) { int count = 0; while (true) { Date next = schedule.nextOccurrence(d, true); if (next == null) { break; } count++; d = next; if (count > 100) { break; // we're looping } } assertEquals(last, d); // last occurrence assertEquals("schedule count", expectedCount, count); } static Date toDate( int year, int month, int day, String dow, int hour, int minute) { return toDate(year, month, day, dow, hour, minute, gmtTz); } static Date toDate( int year, int month, int day, String dow, int hour, int minute, TimeZone tz) { Calendar calendar = ScheduleUtil.createCalendar(year, month, day, hour, minute, 0); calendar.setTimeZone(tz); assertEquals(daysOfWeek[calendar.get(Calendar.DAY_OF_WEEK)], dow); return calendar.getTime(); } // -------------------------------------------------------------------- // test cases public void testOnceTimeSchedule() { Calendar calendar0827 = ScheduleUtil.createCalendar(time0827); OnceTimeSchedule onceTimeSchedule = new OnceTimeSchedule(calendar0827); Calendar t = onceTimeSchedule.nextOccurrence(null, true); assertEquals(calendar0827, t); Calendar calendar1600 = ScheduleUtil.createCalendar(time1600); t = onceTimeSchedule.nextOccurrence(calendar1600, true); assertEquals((Calendar) null, t); t = onceTimeSchedule.nextOccurrence(calendar0827, true); assertEquals((Calendar) null, t); t = onceTimeSchedule.nextOccurrence(calendar0827, false); assertEquals(calendar0827, t); Calendar calendar0000 = ScheduleUtil.createCalendar(time0000); t = onceTimeSchedule.nextOccurrence(calendar0000, false); assertEquals(calendar0827, t); } public void testOnce() { Schedule schedule = Schedule.createOnce(toDate(2002, 04, 23, "Tue", 8, 27), gmtTz); Date d; d = schedule.nextOccurrence(null, false); assertEquals(2002, 04, 23, "Tue", 8, 27, d); d = schedule.nextOccurrence(toDate(2002, 04, 23, "Tue", 8, 27), false); assertEquals(2002, 04, 23, "Tue", 8, 27, d); d = schedule.nextOccurrence(toDate(2002, 04, 23, "Tue", 8, 27), true); assertEquals(null, d); d = schedule.nextOccurrence(toDate(2002, 06, 03, "Mon", 16, 00), false); assertEquals(null, d); d = schedule.nextOccurrence(toDate(2002, 04, 20, "Sat", 23, 00), true); assertEquals(2002, 04, 23, "Tue", 8, 27, d); d = schedule.nextOccurrence(toDate(2002, 04, 20, "Sat", 23, 00), false); assertEquals(2002, 04, 23, "Tue", 8, 27, d); } public void testDaily() { int period = 1; Schedule schedule = Schedule.createDaily( toDate(2002, 04, 20, "Sat", 8, 27), toDate(2002, 06, 03, "Mon", 8, 27), gmtTz, time0827, period); Date d; d = schedule.nextOccurrence(null, false); assertEquals(2002, 4, 20, "Sat", 8, 27, d); d = schedule.nextOccurrence(toDate(2002, 04, 20, "Sat", 8, 27), false); assertEquals(2002, 4, 20, "Sat", 8, 27, d); d = schedule.nextOccurrence(toDate(2002, 04, 20, "Sat", 23, 00), false); assertEquals(2002, 04, 21, "Sun", 8, 27, d); d = schedule.nextOccurrence(toDate(2002, 06, 03, "Mon", 8, 27), false); assertEquals(null, d); // upper-bound is closed d = schedule.nextOccurrence(toDate(2002, 06, 03, "Mon", 16, 00), false); assertEquals(null, d); d = schedule.nextOccurrence(toDate(2002, 06, 04, "Tue", 8, 27), false); assertEquals(null, d); } public void testDailyNoUpperLimit() { int period = 1; Schedule schedule = Schedule.createDaily( toDate(2002, 4, 20, "Sat", 8, 27), null, gmtTz, time0827, period); Date d = schedule.nextOccurrence(null, false); assertEquals(2002, 4, 20, "Sat", 8, 27, d); d = schedule.nextOccurrence(toDate(2002, 06, 03, "Mon", 16, 00), false); assertEquals(2002, 06, 04, "Tue", 8, 27, d); } public void testDailyPeriodic() { int period = 10; Schedule schedule = Schedule.createDaily( toDate(2002, 4, 20, "Sat", 8, 27), toDate(2002, 06, 03, "Mon", 8, 27), gmtTz, time0827, period); Date d = schedule.nextOccurrence(null, false); assertEquals(2002, 4, 20, "Sat", 8, 27, d); d = schedule.nextOccurrence(toDate(2002, 4, 20, "Sat", 8, 27), true); assertEquals(2002, 04, 30, "Tue", 8, 27, d); } public void testWeeklyEmptyBitmapFails() { boolean failed = false; try { Schedule.createWeekly(null, null, gmtTz, time0827, 1, 0); } catch (Throwable e) { failed = true; } assertTrue(failed); } public void testWeeklyBadBitmapFails() { boolean failed = false; try { int period = 1; Schedule.createWeekly( null, null, gmtTz, time0827, period, (1 << 8)); } catch (Throwable e) { failed = true; } assertTrue(failed); } public void testWeekly() { int thuesday = (1 << Calendar.TUESDAY) | (1 << Calendar.THURSDAY); int period = 1; Schedule schedule = Schedule.createWeekly( toDate(2002, 4, 20, "Sat", 8, 27), toDate(2002, 06, 05, "Wed", 12, 00), gmtTz, time0827, period, thuesday); Date d; d = schedule.nextOccurrence(null, false); assertEquals(2002, 04, 23, "Tue", 8, 27, d); d = schedule.nextOccurrence(toDate(2002, 04, 23, "Tue", 8, 27), false); assertEquals(2002, 04, 23, "Tue", 8, 27, d); assertScheduleCount( schedule, d, toDate(2002, 06, 04, "Tue", 8, 27), 12); } public void testMonthlyByDay() { int period = 1; int daysOfMonth = (1 << 12) | (1 << 21) | (1 << Schedule.LAST_DAY_OF_MONTH); Schedule schedule = Schedule.createMonthlyByDay( toDate(2002, 4, 20, "Sat", 8, 27), toDate(2002, 07, 10, "Wed", 12, 00), gmtTz, time0827, period, daysOfMonth); Date d; d = schedule.nextOccurrence(null, false); assertEquals(2002, 04, 21, "Sun", 8, 27, d); d = schedule.nextOccurrence(d, true); assertEquals(2002, 04, 30, "Tue", 8, 27, d); d = schedule.nextOccurrence(d, false); assertEquals(2002, 04, 30, "Tue", 8, 27, d); d = schedule.nextOccurrence(d, true); assertEquals(2002, 05, 12, "Sun", 8, 27, d); d = schedule.nextOccurrence(d, false); assertEquals(2002, 05, 12, "Sun", 8, 27, d); assertScheduleCount(schedule, d, toDate(2002, 6, 30, "Sun", 8, 27), 5); } public void testMonthlyByDayPeriodic() { int daysOfMonth = (1 << 12) | (1 << 21) | (1 << Schedule.LAST_DAY_OF_MONTH); int period = 2; Schedule schedule = Schedule.createMonthlyByDay( toDate(2002, 04, 30, "Tue", 8, 27), toDate(2002, 7, 10, "Wed", 12, 00), gmtTz, time0827, period, daysOfMonth); Date d; // strict=true means strictly greater than null (-infinity), not // strictly greater than the start time (apr30), so apr30 is // correct d = schedule.nextOccurrence(null, true); assertEquals(2002, 04, 30, "Tue", 8, 27, d); d = schedule.nextOccurrence(d, false); assertEquals(2002, 04, 30, "Tue", 8, 27, d); d = schedule.nextOccurrence(d, true); assertEquals(2002, 06, 12, "Wed", 8, 27, d); d = schedule.nextOccurrence(d, false); assertEquals(2002, 06, 12, "Wed", 8, 27, d); assertScheduleCount(schedule, d, toDate(2002, 6, 30, "Sun", 8, 27), 2); } public void testMonthlyByWeek() { int period = 3; int daysOfWeek = (1 << Calendar.THURSDAY) | (1 << Calendar.SUNDAY); int weeksOfMonth = (1 << 2) | (1 << Schedule.LAST_WEEK_OF_MONTH); Schedule schedule = Schedule.createMonthlyByWeek( toDate(2002, 4, 20, "Sat", 8, 27), toDate(2004, 4, 19, "Mon", 12, 00), gmtTz, time0827, period, daysOfWeek, weeksOfMonth); Date d; d = schedule.nextOccurrence(null, false); assertEquals(2002, 04, 25, "Thu", 8, 27, d); d = schedule.nextOccurrence(toDate(2002, 04, 23, "Tue", 8, 27), false); assertEquals(2002, 04, 25, "Thu", 8, 27, d); d = schedule.nextOccurrence(d, true); assertEquals(2002, 04, 28, "Sun", 8, 27, d); d = schedule.nextOccurrence(d, true); assertEquals(2002, 7, 11, "Thu", 8, 27, d); assertScheduleCount(schedule, d, toDate(2004, 4, 11, "Sun", 8, 27), 29); } public void testTimeZone () { int period = 1; int daysOfWeek = (1 << Calendar.THURSDAY); int weeksOfMonth = (1 << Schedule.LAST_WEEK_OF_MONTH); Schedule schedule = Schedule.createMonthlyByWeek( toDate(2002, 3, 07, "Thu", 14, 00), toDate(2004, 4, 19, "Mon", 12, 00), jstTz, time0827, period, daysOfWeek, weeksOfMonth); Date d; d = schedule.nextOccurrence(null, true); // 1st occurrence is // Thu 28 Mar 08:27 JST, which is // Wed 27 Mar 23:27 GMT (9 hours difference) and // Wed 27 Mar 15:27 PST (a further 8 hours) assertEquals(2002, 03, 27, "Wed", 23, 27, d); d = schedule.nextOccurrence(d, true); // 2nd occurrence is // Thu 25 Apr 08:27 JST, which is // Wed 24 Apr 23:27 GMT (Japan does not have daylight savings) assertEquals(2002, 04, 24, "Wed", 23, 27, d); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); // 5th occurrence is // Thu 25 Jul 08:27 JST, which is // Wed 24 Jul 23:27 GMT assertEquals(2002, 07, 24, "Wed", 23, 27, d); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); // 8th occurrence is // Thu 31 Oct 08:27 JST, which is // Wed 30 Oct 23:27 GMT assertEquals(2002, 10, 30, "Wed", 23, 27, d); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); // 22nd occurrence is // Thu 25 Dec 08:27 JST, 2003, which is // Wed 24 Dec 23:27 GMT. Note that // this is NOT the last Wednesday in the month. assertEquals(2003, 12, 24, "Wed", 23, 27, d); } public void testTimeZoneChange () { int period = 1; TimeZone tz = pstTz; Schedule schedule = Schedule.createDaily( toDate(2002, 04, 03, "Wed", 8, 27, tz), null, tz, time0233, period); Date d; d = schedule.nextOccurrence(null, false); // 1st occurrence is // Thu 04 Apr 02:33 PST which is // Thu 04 Apr 10:33 GMT (no daylight savings yet) assertEquals(toDate(2002, 04, 04, "Thu", 02, 33, tz), d); d = schedule.nextOccurrence(d, true); d = schedule.nextOccurrence(d, true); // 3rd occurrence is // Sat 06 Apr 02:33 PST which is // Sat 06 Apr 10:33 GMT (still no daylight savings) assertEquals(2002, 04, 06, "Sat", 10, 33, d); d = schedule.nextOccurrence(d, true); // 4th occurrence occurs during the switch to daylight savings, // Sun 07 Apr 01:33 PST which is equivalent to // Sun 07 Apr 02:33 PDT which is // Sun 07 Apr 09:33 GMT assertEquals(2002, 04, 07, "Sun", 9, 33, d); d = schedule.nextOccurrence(d, true); // 5th occurrence is // Mon 08 Apr 02:33 PDT which is // Mon 08 Apr 09:33 GMT (daylight savings has started) assertEquals(2002, 04, 8, "Mon", 9, 33, d); for (int i = 5; i < 206; i++) { d = schedule.nextOccurrence(d, true); } // 206th occurrence is // Sat 26 Oct 02:33 PDT which is // Sat 26 Oct 09:33 GMT assertEquals(2002, 10, 26, "Sat", 9, 33, d); d = schedule.nextOccurrence(d, true); // 207th occurrence occurs during the 'fall back', // don't care what time we fire as long as we only fire once // Sun 27 Oct 01:33 PDT which is equivalent to // Sun 27 Oct 02:33 PST which is // Sat 27 Oct 10:33 GMT assertEquals(toDate(2002, 10, 27, "Sun", 02, 33, tz), d); d = schedule.nextOccurrence(d, true); // 208th occurrence is // Mon 28 Oct 02:33 PST which is // Mon 28 Oct 10:33 GMT assertEquals(2002, 10, 28, "Mon", 10, 33, d); d = schedule.nextOccurrence(d, true); // 209th occurrence is // Tue 29 Oct 02:33 PST which is // Tue 29 Oct 10:33 GMT assertEquals(2002, 10, 29, "Tue", 10, 33, d); } } // End ScheduleTest.java mondrian-3.4.1/testsrc/main/mondrian/util/Base64Test.java0000644000175000017500000000665611735330606023171 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho and others // All Rights Reserved. */ package mondrian.util; import junit.framework.TestCase; import java.io.*; import java.util.Arrays; import java.util.Random; /** * Test case for {@link Base64}. * * @author Brian Burton, Julian Hyde */ public class Base64Test extends TestCase { private static final long SEED = 12345678; private static Random s_random = new Random(SEED); private byte[] createData(int length) throws Exception { byte[] bytes = new byte[length]; s_random.nextBytes(bytes); return bytes; } private void runStreamTest(int length) throws Exception { byte[] data = createData(length); ByteArrayOutputStream out_bytes = new ByteArrayOutputStream(); OutputStream out = new Base64.OutputStream(out_bytes); out.write(data); out.close(); byte[] encoded = out_bytes.toByteArray(); byte[] decoded = Base64.decode(encoded, 0, encoded.length); assertTrue(Arrays.equals(data, decoded)); Base64.InputStream in = new Base64.InputStream(new ByteArrayInputStream(encoded)); out_bytes = new ByteArrayOutputStream(); byte[] buffer = new byte[3]; for (int n = in.read(buffer); n > 0; n = in.read(buffer)) { out_bytes.write(buffer, 0, n); } out_bytes.close(); in.close(); decoded = out_bytes.toByteArray(); assertTrue(Arrays.equals(data, decoded)); } public void testStreams() throws Exception { for (int i = 0; i < 100; ++i) { runStreamTest(i); } for (int i = 100; i < 2000; i += 250) { runStreamTest(i); } for (int i = 2000; i < 80000; i += 1000) { runStreamTest(i); } } public void testSimple() { String s = "Man is distinguished, not only by his reason, but by this " + "singular passion from other animals, which is a lust of the " + "mind, that by a perseverance of delight in the continued and " + "indefatigable generation of knowledge, exceeds the short " + "vehemence of any carnal pleasure."; String encoded = Base64.encodeBytes(s.getBytes()); String expected = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvb" + "mx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz\n" + "IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlc" + "iBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg\n" + "dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlc" + "mFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu\n" + "dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyY" + "XRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo\n" + "ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhc" + "m5hbCBwbGVhc3VyZS4="; assertEquals(expected, encoded); byte[] s1 = Base64.decode(encoded); assertEqualsByteArray(s.getBytes(), s1); } private void assertEqualsByteArray(byte[] bytes, byte[] bytes1) { assertEquals(bytes.length, bytes1.length); for (int i = 0; i < bytes.length; i++) { assertEquals(bytes[i], bytes1[i]); } } } // End Base64Test.java mondrian-3.4.1/testsrc/main/mondrian/util/FilteredIterableTest.java0000644000175000017500000000515011735330606025337 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.util; import mondrian.test.FoodMartTestCase; import java.util.ArrayList; import java.util.List; /** * Unit-test for FilteredIterable * * @author jlopez, lcanals * @since May, 2008 */ public class FilteredIterableTest extends FoodMartTestCase { public FilteredIterableTest() { } public FilteredIterableTest(String name) { super(name); } public void testEmptyList() throws Exception { final List base = new ArrayList(); for (int i = 0; i < 10; i++) { base.add(i); } final List empty = new FilteredIterableList( base, new FilteredIterableList.Filter() { public boolean accept(final Integer i) { return false; } }); for (final Integer x : empty) { fail("All elements should have been filtered"); } } public void testGetter() throws Exception { final List base = new ArrayList(); for (int i = 0; i < 10; i++) { base.add(i); } final List empty = new FilteredIterableList( base, new FilteredIterableList.Filter() { public boolean accept(final Integer i) { return i < 2; } }); for (int i = 0; i < 2; i++) { assertEquals(new Integer(i), empty.get(i)); } } public void test2Elements() throws Exception { final List base = new ArrayList(); for (int i = 0; i < 2; i++) { base.add(i); } final List identical = new FilteredIterableList( base, new FilteredIterableList.Filter() { public boolean accept(final Integer i) { return true; } }); assertFalse(identical.isEmpty()); assertNotNull(identical.get(0)); int k = 0; for (final Integer i : identical) { assertEquals(i, identical.get(k)); k++; } } } // End FilteredIterableTest.java mondrian-3.4.1/testsrc/pendingQueryFiles/0000755000175000017500000000000011735330606020352 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/pendingQueryFiles/queryTest_7db34e59679e89e5_WTD.xml0000644000175000017500000000641511735330606026101 0ustar drazzibdrazzib WITH MEMBER Measures.WTDProfit AS 'SUM(WTD(), (Measures.[Store Sales] - Measures.[Store Cost]))' SELECT [Time].[1997].CHILDREN ON COLUMNS, {[Product].CHILDREN} ON ROWS FROM [Sales] WHERE (Measures.WTDProfit) [Measures] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[WTDProfit] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] [Product] [Product].[Drink] [Product].[Food] [Product].[Non-Consumable] #ERR #ERR #ERR #ERR #ERR #ERR #ERR #ERR #ERR #ERR #ERR #ERR mondrian-3.4.1/testsrc/pendingQueryFiles/queryTest_bc44389066a6815.xml0000644000175000017500000000471611735330606025112 0ustar drazzibdrazzib with member [Measures].[Check Gender] as 'iif (TupleToStr([Gender].Children.Current) = "F","Woman","Man")' Select {[Measures].[Check Gender]} on columns, {[Gender].Children} on rows from [Sales] [Time] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Time].[1997] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Measures] [Measures].[Check Gender] [Gender] [Gender].[F] [Gender].[M] #ERR #ERR mondrian-3.4.1/testsrc/pendingQueryFiles/queryTest_count_7e5666c8345079a8.xml0000644000175000017500000000407211735330606026416 0ustar drazzibdrazzib WITH MEMBER [Measures].[Dimension Count] AS 'Dimensions.Count' SELECT {[Measures].[Dimension Count]} ON AXIS(0) FROM [Sales] [Time] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Dimension Count] 13.0 mondrian-3.4.1/testsrc/pendingQueryFiles/queryTest_743abff0c2ac9fec_Siblings.xml0000644000175000017500000001023611735330606027531 0ustar drazzibdrazzib WITH MEMBER [Measures].[Cumulative No of Employees] AS 'SUM(HEAD(ORDER({[Store].Siblings}, [Measures].[Number of Employees], BDESC) AS OrderedSiblings,RANK([Store],OrderedSiblings)),[Measures].[Number of Employees])' SELECT {[Measures].[Number of Employees], [Measures].[Cumulative No of Employees]} ON COLUMNS, ORDER( DESCENDANTS( Store.CA, [Store State], AFTER ), [Measures].[Number of Employees], BDESC ) ON ROWS FROM [HR] [Time] [Pay Type] [Store Type] [Position] [Department] [Employees] [Time].[All Times] [Pay Type].[All Pay Types] [Store Type].[All Store Types] [Position].[All Positions] [Department].[All Departments] [Employees].[All Employees] [Measures] [Measures].[Number of Employees] [Measures].[Cumulative No of Employees] [Store] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[Los Angeles].[Store 7] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Diego].[Store 24] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Beverly Hills].[Store 6] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Alameda].[HQ] [Store].[USA].[CA].[San Francisco] [Store].[USA].[CA].[San Francisco].[Store 14] 62.0 62.0 62.0 62.0 62.0 124.0 62.0 62.0 48.0 172.0 48.0 48.0 27.0 199.0 27.0 27.0 4.0 203.0 4.0 4.0 mondrian-3.4.1/testsrc/pendingQueryFiles/queryTest_89c270eca2a8ad8.xml0000644000175000017500000000436011735330606025401 0ustar drazzibdrazzib with member [Measures].[SetToStr Test] as 'SetToStr({[Store].[USA].[CA].children})' Select {[Measures].[SetToStr Test]} on Axis(0) From [Sales] [Time] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[SetToStr Test] {[Store].[USA].[CA].[Alameda], [Store].[USA].[CA].[Beverly Hills], [Store].[USA].[CA].[Los Angeles], [Store].[USA].[CA].[San Diego], [Store].[USA].[CA].[San Francisco]} mondrian-3.4.1/testsrc/pendingQueryFiles/queryTest_f1b72abb932889ee.xml0000644000175000017500000000416311735330606025474 0ustar drazzibdrazzib with member [Measures].[Test] as 'TupleToStr((Time.[1997], Store.[USA].[CA].[San Francisco]))' Select {[Measures].[Test]} on Axis(0) From [Sales] [Time] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Test] ([Time].[1997], [Store].[USA].[CA].[San Francisco]) mondrian-3.4.1/testsrc/pendingQueryFiles/queryTest_5fae7a3481f23f3.xml0000644000175000017500000001134111735330606025315 0ustar drazzibdrazzib Select {ToggleDrillState({[Store].[USA],[Store].[Canada],[Store].[Mexico]}, {[Store].[USA],[Store].[USA].[WA],[Store].[All Stores].[Mexico]},RECURSIVE)} on Axis(0) from [Sales] [Measures] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[OR] [Store].[USA].[WA] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Tacoma] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Yakima] [Store].[Canada] [Store].[Mexico] [Store].[Mexico].[DF] [Store].[Mexico].[Guerrero] [Store].[Mexico].[Jalisco] [Store].[Mexico].[Veracruz] [Store].[Mexico].[Yucatan] [Store].[Mexico].[Zacatecas] 266773.0 74748.0 67659.0 124366.0 2237.0 24576.0 25011.0 23591.0 35257.0 2203.0 11491.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing mondrian-3.4.1/testsrc/pendingQueryFiles/queryTest_b3ff21129b22f5aa.xml0000644000175000017500000000421211735330606025442 0ustar drazzibdrazzib with member [Measures].[TupleToStr Test] as 'TupleToStr((Time.[1997], Store.[San Francisco]))' Select {[Measures].[TupleToStr Test]} on Axis(0) From [Sales] [Time] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[TupleToStr Test] ([Time].[1997], [Store].[USA].[CA].[San Francisco]) mondrian-3.4.1/testsrc/pendingQueryFiles/queryTest_74a45b4dadcba7.xml0000644000175000017500000000420211735330606025357 0ustar drazzibdrazzib Select { crossjoin( {StrToTuple("[Customers].[USA].[CA].Parent")}, {StrToTuple("[Store].[USA].[CA]")} ) } on Axis(0) from [Sales] [Measures] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Customers] [Store] [Customers].[USA] [Store].[USA].[CA] 74748.0 mondrian-3.4.1/testsrc/pendingQueryFiles/queryTest_d64f1e3b6e38d7.xml0000644000175000017500000000473211735330606025246 0ustar drazzibdrazzib with member [Measures].[Check Gender] as 'iif (TupleToStr([Gender].Children.Current) = "[Gender].[F]","Woman","Man")' Select {[Measures].[Check Gender]} on columns, {[Gender].Children} on rows from [Sales] [Time] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Time].[1997] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Measures] [Measures].[Check Gender] [Gender] [Gender].[F] [Gender].[M] #ERR #ERR mondrian-3.4.1/testsrc/pendingQueryFiles/queryTest_count_5f17348b7a7f3ce9.xml0000644000175000017500000000407011735330606026626 0ustar drazzibdrazzib WITH MEMBER [Product].[Product Levels] AS '[Product].Levels.Count' SELECT {[Product].[Product Levels]} ON AXIS(0) FROM [Sales] [Measures] [Time] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Product] [Product].[Product Levels] 7.0 mondrian-3.4.1/testsrc/pendingQueryFiles/queryTest_a745bdb3182f36.xml0000644000175000017500000001113311735330606025145 0ustar drazzibdrazzib select {[Measures].[Store Invoice], [Measures].[Supply Time]} on columns, descendants([Store].[USA], [Store].[Store City], Leaves) on rows from [Warehouse] [Store Size in SQFT] [Store Type] [Time] [Warehouse] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Time].[1997] [Warehouse].[All Warehouses] [Measures] [Measures].[Store Invoice] [Measures].[Supply Time] [Store] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Francisco] [Store].[USA].[OR].[Portland] [Store].[USA].[OR].[Salem] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Tacoma] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Yakima] #Missing #Missing 5132.8974 559.0 11634.9186 1279.0 11850.663 1182.0 988.8204 80.0 4042.9596 401.0 16151.0411 1650.0 1049.4587 216.0 11659.6249 1130.0 11517.1251 1193.0 5781.9634 546.0 16100.8297 1578.0 1093.7695 83.0 5274.3375 528.0 mondrian-3.4.1/testsrc/pendingQueryFiles/queryTest_6d042ea3df09ad4_StrToSet.xml0000644000175000017500000000516411735330606027202 0ustar drazzibdrazzib SELECT Measures.Members ON COLUMNS FROM [Sales] WHERE(StrToSet("{[Christian]}").Item(0).Item(0)) [Time] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[USA].[OR].[Lebanon].[Christian] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Measures].[Store Cost] [Measures].[Store Sales] [Measures].[Sales Count] [Measures].[Customer Count] 49.0 39.8567 102.78 16.0 1.0 mondrian-3.4.1/testsrc/queryFiles/0000755000175000017500000000000011735330606017045 5ustar drazzibdrazzibmondrian-3.4.1/testsrc/queryFiles/queryTest_count_3f4d6a85fe60af51.xml0000644000175000017500000000772211735330606025376 0ustar drazzibdrazzib WITH MEMBER [Product].[Product Count] AS 'Count({[Product].[All Products], [Product].[All Products].Children})' SELECT {[Store].[All Stores], [Store].[All Stores].Children} ON AXIS(0), {[Product].[All Products], [Product].[All Products].Children, [Product].[Product Count]} ON AXIS(1) FROM [Sales] [Measures] [Time] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[All Stores] [Store].[Canada] [Store].[Mexico] [Store].[USA] [Product] [Product].[All Products] [Product].[Drink] [Product].[Food] [Product].[Non-Consumable] [Product].[Product Count] 266773.0 #Missing #Missing 266773.0 24597.0 #Missing #Missing 24597.0 191940.0 #Missing #Missing 191940.0 50236.0 #Missing #Missing 50236.0 4.0 4.0 4.0 4.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_df27cb9d8192570_Variance.xml0000644000175000017500000000561411735330606025556 0ustar drazzibdrazzib with member [Promotions].[Var Store Sales] as 'Variance([Promotions].members, [Measures].[Store Sales])' Select {[Promotions].[Var Store Sales]} on columns, {[Time].[1997].children} on rows from [Sales] [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Var Store Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 9.040405865432781E8 9.888390945963147E8 1.0474315199301329E9 1.3019088908930557E9 mondrian-3.4.1/testsrc/queryFiles/queryTest_coalesceEmpty_5dc721f6c5ff5751.xml0000644000175000017500000000714011735330606026762 0ustar drazzibdrazzib WITH MEMBER [Measures].[Foo] AS 'CoalesceEmpty([Measures].[Unit Sales], 7)' SELECT {[Store].[All Stores], [Store].[All Stores].Children} ON AXIS(0), {[Product].[All Products], [Product].[All Products].Children} ON AXIS(1) FROM [Sales] WHERE [Measures].[Foo] [Measures] [Time] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Foo] [Time].[1997] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[All Stores] [Store].[Canada] [Store].[Mexico] [Store].[USA] [Product] [Product].[All Products] [Product].[Drink] [Product].[Food] [Product].[Non-Consumable] 266773.0 7.0 7.0 266773.0 24597.0 7.0 7.0 24597.0 191940.0 7.0 7.0 191940.0 50236.0 7.0 7.0 50236.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_fdc4dd3983d1c2c.xml0000644000175000017500000000505511735330606024155 0ustar drazzibdrazzib select { [Measures].[Unit Sales] } on columns, filter(descendants([Store].[USA]), ([Measures].[Customer Count], [Time].[1997].[Q3]) &gt;= 1000 and ([Measures].[Customer Count], [Time].[1997].[Q3]) &lt;= 3000) on rows from [Sales] where ([Time].[1997].[Q2]) [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997].[Q2] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA].[CA] [Store].[USA].[WA] 18052.0 29479.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_correlation_49cd2c5bade7a939.xml0000644000175000017500000000740111735330606026645 0ustar drazzibdrazzib with member [Measures].[Store Sales Q1 over All USA] as 'CORRELATION([Store].[USA].children, ([Measures].[Store Sales], [Time].[1997].[Q1]), ([Measures].[Store Sales], [Store].currentmember))' select crossjoin({[Measures].[Store Sales Q1 over All USA]}, [Time].[1997].children) on columns, [Store].[USA].children on rows from [Sales] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Time] [Measures].[Store Sales Q1 over All USA] [Time].[1997].[Q1] [Measures].[Store Sales Q1 over All USA] [Time].[1997].[Q2] [Measures].[Store Sales Q1 over All USA] [Time].[1997].[Q3] [Measures].[Store Sales Q1 over All USA] [Time].[1997].[Q4] [Store] [Store].[USA].[CA] [Store].[USA].[OR] [Store].[USA].[WA] 1.0 0.9416770690866142 0.9694826103273714 0.9169740461138594 1.0 0.9416770690866142 0.9694826103273714 0.9169740461138594 1.0 0.9416770690866142 0.9694826103273714 0.9169740461138594 mondrian-3.4.1/testsrc/queryFiles/queryTest_8e89a6f2907e4bfa.xml0000644000175000017500000000554111735330606024177 0ustar drazzibdrazzib with member [Promotions].[Max Store Sales] as 'Max([Promotions].members, [Measures].[Store Sales])' Select { [Promotions].[Max Store Sales]} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Max Store Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 139628.35 132666.27 140271.89 152671.62 mondrian-3.4.1/testsrc/queryFiles/queryTest_nextMember_ded093b6819c35f4.xml0000644000175000017500000000575711735330606026307 0ustar drazzibdrazzib SELECT {[Product], [Product].Children} ON AXIS(0), {[Time].[1997].[Q1].[1].NextMember, [Time].[1997].[Q1].[2].NextMember} ON AXIS(1) FROM [Sales] WHERE [Marital Status].[M].NextMember [Measures] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[S] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Product] [Product].[All Products] [Product].[Drink] [Product].[Food] [Product].[Non-Consumable] [Time] [Time].[1997].[Q1].[2] [Time].[1997].[Q1].[3] 10466.0 954.0 7555.0 1957.0 12073.0 1054.0 8659.0 2360.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_head_198f0ceb268aa74.xml0000644000175000017500000000544511735330606025003 0ustar drazzibdrazzib SELECT {HEAD([Store].[All Stores].Children, 1)} ON AXIS(0), {[Product].[All Products], [Product].[All Products].Children} ON AXIS(1) FROM [Sales] [Measures] [Time] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[Canada] [Product] [Product].[All Products] [Product].[Drink] [Product].[Food] [Product].[Non-Consumable] #Missing #Missing #Missing #Missing mondrian-3.4.1/testsrc/queryFiles/queryTest_171868b15cbf2ca.xml0000644000175000017500000000440011735330606024001 0ustar drazzibdrazzib SELECT {crossjoin({Dimensions(0)},{Dimensions(1)})} on Axis(0), {crossjoin({Dimensions("Customers")},{Dimensions("Gender")})} on Axis(1) from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Measures] [Store] [Measures].[Unit Sales] [Store].[All Stores] [Customers] [Gender] [Customers].[All Customers] [Gender].[All Gender] 266773.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_f365697c46dec3d0.xml0000644000175000017500000000557311735330606024117 0ustar drazzibdrazzib with member [Promotions].[Sum of Unit Sales] as 'Sum([Promotions].members)' Select { [Promotions].[Sum of Unit Sales]} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] WHERE ([Education Level].[All Education Levels]) [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Sum of Unit Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 132582.0 125220.0 131696.0 144048.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_b714558e2fc2355.xml0000644000175000017500000000512211735330606023652 0ustar drazzibdrazzib SELECT {[Measures].[Unit Sales]} ON COLUMNS, TAIL([Time].[Month].MEMBERS, 3) ON ROWS FROM [Sales] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Time] [Time].[1998].[Q4].[10] [Time].[1998].[Q4].[11] [Time].[1998].[Q4].[12] #Missing #Missing #Missing mondrian-3.4.1/testsrc/queryFiles/queryTest_f273544cf94f_Name.xml0000644000175000017500000000411711735330606024371 0ustar drazzibdrazzib WITH MEMBER [Customers].[Hierarchy Name] AS '[Customers].[USA].[CA].hierarchy.Name' SELECT {[Customers].[Hierarchy Name]} on columns From [Sales] [Measures] [Time] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Customers] [Customers].[Hierarchy Name] Customers mondrian-3.4.1/testsrc/queryFiles/queryTest_92eff5f17be36b5.xml0000644000175000017500000001761511735330606024113 0ustar drazzibdrazzib select {[Measures].[Store Invoice], [Measures].[Supply Time]} on columns, descendants([Store].[USA], [Store].[Store City], Self_and_After) on rows from [Warehouse] [Store Size in SQFT] [Store Type] [Time] [Warehouse] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Time].[1997] [Warehouse].[All Warehouses] [Measures] [Measures].[Store Invoice] [Measures].[Supply Time] [Store] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Alameda].[HQ] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Beverly Hills].[Store 6] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[Los Angeles].[Store 7] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Diego].[Store 24] [Store].[USA].[CA].[San Francisco] [Store].[USA].[CA].[San Francisco].[Store 14] [Store].[USA].[OR].[Portland] [Store].[USA].[OR].[Portland].[Store 11] [Store].[USA].[OR].[Salem] [Store].[USA].[OR].[Salem].[Store 13] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Bellingham].[Store 2] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Bremerton].[Store 3] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Seattle].[Store 15] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Spokane].[Store 16] [Store].[USA].[WA].[Tacoma] [Store].[USA].[WA].[Tacoma].[Store 17] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Walla Walla].[Store 22] [Store].[USA].[WA].[Yakima] [Store].[USA].[WA].[Yakima].[Store 23] #Missing #Missing #Missing #Missing 5132.8974 559.0 5132.8974 559.0 11634.9186 1279.0 11634.9186 1279.0 11850.663 1182.0 11850.663 1182.0 988.8204 80.0 988.8204 80.0 4042.9596 401.0 4042.9596 401.0 16151.0411 1650.0 16151.0411 1650.0 1049.4587 216.0 1049.4587 216.0 11659.6249 1130.0 11659.6249 1130.0 11517.1251 1193.0 11517.1251 1193.0 5781.9634 546.0 5781.9634 546.0 16100.8297 1578.0 16100.8297 1578.0 1093.7695 83.0 1093.7695 83.0 5274.3375 528.0 5274.3375 528.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_hierarchize_5ecd8fe83e36425.xml0000644000175000017500000000641711735330606026410 0ustar drazzibdrazzib SELECT {[Store].[All Stores], [Store].[All Stores].Children} ON AXIS(0), Hierarchize({[Product].[Food], [Product].[Non-Consumable], [Product].[Drink]}) ON AXIS(1) FROM [Sales] [Measures] [Time] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[All Stores] [Store].[Canada] [Store].[Mexico] [Store].[USA] [Product] [Product].[Drink] [Product].[Food] [Product].[Non-Consumable] 24597.0 #Missing #Missing 24597.0 191940.0 #Missing #Missing 191940.0 50236.0 #Missing #Missing 50236.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_6d93c92e9f1b2588_FirstSibling.xml0000644000175000017500000000557311735330606026526 0ustar drazzibdrazzib SELECT {[Product].FirstSibling} on ROWS, {Measures.[Sales Count], Measures.[Store Cost], Measures.[Store Sales], Measures.[Unit Sales], Measures.[Customer Count]} on COLUMNS FROM [Sales] [Time] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Sales Count] [Measures].[Store Cost] [Measures].[Store Sales] [Measures].[Unit Sales] [Measures].[Customer Count] [Product] [Product].[All Products] 86837.0 225627.2336 565238.13 266773.0 5581.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_bf2581637adb117c.xml0000644000175000017500000001455011735330606024073 0ustar drazzibdrazzib WITH MEMBER [Measures].[Qualified Count] AS 'COUNT(FILTER(DESCENDANTS(Customers.CURRENTMEMBER, [Customers].[Name]), ([Measures].[Store Sales]) &gt; 10000 OR ([Measures].[Unit Sales]) &gt; 10))' MEMBER [Measures].[Qualified Sales] AS 'SUM(FILTER(DESCENDANTS(Customers.CURRENTMEMBER, [Customers].[Name]), ([Measures].[Store Sales]) &gt; 10000 OR ([Measures].[Unit Sales]) &gt; 10), ([Measures].[Store Sales]))' SELECT {[Measures].[Qualified Count], [Measures].[Qualified Sales]} ON COLUMNS, DESCENDANTS([Customers].[All Customers], [State Province], SELF_AND_BEFORE) ON ROWS FROM [Sales] [Time] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Qualified Count] [Measures].[Qualified Sales] [Customers] [Customers].[All Customers] [Customers].[Canada] [Customers].[Canada].[BC] [Customers].[Mexico] [Customers].[Mexico].[DF] [Customers].[Mexico].[Guerrero] [Customers].[Mexico].[Jalisco] [Customers].[Mexico].[Mexico] [Customers].[Mexico].[Oaxaca] [Customers].[Mexico].[Sinaloa] [Customers].[Mexico].[Veracruz] [Customers].[Mexico].[Yucatan] [Customers].[Mexico].[Zacatecas] [Customers].[USA] [Customers].[USA].[CA] [Customers].[USA].[OR] [Customers].[USA].[WA] 4719.0 553587.77 0.0 #Missing 0.0 #Missing 0.0 #Missing 0.0 #Missing 0.0 #Missing 0.0 #Missing 0.0 #Missing 0.0 #Missing 0.0 #Missing 0.0 #Missing 0.0 #Missing 0.0 #Missing 4719 553587.77 2149 151509.69 1008 141899.84 1562 260178.24 mondrian-3.4.1/testsrc/queryFiles/queryTest_8534dfefe63645.xml0000644000175000017500000000671211735330606023667 0ustar drazzibdrazzib select {[Measures].[Unit Sales]} on columns, drilldownlevel({[Store].[USA].[WA], [Store].[USA].[CA].[Los Angeles], [Store].[USA].[CA], [Store].[USA], [Store].[USA].[WA].[Spokane]}, [Store].[Store City]) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA].[WA] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[Los Angeles].[Store 7] [Store].[USA].[CA] [Store].[USA] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Spokane].[Store 16] 124366.0 25663.0 25663.0 74748.0 266773.0 23591.0 23591.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_count_4369921d56b9648c.xml0000644000175000017500000000774011735330606025114 0ustar drazzibdrazzib WITH MEMBER [Product].[Product Count] AS 'Count({[Product].[All Products], [Product].[All Products].Children}, ExcludeEmpty)' SELECT {[Store].[All Stores], [Store].[All Stores].Children} ON AXIS(0), {[Product].[All Products], [Product].[All Products].Children, [Product].[Product Count]} ON AXIS(1) FROM [Sales] [Measures] [Time] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[All Stores] [Store].[Canada] [Store].[Mexico] [Store].[USA] [Product] [Product].[All Products] [Product].[Drink] [Product].[Food] [Product].[Non-Consumable] [Product].[Product Count] 266773.0 #Missing #Missing 266773.0 24597.0 #Missing #Missing 24597.0 191940.0 #Missing #Missing 191940.0 50236.0 #Missing #Missing 50236.0 4.0 0.0 0.0 4.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_39a23182cd8ea07a.xml0000644000175000017500000000707411735330606024075 0ustar drazzibdrazzib select { [Measures].[Customer Count] } on columns, filter(descendants([Store].[USA]), ([Measures].[Customer Count]) &gt; 1000) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Customer Count] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Beverly Hills].[Store 6] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[Los Angeles].[Store 7] [Store].[USA].[OR] [Store].[USA].[WA] 5581.0 2716.0 1059.0 1059.0 1147.0 1147.0 1037.0 1828.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_acbef2c0a5781654.xml0000644000175000017500000000552211735330606024152 0ustar drazzibdrazzib with member [Promotions].[Min Store Sales] as 'Min([Promotions].members, [Measures].[Store Sales])' Select { [Promotions].[Min Store Sales]} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Min Store Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 66.34 429.38 56.01 25.07 mondrian-3.4.1/testsrc/queryFiles/queryTest_cd5d651b9ef5da4_Name.xml0000644000175000017500000000406611735330606025121 0ustar drazzibdrazzib WITH MEMBER [Customers].[Member Name] AS '[Customers].[USA].[CA].Name' SELECT {[Customers].[Member Name]} on columns From [Sales] [Measures] [Time] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Customers] [Customers].[Member Name] CA mondrian-3.4.1/testsrc/queryFiles/queryTest_df48f9ad2ee74746.xml0000644000175000017500000001020211735330606024170 0ustar drazzibdrazzib with member [Store].[Median CA Store] as 'Median([Store].[USA].[CA].children)' Select { [Store].[Median CA Store],[Store].[USA].[CA].children} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[Median CA Store] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Francisco] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 5039.0 #Missing 3822.0 6373.0 6256.0 439.0 5695.5 #Missing 5837.0 5554.0 6125.0 536.0 5597.0 #Missing 4724.0 6470.0 6619.0 557.0 6792.5 #Missing 6950.0 7266.0 6635.0 585.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_f0d427779a61954f.xml0000644000175000017500000000563211735330606023757 0ustar drazzibdrazzib with member [Promotions].[Var Unit Sales] as 'Var([Promotions].members)' Select {[Promotions].[Var Unit Sales]} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] WHERE ([Education Level].[High School Degree]) [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[High School Degree] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Var Unit Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 1.8806805608374383E7 1.822922393846154E7 1.947790383068783E7 2.6989023698461536E7 mondrian-3.4.1/testsrc/queryFiles/queryTest_fec838c3f6f1949_TOPPERCENT.xml0000644000175000017500000001040411735330606025575 0ustar drazzibdrazzib SELECT {Measures.[Sales Count], Measures.[Store Cost], Measures.[Store Sales], Measures.[Unit Sales], Measures.[Customer Count]} ON COLUMNS, TOPPERCENT({[Store].[Store City].MEMBERS}, 50, Measures.[Sales Count]) ON ROWS FROM [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Sales Count] [Measures].[Store Cost] [Measures].[Store Sales] [Measures].[Unit Sales] [Measures].[Customer Count] [Store] [Store].[USA].[OR].[Salem] [Store].[USA].[WA].[Tacoma] [Store].[USA].[OR].[Portland] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] 13347.0 34823.5566 87218.28 41580.0 474.0 11184.0 29959.2813 74843.96 35257.0 278.0 8264.0 21948.944 55058.79 26079.0 563.0 8207.0 21771.536 54545.28 25663.0 1147.0 8095.0 21713.5328 54431.14 25635.0 962.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_29ce2f3512f79d38_TOPCOUNT.xml0000644000175000017500000001614011735330606025363 0ustar drazzibdrazzib WITH MEMBER [Store].[Other Cities] AS '([Store].[All Stores], Measures.CURRENTMEMBER) - SUM(TOPCOUNT([Store].[Store City].MEMBERS, 12, [Sales Count]), Measures.CURRENTMEMBER)' SELECT {Measures.[Sales Count], Measures.[Store Cost], Measures.[Store Sales], Measures.[Unit Sales], Measures.[Customer Count]} ON COLUMNS, {TOPCOUNT([Store].[Store City].MEMBERS, 12, [Sales Count]), [Store].[Other Cities]} ON ROWS FROM [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Sales Count] [Measures].[Store Cost] [Measures].[Store Sales] [Measures].[Unit Sales] [Measures].[Customer Count] [Store] [Store].[USA].[OR].[Salem] [Store].[USA].[WA].[Tacoma] [Store].[USA].[OR].[Portland] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Spokane] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[WA].[Yakima] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Walla Walla] [Store].[Other Cities] 13347.0 34823.5566 87218.28 41580.0 474.0 11184.0 29959.2813 74843.96 35257.0 278.0 8264.0 21948.944 55058.79 26079.0 563.0 8207.0 21771.536 54545.28 25663.0 1147.0 8095.0 21713.5328 54431.14 25635.0 962.0 7956.0 20956.8025 52644.07 25011.0 906.0 7876.0 21121.9631 52896.3 24576.0 179.0 7397.0 19795.491 49634.46 23591.0 84.0 6815.0 18266.4404 45750.24 21333.0 1059.0 3652.0 9713.813 24329.23 11491.0 95.0 1380.0 1896.6174 4739.23 2237.0 190.0 1339.0 1880.3396 4705.97 2203.0 96.0 1325.0 1778.9159 4441.18 2117.0 -452.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_aggregate_abf4dc9a944e33c5.xml0000644000175000017500000000670311735330606026251 0ustar drazzibdrazzib WITH MEMBER [Product].[Food].[Deli].[Meat].[BBQ] AS 'AGGREGATE({[Product].[Food].[Deli].[Meat].[Fresh Chicken], [Product].[Food].[Deli].[Meat].[Hot Dogs]})' SELECT {[Store].[All Stores]} ON AXIS(0), {[Product].[Food].[Deli].[Meat], [Product].[Food].[Deli].[Meat].Children, [Product].[Food].[Deli].[Meat].[BBQ]} ON AXIS(1) FROM [Sales] [Measures] [Time] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[All Stores] [Product] [Product].[Food].[Deli].[Meat] [Product].[Food].[Deli].[Meat].[Bologna] [Product].[Food].[Deli].[Meat].[Deli Meats] [Product].[Food].[Deli].[Meat].[Fresh Chicken] [Product].[Food].[Deli].[Meat].[Hot Dogs] [Product].[Food].[Deli].[Meat].[BBQ] 9433.0 2588.0 3339.0 878.0 2628.0 3506.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_0b1c51fc037104c.xml0000644000175000017500000006334611735330606023716 0ustar drazzibdrazzib with member [Measures].[Store Profit] as '[Measures].[Store Sales] - [Measures].[Store Cost]' select { [Time].[1997].children } on columns, hierarchize(intersect( crossjoin({ [Measures].members, [Measures].[Store Profit] }, [Store].members), generate([Store].members, { ([Measures].[Store Profit], [Store].currentmember) } ))) on rows from [Sales] where ( [Product].[Food] ) [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Product].[Food] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] [Measures] [Store] [Measures].[Store Profit] [Store].[All Stores] [Measures].[Store Profit] [Store].[Canada] [Measures].[Store Profit] [Store].[Canada].[BC] [Measures].[Store Profit] [Store].[Canada].[BC].[Vancouver] [Measures].[Store Profit] [Store].[Canada].[BC].[Vancouver].[Store 19] [Measures].[Store Profit] [Store].[Canada].[BC].[Victoria] [Measures].[Store Profit] [Store].[Canada].[BC].[Victoria].[Store 20] [Measures].[Store Profit] [Store].[Mexico] [Measures].[Store Profit] [Store].[Mexico].[DF] [Measures].[Store Profit] [Store].[Mexico].[DF].[Mexico City] [Measures].[Store Profit] [Store].[Mexico].[DF].[Mexico City].[Store 9] [Measures].[Store Profit] [Store].[Mexico].[DF].[San Andres] [Measures].[Store Profit] [Store].[Mexico].[DF].[San Andres].[Store 21] [Measures].[Store Profit] [Store].[Mexico].[Guerrero] [Measures].[Store Profit] [Store].[Mexico].[Guerrero].[Acapulco] [Measures].[Store Profit] [Store].[Mexico].[Guerrero].[Acapulco].[Store 1] [Measures].[Store Profit] [Store].[Mexico].[Jalisco] [Measures].[Store Profit] [Store].[Mexico].[Jalisco].[Guadalajara] [Measures].[Store Profit] [Store].[Mexico].[Jalisco].[Guadalajara].[Store 5] [Measures].[Store Profit] [Store].[Mexico].[Veracruz] [Measures].[Store Profit] [Store].[Mexico].[Veracruz].[Orizaba] [Measures].[Store Profit] [Store].[Mexico].[Veracruz].[Orizaba].[Store 10] [Measures].[Store Profit] [Store].[Mexico].[Yucatan] [Measures].[Store Profit] [Store].[Mexico].[Yucatan].[Merida] [Measures].[Store Profit] [Store].[Mexico].[Yucatan].[Merida].[Store 8] [Measures].[Store Profit] [Store].[Mexico].[Zacatecas] [Measures].[Store Profit] [Store].[Mexico].[Zacatecas].[Camacho] [Measures].[Store Profit] [Store].[Mexico].[Zacatecas].[Camacho].[Store 4] [Measures].[Store Profit] [Store].[Mexico].[Zacatecas].[Hidalgo] [Measures].[Store Profit] [Store].[Mexico].[Zacatecas].[Hidalgo].[Store 12] [Measures].[Store Profit] [Store].[Mexico].[Zacatecas].[Hidalgo].[Store 18] [Measures].[Store Profit] [Store].[USA] [Measures].[Store Profit] [Store].[USA].[CA] [Measures].[Store Profit] [Store].[USA].[CA].[Alameda] [Measures].[Store Profit] [Store].[USA].[CA].[Alameda].[HQ] [Measures].[Store Profit] [Store].[USA].[CA].[Beverly Hills] [Measures].[Store Profit] [Store].[USA].[CA].[Beverly Hills].[Store 6] [Measures].[Store Profit] [Store].[USA].[CA].[Los Angeles] [Measures].[Store Profit] [Store].[USA].[CA].[Los Angeles].[Store 7] [Measures].[Store Profit] [Store].[USA].[CA].[San Diego] [Measures].[Store Profit] [Store].[USA].[CA].[San Diego].[Store 24] [Measures].[Store Profit] [Store].[USA].[CA].[San Francisco] [Measures].[Store Profit] [Store].[USA].[CA].[San Francisco].[Store 14] [Measures].[Store Profit] [Store].[USA].[OR] [Measures].[Store Profit] [Store].[USA].[OR].[Portland] [Measures].[Store Profit] [Store].[USA].[OR].[Portland].[Store 11] [Measures].[Store Profit] [Store].[USA].[OR].[Salem] [Measures].[Store Profit] [Store].[USA].[OR].[Salem].[Store 13] [Measures].[Store Profit] [Store].[USA].[WA] [Measures].[Store Profit] [Store].[USA].[WA].[Bellingham] [Measures].[Store Profit] [Store].[USA].[WA].[Bellingham].[Store 2] [Measures].[Store Profit] [Store].[USA].[WA].[Bremerton] [Measures].[Store Profit] [Store].[USA].[WA].[Bremerton].[Store 3] [Measures].[Store Profit] [Store].[USA].[WA].[Seattle] [Measures].[Store Profit] [Store].[USA].[WA].[Seattle].[Store 15] [Measures].[Store Profit] [Store].[USA].[WA].[Spokane] [Measures].[Store Profit] [Store].[USA].[WA].[Spokane].[Store 16] [Measures].[Store Profit] [Store].[USA].[WA].[Tacoma] [Measures].[Store Profit] [Store].[USA].[WA].[Tacoma].[Store 17] [Measures].[Store Profit] [Store].[USA].[WA].[Walla Walla] [Measures].[Store Profit] [Store].[USA].[WA].[Walla Walla].[Store 22] [Measures].[Store Profit] [Store].[USA].[WA].[Yakima] [Measures].[Store Profit] [Store].[USA].[WA].[Yakima].[Store 23] 60814.4714 57323.3736 61262.5473 66364.4742 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 60814.4714 57323.3736 61262.5473 66364.4742 15651.0004 16743.9595 17189.7717 19628.088 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 3583.4563 5538.0341 4534.0642 6370.6271 3583.4563 5538.0341 4534.0642 6370.6271 5906.233 5097.82 6040.0404 6554.1819 5906.233 5097.82 6040.0404 6554.1819 5746.7441 5580.7009 6115.4902 6184.897 5746.7441 5580.7009 6115.4902 6184.897 414.567 527.4045 500.1769 518.382 414.567 527.4045 500.1769 518.382 17230.4541 13671.4779 15800.1723 14895.1158 6096.9216 6180.8576 5108.6427 6432.4504 6096.9216 6180.8576 5108.6427 6432.4504 11133.5325 7490.6203 10691.5296 8462.6654 11133.5325 7490.6203 10691.5296 8462.6654 27933.0169 26907.9362 28272.6033 31841.2704 468.9205 432.7989 459.0532 674.0213 468.9205 432.7989 459.0532 674.0213 5501.8184 5551.0509 5757.519 6321.5162 5501.8184 5551.0509 5757.519 6321.5162 5639.1092 5425.6842 5789.5264 6271.9034 5639.1092 5425.6842 5789.5264 6271.9034 5189.7487 5002.3148 5525.279 5940.2173 5189.7487 5002.3148 5525.279 5940.2173 7867.3664 7375.4887 8090.5428 9164.3626 7867.3664 7375.4887 8090.5428 9164.3626 445.6996 534.9483 501.2902 632.4563 445.6996 534.9483 501.2902 632.4563 2820.3541 2585.6504 2149.3927 2836.7933 2820.3541 2585.6504 2149.3927 2836.7933 mondrian-3.4.1/testsrc/queryFiles/queryTest_3b6a02511345dcc.xml0000644000175000017500000012153311735330606023713 0ustar drazzibdrazzib select { ([Measures].[Store Sales]) } on columns, generate( [Product].[Drink].children, crossjoin([Promotions].children, {[Product].currentmember})) on rows from [Sales] [Time] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Store Sales] [Promotions] [Product] [Promotions].[Bag Stuffers] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Best Savings] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Big Promo] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Big Time Discounts] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Big Time Savings] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Bye Bye Baby] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Cash Register Lottery] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Coupon Spectacular] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Dimes Off] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Dollar Cutters] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Dollar Days] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Double Down Sale] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Double Your Savings] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Fantastic Discounts] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Free For All] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Go For It] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Green Light Days] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Green Light Special] [Product].[Drink].[Alcoholic Beverages] [Promotions].[High Roller Savings] [Product].[Drink].[Alcoholic Beverages] [Promotions].[I Cant Believe It Sale] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Money Grabbers] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Money Savers] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Mystery Sale] [Product].[Drink].[Alcoholic Beverages] [Promotions].[No Promotion] [Product].[Drink].[Alcoholic Beverages] [Promotions].[One Day Sale] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Pick Your Savings] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Price Cutters] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Price Destroyers] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Price Savers] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Price Slashers] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Price Smashers] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Price Winners] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Sale Winners] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Sales Days] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Sales Galore] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Save-It Sale] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Saving Days] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Savings Galore] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Shelf Clearing Days] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Shelf Emptiers] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Super Duper Savers] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Super Savers] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Super Wallet Savers] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Three for One] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Tip Top Savings] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Two Day Sale] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Two for One] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Unbeatable Price Savers] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Wallet Savers] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Weekend Markdown] [Product].[Drink].[Alcoholic Beverages] [Promotions].[You Save Days] [Product].[Drink].[Alcoholic Beverages] [Promotions].[Bag Stuffers] [Product].[Drink].[Beverages] [Promotions].[Best Savings] [Product].[Drink].[Beverages] [Promotions].[Big Promo] [Product].[Drink].[Beverages] [Promotions].[Big Time Discounts] [Product].[Drink].[Beverages] [Promotions].[Big Time Savings] [Product].[Drink].[Beverages] [Promotions].[Bye Bye Baby] [Product].[Drink].[Beverages] [Promotions].[Cash Register Lottery] [Product].[Drink].[Beverages] [Promotions].[Coupon Spectacular] [Product].[Drink].[Beverages] [Promotions].[Dimes Off] [Product].[Drink].[Beverages] [Promotions].[Dollar Cutters] [Product].[Drink].[Beverages] [Promotions].[Dollar Days] [Product].[Drink].[Beverages] [Promotions].[Double Down Sale] [Product].[Drink].[Beverages] [Promotions].[Double Your Savings] [Product].[Drink].[Beverages] [Promotions].[Fantastic Discounts] [Product].[Drink].[Beverages] [Promotions].[Free For All] [Product].[Drink].[Beverages] [Promotions].[Go For It] [Product].[Drink].[Beverages] [Promotions].[Green Light Days] [Product].[Drink].[Beverages] [Promotions].[Green Light Special] [Product].[Drink].[Beverages] [Promotions].[High Roller Savings] [Product].[Drink].[Beverages] [Promotions].[I Cant Believe It Sale] [Product].[Drink].[Beverages] [Promotions].[Money Grabbers] [Product].[Drink].[Beverages] [Promotions].[Money Savers] [Product].[Drink].[Beverages] [Promotions].[Mystery Sale] [Product].[Drink].[Beverages] [Promotions].[No Promotion] [Product].[Drink].[Beverages] [Promotions].[One Day Sale] [Product].[Drink].[Beverages] [Promotions].[Pick Your Savings] [Product].[Drink].[Beverages] [Promotions].[Price Cutters] [Product].[Drink].[Beverages] [Promotions].[Price Destroyers] [Product].[Drink].[Beverages] [Promotions].[Price Savers] [Product].[Drink].[Beverages] [Promotions].[Price Slashers] [Product].[Drink].[Beverages] [Promotions].[Price Smashers] [Product].[Drink].[Beverages] [Promotions].[Price Winners] [Product].[Drink].[Beverages] [Promotions].[Sale Winners] [Product].[Drink].[Beverages] [Promotions].[Sales Days] [Product].[Drink].[Beverages] [Promotions].[Sales Galore] [Product].[Drink].[Beverages] [Promotions].[Save-It Sale] [Product].[Drink].[Beverages] [Promotions].[Saving Days] [Product].[Drink].[Beverages] [Promotions].[Savings Galore] [Product].[Drink].[Beverages] [Promotions].[Shelf Clearing Days] [Product].[Drink].[Beverages] [Promotions].[Shelf Emptiers] [Product].[Drink].[Beverages] [Promotions].[Super Duper Savers] [Product].[Drink].[Beverages] [Promotions].[Super Savers] [Product].[Drink].[Beverages] [Promotions].[Super Wallet Savers] [Product].[Drink].[Beverages] [Promotions].[Three for One] [Product].[Drink].[Beverages] [Promotions].[Tip Top Savings] [Product].[Drink].[Beverages] [Promotions].[Two Day Sale] [Product].[Drink].[Beverages] [Promotions].[Two for One] [Product].[Drink].[Beverages] [Promotions].[Unbeatable Price Savers] [Product].[Drink].[Beverages] [Promotions].[Wallet Savers] [Product].[Drink].[Beverages] [Promotions].[Weekend Markdown] [Product].[Drink].[Beverages] [Promotions].[You Save Days] [Product].[Drink].[Beverages] [Promotions].[Bag Stuffers] [Product].[Drink].[Dairy] [Promotions].[Best Savings] [Product].[Drink].[Dairy] [Promotions].[Big Promo] [Product].[Drink].[Dairy] [Promotions].[Big Time Discounts] [Product].[Drink].[Dairy] [Promotions].[Big Time Savings] [Product].[Drink].[Dairy] [Promotions].[Bye Bye Baby] [Product].[Drink].[Dairy] [Promotions].[Cash Register Lottery] [Product].[Drink].[Dairy] [Promotions].[Coupon Spectacular] [Product].[Drink].[Dairy] [Promotions].[Dimes Off] [Product].[Drink].[Dairy] [Promotions].[Dollar Cutters] [Product].[Drink].[Dairy] [Promotions].[Dollar Days] [Product].[Drink].[Dairy] [Promotions].[Double Down Sale] [Product].[Drink].[Dairy] [Promotions].[Double Your Savings] [Product].[Drink].[Dairy] [Promotions].[Fantastic Discounts] [Product].[Drink].[Dairy] [Promotions].[Free For All] [Product].[Drink].[Dairy] [Promotions].[Go For It] [Product].[Drink].[Dairy] [Promotions].[Green Light Days] [Product].[Drink].[Dairy] [Promotions].[Green Light Special] [Product].[Drink].[Dairy] [Promotions].[High Roller Savings] [Product].[Drink].[Dairy] [Promotions].[I Cant Believe It Sale] [Product].[Drink].[Dairy] [Promotions].[Money Grabbers] [Product].[Drink].[Dairy] [Promotions].[Money Savers] [Product].[Drink].[Dairy] [Promotions].[Mystery Sale] [Product].[Drink].[Dairy] [Promotions].[No Promotion] [Product].[Drink].[Dairy] [Promotions].[One Day Sale] [Product].[Drink].[Dairy] [Promotions].[Pick Your Savings] [Product].[Drink].[Dairy] [Promotions].[Price Cutters] [Product].[Drink].[Dairy] [Promotions].[Price Destroyers] [Product].[Drink].[Dairy] [Promotions].[Price Savers] [Product].[Drink].[Dairy] [Promotions].[Price Slashers] [Product].[Drink].[Dairy] [Promotions].[Price Smashers] [Product].[Drink].[Dairy] [Promotions].[Price Winners] [Product].[Drink].[Dairy] [Promotions].[Sale Winners] [Product].[Drink].[Dairy] [Promotions].[Sales Days] [Product].[Drink].[Dairy] [Promotions].[Sales Galore] [Product].[Drink].[Dairy] [Promotions].[Save-It Sale] [Product].[Drink].[Dairy] [Promotions].[Saving Days] [Product].[Drink].[Dairy] [Promotions].[Savings Galore] [Product].[Drink].[Dairy] [Promotions].[Shelf Clearing Days] [Product].[Drink].[Dairy] [Promotions].[Shelf Emptiers] [Product].[Drink].[Dairy] [Promotions].[Super Duper Savers] [Product].[Drink].[Dairy] [Promotions].[Super Savers] [Product].[Drink].[Dairy] [Promotions].[Super Wallet Savers] [Product].[Drink].[Dairy] [Promotions].[Three for One] [Product].[Drink].[Dairy] [Promotions].[Tip Top Savings] [Product].[Drink].[Dairy] [Promotions].[Two Day Sale] [Product].[Drink].[Dairy] [Promotions].[Two for One] [Product].[Drink].[Dairy] [Promotions].[Unbeatable Price Savers] [Product].[Drink].[Dairy] [Promotions].[Wallet Savers] [Product].[Drink].[Dairy] [Promotions].[Weekend Markdown] [Product].[Drink].[Dairy] [Promotions].[You Save Days] [Product].[Drink].[Dairy] 74.39 155.61 101.01 35.73 5.39 57.59 265.15 #Missing 56.37 32.91 118.24 58.98 38.31 #Missing 97.42 5.52 73.32 18.35 153.28 21.48 #Missing 100.1 79.81 10136.65 127.71 29.25 48.08 112.6 249.69 51.92 24.29 78.56 10.56 88.72 127.59 104.15 121.84 58.49 51.49 98.82 147.34 96.41 43.71 65.68 15.65 146.06 5.62 139.39 44.17 53.99 201.69 98.01 181.49 144.85 116.43 61.89 87.3 517.07 #Missing 105.64 44.6 155.07 255.11 31.32 #Missing 165.92 93.64 134.52 55.74 352.56 8.58 #Missing 54.98 147.57 20381.82 264.62 57.12 201.11 206.59 437.91 113.2 59.0 122.91 48.02 276.23 304.35 175.49 136.45 123.79 32.22 233.02 310.45 319.21 122.5 104.89 40.25 187.36 2.2 160.01 46.61 115.21 353.7 18.62 62.03 36.84 11.56 19.26 28.96 142.78 #Missing 45.23 7.74 69.04 37.44 41.14 #Missing 11.32 3.29 8.49 #Missing 75.93 5.39 #Missing 34.58 12.33 5163.38 62.03 6.91 66.51 88.45 97.0 30.87 44.24 47.74 1.84 41.55 57.09 44.58 65.03 49.37 31.87 21.67 45.97 77.5 41.53 34.46 16.21 37.4 9.69 50.21 53.12 13.67 86.74 mondrian-3.4.1/testsrc/queryFiles/queryTest_1dfd724ee6d136_FIRSTCHILD.xml0000644000175000017500000002026311735330606025443 0ustar drazzibdrazzib SELECT GENERATE([Time].[Year].MEMBERS, {[Time].[Time].CURRENTMEMBER.FIRSTCHILD}) ON COLUMNS, [Store].[Store Name].MEMBERS ON ROWS FROM [Sales] WHERE (Measures.[Store Sales]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Store Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Time].[1998].[Q1] [Store] [Store].[Canada].[BC].[Vancouver].[Store 19] [Store].[Canada].[BC].[Victoria].[Store 20] [Store].[Mexico].[DF].[Mexico City].[Store 9] [Store].[Mexico].[DF].[San Andres].[Store 21] [Store].[Mexico].[Guerrero].[Acapulco].[Store 1] [Store].[Mexico].[Jalisco].[Guadalajara].[Store 5] [Store].[Mexico].[Veracruz].[Orizaba].[Store 10] [Store].[Mexico].[Yucatan].[Merida].[Store 8] [Store].[Mexico].[Zacatecas].[Camacho].[Store 4] [Store].[Mexico].[Zacatecas].[Hidalgo].[Store 12] [Store].[Mexico].[Zacatecas].[Hidalgo].[Store 18] [Store].[USA].[CA].[Alameda].[HQ] [Store].[USA].[CA].[Beverly Hills].[Store 6] [Store].[USA].[CA].[Los Angeles].[Store 7] [Store].[USA].[CA].[San Diego].[Store 24] [Store].[USA].[CA].[San Francisco].[Store 14] [Store].[USA].[OR].[Portland].[Store 11] [Store].[USA].[OR].[Salem].[Store 13] [Store].[USA].[WA].[Bellingham].[Store 2] [Store].[USA].[WA].[Bremerton].[Store 3] [Store].[USA].[WA].[Seattle].[Store 15] [Store].[USA].[WA].[Spokane].[Store 16] [Store].[USA].[WA].[Tacoma].[Store 17] [Store].[USA].[WA].[Walla Walla].[Store 22] [Store].[USA].[WA].[Yakima].[Store 23] #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 8203.89 #Missing 13736.97 #Missing 13297.83 #Missing 936.51 #Missing 14171.2 #Missing 25999.09 #Missing 1095.46 #Missing 12485.77 #Missing 12760.64 #Missing 11630.11 #Missing 17865.75 #Missing 1005.81 #Missing 6439.32 #Missing mondrian-3.4.1/testsrc/queryFiles/queryTest_d5e97dd90c231d0.xml0000644000175000017500000000433111735330606024010 0ustar drazzibdrazzib SELECT {[Measures].[Unit Sales]} ON COLUMNS, TAIL([Time].[Month].MEMBERS) ON ROWS FROM [Sales] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Time] [Time].[1998].[Q4].[12] #Missing mondrian-3.4.1/testsrc/queryFiles/queryTest_3ee94f9757fc8698.xml0000644000175000017500000000553111735330606024063 0ustar drazzibdrazzib select {[Measures].[Unit Sales]} on columns, drilldownlevel({[Store].[USA].[CA].[Los Angeles], [Store].[USA].[CA], [Store].[USA]}, [Store].[Store City]) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[Los Angeles].[Store 7] [Store].[USA].[CA] [Store].[USA] 25663.0 25663.0 74748.0 266773.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_a0f9f4f161c69f2c_TOPSUM.xml0000644000175000017500000000545711735330606025302 0ustar drazzibdrazzib SELECT {Measures.[Unit Sales]} ON COLUMNS, CROSSJOIN(Customers.CHILDREN, TOPSUM(DESCENDANTS([Store].CURRENTMEMBER,[Store].[Store Name]),1,[Measures].[Sales Count])) ON ROWS FROM [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Customers] [Store] [Customers].[Canada] [Store].[USA].[OR].[Salem].[Store 13] [Customers].[Mexico] [Store].[USA].[OR].[Salem].[Store 13] [Customers].[USA] [Store].[USA].[OR].[Salem].[Store 13] #Missing #Missing 41580.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_bed44a518dce24.xml0000644000175000017500000001260111735330606023777 0ustar drazzibdrazzib select {[Measures].[Unit Sales]} on columns, drilldownlevel(drilldownlevel({[Store].[USA]})) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Francisco] [Store].[USA].[OR] [Store].[USA].[OR].[Portland] [Store].[USA].[OR].[Salem] [Store].[USA].[WA] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Tacoma] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Yakima] 266773.0 74748.0 #Missing 21333.0 25663.0 25635.0 2117.0 67659.0 26079.0 41580.0 124366.0 2237.0 24576.0 25011.0 23591.0 35257.0 2203.0 11491.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_4e509257ee5d860_CURRENTMEMBER.xml0000644000175000017500000000547111735330606025762 0ustar drazzibdrazzib WITH MEMBER Measures.[Closing Balance] AS '([Measures].[Units Ordered], CLOSINGPERIOD( [Time].[Month], [Time].[Time].CURRENTMEMBER)) - ([Measures].[Units Shipped], CLOSINGPERIOD( [Time].[Month], [Time].[Time].CURRENTMEMBER))' SELECT {[Measures].[Closing Balance]} ON COLUMNS, [Store Type].MEMBERS ON ROWS FROM [Warehouse] [Store] [Store Size in SQFT] [Time] [Warehouse] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Time].[1997] [Warehouse].[All Warehouses] [Measures] [Measures].[Closing Balance] [Store Type] [Store Type].[All Store Types] [Store Type].[Deluxe Supermarket] [Store Type].[Gourmet Supermarket] [Store Type].[HeadQuarters] [Store Type].[Mid-Size Grocery] [Store Type].[Small Grocery] [Store Type].[Supermarket] 1218.0 336.0 221.0 #Missing #Missing #Missing 661.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_4077b19efab23af0.xml0000644000175000017500000000546711735330606024157 0ustar drazzibdrazzib with member [Promotions].[Med Unit Sales] as 'Median([Promotions].members)' Select { [Promotions].[Med Unit Sales]} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Med Unit Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 570.0 536.5 485.5 573.5 mondrian-3.4.1/testsrc/queryFiles/queryTest_942ce548f98a38.xml0000644000175000017500000606011011735330606023612 0ustar drazzibdrazzib select {[Measures].[Store Invoice], [Measures].[Supply Time]} on columns, hierarchize(crossjoin([Warehouse].members, crossjoin([Store].[Store State].members, [Store Type].members))) on rows from [Warehouse] [Store Size in SQFT] [Time] [Store Size in SQFT].[All Store Size in SQFTs] [Time].[1997] [Measures] [Measures].[Store Invoice] [Measures].[Supply Time] [Warehouse] [Store] [Store Type] [Warehouse].[All Warehouses] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[All Warehouses] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[All Warehouses] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[All Warehouses] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[All Warehouses] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[All Warehouses] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[All Warehouses] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[All Warehouses] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[All Warehouses] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[All Warehouses] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[All Warehouses] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[All Warehouses] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[All Warehouses] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[All Warehouses] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[All Warehouses] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[All Warehouses] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[All Warehouses] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[All Warehouses] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[All Warehouses] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[All Warehouses] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[All Warehouses] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[All Warehouses] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[All Warehouses] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[All Warehouses] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[All Warehouses] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[All Warehouses] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[All Warehouses] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[All Warehouses] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[All Warehouses] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[All Warehouses] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[All Warehouses] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[All Warehouses] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[All Warehouses] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[All Warehouses] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[All Warehouses] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[All Warehouses] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[All Warehouses] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[All Warehouses] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[All Warehouses] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[All Warehouses] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[All Warehouses] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[All Warehouses] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[All Warehouses] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[All Warehouses] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[All Warehouses] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[All Warehouses] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[All Warehouses] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[All Warehouses] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[All Warehouses] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[All Warehouses] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[All Warehouses] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[All Warehouses] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[All Warehouses] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Canada].[BC] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Canada].[BC] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Canada].[BC] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Canada].[BC] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Canada].[BC] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Canada].[BC] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Canada].[BC] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Canada].[BC] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Canada].[BC] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Canada].[BC] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Canada].[BC] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Canada].[BC] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Canada].[BC] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Canada].[BC] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Vancouver].[Bellmont Distributing] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Canada].[BC].[Victoria].[Rose Food Warehousing] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[Mexico City].[Freeman And Co] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[DF].[San Andres].[Derby and Hunt] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Guerrero].[Acapulco].[Salka Warehousing] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Jalisco].[Guadalajara].[Focus, Inc.] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Veracruz].[Orizaba].[Jamison, Inc.] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Yucatan].[Marida].[Bastani and Sons] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Camacho].[Anderson Warehousing] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Arnold and Sons] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico].[Zacatecas].[Hidalgo].[Worthington Food Products] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[CA] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[CA] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[CA] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[CA] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[CA] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[CA] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[CA] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[CA] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[CA] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[CA] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[CA] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[CA] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[CA] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[CA] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[CA] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[CA] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[CA] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[CA] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[OR] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[OR] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[OR] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[OR] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[OR] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[OR] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[OR] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[OR] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[OR] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[OR] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[OR] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[OR] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[OR] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[OR] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[OR] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[OR] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[OR] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[OR] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[OR] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[WA] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[WA] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[WA] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[WA] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[WA] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[WA] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[WA] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[WA] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[WA] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[WA] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[WA] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[WA] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[WA] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[WA] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[WA] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[WA] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[WA] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[WA] [Store Type].[Supermarket] #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 29607.2994 3100.0 #Missing #Missing 5132.8974 559.0 #Missing #Missing #Missing #Missing 988.8204 80.0 23485.5816 2461.0 20194.0007 2051.0 16151.0411 1650.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 4042.9596 401.0 52477.1088 5274.0 16100.8297 1578.0 #Missing #Missing #Missing #Missing 5274.3375 528.0 2143.2282 299.0 28958.7134 2869.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 29607.2994 3100.0 #Missing #Missing 5132.8974 559.0 #Missing #Missing #Missing #Missing 988.8204 80.0 23485.5816 2461.0 20194.0007 2051.0 16151.0411 1650.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 4042.9596 401.0 52477.1088 5274.0 16100.8297 1578.0 #Missing #Missing #Missing #Missing 5274.3375 528.0 2143.2282 299.0 28958.7134 2869.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 29607.2994 3100.0 #Missing #Missing 5132.8974 559.0 #Missing #Missing #Missing #Missing 988.8204 80.0 23485.5816 2461.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 5132.8974 559.0 #Missing #Missing 5132.8974 559.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 5132.8974 559.0 #Missing #Missing 5132.8974 559.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 11634.9186 1279.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 11634.9186 1279.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 11634.9186 1279.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 11634.9186 1279.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 11850.663 1182.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 11850.663 1182.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 11850.663 1182.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 11850.663 1182.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 988.8204 80.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 988.8204 80.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 988.8204 80.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 988.8204 80.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 20194.0007 2051.0 16151.0411 1650.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 4042.9596 401.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 4042.9596 401.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 4042.9596 401.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 4042.9596 401.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 4042.9596 401.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 16151.0411 1650.0 16151.0411 1650.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 16151.0411 1650.0 16151.0411 1650.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 52477.1088 5274.0 16100.8297 1578.0 #Missing #Missing #Missing #Missing 5274.3375 528.0 2143.2282 299.0 28958.7134 2869.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 1049.4587 216.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 1049.4587 216.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 1049.4587 216.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 1049.4587 216.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 11659.6249 1130.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 11659.6249 1130.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 11659.6249 1130.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 11659.6249 1130.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 11517.1251 1193.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 11517.1251 1193.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 11517.1251 1193.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 11517.1251 1193.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 5781.9634 546.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 5781.9634 546.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 5781.9634 546.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 5781.9634 546.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 16100.8297 1578.0 16100.8297 1578.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 16100.8297 1578.0 16100.8297 1578.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 1093.7695 83.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 1093.7695 83.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 1093.7695 83.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 1093.7695 83.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 5274.3375 528.0 #Missing #Missing #Missing #Missing #Missing #Missing 5274.3375 528.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 5274.3375 528.0 #Missing #Missing #Missing #Missing #Missing #Missing 5274.3375 528.0 #Missing #Missing #Missing #Missing mondrian-3.4.1/testsrc/queryFiles/queryTest_1662791d39c739.xml0000644000175000017500000001011611735330606023440 0ustar drazzibdrazzib select {[Measures].[Store Sales], [Measures].[Unit Sales]} on columns, crossjoin([Time].[Time].children, [Store].children) on rows from [Warehouse and Sales] [Measures] [Measures].[Store Sales] [Measures].[Unit Sales] [Time] [Store] [Time].[1997].[Q1] [Store].[Canada] [Time].[1997].[Q1] [Store].[Mexico] [Time].[1997].[Q1] [Store].[USA] [Time].[1997].[Q2] [Store].[Canada] [Time].[1997].[Q2] [Store].[Mexico] [Time].[1997].[Q2] [Store].[USA] [Time].[1997].[Q3] [Store].[Canada] [Time].[1997].[Q3] [Store].[Mexico] [Time].[1997].[Q3] [Store].[USA] [Time].[1997].[Q4] [Store].[Canada] [Time].[1997].[Q4] [Store].[Mexico] [Time].[1997].[Q4] [Store].[USA] #Missing #Missing #Missing #Missing 139628.35 66291.0 #Missing #Missing #Missing #Missing 132666.27 62610.0 #Missing #Missing #Missing #Missing 140271.89 65848.0 #Missing #Missing #Missing #Missing 152671.62 72024.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_72fbd5b8f2963a70_order.xml0000644000175000017500000001616211735330606025302 0ustar drazzibdrazzib SELECT {[Measures].[Unit Sales]} ON COLUMNS, ORDER([Store].[Store Name].MEMBERS, (Measures.[Unit Sales]),BDESC) ON ROWS FROM [Sales] WHERE [Product].[Non-Consumable] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[Non-Consumable] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA].[OR].[Salem].[Store 13] [Store].[USA].[WA].[Tacoma].[Store 17] [Store].[USA].[OR].[Portland].[Store 11] [Store].[USA].[CA].[Los Angeles].[Store 7] [Store].[USA].[CA].[San Diego].[Store 24] [Store].[USA].[WA].[Seattle].[Store 15] [Store].[USA].[WA].[Bremerton].[Store 3] [Store].[USA].[WA].[Spokane].[Store 16] [Store].[USA].[CA].[Beverly Hills].[Store 6] [Store].[USA].[WA].[Yakima].[Store 23] [Store].[USA].[WA].[Bellingham].[Store 2] [Store].[USA].[WA].[Walla Walla].[Store 22] [Store].[USA].[CA].[San Francisco].[Store 14] [Store].[Canada].[BC].[Vancouver].[Store 19] [Store].[Canada].[BC].[Victoria].[Store 20] [Store].[Mexico].[DF].[Mexico City].[Store 9] [Store].[Mexico].[DF].[San Andres].[Store 21] [Store].[Mexico].[Guerrero].[Acapulco].[Store 1] [Store].[Mexico].[Jalisco].[Guadalajara].[Store 5] [Store].[Mexico].[Veracruz].[Orizaba].[Store 10] [Store].[Mexico].[Yucatan].[Merida].[Store 8] [Store].[Mexico].[Zacatecas].[Camacho].[Store 4] [Store].[Mexico].[Zacatecas].[Hidalgo].[Store 12] [Store].[Mexico].[Zacatecas].[Hidalgo].[Store 18] [Store].[USA].[CA].[Alameda].[HQ] 7940.0 6712.0 5076.0 4947.0 4706.0 4639.0 4479.0 4428.0 3950.0 2140.0 442.0 390.0 387.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing mondrian-3.4.1/testsrc/queryFiles/queryTest_7ccc4cf23a5e748.xml0000644000175000017500000001201211735330606024067 0ustar drazzibdrazzib select { ([Measures].[Store Sales]) } on columns, generate( crossjoin([Product].[Drink].children, [Time].[1997].children), { ([Product].currentmember, [Time].[Time].currentmember) }) on rows from [Sales] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Store Sales] [Product] [Time] [Product].[Drink].[Alcoholic Beverages] [Time].[1997].[Q1] [Product].[Drink].[Alcoholic Beverages] [Time].[1997].[Q2] [Product].[Drink].[Alcoholic Beverages] [Time].[1997].[Q3] [Product].[Drink].[Alcoholic Beverages] [Time].[1997].[Q4] [Product].[Drink].[Beverages] [Time].[1997].[Q1] [Product].[Drink].[Beverages] [Time].[1997].[Q2] [Product].[Drink].[Beverages] [Time].[1997].[Q3] [Product].[Drink].[Beverages] [Time].[1997].[Q4] [Product].[Drink].[Dairy] [Time].[1997].[Q1] [Product].[Drink].[Dairy] [Time].[1997].[Q2] [Product].[Drink].[Dairy] [Time].[1997].[Q3] [Product].[Drink].[Dairy] [Time].[1997].[Q4] 3082.0 3506.37 3450.49 3990.22 6770.79 6771.57 6881.84 7324.33 1733.01 1636.64 1661.67 2027.28 mondrian-3.4.1/testsrc/queryFiles/queryTest_590b8a75073c2fd.xml0000644000175000017500000000413511735330606023734 0ustar drazzibdrazzib select non empty {[Warehouse].children * {[Measures].[Store Invoice], [Measures].[Supply Time]}} on rows, {[Store].[USA].children} on columns from [Warehouse] [Store Size in SQFT] [Store Type] [Time] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Time].[1997] [Store] [Store].[USA].[CA] [Store].[USA].[OR] [Store].[USA].[WA] [Warehouse] [Measures] [Warehouse].[USA] [Measures].[Store Invoice] [Warehouse].[USA] [Measures].[Supply Time] 29607.2994 20194.0007 52477.1088 3100.0 2051.0 5274.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_8351bd6ef1cea1ec.xml0000644000175000017500000000653711735330606024321 0ustar drazzibdrazzib select {[Warehouse].children * {[Measures].[Store Invoice], [Measures].[Supply Time]}} on rows, {[Store].[USA].children} on columns from [Warehouse] [Store Size in SQFT] [Store Type] [Time] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Time].[1997] [Store] [Store].[USA].[CA] [Store].[USA].[OR] [Store].[USA].[WA] [Warehouse] [Measures] [Warehouse].[Canada] [Measures].[Store Invoice] [Warehouse].[Canada] [Measures].[Supply Time] [Warehouse].[Mexico] [Measures].[Store Invoice] [Warehouse].[Mexico] [Measures].[Supply Time] [Warehouse].[USA] [Measures].[Store Invoice] [Warehouse].[USA] [Measures].[Supply Time] #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 29607.2994 20194.0007 52477.1088 3100.0 2051.0 5274.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_26e7ec5979bbb15e_order.xml0000644000175000017500000000515311735330606025365 0ustar drazzibdrazzib Select {[Measures].[Unit Sales]} on columns, Order( [Product].[All Products].children,[Measures].[MeasuresLevel].[Unit Sales],DESC) On Rows From [Sales] [Time] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Product] [Product].[Food] [Product].[Non-Consumable] [Product].[Drink] 191940.0 50236.0 24597.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_parent_1e712d6b848e11d.xml0000644000175000017500000000734311735330606025306 0ustar drazzibdrazzib SELECT {Crossjoin({[Store].[Canada].Parent}, {[Time].[1997].Children})} ON AXIS(0), {[Product].[All Products], [Product].[All Products].Children} ON AXIS(1) FROM [Sales] [Measures] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Time] [Store].[All Stores] [Time].[1997].[Q1] [Store].[All Stores] [Time].[1997].[Q2] [Store].[All Stores] [Time].[1997].[Q3] [Store].[All Stores] [Time].[1997].[Q4] [Product] [Product].[All Products] [Product].[Drink] [Product].[Food] [Product].[Non-Consumable] 66291.0 62610.0 65848.0 72024.0 5976.0 5895.0 6065.0 6661.0 47809.0 44825.0 47440.0 51866.0 12506.0 11890.0 12343.0 13497.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_ac6d217d3a16febe.xml0000644000175000017500000000721211735330606024306 0ustar drazzibdrazzib with member [Product].[Level 0 Name] as '[Product].Levels(0).Name', SOLVE_ORDER = 0 member [Product].[Level 1 Name] as '[Product].Levels(1).Name', SOLVE_ORDER = 1 member [Product].[Level 2 Name] as '[Product].Levels(2).Name',SOLVE_ORDER = 2 member [Product].[Level 3 Name] as '[Product].Levels(3).Name',SOLVE_ORDER = 3 member [Product].[Level 4 Name] as '[Product].Levels(4).Name',SOLVE_ORDER = 4 member [Product].[Level 5 Name] as '[Product].Levels(5).Name',SOLVE_ORDER = 5 member [Product].[Level 6 Name] as '[Product].Levels(6).Name',SOLVE_ORDER = 6 select {[Product].[Level 0 Name],[Product].[Level 1 Name],[Product].[Level 2 Name],[Product].[Level 3 Name],[Product].[Level 4 Name],[Product].[Level 5 Name],[Product].[Level 6 Name]} on Axis(0) from [Sales] [Measures] [Time] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Product] [Product].[Level 0 Name] [Product].[Level 1 Name] [Product].[Level 2 Name] [Product].[Level 3 Name] [Product].[Level 4 Name] [Product].[Level 5 Name] [Product].[Level 6 Name] (All) Product Family Product Department Product Category Product Subcategory Brand Name Product Name mondrian-3.4.1/testsrc/queryFiles/queryTest_dc9f80a1a2ce7c2.xml0000644000175000017500000000470711735330606024152 0ustar drazzibdrazzib select {[Measures].[Unit Sales]} on columns, drilldownlevel({[Store].[USA], [Store].[USA].[CA].[Los Angeles]}, [Store].[Store Country]) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA] [Store].[USA].[CA].[Los Angeles] 266773.0 25663.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_c63fa1f3ad94ce_CLOSINGPERIOD.xml0000644000175000017500000000267311735330606026153 0ustar drazzibdrazzib Select {CLOSINGPERIOD([Time].[Quarter], [Time].[1997]), [Time].[1997].[Q3]} on columns from [Warehouse] [Measures] [Store] [Store Size in SQFT] [Store Type] [Warehouse] [Measures].[Store Invoice] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Warehouse].[All Warehouses] [Time] [Time].[1997].[Q4] [Time].[1997].[Q3] 24089.6295 28825.8276 mondrian-3.4.1/testsrc/queryFiles/queryTest_prevMember_29d35e3111636ef1.xml0000644000175000017500000000667611735330606026133 0ustar drazzibdrazzib SELECT {[Education Level].[Partial College].PrevMember, [Education Level].Children} ON AXIS(0), {[Time].[1997].[Q1].[2].PrevMember, [Time].[1997].[Q1].[2].NextMember} ON AXIS(1) FROM [Sales] [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Education Level] [Education Level].[High School Degree] [Education Level].[Bachelors Degree] [Education Level].[Graduate Degree] [Education Level].[High School Degree] [Education Level].[Partial College] [Education Level].[Partial High School] [Time] [Time].[1997].[Q1].[1] [Time].[1997].[Q1].[3] 6316.0 5420.0 1197.0 6316.0 2120.0 6575.0 7456.0 6301.0 1265.0 7456.0 2230.0 6454.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_d179da7db6f6c50.xml0000644000175000017500000001076111735330606024101 0ustar drazzibdrazzib with member [Measures].[Test Measure] as '[Measures].[Customer Count] * 2 - [Measures].[Sales Count]' select { [Measures].[Test Measure] } on columns, filter(descendants([Store].[USA]), ([Measures].[Test Measure], [Time].[1997].[Q2]) &lt; -1000) on rows from [Sales] where ([Time].[1997].[Q3], [Product].[Product Family].[Food]) [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997].[Q3] [Product].[Food] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Test Measure] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[OR] [Store].[USA].[OR].[Salem] [Store].[USA].[OR].[Salem].[Store 13] [Store].[USA].[WA] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Bremerton].[Store 3] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Spokane].[Store 16] [Store].[USA].[WA].[Tacoma] [Store].[USA].[WA].[Tacoma].[Store 17] -9571.0 -2007.0 -2545.0 -1870.0 -1870.0 -5019.0 -1074.0 -1074.0 -1199.0 -1199.0 -1475.0 -1475.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_c6a5a837b6dd48b5.xml0000644000175000017500000000567111735330606024171 0ustar drazzibdrazzib with member [Promotions].[VarP Store Sales] as 'VarP([Promotions].members, [Measures].[Store Sales])' Select {[Promotions].[VarP Store Sales]} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] WHERE ([Education Level].[High School Degree]) [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[High School Degree] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[VarP Store Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 8.191633743036057E7 7.807854007101597E7 8.494469379849999E7 1.163169385421302E8 mondrian-3.4.1/testsrc/queryFiles/queryTest_86e3b6eb3551af2_Name.xml0000644000175000017500000000407111735330606024751 0ustar drazzibdrazzib WITH MEMBER [Measures].[Dimension Name] AS '[Measures].Name' SELECT {[Measures].[Dimension Name]} on columns From [Sales] [Time] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Dimension Name] Measures mondrian-3.4.1/testsrc/queryFiles/queryTest_4a7b5babf69b6ca.xml0000644000175000017500000000544111735330606024230 0ustar drazzibdrazzib select { ([Measures].[Store Sales]) } on columns, generate( { [Store].[USA], [Store].[Mexico] }, { [Time].[1997], [Time].[1998] }, all) on rows from [Sales] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Store Sales] [Time] [Time].[1997] [Time].[1998] [Time].[1997] [Time].[1998] 565238.13 #Missing 565238.13 #Missing mondrian-3.4.1/testsrc/queryFiles/queryTest_b7ed8378efbb5fce.xml0000644000175000017500000000562611735330606024422 0ustar drazzibdrazzib with member [Promotions].[Std DevP Store Sales] as 'StDevP([Promotions].members, [Measures].[Store Sales])' Select {[Promotions].[Std DevP Store Sales]} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Std DevP Store Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 29561.89947085892 30835.155613799416 31780.862973827498 35381.287031601234 mondrian-3.4.1/testsrc/queryFiles/queryTest_c0d8158f8ff9494.xml0000644000175000017500000000623211735330606023757 0ustar drazzibdrazzib SELECT {[Time].[1997].[Q1]} ON COLUMNS, bottomcount({[Store].[USA].[CA].[Beverly Hills], [Store].[USA].[CA].[Los Angeles], [Store].[USA].[CA].[San Diego], [Store].[USA].[CA].[San Francisco], [Store].[USA].[OR].[Portland], [Store].[All Stores].[USA].[OR].[Salem], [Store].[All Stores].[USA].[WA].[Bellingham], [Store].[All Stores].[USA].[WA].[Bremerton], [Store].[All Stores].[USA].[WA].[Seattle], [Store].[All Stores].[USA].[WA].[Spokane], [Store].[All Stores].[USA].[WA].[Tacoma], [Store].[All Stores].[USA].[WA].[Walla Walla], [Store].[All Stores].[USA].[WA].[Yakima]}, 3, [Measures].[Customer Count]) ON ROWS FROM [Sales] WHERE([Measures].[Unit Sales]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Store] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Yakima] [Store].[USA].[WA].[Walla Walla] 5607.0 3096.0 500.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_ed7649f92d14cd2a_TOPPERCENT.xml0000644000175000017500000000605211735330606025730 0ustar drazzibdrazzib WITH MEMBER Measures.[Country Name] AS 'Ancestor(Store.CurrentMember, [Store Country]).Name' SELECT {Measures.[Country Name], Measures.[Unit Sales]} ON COLUMNS, GENERATE([Store Country].MEMBERS, TOPPERCENT(DESCENDANTS([Store].CURRENTMEMBER,[Store].[Store Name]),1,[Measures].[Unit Sales])) ON ROWS FROM [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Country Name] [Measures].[Unit Sales] [Store] [Store].[Canada].[BC].[Vancouver].[Store 19] [Store].[Mexico].[DF].[Mexico City].[Store 9] [Store].[USA].[OR].[Salem].[Store 13] Canada #Missing Mexico #Missing USA 41580.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_fd20bd8afe4ec683.xml0000644000175000017500000001131111735330606024313 0ustar drazzibdrazzib SELECT {[Time].[1997].[Q1]} ON COLUMNS, bottompercent(filter( {[Store].[USA].[CA].children, [Store].[USA].[OR].children, [Store].[USA].[WA].children}, [Measures].[Store Sales] &gt; 0), 100, [Measures].[Store Sales]) ON ROWS FROM [Sales] WHERE([Measures].[Store Sales]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Store Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Store] [Store].[USA].[CA].[San Francisco] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Yakima] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Bremerton] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[OR].[Portland] [Store].[USA].[WA].[Tacoma] [Store].[USA].[OR].[Salem] 936.51 1005.81 1095.46 6439.32 8203.89 11630.11 12760.64 12485.77 13297.83 13736.97 14171.2 17865.75 25999.09 mondrian-3.4.1/testsrc/queryFiles/queryTest_6bb1e348f56d535.xml0000644000175000017500000010056311735330606023741 0ustar drazzibdrazzib select {[Measures].[Store Invoice], [Measures].[Supply Time]} on columns, non empty hierarchize(crossjoin([Warehouse].members, crossjoin([Store].[Store State].members, [Store Type].members))) on rows from [Warehouse] [Store Size in SQFT] [Time] [Store Size in SQFT].[All Store Size in SQFTs] [Time].[1997] [Measures] [Measures].[Store Invoice] [Measures].[Supply Time] [Warehouse] [Store] [Store Type] [Warehouse].[All Warehouses] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[All Warehouses] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[All Warehouses] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[All Warehouses] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[All Warehouses] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[All Warehouses] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[All Warehouses] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[All Warehouses] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[All Warehouses] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[All Warehouses] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[All Warehouses] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[All Warehouses] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Beverly Hills].[Big Quality Warehouse] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[Los Angeles].[Artesia Warehousing, Inc.] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Diego].[Jorgensen Service Storage] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA].[San Francisco].[Food Service Storage, Inc.] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Portland].[Quality Distribution, Inc.] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[OR].[Salem].[Treehouse Distribution] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bellingham].[Foster Products] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Bremerton].[Destination, Inc.] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Seattle].[Quality Warehousing and Trucking] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Spokane].[Jones International] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Tacoma].[Jorge Garcia, Inc.] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Walla Walla].[Valdez Warehousing] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA].[Yakima].[Maddock Stored Foods] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] 29607.2994 3100.0 5132.8974 559.0 988.8204 80.0 23485.5816 2461.0 20194.0007 2051.0 16151.0411 1650.0 4042.9596 401.0 52477.1088 5274.0 16100.8297 1578.0 5274.3375 528.0 2143.2282 299.0 28958.7134 2869.0 29607.2994 3100.0 5132.8974 559.0 988.8204 80.0 23485.5816 2461.0 20194.0007 2051.0 16151.0411 1650.0 4042.9596 401.0 52477.1088 5274.0 16100.8297 1578.0 5274.3375 528.0 2143.2282 299.0 28958.7134 2869.0 29607.2994 3100.0 5132.8974 559.0 988.8204 80.0 23485.5816 2461.0 5132.8974 559.0 5132.8974 559.0 5132.8974 559.0 5132.8974 559.0 11634.9186 1279.0 11634.9186 1279.0 11634.9186 1279.0 11634.9186 1279.0 11850.663 1182.0 11850.663 1182.0 11850.663 1182.0 11850.663 1182.0 988.8204 80.0 988.8204 80.0 988.8204 80.0 988.8204 80.0 20194.0007 2051.0 16151.0411 1650.0 4042.9596 401.0 4042.9596 401.0 4042.9596 401.0 4042.9596 401.0 4042.9596 401.0 16151.0411 1650.0 16151.0411 1650.0 16151.0411 1650.0 16151.0411 1650.0 52477.1088 5274.0 16100.8297 1578.0 5274.3375 528.0 2143.2282 299.0 28958.7134 2869.0 1049.4587 216.0 1049.4587 216.0 1049.4587 216.0 1049.4587 216.0 11659.6249 1130.0 11659.6249 1130.0 11659.6249 1130.0 11659.6249 1130.0 11517.1251 1193.0 11517.1251 1193.0 11517.1251 1193.0 11517.1251 1193.0 5781.9634 546.0 5781.9634 546.0 5781.9634 546.0 5781.9634 546.0 16100.8297 1578.0 16100.8297 1578.0 16100.8297 1578.0 16100.8297 1578.0 1093.7695 83.0 1093.7695 83.0 1093.7695 83.0 1093.7695 83.0 5274.3375 528.0 5274.3375 528.0 5274.3375 528.0 5274.3375 528.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_count_5feb8c38ff9cc1f4.xml0000644000175000017500000000774011735330606025551 0ustar drazzibdrazzib WITH MEMBER [Product].[Product Count] AS 'Count({[Product].[All Products], [Product].[All Products].Children}, IncludeEmpty)' SELECT {[Store].[All Stores], [Store].[All Stores].Children} ON AXIS(0), {[Product].[All Products], [Product].[All Products].Children, [Product].[Product Count]} ON AXIS(1) FROM [Sales] [Measures] [Time] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[All Stores] [Store].[Canada] [Store].[Mexico] [Store].[USA] [Product] [Product].[All Products] [Product].[Drink] [Product].[Food] [Product].[Non-Consumable] [Product].[Product Count] 266773.0 #Missing #Missing 266773.0 24597.0 #Missing #Missing 24597.0 191940.0 #Missing #Missing 191940.0 50236.0 #Missing #Missing 50236.0 4.0 4.0 4.0 4.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_d04727679fe397.xml0000644000175000017500000002137511735330606023537 0ustar drazzibdrazzib select {[Measures].[Store Invoice], [Measures].[Supply Time]} on columns, descendants([Store].[USA], [Store].[Store City], Self_Before_After) on rows from [Warehouse] [Store Size in SQFT] [Store Type] [Time] [Warehouse] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Time].[1997] [Warehouse].[All Warehouses] [Measures] [Measures].[Store Invoice] [Measures].[Supply Time] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Alameda].[HQ] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Beverly Hills].[Store 6] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[Los Angeles].[Store 7] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Diego].[Store 24] [Store].[USA].[CA].[San Francisco] [Store].[USA].[CA].[San Francisco].[Store 14] [Store].[USA].[OR] [Store].[USA].[OR].[Portland] [Store].[USA].[OR].[Portland].[Store 11] [Store].[USA].[OR].[Salem] [Store].[USA].[OR].[Salem].[Store 13] [Store].[USA].[WA] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Bellingham].[Store 2] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Bremerton].[Store 3] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Seattle].[Store 15] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Spokane].[Store 16] [Store].[USA].[WA].[Tacoma] [Store].[USA].[WA].[Tacoma].[Store 17] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Walla Walla].[Store 22] [Store].[USA].[WA].[Yakima] [Store].[USA].[WA].[Yakima].[Store 23] 102278.4089 10425.0 29607.2994 3100.0 #Missing #Missing #Missing #Missing 5132.8974 559.0 5132.8974 559.0 11634.9186 1279.0 11634.9186 1279.0 11850.663 1182.0 11850.663 1182.0 988.8204 80.0 988.8204 80.0 20194.0007 2051.0 4042.9596 401.0 4042.9596 401.0 16151.0411 1650.0 16151.0411 1650.0 52477.1088 5274.0 1049.4587 216.0 1049.4587 216.0 11659.6249 1130.0 11659.6249 1130.0 11517.1251 1193.0 11517.1251 1193.0 5781.9634 546.0 5781.9634 546.0 16100.8297 1578.0 16100.8297 1578.0 1093.7695 83.0 1093.7695 83.0 5274.3375 528.0 5274.3375 528.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_2cbb2b1ddd693b30.xml0000644000175000017500000000542211735330606024217 0ustar drazzibdrazzib SELECT {[Measures].[Unit Sales]} ON COLUMNS, TAIL(HEAD([Time].[Month].MEMBERS, 12), 4) ON ROWS FROM [Sales] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Time] [Time].[1997].[Q3].[9] [Time].[1997].[Q4].[10] [Time].[1997].[Q4].[11] [Time].[1997].[Q4].[12] 20388.0 19958.0 25270.0 26796.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_25e9b258b27b8e5f_UNION.xml0000644000175000017500000000553311735330606025064 0ustar drazzibdrazzib SELECT {[Measures].[Warehouse Sales]} ON COLUMNS, UNION( {[Store].[USA].[WA].Children}, {[Store].[USA].[OR].[Portland]}) ON ROWS FROM [Warehouse] [Store Size in SQFT] [Store Type] [Time] [Warehouse] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Time].[1997] [Warehouse].[All Warehouses] [Measures] [Measures].[Warehouse Sales] [Store] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Tacoma] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Yakima] [Store].[USA].[OR].[Portland] 2058.0574 22187.4183 22046.9416 10879.6737 30743.7722 1992.9122 10212.2007 7718.6783 mondrian-3.4.1/testsrc/queryFiles/queryTest_4e509257ee5d860_CLOSINGPERIOD.xml0000644000175000017500000000547111735330606025751 0ustar drazzibdrazzib WITH MEMBER Measures.[Closing Balance] AS '([Measures].[Units Ordered], CLOSINGPERIOD( [Time].[Month], [Time].[Time].CURRENTMEMBER)) - ([Measures].[Units Shipped], CLOSINGPERIOD( [Time].[Month], [Time].[Time].CURRENTMEMBER))' SELECT {[Measures].[Closing Balance]} ON COLUMNS, [Store Type].MEMBERS ON ROWS FROM [Warehouse] [Store] [Store Size in SQFT] [Time] [Warehouse] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Time].[1997] [Warehouse].[All Warehouses] [Measures] [Measures].[Closing Balance] [Store Type] [Store Type].[All Store Types] [Store Type].[Deluxe Supermarket] [Store Type].[Gourmet Supermarket] [Store Type].[HeadQuarters] [Store Type].[Mid-Size Grocery] [Store Type].[Small Grocery] [Store Type].[Supermarket] 1218.0 336.0 221.0 #Missing #Missing #Missing 661.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_5533c6a5409b9e11.xml0000644000175000017500000000422211735330606023731 0ustar drazzibdrazzib with member [Promotions].[Max Store Sales] as 'Max([Promotions].members, [Measures].[Store Sales])' Select { [Promotions].[Max Store Sales]} on columns from Sales where([Measures].[Unit Sales], [Time].[1997]) [Measures] [Time] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Max Store Sales] 565238.13 mondrian-3.4.1/testsrc/queryFiles/queryTest_correlation_26d03286d4295e97.xml0000644000175000017500000000741211735330606026274 0ustar drazzibdrazzib WITH MEMBER [Measures].[Store Sales Q1 over All USA] AS 'CORRELATION([Store].[USA].Children, ([Measures].[Store Sales], [Time].[1997].[Q1]), ([Measures].[Store Sales], [Store].CurrentMember))' SELECT CROSSJOIN({[Measures].[Store Sales Q1 over All USA]}, [Time].[1997].Children) ON COLUMNS, [Store].[USA].Children ON ROWS from [Sales] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Time] [Measures].[Store Sales Q1 over All USA] [Time].[1997].[Q1] [Measures].[Store Sales Q1 over All USA] [Time].[1997].[Q2] [Measures].[Store Sales Q1 over All USA] [Time].[1997].[Q3] [Measures].[Store Sales Q1 over All USA] [Time].[1997].[Q4] [Store] [Store].[USA].[CA] [Store].[USA].[OR] [Store].[USA].[WA] 1.0 0.9416770690866142 0.9694826103273714 0.9169740461138594 1.0 0.9416770690866142 0.9694826103273714 0.9169740461138594 1.0 0.9416770690866142 0.9694826103273714 0.9169740461138594 mondrian-3.4.1/testsrc/queryFiles/queryTest_942cf71cc1e113.xml0000644000175000017500000000470611735330606023643 0ustar drazzibdrazzib select {[Measures].[Unit Sales]} on columns, drilldownlevel({[Store].[USA], [Store].[USA].[CA].[Los Angeles]}, [Store].[Store Country]) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA] [Store].[USA].[CA].[Los Angeles] 266773.0 25663.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_a8c58f52ffec61eb.xml0000644000175000017500000000522011735330606024322 0ustar drazzibdrazzib select {[Measures].[Unit Sales]} on columns, drilldownlevel({[Store].[USA].[CA].[Los Angeles], [Store].[USA]}, [Store].[Store City]) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[Los Angeles].[Store 7] [Store].[USA] 25663.0 25663.0 266773.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_16d5817036cb9af9_QTD.xml0000644000175000017500000002364211735330606024541 0ustar drazzibdrazzib WITH MEMBER [Measures].[QTDProfit] AS 'SUM(QTD(), ([Measures].[Store Sales] - [Measures].[Store Cost]))' SELECT hierarchize([Time].[Time].MEMBERS) ON COLUMNS, {[Product].CHILDREN} ON ROWS FROM [Sales] WHERE ([Measures].[QTDProfit]) [Measures] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[QTDProfit] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997] [Time].[1997].[Q1] [Time].[1997].[Q1].[1] [Time].[1997].[Q1].[2] [Time].[1997].[Q1].[3] [Time].[1997].[Q2] [Time].[1997].[Q2].[4] [Time].[1997].[Q2].[5] [Time].[1997].[Q2].[6] [Time].[1997].[Q3] [Time].[1997].[Q3].[7] [Time].[1997].[Q3].[8] [Time].[1997].[Q3].[9] [Time].[1997].[Q4] [Time].[1997].[Q4].[10] [Time].[1997].[Q4].[11] [Time].[1997].[Q4].[12] [Time].[1998] [Time].[1998].[Q1] [Time].[1998].[Q1].[1] [Time].[1998].[Q1].[2] [Time].[1998].[Q1].[3] [Time].[1998].[Q2] [Time].[1998].[Q2].[4] [Time].[1998].[Q2].[5] [Time].[1998].[Q2].[6] [Time].[1998].[Q3] [Time].[1998].[Q3].[7] [Time].[1998].[Q3].[8] [Time].[1998].[Q3].[9] [Time].[1998].[Q4] [Time].[1998].[Q4].[10] [Time].[1998].[Q4].[11] [Time].[1998].[Q4].[12] [Product] [Product].[Drink] [Product].[Food] [Product].[Non-Consumable] #Missing 6964.2966 2261.6937 4452.9824 6964.2966 7186.1098 2347.3109 4821.1774 7186.1098 7203.3445 2654.7216 4885.2686 7203.3445 8005.2245 2266.8365 5128.5049 8005.2245 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 60814.4714 19827.935 39117.3354 60814.4714 57323.3736 18455.241 37580.0737 57323.3736 61262.5473 21858.1175 42218.006 61262.5473 66364.4742 18208.8711 41442.1243 66364.4742 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 16097.3415 5271.5687 10250.0008 16097.3415 15192.5618 4963.9988 10039.0339 15192.5618 15901.1288 5728.2499 10900.8936 15901.1288 17296.0224 4964.5568 10875.5866 17296.0224 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing mondrian-3.4.1/testsrc/queryFiles/queryTest_267530ffa034ac5a.xml0000644000175000017500000001016411735330606024062 0ustar drazzibdrazzib with member [Store].[Max CA Store] as 'Max([Store].[USA].[CA].children)' Select { [Store].[Max CA Store],[Store].[USA].[CA].children} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[Max CA Store] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Francisco] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 6373.0 #Missing 3822.0 6373.0 6256.0 439.0 6125.0 #Missing 5837.0 5554.0 6125.0 536.0 6619.0 #Missing 4724.0 6470.0 6619.0 557.0 7266.0 #Missing 6950.0 7266.0 6635.0 585.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_db6ae843fa6d13.xml0000644000175000017500000000473411735330606024011 0ustar drazzibdrazzib SELECT {[Time].[1997].[Q1]} ON COLUMNS, bottomsum(filter( {[Store].[USA].[CA].children, [Store].[USA].[OR].children, [Store].[USA].[WA].children}, [Measures].[Unit Sales] &gt; 0), 100, [Measures].[Store Sales]) ON ROWS FROM [Sales] WHERE([Measures].[Unit Sales]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Store] [Store].[USA].[CA].[San Francisco] 439.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_8db2ece1a44277c0_DefaultMember.xml0000644000175000017500000000504711735330606026747 0ustar drazzibdrazzib SELECT [Product].Children on ROWS, {Time.Time.DefaultMember} on COLUMNS FROM [Sales] [Measures] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997] [Product] [Product].[Drink] [Product].[Food] [Product].[Non-Consumable] 24597.0 191940.0 50236.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_members_731f8d9f9244778.xml0000644000175000017500000006424511735330606025345 0ustar drazzibdrazzib SELECT {[Education Level].Members} ON AXIS(0), {HIERARCHIZE([Store].Members)} ON AXIS(1) FROM [Sales] [Measures] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Education Level] [Education Level].[All Education Levels] [Education Level].[Bachelors Degree] [Education Level].[Graduate Degree] [Education Level].[High School Degree] [Education Level].[Partial College] [Education Level].[Partial High School] [Store] [Store].[All Stores] [Store].[Canada] [Store].[Canada].[BC] [Store].[Canada].[BC].[Vancouver] [Store].[Canada].[BC].[Vancouver].[Store 19] [Store].[Canada].[BC].[Victoria] [Store].[Canada].[BC].[Victoria].[Store 20] [Store].[Mexico] [Store].[Mexico].[DF] [Store].[Mexico].[DF].[Mexico City] [Store].[Mexico].[DF].[Mexico City].[Store 9] [Store].[Mexico].[DF].[San Andres] [Store].[Mexico].[DF].[San Andres].[Store 21] [Store].[Mexico].[Guerrero] [Store].[Mexico].[Guerrero].[Acapulco] [Store].[Mexico].[Guerrero].[Acapulco].[Store 1] [Store].[Mexico].[Jalisco] [Store].[Mexico].[Jalisco].[Guadalajara] [Store].[Mexico].[Jalisco].[Guadalajara].[Store 5] [Store].[Mexico].[Veracruz] [Store].[Mexico].[Veracruz].[Orizaba] [Store].[Mexico].[Veracruz].[Orizaba].[Store 10] [Store].[Mexico].[Yucatan] [Store].[Mexico].[Yucatan].[Merida] [Store].[Mexico].[Yucatan].[Merida].[Store 8] [Store].[Mexico].[Zacatecas] [Store].[Mexico].[Zacatecas].[Camacho] [Store].[Mexico].[Zacatecas].[Camacho].[Store 4] [Store].[Mexico].[Zacatecas].[Hidalgo] [Store].[Mexico].[Zacatecas].[Hidalgo].[Store 12] [Store].[Mexico].[Zacatecas].[Hidalgo].[Store 18] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Alameda].[HQ] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Beverly Hills].[Store 6] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[Los Angeles].[Store 7] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Diego].[Store 24] [Store].[USA].[CA].[San Francisco] [Store].[USA].[CA].[San Francisco].[Store 14] [Store].[USA].[OR] [Store].[USA].[OR].[Portland] [Store].[USA].[OR].[Portland].[Store 11] [Store].[USA].[OR].[Salem] [Store].[USA].[OR].[Salem].[Store 13] [Store].[USA].[WA] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Bellingham].[Store 2] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Bremerton].[Store 3] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Seattle].[Store 15] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Spokane].[Store 16] [Store].[USA].[WA].[Tacoma] [Store].[USA].[WA].[Tacoma].[Store 17] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Walla Walla].[Store 22] [Store].[USA].[WA].[Yakima] [Store].[USA].[WA].[Yakima].[Store 23] 266773.0 68839.0 15570.0 78664.0 24545.0 79155.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 266773.0 68839.0 15570.0 78664.0 24545.0 79155.0 74748.0 19836.0 4174.0 21131.0 6765.0 22842.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 21333.0 5656.0 1090.0 5895.0 2068.0 6624.0 21333.0 5656.0 1090.0 5895.0 2068.0 6624.0 25663.0 6912.0 1413.0 7732.0 2111.0 7495.0 25663.0 6912.0 1413.0 7732.0 2111.0 7495.0 25635.0 6741.0 1589.0 6863.0 2302.0 8140.0 25635.0 6741.0 1589.0 6863.0 2302.0 8140.0 2117.0 527.0 82.0 641.0 284.0 583.0 2117.0 527.0 82.0 641.0 284.0 583.0 67659.0 15767.0 4329.0 20702.0 6370.0 20491.0 26079.0 6091.0 1709.0 8231.0 2512.0 7536.0 26079.0 6091.0 1709.0 8231.0 2512.0 7536.0 41580.0 9676.0 2620.0 12471.0 3858.0 12955.0 41580.0 9676.0 2620.0 12471.0 3858.0 12955.0 124366.0 33236.0 7067.0 36831.0 11410.0 35822.0 2237.0 553.0 95.0 647.0 160.0 782.0 2237.0 553.0 95.0 647.0 160.0 782.0 24576.0 5769.0 1835.0 8129.0 2186.0 6657.0 24576.0 5769.0 1835.0 8129.0 2186.0 6657.0 25011.0 6662.0 1277.0 6684.0 2715.0 7673.0 25011.0 6662.0 1277.0 6684.0 2715.0 7673.0 23591.0 6720.0 1434.0 6445.0 1709.0 7283.0 23591.0 6720.0 1434.0 6445.0 1709.0 7283.0 35257.0 9903.0 1084.0 11160.0 3553.0 9557.0 35257.0 9903.0 1084.0 11160.0 3553.0 9557.0 2203.0 436.0 139.0 649.0 129.0 850.0 2203.0 436.0 139.0 649.0 129.0 850.0 11491.0 3193.0 1203.0 3117.0 958.0 3020.0 11491.0 3193.0 1203.0 3117.0 958.0 3020.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_a9d52afbb4612950.xml0000644000175000017500000001073511735330606024074 0ustar drazzibdrazzib with member [Measures].[Test Measure] as '[Measures].[Customer Count] * 2 - [Measures].[Sales Count]' select { [Measures].[Test Measure] } on columns, filter(descendants([Store].[USA]), ([Measures].[Test Measure]) &lt; -1000) on rows from [Sales] where ([Time].[1997].[Q3], [Product].[Product Family].[Food]) [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997].[Q3] [Product].[Food] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Test Measure] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[OR] [Store].[USA].[OR].[Salem] [Store].[USA].[OR].[Salem].[Store 13] [Store].[USA].[WA] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Bremerton].[Store 3] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Spokane].[Store 16] [Store].[USA].[WA].[Tacoma] [Store].[USA].[WA].[Tacoma].[Store 17] -9571.0 -2007.0 -2545.0 -1870.0 -1870.0 -5019.0 -1074.0 -1074.0 -1199.0 -1199.0 -1475.0 -1475.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_a1e4721945d222b7.xml0000644000175000017500000000535111735330606023731 0ustar drazzibdrazzib select {[Measures].[Unit Sales]} on columns, drilldownlevel({[Store].[USA]}) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[OR] [Store].[USA].[WA] 266773.0 74748.0 67659.0 124366.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_b449ddf3544271b.xml0000644000175000017500000000773411735330606023743 0ustar drazzibdrazzib SELECT {[Time].[1997].[Q1]} ON COLUMNS, bottompercent(filter( {[Store].[USA].[CA].children, [Store].[USA].[OR].children, [Store].[USA].[WA].children}, [Measures].[Unit Sales] &gt; 0), 50, [Measures].[Store Sales]) ON ROWS FROM [Sales] WHERE([Measures].[Unit Sales]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Store] [Store].[USA].[CA].[San Francisco] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Yakima] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Bremerton] [Store].[USA].[CA].[San Diego] 439.0 500.0 518.0 3096.0 3822.0 5607.0 6098.0 5896.0 6256.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_c93ab8a76f6497cf_CLOSINGPERIOD.xml0000644000175000017500000000271311735330606026260 0ustar drazzibdrazzib Select {CLOSINGPERIOD([Time].[Month], [Time].[Time].CURRENTMEMBER), [Time].[1997].[Q3]} on columns from [Warehouse] [Measures] [Store] [Store Size in SQFT] [Store Type] [Warehouse] [Measures].[Store Invoice] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Warehouse].[All Warehouses] [Time] [Time].[1997].[Q4].[12] [Time].[1997].[Q3] 7200.2753 28825.8276 mondrian-3.4.1/testsrc/queryFiles/queryTest_memberOrdinal.xml0000644000175000017500000001736111735330606024444 0ustar drazzibdrazzib select {[Product]} on columns, Hierarchize(crossjoin([Measures].allMembers, [Time].[Time].children)) on rows from [Sales 2] [Time] [Product] [Time].[1997] [Product].[All Products] [Product] [Product].[All Products] [Measures] [Time] [Measures].[Sales Count] [Time].[1997].[Q1] [Measures].[Sales Count] [Time].[1997].[Q2] [Measures].[Sales Count] [Time].[1997].[Q3] [Measures].[Sales Count] [Time].[1997].[Q4] [Measures].[Unit Sales] [Time].[1997].[Q1] [Measures].[Unit Sales] [Time].[1997].[Q2] [Measures].[Unit Sales] [Time].[1997].[Q3] [Measures].[Unit Sales] [Time].[1997].[Q4] [Measures].[Store Sales] [Time].[1997].[Q1] [Measures].[Store Sales] [Time].[1997].[Q2] [Measures].[Store Sales] [Time].[1997].[Q3] [Measures].[Store Sales] [Time].[1997].[Q4] [Measures].[Profit] [Time].[1997].[Q1] [Measures].[Profit] [Time].[1997].[Q2] [Measures].[Profit] [Time].[1997].[Q3] [Measures].[Profit] [Time].[1997].[Q4] [Measures].[Profit last Period] [Time].[1997].[Q1] [Measures].[Profit last Period] [Time].[1997].[Q2] [Measures].[Profit last Period] [Time].[1997].[Q3] [Measures].[Profit last Period] [Time].[1997].[Q4] [Measures].[Store Cost] [Time].[1997].[Q1] [Measures].[Store Cost] [Time].[1997].[Q2] [Measures].[Store Cost] [Time].[1997].[Q3] [Measures].[Store Cost] [Time].[1997].[Q4] [Measures].[Customer Count] [Time].[1997].[Q1] [Measures].[Customer Count] [Time].[1997].[Q2] [Measures].[Customer Count] [Time].[1997].[Q3] [Measures].[Customer Count] [Time].[1997].[Q4] 21588 20368 21453 23428 66291 62610 65848 72024 139628.35 132666.27 140271.89 152671.62 83876.11 79702.0452 84367.02 91665.7211 83876.11 83876.11 79702.0452 84367.02 55752.24 52964.2248 55904.87 61005.8989 2981 2973 3026 3261 mondrian-3.4.1/testsrc/queryFiles/queryTest_9d7658e66b8d1053.xml0000644000175000017500000001073511735330606023760 0ustar drazzibdrazzib with member [Measures].[Test Measure] as '[Measures].[Customer Count] * 2 - [Measures].[Sales Count]' select { [Measures].[Test Measure] } on columns, filter(descendants([Store].[USA]), ([Measures].[Test Measure]) &lt; -1000) on rows from [Sales] where ([Time].[1997].[Q2], [Product].[Product Family].[Food]) [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997].[Q2] [Product].[Food] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Test Measure] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[OR] [Store].[USA].[OR].[Salem] [Store].[USA].[OR].[Salem].[Store 13] [Store].[USA].[WA] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Bremerton].[Store 3] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Spokane].[Store 16] [Store].[USA].[WA].[Tacoma] [Store].[USA].[WA].[Tacoma].[Store 17] -8794.0 -2022.0 -2004.0 -1185.0 -1185.0 -4768.0 -1006.0 -1006.0 -1054.0 -1054.0 -1351.0 -1351.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_b4c19426e7784361.xml0000644000175000017500000000621011735330606023662 0ustar drazzibdrazzib select { [Time].[1997].children } on columns, intersect( {[Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods]}, {[Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods]}) on rows from [Sales] where ( [Measures].[Store Cost] ) [Measures] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Store Cost] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] [Product] [Product].[Food].[Baking Goods] [Product].[Food].[Breakfast Foods] 3804.7026 3633.6761 3772.9881 4159.2433 680.1564 652.8166 661.5138 762.3098 mondrian-3.4.1/testsrc/queryFiles/queryTest_aggregate_ffc012281623093.xml0000644000175000017500000002202211735330606025563 0ustar drazzibdrazzib WITH MEMBER [Store Size in SQFT].[Less Than 23000] AS 'AGGREGATE({[20319], [21215], [22478]}, (4 * 5) / 6)' SELECT {[Store].[All Stores], [Store].[All Stores].Children} ON AXIS(0), {[Store Size in SQFT].[Less Than 23000], [Store Size in SQFT].Children} ON AXIS(1) FROM [Sales] [Measures] [Time] [Product] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Product].[All Products] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[All Stores] [Store].[Canada] [Store].[Mexico] [Store].[USA] [Store Size in SQFT] [Store Size in SQFT].[Less Than 23000] [Store Size in SQFT].[#null] [Store Size in SQFT].[20319] [Store Size in SQFT].[21215] [Store Size in SQFT].[22478] [Store Size in SQFT].[23112] [Store Size in SQFT].[23593] [Store Size in SQFT].[23598] [Store Size in SQFT].[23688] [Store Size in SQFT].[23759] [Store Size in SQFT].[24597] [Store Size in SQFT].[27694] [Store Size in SQFT].[28206] [Store Size in SQFT].[30268] [Store Size in SQFT].[30584] [Store Size in SQFT].[30797] [Store Size in SQFT].[33858] [Store Size in SQFT].[34452] [Store Size in SQFT].[34791] [Store Size in SQFT].[36509] [Store Size in SQFT].[38382] [Store Size in SQFT].[39696] 10.0 10.0 10.0 10.0 39329.0 #Missing #Missing 39329.0 26079.0 #Missing #Missing 26079.0 25011.0 #Missing #Missing 25011.0 2117.0 #Missing #Missing 2117.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 25663.0 #Missing #Missing 25663.0 21333.0 #Missing #Missing 21333.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 41580.0 #Missing #Missing 41580.0 2237.0 #Missing #Missing 2237.0 23591.0 #Missing #Missing 23591.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 35257.0 #Missing #Missing 35257.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 24576.0 #Missing #Missing 24576.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_f7b258919a7869e.xml0000644000175000017500000000557111735330606023710 0ustar drazzibdrazzib with member [Promotions].[Std DevP Unit Sales] as 'StDevP([Promotions].members)' Select {[Promotions].[Std DevP Unit Sales]} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Std DevP Unit Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 14038.971922473525 14544.712464281962 14928.004257168497 16693.716249326204 mondrian-3.4.1/testsrc/queryFiles/queryTest_cbdc267d808b8c95.xml0000644000175000017500000000574611735330606024201 0ustar drazzibdrazzib select {[Measures].[Unit Sales]} on columns, drilldownlevel({[Store].[USA].[CA].[Los Angeles], [Store].[USA]}, [Store].[Store Country]) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA].[CA].[Los Angeles] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[OR] [Store].[USA].[WA] 25663.0 266773.0 74748.0 67659.0 124366.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_6e272f678215815.xml0000644000175000017500000001263011735330606023524 0ustar drazzibdrazzib select {[Measures].[Store Invoice], [Measures].[Supply Time]} on columns, non empty {[Warehouse].[USA].children * [Store].[USA].children * [Store Type].members} on rows from [Warehouse] [Store Size in SQFT] [Time] [Store Size in SQFT].[All Store Size in SQFTs] [Time].[1997] [Measures] [Measures].[Store Invoice] [Measures].[Supply Time] [Warehouse] [Store] [Store Type] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Supermarket] 29607.2994 3100.0 5132.8974 559.0 988.8204 80.0 23485.5816 2461.0 20194.0007 2051.0 16151.0411 1650.0 4042.9596 401.0 52477.1088 5274.0 16100.8297 1578.0 5274.3375 528.0 2143.2282 299.0 28958.7134 2869.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_9b8562c9286bdab.xml0000644000175000017500000001263311735330606024025 0ustar drazzibdrazzib select {[Measures].[Store Invoice], [Measures].[Supply Time]} on columns, non empty generate([Warehouse].children, crossjoin({[Warehouse].currentmember}, crossjoin([Store].[Store State].members, [Store Type].members)), all) on rows from [Warehouse] [Store Size in SQFT] [Time] [Store Size in SQFT].[All Store Size in SQFTs] [Time].[1997] [Measures] [Measures].[Store Invoice] [Measures].[Supply Time] [Warehouse] [Store] [Store Type] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Supermarket] 29607.2994 3100.0 5132.8974 559.0 988.8204 80.0 23485.5816 2461.0 20194.0007 2051.0 16151.0411 1650.0 4042.9596 401.0 52477.1088 5274.0 16100.8297 1578.0 5274.3375 528.0 2143.2282 299.0 28958.7134 2869.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_b11de0f13b2a073.xml0000644000175000017500000000503011735330606023757 0ustar drazzibdrazzib Select {ToggleDrillState({[Store].[Canada], [Store].[Mexico], [Store].[USA],[Store].[USA].[WA],[Store].[USA].[WA].[Seattle]},{[Store].[All Stores].[USA].[WA]})} on Axis(0) from [Sales] [Measures] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[Canada] [Store].[Mexico] [Store].[USA] [Store].[USA].[WA] #Missing #Missing 266773.0 124366.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_ce14fddee524718.xml0000644000175000017500000001042411735330606024074 0ustar drazzibdrazzib select { ([Measures].[Store Sales]) } on columns, generate( [Product].[Drink].children, generate ( [Store].[USA].children, { ([Product].currentmember, [Store].currentmember) } )) on rows from [Sales] [Time] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Store Sales] [Product] [Store] [Product].[Drink].[Alcoholic Beverages] [Store].[USA].[CA] [Product].[Drink].[Alcoholic Beverages] [Store].[USA].[OR] [Product].[Drink].[Alcoholic Beverages] [Store].[USA].[WA] [Product].[Drink].[Beverages] [Store].[USA].[CA] [Product].[Drink].[Beverages] [Store].[USA].[OR] [Product].[Drink].[Beverages] [Store].[USA].[WA] [Product].[Drink].[Dairy] [Store].[USA].[CA] [Product].[Drink].[Dairy] [Store].[USA].[OR] [Product].[Drink].[Dairy] [Store].[USA].[WA] 4011.37 3460.32 6557.39 8006.53 6947.13 12794.87 2185.34 1729.84 3143.42 mondrian-3.4.1/testsrc/queryFiles/queryTest_6485a19764d2d7bc_PROPERTIES.xml0000644000175000017500000001102611735330606025600 0ustar drazzibdrazzib SELECT {[Measures].[Units Shipped], [Measures].[Units Ordered]} ON COLUMNS, NON EMPTY [Store].[Store Name].MEMBERS DIMENSION PROPERTIES [Store].[Store Name].[Store Sqft] ON ROWS FROM [Warehouse] [Store Size in SQFT] [Store Type] [Time] [Warehouse] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Time].[1997] [Warehouse].[All Warehouses] [Measures] [Measures].[Units Shipped] [Measures].[Units Ordered] [Store] [Store].[USA].[CA].[Beverly Hills].[Store 6] [Store].[USA].[CA].[Los Angeles].[Store 7] [Store].[USA].[CA].[San Diego].[Store 24] [Store].[USA].[CA].[San Francisco].[Store 14] [Store].[USA].[OR].[Portland].[Store 11] [Store].[USA].[OR].[Salem].[Store 13] [Store].[USA].[WA].[Bellingham].[Store 2] [Store].[USA].[WA].[Bremerton].[Store 3] [Store].[USA].[WA].[Seattle].[Store 15] [Store].[USA].[WA].[Spokane].[Store 16] [Store].[USA].[WA].[Tacoma].[Store 17] [Store].[USA].[WA].[Walla Walla].[Store 22] [Store].[USA].[WA].[Yakima].[Store 23] 10759.0 11699.0 24587.0 26463.0 23835.0 26270.0 1696.0 1875.0 8515.0 9109.0 32393.0 35797.0 2348.0 2454.0 22734.0 24610.0 24110.0 26703.0 11889.0 12828.0 32411.0 35930.0 1860.0 2074.0 10589.0 11426.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_a787eda4ece6832b.xml0000644000175000017500000000566311735330606024254 0ustar drazzibdrazzib with member [Promotions].[Var Store Sales] as 'Var([Promotions].members, [Measures].[Store Sales])' Select {[Promotions].[Var Store Sales]} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] WHERE ([Education Level].[High School Degree]) [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[High School Degree] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Var Store Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 8.48419209100163E7 8.12016816738566E7 8.809079356881481E7 1.209696160838154E8 mondrian-3.4.1/testsrc/queryFiles/queryTest_2bf0d41b85d47f2.xml0000644000175000017500000000554611735330606024016 0ustar drazzibdrazzib select { ([Measures].[Unit Sales], [Time].[1997].[Q2]), ([Measures].[Unit Sales], [Time].[1997].[Q3]) } on columns, filter(descendants([Store].[USA]), ([Measures].[Unit Sales], [Time].[1997].[Q3]) / ([Measures].[Unit Sales], [Time].[1997].[Q2]) &gt; 1.2) on rows from [Sales] where ([Product].[Product Family].[Food]) [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Product].[Food] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Time] [Measures].[Unit Sales] [Time].[1997].[Q2] [Measures].[Unit Sales] [Time].[1997].[Q3] [Store] [Store].[USA].[OR].[Salem] [Store].[USA].[OR].[Salem].[Store 13] 5866.0 8353.0 5866.0 8353.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_cf7012a6b58cc568.xml0000644000175000017500000000563711735330606024110 0ustar drazzibdrazzib with member [Promotions].[VarP Unit Sales] as 'VarP([Promotions].members)' Select {[Promotions].[VarP Unit Sales]} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] WHERE ([Education Level].[High School Degree]) [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[High School Degree] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[VarP Unit Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 1.8158295070154577E7 1.75280999408284E7 1.8782264408163264E7 2.5950984325443786E7 mondrian-3.4.1/testsrc/queryFiles/queryTest_19873fb52f747e59.xml0000644000175000017500000000522011735330606023760 0ustar drazzibdrazzib select {[Measures].[Unit Sales]} on columns, drilldownlevel({[Store].[USA], [Store].[USA].[CA].[Los Angeles]}, [Store].[Store City]) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[Los Angeles].[Store 7] 266773.0 25663.0 25663.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_d04bbeb9c48a09c.xml0000644000175000017500000000402011735330606024137 0ustar drazzibdrazzib SELECT {[Time].[Time].CurrentMember.Hierarchy.CurrentMember} on Axis(0) from [Sales] [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997] 266773.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_817a2eb71810feed_YTD.xml0000644000175000017500000011060011735330606024670 0ustar drazzibdrazzib WITH MEMBER [Measures].[YTDSales] AS 'SUM(YTD(), [Measures].[Store Sales])', FORMAT_STRING = '#.00' SELECT {DESCENDANTS([Time].[1997], [Month])} ON COLUMNS, {[Product].[Product Category].MEMBERS} ON ROWS FROM [Sales] WHERE ([Measures].[YTDSales]) [Measures] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[YTDSales] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1].[1] [Time].[1997].[Q1].[2] [Time].[1997].[Q1].[3] [Time].[1997].[Q2].[4] [Time].[1997].[Q2].[5] [Time].[1997].[Q2].[6] [Time].[1997].[Q3].[7] [Time].[1997].[Q3].[8] [Time].[1997].[Q3].[9] [Time].[1997].[Q4].[10] [Time].[1997].[Q4].[11] [Time].[1997].[Q4].[12] [Product] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] [Product].[Drink].[Beverages].[Carbonated Beverages] [Product].[Drink].[Beverages].[Drinks] [Product].[Drink].[Beverages].[Hot Beverages] [Product].[Drink].[Beverages].[Pure Juice Beverages] [Product].[Drink].[Dairy].[Dairy] [Product].[Food].[Baked Goods].[Bread] [Product].[Food].[Baking Goods].[Baking Goods] [Product].[Food].[Baking Goods].[Jams and Jellies] [Product].[Food].[Breakfast Foods].[Breakfast Foods] [Product].[Food].[Canned Foods].[Canned Anchovies] [Product].[Food].[Canned Foods].[Canned Clams] [Product].[Food].[Canned Foods].[Canned Oysters] [Product].[Food].[Canned Foods].[Canned Sardines] [Product].[Food].[Canned Foods].[Canned Shrimp] [Product].[Food].[Canned Foods].[Canned Soup] [Product].[Food].[Canned Foods].[Canned Tuna] [Product].[Food].[Canned Foods].[Vegetables] [Product].[Food].[Canned Products].[Fruit] [Product].[Food].[Dairy].[Dairy] [Product].[Food].[Deli].[Meat] [Product].[Food].[Deli].[Side Dishes] [Product].[Food].[Eggs].[Eggs] [Product].[Food].[Frozen Foods].[Breakfast Foods] [Product].[Food].[Frozen Foods].[Frozen Desserts] [Product].[Food].[Frozen Foods].[Frozen Entrees] [Product].[Food].[Frozen Foods].[Meat] [Product].[Food].[Frozen Foods].[Pizza] [Product].[Food].[Frozen Foods].[Vegetables] [Product].[Food].[Meat].[Meat] [Product].[Food].[Produce].[Fruit] [Product].[Food].[Produce].[Packaged Vegetables] [Product].[Food].[Produce].[Specialty] [Product].[Food].[Produce].[Vegetables] [Product].[Food].[Seafood].[Seafood] [Product].[Food].[Snack Foods].[Snack Foods] [Product].[Food].[Snacks].[Candy] [Product].[Food].[Starchy Foods].[Starchy Foods] [Product].[Non-Consumable].[Carousel].[Specialty] [Product].[Non-Consumable].[Checkout].[Hardware] [Product].[Non-Consumable].[Checkout].[Miscellaneous] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] [Product].[Non-Consumable].[Household].[Bathroom Products] [Product].[Non-Consumable].[Household].[Candles] [Product].[Non-Consumable].[Household].[Cleaning Supplies] [Product].[Non-Consumable].[Household].[Electrical] [Product].[Non-Consumable].[Household].[Hardware] [Product].[Non-Consumable].[Household].[Kitchen Products] [Product].[Non-Consumable].[Household].[Paper Products] [Product].[Non-Consumable].[Household].[Plastic Products] [Product].[Non-Consumable].[Periodicals].[Magazines] 981.88 1901.15 3082.0 4215.73 5329.98 6588.37 7891.24 8925.15 10038.86 11228.58 12587.6 14029.08 443.92 1058.94 1459.85 1944.51 2532.49 3062.46 3667.48 4184.75 4652.49 5146.18 5760.41 6236.35 448.32 792.45 1341.67 1772.26 2272.52 2679.01 3164.24 3541.92 3988.74 4444.27 4938.26 5642.29 875.8 1505.07 2315.3 3066.44 3912.45 4589.73 5412.72 6128.21 6827.7 7383.97 8341.01 9261.74 516.43 1092.89 1653.97 2197.81 2727.89 3211.16 3774.94 4380.34 4955.27 5384.57 6053.4 6608.15 509.01 1062.85 1733.01 2273.58 2801.44 3369.65 3999.34 4457.14 5031.32 5664.25 6353.5 7058.6 1306.7 2597.09 4024.78 5148.25 6392.93 7667.82 9155.56 10566.85 11831.73 12990.24 14650.4 16455.43 1389.85 2611.52 4080.42 5164.8 6569.89 7661.96 8878.65 10070.32 11387.13 12515.13 13802.39 15446.69 1780.81 3577.2 5497.73 7356.5 9156.44 11019.14 13035.15 15058.14 16810.78 18474.79 20684.85 23223.72 541.6 1085.84 1711.14 2277.9 2781.11 3340.38 3892.33 4504.0 5031.15 5515.86 6236.99 6941.46 132.4 278.65 503.53 687.1 831.44 1053.48 1242.59 1398.76 1595.86 1756.66 2042.32 2296.38 172.89 353.31 514.67 694.49 807.76 955.57 1169.58 1312.66 1441.63 1532.43 1717.2 1912.68 98.33 231.25 370.22 485.19 587.63 671.71 841.97 948.72 1085.54 1203.93 1309.94 1442.77 165.15 285.69 381.87 481.01 565.68 693.82 837.08 933.22 1030.63 1095.3 1246.62 1357.8 186.78 383.73 619.36 773.49 934.74 1118.03 1237.14 1408.88 1594.33 1752.67 1934.03 2146.49 1267.16 2469.24 3918.91 5244.0 6510.58 7784.77 9177.87 10527.93 11622.79 12779.29 14372.75 15966.1 326.11 564.86 822.48 1027.51 1258.36 1481.01 1722.43 1989.54 2335.06 2596.94 2904.4 3210.76 878.09 1744.72 2695.87 3570.44 4517.16 5386.33 6217.44 7218.54 8185.93 9014.18 10211.14 11441.36 237.75 448.46 724.7 964.4 1280.28 1526.35 1777.19 2048.47 2332.07 2579.4 2937.96 3314.52 2703.93 5103.24 7708.75 10221.55 12619.49 14921.59 17818.4 20186.21 22321.66 24490.02 27372.29 30508.85 1720.78 3201.66 5125.88 6480.51 8012.81 9851.7 11690.14 13291.22 14787.2 16268.51 18584.32 20616.29 375.09 703.84 1139.45 1458.93 1868.05 2246.77 2631.02 2934.83 3344.44 3662.31 4174.56 4702.64 681.88 1273.5 1949.79 2705.98 3422.22 4185.94 5116.64 5872.95 6560.89 7334.13 8342.85 9200.76 771.47 1403.92 2213.47 2824.04 3418.79 4058.45 4831.83 5537.6 6193.33 6879.75 7839.24 8615.47 1005.88 2144.66 3170.77 4164.11 5116.16 6196.85 7375.01 8521.24 9462.65 10569.43 11812.13 13042.95 465.75 933.82 1509.48 1933.65 2467.62 2888.72 3347.37 3943.1 4396.15 4909.93 5430.11 5950.36 500.48 965.28 1500.68 2082.73 2675.15 3193.89 3837.97 4269.12 4766.64 5224.21 5696.16 6288.6 503.55 945.64 1463.23 2020.36 2599.63 3050.86 3643.35 4109.72 4631.3 5128.92 5878.14 6540.3 1060.06 2312.37 3768.64 4857.74 6130.97 7244.78 8625.32 9913.54 10903.21 11981.73 13197.15 14769.82 233.87 500.46 818.44 1104.76 1381.91 1659.87 1964.48 2315.59 2644.97 2942.16 3272.79 3669.89 2115.13 4418.45 6581.18 8299.53 10288.99 12478.92 14644.41 16865.9 18840.54 20749.42 23030.17 25816.13 201.71 258.47 422.08 602.78 800.31 903.12 1136.16 1327.81 1479.71 1617.53 1765.62 1977.86 634.65 1323.89 2106.73 2766.22 3515.75 4387.24 5054.94 5789.64 6545.51 7369.46 8250.9 9269.02 3604.0 7460.29 11444.13 14858.14 18341.98 22124.75 26143.37 29947.51 33569.49 37062.07 41145.5 45185.41 272.57 563.9 842.81 1167.64 1443.78 1705.49 2088.69 2370.43 2610.23 2943.11 3328.11 3809.14 5490.25 10798.55 17089.74 21897.1 27069.71 32751.33 39093.34 44681.78 50067.8 54726.33 60734.29 67609.82 1160.58 2292.59 3578.41 4761.18 5704.61 6716.1 7965.02 9196.57 10503.98 11663.04 13123.66 14550.05 1007.87 1896.94 2961.98 3926.5 4832.84 5770.58 6829.64 7804.04 8590.59 9513.16 10522.21 11756.07 103.97 243.65 343.03 467.37 610.84 705.94 833.75 914.36 1072.26 1202.24 1383.76 1500.11 143.7 351.71 541.02 656.59 806.99 960.85 1039.96 1122.21 1246.41 1376.62 1582.89 1785.5 141.73 311.18 595.58 710.17 857.22 1018.01 1215.62 1405.81 1540.74 1666.89 1811.32 1982.21 983.45 1778.19 2922.93 3737.02 4773.13 5780.78 6798.85 7712.71 8564.49 9595.58 10665.72 11881.6 219.46 445.5 797.89 1043.96 1390.22 1634.27 1930.31 2188.16 2374.95 2615.8 2977.73 3356.71 271.72 589.3 784.17 977.98 1266.32 1601.25 1905.99 2190.38 2449.25 2593.77 2937.52 3300.54 481.6 940.85 1500.47 1952.62 2411.15 2924.02 3492.0 3984.52 4458.75 4953.17 5503.64 6062.45 528.37 1179.02 1910.25 2548.63 3225.5 3854.98 4597.66 5233.84 5825.24 6499.7 7223.3 7970.56 114.84 213.98 332.93 396.8 487.16 585.16 690.66 786.03 865.45 946.54 1032.45 1147.27 107.43 172.56 295.22 394.62 530.66 684.7 805.12 921.87 1064.1 1138.27 1237.09 1360.1 684.67 1293.63 1913.14 2514.01 3084.36 3551.82 4172.65 4757.47 5351.24 5901.47 6568.48 7113.02 1380.97 2742.43 4308.35 5533.55 6634.09 7799.43 9088.42 10469.49 11847.45 12981.14 14454.34 16171.64 240.53 513.22 886.0 1126.56 1417.47 1700.13 2021.97 2315.07 2603.59 2932.63 3219.25 3627.02 637.65 1404.16 2141.35 3017.39 3701.01 4446.05 5404.85 6062.93 6770.01 7546.43 8513.38 9280.2 1369.89 2436.62 3742.47 4976.06 6204.47 7344.38 8734.25 9903.09 11132.18 12225.0 13599.77 15442.88 568.67 997.0 1506.39 2021.91 2379.31 3028.69 3561.91 4148.2 4591.56 5089.11 5682.79 6327.76 792.56 1439.1 2260.04 2952.5 3701.45 4476.46 5315.49 6142.06 6809.54 7550.56 8293.69 9056.76 mondrian-3.4.1/testsrc/queryFiles/queryTest_4194b1df527ec743.xml0000644000175000017500000000713211735330606024021 0ustar drazzibdrazzib select {[Measures].[Unit Sales]} on columns, drilldownlevel({[Store].[USA].[CA].[Los Angeles], [Store].[USA].[CA], [Store].[USA]}, [Store].[Store State]) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Francisco] [Store].[USA] 25663.0 74748.0 #Missing 21333.0 25663.0 25635.0 2117.0 266773.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_41abb42ac6e42b22.xml0000644000175000017500000022220011735330606024124 0ustar drazzibdrazzib select {[Measures].[Store Invoice], [Measures].[Supply Time]} on columns, generate([Warehouse].children, crossjoin({[Warehouse].currentmember}, crossjoin([Store].[Store State].members, [Store Type].members)), all) on rows from [Warehouse] [Store Size in SQFT] [Time] [Store Size in SQFT].[All Store Size in SQFTs] [Time].[1997] [Measures] [Measures].[Store Invoice] [Measures].[Supply Time] [Warehouse] [Store] [Store Type] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Supermarket] #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 29607.2994 3100.0 #Missing #Missing 5132.8974 559.0 #Missing #Missing #Missing #Missing 988.8204 80.0 23485.5816 2461.0 20194.0007 2051.0 16151.0411 1650.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 4042.9596 401.0 52477.1088 5274.0 16100.8297 1578.0 #Missing #Missing #Missing #Missing 5274.3375 528.0 2143.2282 299.0 28958.7134 2869.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_d236c4a6be90a755_Name.xml0000644000175000017500000000410511735330606025032 0ustar drazzibdrazzib WITH MEMBER [Customers].[Level Name] AS '[Customers].[USA].[CA].level.Name' SELECT {[Customers].[Level Name]} on columns From [Sales] [Measures] [Time] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Customers] [Customers].[Level Name] State Province mondrian-3.4.1/testsrc/queryFiles/queryTest_6236cbb13278115.xml0000644000175000017500000000426611735330606023567 0ustar drazzibdrazzib select [Time].[Time].children on axis(0), [Store].children on axis(1) from [Warehouse and Sales] where ([Measures].[Store Sales]) [Measures] [Measures].[Store Sales] [Time] [Time].[1997].[Q1] s [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] [Store] [Store].[Canada] [Store].[Mexico] [Store].[USA] #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 139628.35 132666.27 140271.89 152671.62 mondrian-3.4.1/testsrc/queryFiles/queryTest_b89b717be8752ffc.xml0000644000175000017500000000556511735330606024206 0ustar drazzibdrazzib with member [Promotions].[Std Dev Unit Sales] as 'StDev([Promotions].members)' Select {[Promotions].[Std Dev Unit Sales]} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Std Dev Unit Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 14278.971733479873 14832.754534993985 15201.93544068837 17024.316981939053 mondrian-3.4.1/testsrc/queryFiles/queryTest_e03db2e8d43a9f7.xml0000644000175000017500000002221311735330606024072 0ustar drazzibdrazzib select {[Product].[Product Family].members} on columns, {[Store].[Store Name].members} properties [Store].[Store Sqft] on rows from [Sales] where([Measures].[Unit Sales]) [Measures] [Time] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Product] [Product].[Drink] [Product].[Food] [Product].[Non-Consumable] [Store] [Store].[Canada].[BC].[Vancouver].[Store 19] [Store].[Canada].[BC].[Victoria].[Store 20] [Store].[Mexico].[DF].[Mexico City].[Store 9] [Store].[Mexico].[DF].[San Andres].[Store 21] [Store].[Mexico].[Guerrero].[Acapulco].[Store 1] [Store].[Mexico].[Jalisco].[Guadalajara].[Store 5] [Store].[Mexico].[Veracruz].[Orizaba].[Store 10] [Store].[Mexico].[Yucatan].[Merida].[Store 8] [Store].[Mexico].[Zacatecas].[Camacho].[Store 4] [Store].[Mexico].[Zacatecas].[Hidalgo].[Store 12] [Store].[Mexico].[Zacatecas].[Hidalgo].[Store 18] [Store].[USA].[CA].[Alameda].[HQ] [Store].[USA].[CA].[Beverly Hills].[Store 6] [Store].[USA].[CA].[Los Angeles].[Store 7] [Store].[USA].[CA].[San Diego].[Store 24] [Store].[USA].[CA].[San Francisco].[Store 14] [Store].[USA].[OR].[Portland].[Store 11] [Store].[USA].[OR].[Salem].[Store 13] [Store].[USA].[WA].[Bellingham].[Store 2] [Store].[USA].[WA].[Bremerton].[Store 3] [Store].[USA].[WA].[Seattle].[Store 15] [Store].[USA].[WA].[Spokane].[Store 16] [Store].[USA].[WA].[Tacoma].[Store 17] [Store].[USA].[WA].[Walla Walla].[Store 22] [Store].[USA].[WA].[Yakima].[Store 23] #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 1945.0 15438.0 3950.0 2422.0 18294.0 4947.0 2560.0 18369.0 4706.0 175.0 1555.0 387.0 2371.0 18632.0 5076.0 3735.0 29905.0 7940.0 208.0 1587.0 442.0 2288.0 17809.0 4479.0 2213.0 18159.0 4639.0 2238.0 16925.0 4428.0 3092.0 25453.0 6712.0 191.0 1622.0 390.0 1159.0 8192.0 2140.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_33f161ca6d98519_VarianceP.xml0000644000175000017500000000562011735330606025610 0ustar drazzibdrazzib with member [Promotions].[VarP Store Sales] as 'VarianceP([Promotions].members, [Measures].[Store Sales])' Select {[Promotions].[VarP Store Sales]} on columns, {[Time].[1997].children} on rows from [Sales] [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[VarP Store Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 8.739059003251688E8 9.508068217272257E8 1.0100232513611995E9 1.2518354720125535E9 mondrian-3.4.1/testsrc/queryFiles/queryTest_96a0ffa3a8ce52f5_GENERATE.xml0000644000175000017500000007247311735330606025525 0ustar drazzibdrazzib SELECT {GENERATE([Time].[Year].MEMBERS, {[Time].[Time].CURRENTMEMBER, [Time].[Time].CURRENTMEMBER.CHILDREN})} ON COLUMNS, [Promotions].[All Promotions].CHILDREN ON ROWS FROM [Sales] WHERE (Measures.[Unit Sales]) [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] [Time].[1998] [Time].[1998].[Q1] [Time].[1998].[Q2] [Time].[1998].[Q3] [Time].[1998].[Q4] [Promotions] [Promotions].[Bag Stuffers] [Promotions].[Best Savings] [Promotions].[Big Promo] [Promotions].[Big Time Discounts] [Promotions].[Big Time Savings] [Promotions].[Bye Bye Baby] [Promotions].[Cash Register Lottery] [Promotions].[Coupon Spectacular] [Promotions].[Dimes Off] [Promotions].[Dollar Cutters] [Promotions].[Dollar Days] [Promotions].[Double Down Sale] [Promotions].[Double Your Savings] [Promotions].[Fantastic Discounts] [Promotions].[Free For All] [Promotions].[Go For It] [Promotions].[Green Light Days] [Promotions].[Green Light Special] [Promotions].[High Roller Savings] [Promotions].[I Cant Believe It Sale] [Promotions].[Money Grabbers] [Promotions].[Money Savers] [Promotions].[Mystery Sale] [Promotions].[No Promotion] [Promotions].[One Day Sale] [Promotions].[Pick Your Savings] [Promotions].[Price Cutters] [Promotions].[Price Destroyers] [Promotions].[Price Savers] [Promotions].[Price Slashers] [Promotions].[Price Smashers] [Promotions].[Price Winners] [Promotions].[Sale Winners] [Promotions].[Sales Days] [Promotions].[Sales Galore] [Promotions].[Save-It Sale] [Promotions].[Saving Days] [Promotions].[Savings Galore] [Promotions].[Shelf Clearing Days] [Promotions].[Shelf Emptiers] [Promotions].[Super Duper Savers] [Promotions].[Super Savers] [Promotions].[Super Wallet Savers] [Promotions].[Three for One] [Promotions].[Tip Top Savings] [Promotions].[Two Day Sale] [Promotions].[Two for One] [Promotions].[Unbeatable Price Savers] [Promotions].[Wallet Savers] [Promotions].[Weekend Markdown] [Promotions].[You Save Days] 901.0 42.0 #Missing 617.0 242.0 #Missing #Missing #Missing #Missing #Missing 2081.0 #Missing 473.0 597.0 1011.0 #Missing #Missing #Missing #Missing #Missing 1789.0 #Missing 427.0 38.0 1324.0 #Missing #Missing #Missing #Missing #Missing 932.0 399.0 #Missing #Missing 533.0 #Missing #Missing #Missing #Missing #Missing 700.0 351.0 #Missing 294.0 55.0 #Missing #Missing #Missing #Missing #Missing 921.0 #Missing 921.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing 4792.0 3016.0 1041.0 735.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 1219.0 #Missing #Missing #Missing 1219.0 #Missing #Missing #Missing #Missing #Missing 781.0 #Missing 358.0 423.0 #Missing #Missing #Missing #Missing #Missing #Missing 1652.0 394.0 1258.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing 1959.0 237.0 #Missing 1222.0 500.0 #Missing #Missing #Missing #Missing #Missing 843.0 #Missing #Missing 325.0 518.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 1638.0 555.0 1083.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing 689.0 55.0 #Missing 433.0 201.0 #Missing #Missing #Missing #Missing #Missing 1607.0 #Missing 460.0 #Missing 1147.0 #Missing #Missing #Missing #Missing #Missing 436.0 436.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 2654.0 2654.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 253.0 253.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 899.0 #Missing #Missing #Missing 899.0 #Missing #Missing #Missing #Missing #Missing 1021.0 510.0 511.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing 195448.0 45559.0 46582.0 50081.0 53226.0 #Missing #Missing #Missing #Missing #Missing 1973.0 #Missing 951.0 1022.0 #Missing #Missing #Missing #Missing #Missing #Missing 323.0 #Missing 323.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing 1624.0 #Missing 1053.0 #Missing 571.0 #Missing #Missing #Missing #Missing #Missing 2173.0 821.0 447.0 905.0 #Missing #Missing #Missing #Missing #Missing #Missing 4094.0 585.0 #Missing 1807.0 1702.0 #Missing #Missing #Missing #Missing #Missing 1148.0 420.0 365.0 363.0 #Missing #Missing #Missing #Missing #Missing #Missing 504.0 #Missing #Missing 504.0 #Missing #Missing #Missing #Missing #Missing #Missing 1294.0 #Missing 827.0 467.0 #Missing #Missing #Missing #Missing #Missing #Missing 444.0 #Missing #Missing #Missing 444.0 #Missing #Missing #Missing #Missing #Missing 2055.0 1393.0 191.0 460.0 11.0 #Missing #Missing #Missing #Missing #Missing 2572.0 #Missing 398.0 1641.0 533.0 #Missing #Missing #Missing #Missing #Missing 2203.0 #Missing #Missing 439.0 1764.0 #Missing #Missing #Missing #Missing #Missing 1446.0 #Missing 1446.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing 1382.0 922.0 400.0 #Missing 60.0 #Missing #Missing #Missing #Missing #Missing 754.0 #Missing #Missing 314.0 440.0 #Missing #Missing #Missing #Missing #Missing 2118.0 278.0 562.0 1278.0 #Missing #Missing #Missing #Missing #Missing #Missing 2628.0 946.0 #Missing #Missing 1682.0 #Missing #Missing #Missing #Missing #Missing 2497.0 396.0 882.0 617.0 602.0 #Missing #Missing #Missing #Missing #Missing 1183.0 387.0 #Missing 796.0 #Missing #Missing #Missing #Missing #Missing #Missing 1155.0 650.0 461.0 44.0 #Missing #Missing #Missing #Missing #Missing #Missing 525.0 26.0 #Missing 24.0 475.0 #Missing #Missing #Missing #Missing #Missing 2053.0 1477.0 #Missing #Missing 576.0 #Missing #Missing #Missing #Missing #Missing 335.0 #Missing #Missing 335.0 #Missing #Missing #Missing #Missing #Missing #Missing 2100.0 1155.0 878.0 67.0 #Missing #Missing #Missing #Missing #Missing #Missing 916.0 916.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 914.0 602.0 312.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing 3145.0 856.0 #Missing #Missing 2289.0 #Missing #Missing #Missing #Missing #Missing mondrian-3.4.1/testsrc/queryFiles/queryTest_avg_bccad92d5f2b37bf.xml0000644000175000017500000000657611735330606025243 0ustar drazzibdrazzib WITH MEMBER [Promotions].[Foo] AS 'AVG({[Bag Stuffers], [Best Savings]})' SELECT {[Promotions].[Bag Stuffers], [Promotions].[Best Savings], [Promotions].[Foo]} ON AXIS(0), {[Store].[All Stores], [Store].[All Stores].Children} ON AXIS(1) FROM [Sales] [Measures] [Time] [Product] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Bag Stuffers] [Promotions].[Best Savings] [Promotions].[Foo] [Store] [Store].[All Stores] [Store].[Canada] [Store].[Mexico] [Store].[USA] 901.0 2081.0 1491.0 #Missing #Missing #Missing #Missing #Missing #Missing 901.0 2081.0 1491.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_e43623c87d16133b.xml0000644000175000017500000000767411735330606023746 0ustar drazzibdrazzib select {[Measures].[Unit Sales]} on columns, drilldownlevel({[Store].[USA], [Store].[USA].[CA], [Store].[USA].[OR]}, [Store].[Store State]) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Francisco] [Store].[USA].[OR] [Store].[USA].[OR].[Portland] [Store].[USA].[OR].[Salem] 266773.0 74748.0 #Missing 21333.0 25663.0 25635.0 2117.0 67659.0 26079.0 41580.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_prevMember_79545abdc7d7cab7.xml0000644000175000017500000000475211735330606026436 0ustar drazzibdrazzib SELECT {[Education Level].[Partial College].PrevMember} ON AXIS(0), {[Time].[1997].[Q3].[9].PrevMember, [Time].[1997].[Q2].[4].NextMember} ON AXIS(1) FROM [Sales] WHERE [Store Size in SQFT].[21215].PrevMember [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[20319] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Education Level] [Education Level].[High School Degree] [Time] [Time].[1997].[Q3].[8] [Time].[1997].[Q2].[5] 479.0 616.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_c59fb9ab5b11f3eb.xml0000644000175000017500000022212411735330606024314 0ustar drazzibdrazzib select {[Measures].[Store Invoice], [Measures].[Supply Time]} on columns, crossjoin([Warehouse].children, crossjoin([Store].[Store State].members, [Store Type].members)) on rows from [Warehouse] [Store Size in SQFT] [Time] [Store Size in SQFT].[All Store Size in SQFTs] [Time].[1997] [Measures] [Measures].[Store Invoice] [Measures].[Supply Time] [Warehouse] [Store] [Store Type] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Supermarket] #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 29607.2994 3100.0 #Missing #Missing 5132.8974 559.0 #Missing #Missing #Missing #Missing 988.8204 80.0 23485.5816 2461.0 20194.0007 2051.0 16151.0411 1650.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 4042.9596 401.0 52477.1088 5274.0 16100.8297 1578.0 #Missing #Missing #Missing #Missing 5274.3375 528.0 2143.2282 299.0 28958.7134 2869.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_42d09946aca4654.xml0000644000175000017500000001133011735330606023647 0ustar drazzibdrazzib SELECT {[Time].[1997].[Q1]} ON COLUMNS, bottompercent(filter( {[Store].[USA].[CA].children, [Store].[USA].[OR].children, [Store].[USA].[WA].children}, [Measures].[Unit Sales] &gt; 0), 100, [Measures].[Store Sales]) ON ROWS FROM [Sales] WHERE([Measures].[Unit Sales]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Store] [Store].[USA].[CA].[San Francisco] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Yakima] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Bremerton] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[OR].[Portland] [Store].[USA].[WA].[Tacoma] [Store].[USA].[OR].[Salem] 439.0 500.0 518.0 3096.0 3822.0 5607.0 6098.0 5896.0 6256.0 6373.0 6709.0 8399.0 12578.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_90a4cab10da5631.xml0000644000175000017500000000553711735330606023776 0ustar drazzibdrazzib with member [Promotions].[Med Store Sales] as 'Median([Promotions].members, [Measures].[Store Sales])' Select { [Promotions].[Med Store Sales]} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Med Store Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 1169.59 1148.59 1108.965 1208.675 mondrian-3.4.1/testsrc/queryFiles/queryTest_70faea62f26eeae.xml0000644000175000017500000000725311735330606024234 0ustar drazzibdrazzib Select {ToggleDrillState({[Store].[USA],[Store].[Canada],[Store].[Mexico]}, {[Store].[USA],[Store].[USA].[WA],[Store].[All Stores].[Mexico]})} on Axis(0) from [Sales] [Measures] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[OR] [Store].[USA].[WA] [Store].[Canada] [Store].[Mexico] [Store].[Mexico].[DF] [Store].[Mexico].[Guerrero] [Store].[Mexico].[Jalisco] [Store].[Mexico].[Veracruz] [Store].[Mexico].[Yucatan] [Store].[Mexico].[Zacatecas] 266773.0 74748.0 67659.0 124366.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing mondrian-3.4.1/testsrc/queryFiles/queryTest_6323d67192567b29.xml0000644000175000017500000022210211735330606023604 0ustar drazzibdrazzib select {[Measures].[Store Invoice], [Measures].[Supply Time]} on columns, {[Warehouse].children * [Store].[Store State].members * [Store Type].members} on rows from [Warehouse] [Store Size in SQFT] [Time] [Store Size in SQFT].[All Store Size in SQFTs] [Time].[1997] [Measures] [Measures].[Store Invoice] [Measures].[Supply Time] [Warehouse] [Store] [Store Type] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Canada] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[Mexico] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Canada].[BC] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[DF] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Guerrero] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Jalisco] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Veracruz] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Yucatan] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[Mexico].[Zacatecas] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA] [Store].[USA].[WA] [Store Type].[Supermarket] #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 29607.2994 3100.0 #Missing #Missing 5132.8974 559.0 #Missing #Missing #Missing #Missing 988.8204 80.0 23485.5816 2461.0 20194.0007 2051.0 16151.0411 1650.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 4042.9596 401.0 52477.1088 5274.0 16100.8297 1578.0 #Missing #Missing #Missing #Missing 5274.3375 528.0 2143.2282 299.0 28958.7134 2869.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_4ce92fbbc6f86d8.xml0000644000175000017500000000547411735330606024177 0ustar drazzibdrazzib with member [Promotions].[Max Unit Sales] as 'Max([Promotions].members)' Select { [Promotions].[Max Unit Sales]} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Max Unit Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 66291.0 62610.0 65848.0 72024.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_1e5083a85d947_MTD.xml0000644000175000017500000011202611735330606024135 0ustar drazzibdrazzib WITH MEMBER [Measures].[MTDProfit] AS 'SUM(MTD(), ([Measures].[Store Sales] - Measures.[Store Cost]))' SELECT {DESCENDANTS([Time].[1997], [Month])} ON COLUMNS, {[Product].[Product Category].MEMBERS} ON ROWS FROM [Sales] WHERE ([Measures].[MTDProfit]) [Measures] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[MTDProfit] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1].[1] [Time].[1997].[Q1].[2] [Time].[1997].[Q1].[3] [Time].[1997].[Q2].[4] [Time].[1997].[Q2].[5] [Time].[1997].[Q2].[6] [Time].[1997].[Q3].[7] [Time].[1997].[Q3].[8] [Time].[1997].[Q3].[9] [Time].[1997].[Q4].[10] [Time].[1997].[Q4].[11] [Time].[1997].[Q4].[12] [Product] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] [Product].[Drink].[Beverages].[Carbonated Beverages] [Product].[Drink].[Beverages].[Drinks] [Product].[Drink].[Beverages].[Hot Beverages] [Product].[Drink].[Beverages].[Pure Juice Beverages] [Product].[Drink].[Dairy].[Dairy] [Product].[Food].[Baked Goods].[Bread] [Product].[Food].[Baking Goods].[Baking Goods] [Product].[Food].[Baking Goods].[Jams and Jellies] [Product].[Food].[Breakfast Foods].[Breakfast Foods] [Product].[Food].[Canned Foods].[Canned Anchovies] [Product].[Food].[Canned Foods].[Canned Clams] [Product].[Food].[Canned Foods].[Canned Oysters] [Product].[Food].[Canned Foods].[Canned Sardines] [Product].[Food].[Canned Foods].[Canned Shrimp] [Product].[Food].[Canned Foods].[Canned Soup] [Product].[Food].[Canned Foods].[Canned Tuna] [Product].[Food].[Canned Foods].[Vegetables] [Product].[Food].[Canned Products].[Fruit] [Product].[Food].[Dairy].[Dairy] [Product].[Food].[Deli].[Meat] [Product].[Food].[Deli].[Side Dishes] [Product].[Food].[Eggs].[Eggs] [Product].[Food].[Frozen Foods].[Breakfast Foods] [Product].[Food].[Frozen Foods].[Frozen Desserts] [Product].[Food].[Frozen Foods].[Frozen Entrees] [Product].[Food].[Frozen Foods].[Meat] [Product].[Food].[Frozen Foods].[Pizza] [Product].[Food].[Frozen Foods].[Vegetables] [Product].[Food].[Meat].[Meat] [Product].[Food].[Produce].[Fruit] [Product].[Food].[Produce].[Packaged Vegetables] [Product].[Food].[Produce].[Specialty] [Product].[Food].[Produce].[Vegetables] [Product].[Food].[Seafood].[Seafood] [Product].[Food].[Snack Foods].[Snack Foods] [Product].[Food].[Snacks].[Candy] [Product].[Food].[Starchy Foods].[Starchy Foods] [Product].[Non-Consumable].[Carousel].[Specialty] [Product].[Non-Consumable].[Checkout].[Hardware] [Product].[Non-Consumable].[Checkout].[Miscellaneous] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] [Product].[Non-Consumable].[Household].[Bathroom Products] [Product].[Non-Consumable].[Household].[Candles] [Product].[Non-Consumable].[Household].[Cleaning Supplies] [Product].[Non-Consumable].[Household].[Electrical] [Product].[Non-Consumable].[Household].[Hardware] [Product].[Non-Consumable].[Household].[Kitchen Products] [Product].[Non-Consumable].[Household].[Paper Products] [Product].[Non-Consumable].[Household].[Plastic Products] [Product].[Non-Consumable].[Periodicals].[Magazines] 592.3146 553.3971 712.4829 681.1653 673.115 762.8817 791.4585 624.2659 670.8185 714.5089 813.9311 861.9553 267.2418 372.3767 239.3697 290.2031 349.5211 320.666 357.3312 310.9598 284.6404 298.7037 370.9008 289.8308 268.3569 205.4795 329.5614 263.1877 304.8518 244.6538 296.4343 222.8943 265.3199 278.604 289.6063 426.2308 524.3535 377.555 485.7848 452.7175 501.8988 412.5128 494.5266 427.6874 415.8931 336.0112 572.4087 552.3059 307.6964 346.2096 345.3176 330.7987 320.6036 285.4308 337.945 369.0326 345.2542 257.4875 400.2584 332.3843 301.7305 336.2708 398.7978 329.2386 323.8762 338.7873 377.026 275.707 336.1498 381.5212 414.5631 414.0125 785.9984 771.0192 857.9507 677.2676 738.1725 774.1896 895.5313 848.4926 749.9328 700.1186 1003.7469 1088.9179 833.4271 745.3311 886.417 651.0078 837.6871 661.5386 745.904 713.8255 796.9313 681.7984 778.5297 990.9681 1073.2551 1077.8584 1157.1587 1110.7784 1082.9016 1125.3604 1222.0983 1205.5078 1059.555 1005.172 1331.628 1525.1605 322.2093 337.5244 371.2499 335.6622 300.5777 340.1835 331.5799 374.113 323.5633 290.2831 438.8676 418.8495 83.5721 89.0176 132.3092 108.9983 86.0177 134.8645 115.5963 91.9181 116.7685 99.43 173.019 150.9901 102.4608 110.6954 95.5756 111.2018 68.8332 89.3567 126.8226 86.894 80.5545 56.6965 110.3042 113.4856 61.2867 78.7473 86.9528 68.7524 61.9849 51.3622 100.2098 66.7814 82.239 72.1347 63.9074 76.9092 100.7899 73.5147 60.8561 60.6479 49.6264 77.2709 88.3352 55.7899 58.4226 39.0464 90.1538 65.7548 116.5241 120.3196 139.3604 89.7701 95.6103 108.2189 72.8807 101.3077 111.3908 95.6095 107.1226 129.9877 760.7942 726.9151 866.3797 788.2202 749.6 764.1442 839.0386 804.0824 658.0307 701.5803 941.7297 957.2969 195.072 141.2049 153.117 120.7627 139.1947 132.9365 147.2015 163.1633 207.947 154.9416 181.5695 185.1322 531.9242 517.1411 567.8105 535.125 569.1219 530.7175 501.2376 613.0529 580.9691 494.7718 707.399 735.5199 140.2021 128.2603 164.2302 139.9913 189.3366 149.2908 149.6402 162.4675 174.4968 148.8815 221.892 228.6994 1625.505 1422.9799 1564.0387 1506.3819 1435.2563 1397.1923 1726.273 1424.4165 1282.634 1292.2836 1721.472 1881.5659 1040.4595 891.8004 1141.3777 820.5202 923.421 1100.0055 1104.4133 974.7286 896.4808 891.123 1390.3017 1225.8458 217.3055 196.7813 260.1296 190.7028 244.7697 224.042 232.6635 182.7012 244.2695 194.048 308.2182 313.9511 410.005 354.1706 404.3848 451.034 434.4439 462.6667 556.7757 451.564 407.0097 457.3661 605.3547 521.0842 469.0806 379.6963 486.7459 366.215 362.5006 385.9777 458.5681 429.1292 394.3293 423.7597 578.2492 465.0976 600.1638 685.6317 616.5169 591.5362 574.1585 642.6027 704.2034 686.4782 569.7551 654.9767 749.0642 742.2848 286.9421 278.7126 347.0357 258.2753 319.1774 252.4399 278.7021 351.1019 271.4409 302.6002 310.4236 318.0497 297.6056 275.1458 318.9253 338.5875 356.7537 309.2609 390.2027 270.3641 296.941 276.5984 285.8529 353.163 302.1932 267.6968 304.0552 328.5839 348.5657 273.2065 353.9148 280.9906 323.9571 299.1441 446.2825 402.4598 632.3337 744.1498 879.0035 656.312 768.0067 666.4158 835.6418 774.4948 603.7346 642.5403 738.6534 943.481 142.5213 160.8274 192.5403 171.7278 166.0303 171.492 180.0605 208.5142 198.3663 172.5749 199.4826 240.3308 1269.7775 1386.1438 1294.0776 1029.9485 1188.8894 1308.8268 1298.9834 1343.4876 1194.153 1142.1187 1363.1507 1683.8065 118.6592 34.6187 93.6291 109.6616 120.9814 59.4076 138.801 113.8969 92.403 83.1544 86.542 126.3862 383.5816 418.9843 469.7724 397.1653 445.5288 523.1859 405.1871 440.4774 457.2495 504.8431 527.8272 625.1851 2159.6983 2303.1623 2407.7237 2047.2094 2076.8401 2282.6699 2423.1964 2285.2137 2181.9007 2092.0151 2463.4042 2413.5629 168.1696 175.3648 167.1749 193.2643 165.5014 158.3628 226.3728 178.2459 143.5844 195.7421 228.4197 288.2389 3286.6458 3193.8153 3794.1209 2899.9448 3123.8637 3411.106 3821.3837 3353.0768 3234.2139 2789.1814 3603.5158 4135.6113 696.0916 673.8017 768.064 710.434 563.3818 610.3392 750.8096 747.3913 781.3747 701.6818 870.6841 848.4132 613.6801 528.3678 648.452 589.5508 538.0977 564.6654 635.8886 576.2195 469.9424 552.6551 606.4851 726.1603 60.2108 83.9838 60.9422 76.2301 85.2232 56.8468 78.5509 48.7899 95.7682 81.3182 107.2197 69.0582 85.6283 123.4843 110.1535 68.3166 89.6826 93.0937 45.5829 48.2656 75.9753 75.6028 120.0979 121.8287 86.9161 101.3084 166.7541 68.4241 89.7237 97.2073 115.0459 115.7291 80.2812 75.5105 86.7268 101.3341 609.086 476.658 692.2964 495.9417 613.9622 602.864 618.5276 542.5662 504.9223 622.4763 639.4781 728.0625 129.9377 139.2354 215.4294 145.7417 207.2932 140.5427 176.6702 152.1465 111.1513 149.6635 212.5535 227.8394 162.6998 192.7033 116.0817 117.978 171.4931 200.4298 186.2395 173.9461 154.3124 84.8437 208.8833 219.6385 288.1249 278.2837 336.5121 275.7884 283.3634 308.8279 340.4914 295.1553 286.211 302.4129 322.9848 336.9226 314.0496 387.7057 447.3017 387.7584 406.3162 381.7473 453.085 379.6961 356.2 404.7564 438.1077 442.7762 67.8979 62.3019 70.5731 37.2127 53.4101 57.6187 63.951 58.8285 45.9784 46.3418 51.7865 69.5691 66.7273 40.3493 73.5416 57.401 80.4566 93.402 73.307 72.1824 80.4791 45.0073 62.0794 74.9058 411.6464 364.8927 372.1832 361.865 341.0528 278.0904 367.2833 346.5352 362.9172 324.1082 398.9824 324.1725 831.0281 820.6843 943.0276 736.1872 669.7716 693.3041 779.968 826.0957 836.2413 675.5243 885.617 1039.1276 143.9283 162.3688 226.2726 143.0092 173.8668 170.591 198.4942 177.6213 175.0756 197.397 170.9692 243.3939 379.3286 460.3587 437.377 521.6231 406.3436 448.7946 570.6759 394.8839 425.9775 464.9088 575.6985 462.9333 820.4514 639.8037 789.0168 744.6238 737.8146 674.7847 832.2192 693.1278 743.9836 662.4694 825.1455 1114.07 337.1681 257.0272 301.4177 308.7014 214.197 388.8587 323.4775 350.882 265.3898 304.4622 354.4163 388.1432 476.7394 387.2829 488.46 417.1964 451.0644 466.5242 504.6804 496.1921 399.371 447.7535 450.2832 456.6602 mondrian-3.4.1/testsrc/queryFiles/queryTest_ce24f4e185724fcf.xml0000644000175000017500000005533611735330606024176 0ustar drazzibdrazzib select {[Measures].[Store Invoice], [Measures].[Supply Time]} on columns, {[Warehouse].[USA].children * [Store].[USA].children * [Store Type].members} on rows from [Warehouse] [Store Size in SQFT] [Time] [Store Size in SQFT].[All Store Size in SQFTs] [Time].[1997] [Measures] [Measures].[Store Invoice] [Measures].[Supply Time] [Warehouse] [Store] [Store Type] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[CA] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[CA] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[CA] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[CA] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[CA] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[CA] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[CA] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[OR] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[OR] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[OR] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[OR] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[OR] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[OR] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[OR] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[OR] [Store].[USA].[WA] [Store Type].[Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[CA] [Store Type].[All Store Types] [Warehouse].[USA].[WA] [Store].[USA].[CA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[CA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[CA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA] [Store].[USA].[CA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA] [Store].[USA].[CA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA] [Store].[USA].[CA] [Store Type].[Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[OR] [Store Type].[All Store Types] [Warehouse].[USA].[WA] [Store].[USA].[OR] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[OR] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[OR] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA] [Store].[USA].[OR] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA] [Store].[USA].[OR] [Store Type].[Small Grocery] [Warehouse].[USA].[WA] [Store].[USA].[OR] [Store Type].[Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[All Store Types] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Deluxe Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Gourmet Supermarket] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[HeadQuarters] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Mid-Size Grocery] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Small Grocery] [Warehouse].[USA].[WA] [Store].[USA].[WA] [Store Type].[Supermarket] 29607.2994 3100.0 #Missing #Missing 5132.8974 559.0 #Missing #Missing #Missing #Missing 988.8204 80.0 23485.5816 2461.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 20194.0007 2051.0 16151.0411 1650.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 4042.9596 401.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 52477.1088 5274.0 16100.8297 1578.0 #Missing #Missing #Missing #Missing 5274.3375 528.0 2143.2282 299.0 28958.7134 2869.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_861c32529972e0.xml0000644000175000017500000001024711735330606023435 0ustar drazzibdrazzib with member [Store].[Sum CA Unit Sales] as 'Sum([Store].[USA].[CA].children)' Select { [Store].[Sum CA Unit Sales],[Store].[USA].[CA].children} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] WHERE ([Measures].[Unit Sales]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[Sum CA Unit Sales] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Francisco] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 16890.0 #Missing 3822.0 6373.0 6256.0 439.0 18052.0 #Missing 5837.0 5554.0 6125.0 536.0 18370.0 #Missing 4724.0 6470.0 6619.0 557.0 21436.0 #Missing 6950.0 7266.0 6635.0 585.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_avg_a1c7dadccdca29.xml0000644000175000017500000000656211735330606025057 0ustar drazzibdrazzib WITH MEMBER [Promotions].[Average] AS 'AVG({[Bag Stuffers], [Best Savings]})' SELECT {[Promotions].[Bag Stuffers], [Promotions].[Best Savings], [Promotions].[Average]} ON AXIS(0), {[Store].[USA], [Store].[USA].Children} ON AXIS(1) FROM [Sales] [Measures] [Time] [Product] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Bag Stuffers] [Promotions].[Best Savings] [Promotions].[Average] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[OR] [Store].[USA].[WA] 901.0 2081.0 1491.0 #Missing 1608.0 1608.0 617.0 #Missing 617.0 284.0 473.0 378.5 mondrian-3.4.1/testsrc/queryFiles/queryTest_b8bdcc55995eeda.xml0000644000175000017500000001102311735330606024236 0ustar drazzibdrazzib select { [Time].[1997].children } on columns, intersect( {[Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods], [Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods]}, {[Product].[Food].[Baked Goods], [Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods]}, all) on rows from [Sales] where ( [Measures].[Store Cost] ) [Measures] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Store Cost] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] [Product] [Product].[Food].[Baked Goods] [Product].[Food].[Baking Goods] [Product].[Food].[Breakfast Foods] [Product].[Food].[Baked Goods] [Product].[Food].[Baking Goods] [Product].[Food].[Breakfast Foods] 1609.8117 1453.4103 1669.9533 1830.9166 3804.7026 3633.6761 3772.9881 4159.2433 680.1564 652.8166 661.5138 762.3098 1609.8117 1453.4103 1669.9533 1830.9166 3804.7026 3633.6761 3772.9881 4159.2433 680.1564 652.8166 661.5138 762.3098 mondrian-3.4.1/testsrc/queryFiles/queryTest_ae6c5380f5688f9a.xml0000644000175000017500000000620411735330606024115 0ustar drazzibdrazzib select { [Time].[1997].children } on columns, intersect( {[Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods]}, {[Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods]}) on rows from [Sales] where ( [Measures].[Store Cost] ) [Measures] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Store Cost] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] [Product] [Product].[Food].[Baked Goods] [Product].[Food].[Baking Goods] 1609.8117 1453.4103 1669.9533 1830.9166 3804.7026 3633.6761 3772.9881 4159.2433 mondrian-3.4.1/testsrc/queryFiles/queryTest_5da65ca7d76cc7f_COALESCEEMPTY.xml0000644000175000017500000001225011735330606026234 0ustar drazzibdrazzib WITH MEMBER Measures.[Profit] AS '(Measures.[Store Sales] - Measures.[Store Cost])' MEMBER Measures.[Profit Increase] AS '(Measures.[Profit]) / COALESCEEMPTY((Measures.[Profit], [Time].[Time].PREVMEMBER), Measures.[Profit])', FORMAT_STRING = '#.00%' SELECT {Measures.[Profit], Measures.[Profit Increase]} ON COLUMNS, {DESCENDANTS([Time].[1997], [Month])} ON ROWS FROM [Sales] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Profit] [Measures].[Profit Increase] [Time] [Time].[1997].[Q1].[1] [Time].[1997].[Q1].[2] [Time].[1997].[Q1].[3] [Time].[1997].[Q2].[4] [Time].[1997].[Q2].[5] [Time].[1997].[Q2].[6] [Time].[1997].[Q3].[7] [Time].[1997].[Q3].[8] [Time].[1997].[Q3].[9] [Time].[1997].[Q4].[10] [Time].[1997].[Q4].[11] [Time].[1997].[Q4].[12] 27361.1974 1.0 26459.1212 0.967030821538534 30055.7909 1.1359330747538205 25766.5507 0.8572907226340865 26673.7343 1.035207801407427 27261.7602 1.0220451284918137 30241.089 1.109286002743139 27763.0792 0.9180581823624142 26362.8524 0.9495651476584053 25440.2644 0.9650042421054559 32005.9514 1.2580824985451016 34219.5053 1.0691606967821616 mondrian-3.4.1/testsrc/queryFiles/queryTest_80a3cf15a6bb7096.xml0000644000175000017500000000667711735330606024107 0ustar drazzibdrazzib select { [Measures].[Customer Count] } on columns, filter(descendants([Store].[USA]), ([Measures].[Customer Count]) &gt;= 1000 and ([Measures].[Customer Count]) &lt;= 3000) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Customer Count] [Store] [Store].[USA].[CA] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Beverly Hills].[Store 6] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[Los Angeles].[Store 7] [Store].[USA].[OR] [Store].[USA].[WA] 2716.0 1059.0 1059.0 1147.0 1147.0 1037.0 1828.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_df2bac111bbd7fc.xml0000644000175000017500000000523611735330606024276 0ustar drazzibdrazzib SELECT {[Time].[1997].[Q1]} ON COLUMNS, bottompercent(filter( {[Store].[USA].[CA].children, [Store].[USA].[OR].children, [Store].[USA].[WA].children}, [Measures].[Unit Sales] &gt; 0), 1, [Measures].[Store Sales]) ON ROWS FROM [Sales] WHERE([Measures].[Unit Sales]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Store] [Store].[USA].[CA].[San Francisco] [Store].[USA].[WA].[Walla Walla] 439.0 500.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_d5e930177e10b317.xml0000644000175000017500000000411411735330606023727 0ustar drazzibdrazzib with member [Product].[Level 1 Name] as '[Product].Levels(1).Name' select {[Product].[Level 1 Name]} on Axis(0) from [Sales] [Measures] [Time] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Product] [Product].[Level 1 Name] Product Family mondrian-3.4.1/testsrc/queryFiles/queryTest_22e1ef5ddf2db2f_Subset.xml0000644000175000017500000000701211735330606025550 0ustar drazzibdrazzib SELECT DISTINCT(Subset( ( {[Measures].[Customer Count], [Measures].[Sales Count], [Measures].[Store Cost], [Measures].[Store Sales], [Measures].[Unit Sales]} ), 0, 3 )) ON AXIS(0) , DISTINCT( {[Product].[All Products], [Product].[Product Family].[Drink], [Product].[Product Family].[Food], [Product].[Product Family].[Non-Consumable]} ) ON AXIS(1) FROM [Sales] [Time] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Customer Count] [Measures].[Sales Count] [Measures].[Store Cost] [Product] [Product].[All Products] [Product].[Drink] [Product].[Food] [Product].[Non-Consumable] 5581.0 86837.0 225627.2336 3485.0 7978.0 19477.2346 5525.0 62445.0 163270.7235 4468.0 16414.0 42879.2755 mondrian-3.4.1/testsrc/queryFiles/queryTest_1dd7b1c0801411f1.xml0000644000175000017500000001113011735330606023763 0ustar drazzibdrazzib select { [Time].[1997].children } on columns, intersect( {[Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods], [Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods]}, {[Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods], [Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods]}, all) on rows from [Sales] where ( [Measures].[Store Cost] ) [Measures] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Store Cost] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] [Product] [Product].[Food].[Baked Goods] [Product].[Food].[Baking Goods] [Product].[Food].[Breakfast Foods] [Product].[Food].[Baked Goods] [Product].[Food].[Baking Goods] [Product].[Food].[Breakfast Foods] 1609.8117 1453.4103 1669.9533 1830.9166 3804.7026 3633.6761 3772.9881 4159.2433 680.1564 652.8166 661.5138 762.3098 1609.8117 1453.4103 1669.9533 1830.9166 3804.7026 3633.6761 3772.9881 4159.2433 680.1564 652.8166 661.5138 762.3098 mondrian-3.4.1/testsrc/queryFiles/queryTest_338185fcb9af6edd_Name.xml0000644000175000017500000001372611735330606025216 0ustar drazzibdrazzib SELECT {Measures.[Sales Count], Measures.[Store Cost], Measures.[Store Sales], Measures.[Unit Sales], Measures.[Customer Count]} ON COLUMNS, ORDER({[Store].[Store City].[Beverly Hills]:[Store].[Store City].[Spokane]}, [Store].CURRENTMEMBER.Name, BASC) ON ROWS FROM [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Sales Count] [Measures].[Store Cost] [Measures].[Store Sales] [Measures].[Unit Sales] [Measures].[Customer Count] [Store] [Store].[USA].[WA].[Bellingham] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[WA].[Bremerton] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[OR].[Portland] [Store].[USA].[OR].[Salem] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Francisco] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Spokane] 1380.0 1896.6174 4739.23 2237.0 190.0 6815.0 18266.4404 45750.24 21333.0 1059.0 7876.0 21121.9631 52896.3 24576.0 179.0 8207.0 21771.536 54545.28 25663.0 1147.0 8264.0 21948.944 55058.79 26079.0 563.0 13347.0 34823.5566 87218.28 41580.0 474.0 8095.0 21713.5328 54431.14 25635.0 962.0 1325.0 1778.9159 4441.18 2117.0 296.0 7956.0 20956.8025 52644.07 25011.0 906.0 7397.0 19795.491 49634.46 23591.0 84.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_lastSibling_69655644a9e6eb5a.xml0000644000175000017500000001006611735330606026370 0ustar drazzibdrazzib SELECT {Store, Store.LastChild, Store.[All Stores].LastChild, Store.[USA].[CA].[San Francisco].LastChild} ON Columns, {Product, Product.LastChild, Product.[Drink].LastChild, Product.[Food].[Deli].[Meat].[Bologna].[American].LastChild } ON Rows FROM [Sales] where([Measures].[Unit Sales], [Time].[1997], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[All Store Types], [Promotions].[All Promotions], [Customers].[All Customers], [Education Level].[All Education Levels], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes], [Promotion Media].[All Media], [Gender].[All Gender]) [Measures] [Time] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[All Stores] [Store].[USA] [Store].[USA] [Store].[USA].[CA].[San Francisco].[Store 14] [Product] [Product].[All Products] [Product].[Non-Consumable] [Product].[Drink].[Dairy] [Product].[Food].[Deli].[Meat].[Bologna].[American].[American Pimento Loaf] 266773.0 266773.0 266773.0 2117.0 50236.0 50236.0 50236.0 387.0 4186.0 4186.0 4186.0 31.0 195.0 195.0 195.0 3.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_9e75d6a51fd5dc8.xml0000644000175000017500000000410211735330606024101 0ustar drazzibdrazzib with member [Product].[Level 0 Name] as '[Product].Levels(0).Name' select {[Product].[Level 0 Name]} on Axis(0) from [Sales] [Measures] [Time] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Product] [Product].[Level 0 Name] (All) mondrian-3.4.1/testsrc/queryFiles/queryTest_lastSibling_4534b3d703f05.xml0000644000175000017500000000673311735330606025755 0ustar drazzibdrazzib SELECT {Store.[USA].LastSibling, Store.[USA].[CA].LastSibling, Store.[USA].[CA].[Alameda].LastSibling} on ROWS, {Time.[1997], Time.[1997].[Q3].LastSibling, Time.[1997].[Q3].[9].LastSibling} on COLUMNS FROM [Sales] where([Measures].[Unit Sales], [Product].[All Products], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[All Store Types], [Promotions].[All Promotions], [Customers].[All Customers], [Education Level].[All Education Levels], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes], [Promotion Media].[All Media], [Gender].[All Gender]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997] [Time].[1997].[Q4] [Time].[1997].[Q3].[9] [Store] [Store].[USA] [Store].[USA].[WA] [Store].[USA].[CA].[San Francisco] 266773.0 72024.0 20388.0 124366.0 34235.0 9402.0 2117.0 585.0 201.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_9d3580e3e21f8765.xml0000644000175000017500000000620611735330606023754 0ustar drazzibdrazzib select { [Time].[1997].children } on columns, intersect( {[Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods]}, {[Product].[Food].[Baked Goods], [Product].[Food].[Breakfast Foods]}) on rows from [Sales] where ( [Measures].[Store Cost] ) [Measures] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Store Cost] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] [Product] [Product].[Food].[Baked Goods] [Product].[Food].[Breakfast Foods] 1609.8117 1453.4103 1669.9533 1830.9166 680.1564 652.8166 661.5138 762.3098 mondrian-3.4.1/testsrc/queryFiles/queryTest_90d69f293de8181b.xml0000644000175000017500000000540111735330606024030 0ustar drazzibdrazzib select {[Measures].[Unit Sales]} on columns, drilldownlevel({[Store].[USA]}, [Store].[Store Country]) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[OR] [Store].[USA].[WA] 266773.0 74748.0 67659.0 124366.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_aggregate_6fa7b8253f3e31c0.xml0000644000175000017500000002212211735330606026075 0ustar drazzibdrazzib WITH MEMBER [Store Size in SQFT].[Less Than 23000] AS 'AGGREGATE({[Store Size in SQFT].[20319], [Store Size in SQFT].[21215], [Store Size in SQFT].[22478]})' SELECT {[Store].[All Stores], [Store].[All Stores].Children} ON AXIS(0), {[Store Size in SQFT].[Less Than 23000], [Store Size in SQFT].Children} ON AXIS(1) FROM [Sales] [Measures] [Time] [Product] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Product].[All Products] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[All Stores] [Store].[Canada] [Store].[Mexico] [Store].[USA] [Store Size in SQFT] [Store Size in SQFT].[Less Than 23000] [Store Size in SQFT].[#null] [Store Size in SQFT].[20319] [Store Size in SQFT].[21215] [Store Size in SQFT].[22478] [Store Size in SQFT].[23112] [Store Size in SQFT].[23593] [Store Size in SQFT].[23598] [Store Size in SQFT].[23688] [Store Size in SQFT].[23759] [Store Size in SQFT].[24597] [Store Size in SQFT].[27694] [Store Size in SQFT].[28206] [Store Size in SQFT].[30268] [Store Size in SQFT].[30584] [Store Size in SQFT].[30797] [Store Size in SQFT].[33858] [Store Size in SQFT].[34452] [Store Size in SQFT].[34791] [Store Size in SQFT].[36509] [Store Size in SQFT].[38382] [Store Size in SQFT].[39696] 53207.0 #Missing #Missing 53207.0 39329.0 #Missing #Missing 39329.0 26079.0 #Missing #Missing 26079.0 25011.0 #Missing #Missing 25011.0 2117.0 #Missing #Missing 2117.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 25663.0 #Missing #Missing 25663.0 21333.0 #Missing #Missing 21333.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 41580.0 #Missing #Missing 41580.0 2237.0 #Missing #Missing 2237.0 23591.0 #Missing #Missing 23591.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 35257.0 #Missing #Missing 35257.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 24576.0 #Missing #Missing 24576.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_ef4045594615c7c.xml0000644000175000017500000000467111735330606023666 0ustar drazzibdrazzib select { ([Measures].[Store Sales]) } on columns, generate( { [Store].[USA], [Store].[Mexico] }, { [Time].[1997], [Time].[1998] }) on rows from [Sales] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Store Sales] [Time] [Time].[1997] [Time].[1998] 565238.13 #Missing mondrian-3.4.1/testsrc/queryFiles/queryTest_92fa326a61c9fec6.xml0000644000175000017500000000555111735330606024166 0ustar drazzibdrazzib with member [Promotions].[Sum of Store Sales] as 'Sum([Promotions].members, [Measures].[Store Sales])' Select { [Promotions].[Sum of Store Sales]} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Sum of Store Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 279256.7 265332.54 280543.78 305343.24 mondrian-3.4.1/testsrc/queryFiles/queryTest_3572af958f815277.xml0000644000175000017500000000503111735330606023674 0ustar drazzibdrazzib select { [Measures].[Unit Sales] } on columns, filter(descendants([Store].[USA]), ([Measures].[Customer Count], [Time].[1997].[Q3]) &gt;= 1000 and ([Measures].[Customer Count]) &lt;= 3000) on rows from [Sales] where ([Time].[1997].[Q3]) [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997].[Q3] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA].[CA] [Store].[USA].[WA] 18370.0 30538.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_3ad7c28e574366cf.xml0000644000175000017500000000506211735330606024107 0ustar drazzibdrazzib select {Ancestor([Product].[Drink],[Product].Levels(0)),Ancestor([Product].[Drink],0), Ancestor([Product].[Drink],[Product].Levels(1)),Ancestor([Product].[Drink],1)} on Axis(0) from [Sales] [Measures] [Time] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Product] [Product].[All Products] [Product].[Drink] [Product].[Drink] [Product].[All Products] 266773.0 24597.0 24597.0 266773.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_6394d1ade51cd2e1.xml0000644000175000017500000001247711735330606024160 0ustar drazzibdrazzib select {[Measures].[Unit Sales]} on columns, drilldownlevel({[Store].[USA].[WA], [Store].[USA].[CA].[Los Angeles], [Store].[USA].[CA], [Store].[USA], [Store].[USA].[WA].[Spokane]}, [Store].[Store State]) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA].[WA] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Tacoma] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Yakima] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Francisco] [Store].[USA] [Store].[USA].[WA].[Spokane] 124366.0 2237.0 24576.0 25011.0 23591.0 35257.0 2203.0 11491.0 25663.0 74748.0 #Missing 21333.0 25663.0 25635.0 2117.0 266773.0 23591.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_9f61bc83e47eebf3.xml0000644000175000017500000001112311735330606024244 0ustar drazzibdrazzib select {[Measures].[Store Invoice], [Measures].[Supply Time]} on columns, descendants([Store].[USA], [Store].[Store City]) on rows from [Warehouse] [Store Size in SQFT] [Store Type] [Time] [Warehouse] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Time].[1997] [Warehouse].[All Warehouses] [Measures] [Measures].[Store Invoice] [Measures].[Supply Time] [Store] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Francisco] [Store].[USA].[OR].[Portland] [Store].[USA].[OR].[Salem] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Tacoma] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Yakima] #Missing #Missing 5132.8974 559.0 11634.9186 1279.0 11850.663 1182.0 988.8204 80.0 4042.9596 401.0 16151.0411 1650.0 1049.4587 216.0 11659.6249 1130.0 11517.1251 1193.0 5781.9634 546.0 16100.8297 1578.0 1093.7695 83.0 5274.3375 528.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_5baf1b5edc1b3467.xml0000644000175000017500000000445311735330606024232 0ustar drazzibdrazzib SELECT {crossjoin({Dimensions("Measures").CurrentMember.Hierarchy.CurrentMember},{Dimensions("Product")})} on Axis(0), {[Customers].[USA].Level.Hierarchy.CurrentMember} on Axis(1) from [Sales] [Time] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Product] [Measures].[Unit Sales] [Product].[All Products] [Customers] [Customers].[All Customers] 266773.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_9027755089303df9.xml0000644000175000017500000001135211735330606023612 0ustar drazzibdrazzib select {[Measures].[Store Invoice], [Measures].[Supply Time]} on columns, descendants([Store].[USA], [Store].[Store City], AFTER) on rows from [Warehouse] [Store Size in SQFT] [Store Type] [Time] [Warehouse] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Time].[1997] [Warehouse].[All Warehouses] [Measures] [Measures].[Store Invoice] [Measures].[Supply Time] [Store] [Store].[USA].[CA].[Alameda].[HQ] [Store].[USA].[CA].[Beverly Hills].[Store 6] [Store].[USA].[CA].[Los Angeles].[Store 7] [Store].[USA].[CA].[San Diego].[Store 24] [Store].[USA].[CA].[San Francisco].[Store 14] [Store].[USA].[OR].[Portland].[Store 11] [Store].[USA].[OR].[Salem].[Store 13] [Store].[USA].[WA].[Bellingham].[Store 2] [Store].[USA].[WA].[Bremerton].[Store 3] [Store].[USA].[WA].[Seattle].[Store 15] [Store].[USA].[WA].[Spokane].[Store 16] [Store].[USA].[WA].[Tacoma].[Store 17] [Store].[USA].[WA].[Walla Walla].[Store 22] [Store].[USA].[WA].[Yakima].[Store 23] #Missing #Missing 5132.8974 559.0 11634.9186 1279.0 11850.663 1182.0 988.8204 80.0 4042.9596 401.0 16151.0411 1650.0 1049.4587 216.0 11659.6249 1130.0 11517.1251 1193.0 5781.9634 546.0 16100.8297 1578.0 1093.7695 83.0 5274.3375 528.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_64e38db83fa57498.xml0000644000175000017500000001016111735330606024035 0ustar drazzibdrazzib with member [Store].[Min CA Store] as 'Min([Store].[USA].[CA].children)' Select { [Store].[Min CA Store],[Store].[USA].[CA].children} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[Min CA Store] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Francisco] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 439.0 #Missing 3822.0 6373.0 6256.0 439.0 536.0 #Missing 5837.0 5554.0 6125.0 536.0 557.0 #Missing 4724.0 6470.0 6619.0 557.0 585.0 #Missing 6950.0 7266.0 6635.0 585.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_head_906ddf80348759aa.xml0000644000175000017500000000643211735330606025014 0ustar drazzibdrazzib SELECT {HEAD([Time].[1997].Children, 3)} ON AXIS(0), {[Product].[All Products], [Product].[All Products].Children} ON AXIS(1) FROM [Sales] [Measures] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Product] [Product].[All Products] [Product].[Drink] [Product].[Food] [Product].[Non-Consumable] 66291.0 62610.0 65848.0 5976.0 5895.0 6065.0 47809.0 44825.0 47440.0 12506.0 11890.0 12343.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_fdd8ec9d9e44ef9e.xml0000644000175000017500000000545511735330606024436 0ustar drazzibdrazzib SELECT {[Time].[1997].[Q1]} ON COLUMNS, bottomcount(filter( {[Store].[USA].[CA].children, [Store].[USA].[OR].children, [Store].[USA].[WA].children}, [Measures].[Unit Sales] &gt; 0), 3, [Measures].[Store Sales]) ON ROWS FROM [Sales] WHERE([Measures].[Unit Sales]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Store] [Store].[USA].[CA].[San Francisco] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Bellingham] 439.0 500.0 518.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_ccf74490de18e83b.xml0000644000175000017500000001536411735330606024174 0ustar drazzibdrazzib with member [Measures].[GJProfit] as '[Measures].[Store Sales]-[Measures].[Store Cost]' member [Measures].[GJGarbage] as '[Measures].[GJProfit] * 7 / 2' select { [Measures].[Store Sales], [Measures].[Store Cost], [Measures].[GJProfit], [Measures].[GJGarbage] } on columns, non empty {[Store].[Store Name].members} properties [Store].[Store Name].[Store Sqft] on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Store Sales] [Measures].[Store Cost] [Measures].[GJProfit] [Measures].[GJGarbage] [Store] [Store].[USA].[CA].[Beverly Hills].[Store 6] [Store].[USA].[CA].[Los Angeles].[Store 7] [Store].[USA].[CA].[San Diego].[Store 24] [Store].[USA].[CA].[San Francisco].[Store 14] [Store].[USA].[OR].[Portland].[Store 11] [Store].[USA].[OR].[Salem].[Store 13] [Store].[USA].[WA].[Bellingham].[Store 2] [Store].[USA].[WA].[Bremerton].[Store 3] [Store].[USA].[WA].[Seattle].[Store 15] [Store].[USA].[WA].[Spokane].[Store 16] [Store].[USA].[WA].[Tacoma].[Store 17] [Store].[USA].[WA].[Walla Walla].[Store 22] [Store].[USA].[WA].[Yakima].[Store 23] 45750.24 18266.4404 27483.7996 96193.2986 54545.28 21771.536 32773.744 114708.104 54431.14 21713.5328 32717.6072 114511.6252 4441.18 1778.9159 2662.2641 9317.9243 55058.79 21948.944 33109.846 115884.461 87218.28 34823.5566 52394.7234 183381.5319 4739.23 1896.6174 2842.6126 9949.1441 52896.3 21121.9631 31774.3369 111210.1791 52644.07 20956.8025 31687.2675 110905.4362 49634.46 19795.491 29838.969 104436.3915 74843.96 29959.2813 44884.6787 157096.3754 4705.97 1880.3396 2825.6304 9889.7064 24329.23 9713.813 14615.417 51153.9595 mondrian-3.4.1/testsrc/queryFiles/queryTest_lastChild_4d2a16e2912f86d2.xml0000644000175000017500000001165611735330606026012 0ustar drazzibdrazzib SELECT {Store, Store.[Canada], Store.[Canada].Lead(1), Store.[Canada].Lead(2), Store.[Canada].Lead(0), Store.[USA].Lead(-1) } on ROWS, {Time.[1997].[Q2], Time.[1997].[Q2].Lead(-1), Time.[1997].[Q2].Lead(1), Time.[1997].[Q2].Lead(2), Time.[1997].[Q4].Lead(-3)} on COLUMNS FROM [Sales] where([Measures].[Unit Sales], [Product].[All Products], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[All Store Types], [Promotions].[All Promotions], [Customers].[All Customers], [Education Level].[All Education Levels], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes], [Promotion Media].[All Media], [Gender].[All Gender]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q2] [Time].[1997].[Q1] [Time].[1997].[Q3] [Time].[1997].[Q4] [Time].[1997].[Q1] [Store] [Store].[All Stores] [Store].[Canada] [Store].[Mexico] [Store].[USA] [Store].[Canada] [Store].[Mexico] 62610.0 66291.0 65848.0 72024.0 66291.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing 62610.0 66291.0 65848.0 72024.0 66291.0 #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing #Missing mondrian-3.4.1/testsrc/queryFiles/queryTest_4afe41192c8d9e9.xml0000644000175000017500000000605711735330606024032 0ustar drazzibdrazzib SELECT {[Time].[1997].[Q1]} ON COLUMNS, bottomsum(filter( {[Store].[USA].[CA].children, [Store].[USA].[OR].children, [Store].[USA].[WA].children}, [Measures].[Unit Sales] &gt; 0), 2000, ([Time].[1997].[Q1],[Measures].[Unit Sales])) ON ROWS FROM [Sales] WHERE([Measures].[Unit Sales]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Store] [Store].[USA].[CA].[San Francisco] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Yakima] 439.0 500.0 518.0 3096.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_1267e0433c5ac33.xml0000644000175000017500000000433611735330606023642 0ustar drazzibdrazzib select {[Measures].[Unit Sales]} on columns, drilldownlevel({[Store].[USA]}, [Store].[Store City]) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA] 266773.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_5155556eee90d3e9.xml0000644000175000017500000001265011735330606024034 0ustar drazzibdrazzib select {[Measures].[Unit Sales]} on columns, drilldownlevel(drilldownlevel({[Store].[USA]}, [Store].[Store Country]), [Store State]) on rows from [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Unit Sales] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Francisco] [Store].[USA].[OR] [Store].[USA].[OR].[Portland] [Store].[USA].[OR].[Salem] [Store].[USA].[WA] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Tacoma] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Yakima] 266773.0 74748.0 #Missing 21333.0 25663.0 25635.0 2117.0 67659.0 26079.0 41580.0 124366.0 2237.0 24576.0 25011.0 23591.0 35257.0 2203.0 11491.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_1d839e2be3c177c0.xml0000644000175000017500000000506611735330606024101 0ustar drazzibdrazzib SELECT {[Time].[1997].[Q1]} ON COLUMNS, bottomsum({[Store].[USA].[CA].children, [Store].[USA].[OR].children, [Store].[USA].[WA].children}, 100, [Measures].[Store Sales]) ON ROWS FROM [Sales] WHERE([Measures].[Store Sales]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Store Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Store] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[San Francisco] #Missing 936.51 mondrian-3.4.1/testsrc/queryFiles/queryTest_2e94118cec3f1c0_PeriodsToDate.xml0000644000175000017500000000664011735330606026575 0ustar drazzibdrazzib WITH MEMBER [Measures].[YTD Salaries] AS 'Sum(PeriodsToDate([Time].[Year]),[Org Salary])' SELECT {[Measures].[Org Salary],[Measures].[YTD Salaries]} ON COLUMNS, [Time].[Quarter].Members ON ROWS FROM [HR] [Store] [Pay Type] [Store Type] [Position] [Department] [Employees] [Store].[All Stores] [Pay Type].[All Pay Types] [Store Type].[All Store Types] [Position].[All Position] [Department].[All Departments] [Employees].[All Employees] [Measures] [Measures].[Org Salary] [Measures].[YTD Salaries] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] [Time].[1998].[Q1] [Time].[1998].[Q2] [Time].[1998].[Q3] [Time].[1998].[Q4] 9858.3828 9858.3828 9860.2098 19718.5926 9853.6837 29572.2763 9859.3949 39431.6712 58030.4784 58030.4784 58028.2749 116058.7533 58029.6571 174088.4104 58032.3595 232120.7699 mondrian-3.4.1/testsrc/queryFiles/queryTest_916bd786eec352d3.xml0000644000175000017500000001314211735330606024105 0ustar drazzibdrazzib select {[Measures].[Store Invoice], [Measures].[Supply Time]} on columns, descendants([Store].[USA], [Store].[Store City], BEFORE_and_AFTER) on rows from [Warehouse] [Store Size in SQFT] [Store Type] [Time] [Warehouse] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Time].[1997] [Warehouse].[All Warehouses] [Measures] [Measures].[Store Invoice] [Measures].[Supply Time] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[CA].[Alameda].[HQ] [Store].[USA].[CA].[Beverly Hills].[Store 6] [Store].[USA].[CA].[Los Angeles].[Store 7] [Store].[USA].[CA].[San Diego].[Store 24] [Store].[USA].[CA].[San Francisco].[Store 14] [Store].[USA].[OR] [Store].[USA].[OR].[Portland].[Store 11] [Store].[USA].[OR].[Salem].[Store 13] [Store].[USA].[WA] [Store].[USA].[WA].[Bellingham].[Store 2] [Store].[USA].[WA].[Bremerton].[Store 3] [Store].[USA].[WA].[Seattle].[Store 15] [Store].[USA].[WA].[Spokane].[Store 16] [Store].[USA].[WA].[Tacoma].[Store 17] [Store].[USA].[WA].[Walla Walla].[Store 22] [Store].[USA].[WA].[Yakima].[Store 23] 102278.4089 10425.0 29607.2994 3100.0 #Missing #Missing 5132.8974 559.0 11634.9186 1279.0 11850.663 1182.0 988.8204 80.0 20194.0007 2051.0 4042.9596 401.0 16151.0411 1650.0 52477.1088 5274.0 1049.4587 216.0 11659.6249 1130.0 11517.1251 1193.0 5781.9634 546.0 16100.8297 1578.0 1093.7695 83.0 5274.3375 528.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_3e5c64b7aadcfe4.xml0000644000175000017500000212630411735330606024236 0ustar drazzibdrazzib with member [Measures].[Test] as 'iif ([Product].CurrentMember.level.Ordinal = 2,"Level 2","Not Level 2")' Select {[Measures].[Test]} on columns, hierarchize([Product].members) on rows from [Sales] [Time] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Test] [Product] [Product].[All Products] [Product].[Drink] [Product].[Drink].[Alcoholic Beverages] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Imported Beer] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good].[Good Light Beer] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Pearl] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Pearl].[Pearl Imported Beer] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Pearl].[Pearl Light Beer] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth].[Portsmouth Imported Beer] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth].[Portsmouth Light Beer] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Top Measure] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Top Measure].[Top Measure Imported Beer] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Top Measure].[Top Measure Light Beer] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Walrus] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Walrus].[Walrus Imported Beer] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Walrus].[Walrus Light Beer] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Good] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Good].[Good Chablis Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Good].[Good Chardonnay] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Good].[Good Chardonnay Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Good].[Good Light Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Good].[Good Merlot Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Good].[Good White Zinfandel Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Pearl] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Pearl].[Pearl Chablis Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Pearl].[Pearl Chardonnay] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Pearl].[Pearl Chardonnay Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Pearl].[Pearl Light Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Pearl].[Pearl Merlot Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Pearl].[Pearl White Zinfandel Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Portsmouth] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Portsmouth].[Portsmouth Chablis Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Portsmouth].[Portsmouth Chardonnay] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Portsmouth].[Portsmouth Chardonnay Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Portsmouth].[Portsmouth Light Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Portsmouth].[Portsmouth Merlot Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Portsmouth].[Portsmouth White Zinfandel Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Top Measure] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Top Measure].[Top Measure Chablis Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Top Measure].[Top Measure Chardonnay] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Top Measure].[Top Measure Chardonnay Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Top Measure].[Top Measure Light Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Top Measure].[Top Measure Merlot Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Top Measure].[Top Measure White Zinfandel Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Walrus] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Walrus].[Walrus Chablis Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Walrus].[Walrus Chardonnay] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Walrus].[Walrus Chardonnay Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Walrus].[Walrus Light Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Walrus].[Walrus Merlot Wine] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Walrus].[Walrus White Zinfandel Wine] [Product].[Drink].[Beverages] [Product].[Drink].[Beverages].[Carbonated Beverages] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Excellent] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Excellent].[Excellent Cola] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Excellent].[Excellent Cream Soda] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Excellent].[Excellent Diet Cola] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Excellent].[Excellent Diet Soda] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Fabulous] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Fabulous].[Fabulous Cola] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Fabulous].[Fabulous Cream Soda] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Fabulous].[Fabulous Diet Cola] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Fabulous].[Fabulous Diet Soda] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Skinner] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Skinner].[Skinner Cola] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Skinner].[Skinner Cream Soda] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Skinner].[Skinner Diet Cola] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Skinner].[Skinner Diet Soda] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Token] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Token].[Token Cola] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Token].[Token Cream Soda] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Token].[Token Diet Cola] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Token].[Token Diet Soda] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Washington] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Washington].[Washington Cola] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Washington].[Washington Cream Soda] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Washington].[Washington Diet Cola] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Washington].[Washington Diet Soda] [Product].[Drink].[Beverages].[Drinks] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Excellent] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Excellent].[Excellent Apple Drink] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Excellent].[Excellent Mango Drink] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Excellent].[Excellent Strawberry Drink] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Fabulous] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Fabulous].[Fabulous Apple Drink] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Fabulous].[Fabulous Mango Drink] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Fabulous].[Fabulous Strawberry Drink] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Skinner] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Skinner].[Skinner Apple Drink] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Skinner].[Skinner Mango Drink] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Skinner].[Skinner Strawberry Drink] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Token] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Token].[Token Apple Drink] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Token].[Token Mango Drink] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Token].[Token Strawberry Drink] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Washington] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Washington].[Washington Apple Drink] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Washington].[Washington Mango Drink] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Washington].[Washington Strawberry Drink] [Product].[Drink].[Beverages].[Hot Beverages] [Product].[Drink].[Beverages].[Hot Beverages].[Chocolate] [Product].[Drink].[Beverages].[Hot Beverages].[Chocolate].[BBB Best] [Product].[Drink].[Beverages].[Hot Beverages].[Chocolate].[BBB Best].[BBB Best Hot Chocolate] [Product].[Drink].[Beverages].[Hot Beverages].[Chocolate].[CDR] [Product].[Drink].[Beverages].[Hot Beverages].[Chocolate].[CDR].[CDR Hot Chocolate] [Product].[Drink].[Beverages].[Hot Beverages].[Chocolate].[Landslide] [Product].[Drink].[Beverages].[Hot Beverages].[Chocolate].[Landslide].[Landslide Hot Chocolate] [Product].[Drink].[Beverages].[Hot Beverages].[Chocolate].[Plato] [Product].[Drink].[Beverages].[Hot Beverages].[Chocolate].[Plato].[Plato Hot Chocolate] [Product].[Drink].[Beverages].[Hot Beverages].[Chocolate].[Super] [Product].[Drink].[Beverages].[Hot Beverages].[Chocolate].[Super].[Super Hot Chocolate] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[BBB Best] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[BBB Best].[BBB Best Columbian Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[BBB Best].[BBB Best Decaf Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[BBB Best].[BBB Best French Roast Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[BBB Best].[BBB Best Regular Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[CDR] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[CDR].[CDR Columbian Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[CDR].[CDR Decaf Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[CDR].[CDR French Roast Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[CDR].[CDR Regular Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Landslide] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Landslide].[Landslide Columbian Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Landslide].[Landslide Decaf Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Landslide].[Landslide French Roast Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Landslide].[Landslide Regular Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Plato] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Plato].[Plato Columbian Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Plato].[Plato Decaf Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Plato].[Plato French Roast Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Plato].[Plato Regular Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Super] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Super].[Super Columbian Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Super].[Super Decaf Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Super].[Super French Roast Coffee] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Super].[Super Regular Coffee] [Product].[Drink].[Beverages].[Pure Juice Beverages] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Excellent] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Excellent].[Excellent Apple Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Excellent].[Excellent Berry Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Excellent].[Excellent Cranberry Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Excellent].[Excellent Orange Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Fabulous] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Fabulous].[Fabulous Apple Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Fabulous].[Fabulous Berry Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Fabulous].[Fabulous Cranberry Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Fabulous].[Fabulous Orange Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Skinner] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Skinner].[Skinner Apple Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Skinner].[Skinner Berry Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Skinner].[Skinner Cranberry Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Skinner].[Skinner Orange Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Token] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Token].[Token Apple Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Token].[Token Berry Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Token].[Token Cranberry Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Token].[Token Orange Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Washington] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Washington].[Washington Apple Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Washington].[Washington Berry Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Washington].[Washington Cranberry Juice] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Washington].[Washington Orange Juice] [Product].[Drink].[Dairy] [Product].[Drink].[Dairy].[Dairy] [Product].[Drink].[Dairy].[Dairy].[Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Booker] [Product].[Drink].[Dairy].[Dairy].[Milk].[Booker].[Booker 1% Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Booker].[Booker 2% Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Booker].[Booker Buttermilk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Booker].[Booker Chocolate Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Booker].[Booker Whole Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Carlson] [Product].[Drink].[Dairy].[Dairy].[Milk].[Carlson].[Carlson 1% Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Carlson].[Carlson 2% Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Carlson].[Carlson Buttermilk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Carlson].[Carlson Chocolate Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Carlson].[Carlson Whole Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Club] [Product].[Drink].[Dairy].[Dairy].[Milk].[Club].[Club 1% Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Club].[Club 2% Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Club].[Club Buttermilk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Club].[Club Chocolate Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Club].[Club Whole Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Even Better] [Product].[Drink].[Dairy].[Dairy].[Milk].[Even Better].[Even Better 1% Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Even Better].[Even Better 2% Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Even Better].[Even Better Buttermilk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Even Better].[Even Better Chocolate Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Even Better].[Even Better Whole Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Gorilla] [Product].[Drink].[Dairy].[Dairy].[Milk].[Gorilla].[Gorilla 1% Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Gorilla].[Gorilla 2% Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Gorilla].[Gorilla Buttermilk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Gorilla].[Gorilla Chocolate Milk] [Product].[Drink].[Dairy].[Dairy].[Milk].[Gorilla].[Gorilla Whole Milk] [Product].[Food] [Product].[Food].[Baked Goods] [Product].[Food].[Baked Goods].[Bread] [Product].[Food].[Baked Goods].[Bread].[Bagels] [Product].[Food].[Baked Goods].[Bread].[Bagels].[Colony] [Product].[Food].[Baked Goods].[Bread].[Bagels].[Colony].[Colony Bagels] [Product].[Food].[Baked Goods].[Bread].[Bagels].[Fantastic] [Product].[Food].[Baked Goods].[Bread].[Bagels].[Fantastic].[Fantastic Bagels] [Product].[Food].[Baked Goods].[Bread].[Bagels].[Great] [Product].[Food].[Baked Goods].[Bread].[Bagels].[Great].[Great Bagels] [Product].[Food].[Baked Goods].[Bread].[Bagels].[Modell] [Product].[Food].[Baked Goods].[Bread].[Bagels].[Modell].[Modell Bagels] [Product].[Food].[Baked Goods].[Bread].[Bagels].[Sphinx] [Product].[Food].[Baked Goods].[Bread].[Bagels].[Sphinx].[Sphinx Bagels] [Product].[Food].[Baked Goods].[Bread].[Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Colony] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Colony].[Colony Blueberry Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Colony].[Colony Cranberry Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Colony].[Colony English Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Colony].[Colony Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Fantastic] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Fantastic].[Fantastic Blueberry Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Fantastic].[Fantastic Cranberry Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Fantastic].[Fantastic English Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Fantastic].[Fantastic Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Great] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Great].[Great Blueberry Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Great].[Great Cranberry Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Great].[Great English Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Great].[Great Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Modell] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Modell].[Modell Blueberry Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Modell].[Modell Cranberry Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Modell].[Modell English Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Modell].[Modell Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Sphinx] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Sphinx].[Sphinx Blueberry Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Sphinx].[Sphinx Cranberry Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Sphinx].[Sphinx English Muffins] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Sphinx].[Sphinx Muffins] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Colony] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Colony].[Colony Pumpernickel Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Colony].[Colony Rye Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Colony].[Colony Wheat Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Colony].[Colony White Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Fantastic] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Fantastic].[Fantastic Pumpernickel Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Fantastic].[Fantastic Rye Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Fantastic].[Fantastic Wheat Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Fantastic].[Fantastic White Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Great] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Great].[Great Pumpernickel Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Great].[Great Rye Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Great].[Great Wheat Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Great].[Great White Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Modell] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Modell].[Modell Pumpernickel Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Modell].[Modell Rye Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Modell].[Modell Wheat Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Modell].[Modell White Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Sphinx] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Sphinx].[Sphinx Pumpernickel Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Sphinx].[Sphinx Rye Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Sphinx].[Sphinx Wheat Bread] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Sphinx].[Sphinx White Bread] [Product].[Food].[Baking Goods] [Product].[Food].[Baking Goods].[Baking Goods] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[BBB Best] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[BBB Best].[BBB Best Canola Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[BBB Best].[BBB Best Corn Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[BBB Best].[BBB Best Sesame Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[BBB Best].[BBB Best Vegetable Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[CDR] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[CDR].[CDR Canola Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[CDR].[CDR Corn Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[CDR].[CDR Sesame Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[CDR].[CDR Vegetable Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Landslide] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Landslide].[Landslide Canola Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Landslide].[Landslide Corn Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Landslide].[Landslide Sesame Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Landslide].[Landslide Vegetable Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Plato] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Plato].[Plato Canola Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Plato].[Plato Corn Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Plato].[Plato Sesame Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Plato].[Plato Vegetable Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Super] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Super].[Super Canola Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Super].[Super Corn Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Super].[Super Sesame Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Super].[Super Vegetable Oil] [Product].[Food].[Baking Goods].[Baking Goods].[Sauces] [Product].[Food].[Baking Goods].[Baking Goods].[Sauces].[BBB Best] [Product].[Food].[Baking Goods].[Baking Goods].[Sauces].[BBB Best].[BBB Best Tomato Sauce] [Product].[Food].[Baking Goods].[Baking Goods].[Sauces].[CDR] [Product].[Food].[Baking Goods].[Baking Goods].[Sauces].[CDR].[CDR Tomato Sauce] [Product].[Food].[Baking Goods].[Baking Goods].[Sauces].[Landslide] [Product].[Food].[Baking Goods].[Baking Goods].[Sauces].[Landslide].[Landslide Tomato Sauce] [Product].[Food].[Baking Goods].[Baking Goods].[Sauces].[Plato] [Product].[Food].[Baking Goods].[Baking Goods].[Sauces].[Plato].[Plato Tomato Sauce] [Product].[Food].[Baking Goods].[Baking Goods].[Sauces].[Super] [Product].[Food].[Baking Goods].[Baking Goods].[Sauces].[Super].[Super Tomato Sauce] [Product].[Food].[Baking Goods].[Baking Goods].[Spices] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Oregano] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Pepper] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best].[BBB Best Salt] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[CDR] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[CDR].[CDR Oregano] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[CDR].[CDR Pepper] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[CDR].[CDR Salt] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[Landslide] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[Landslide].[Landslide Oregano] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[Landslide].[Landslide Pepper] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[Landslide].[Landslide Salt] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[Plato] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[Plato].[Plato Oregano] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[Plato].[Plato Pepper] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[Plato].[Plato Salt] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[Super] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[Super].[Super Oregano] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[Super].[Super Pepper] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[Super].[Super Salt] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[BBB Best] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[BBB Best].[BBB Best Brown Sugar] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[BBB Best].[BBB Best White Sugar] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[CDR] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[CDR].[CDR Brown Sugar] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[CDR].[CDR White Sugar] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[Landslide] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[Landslide].[Landslide Brown Sugar] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[Landslide].[Landslide White Sugar] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[Plato] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[Plato].[Plato Brown Sugar] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[Plato].[Plato White Sugar] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[Super] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[Super].[Super Brown Sugar] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[Super].[Super White Sugar] [Product].[Food].[Baking Goods].[Jams and Jellies] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[BBB Best] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[BBB Best].[BBB Best Apple Jam] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[BBB Best].[BBB Best Grape Jam] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[BBB Best].[BBB Best Strawberry Jam] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[CDR] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[CDR].[CDR Apple Jam] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[CDR].[CDR Grape Jam] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[CDR].[CDR Strawberry Jam] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[Landslide] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[Landslide].[Landslide Apple Jam] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[Landslide].[Landslide Grape Jam] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[Landslide].[Landslide Strawberry Jam] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[Plato] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[Plato].[Plato Apple Jam] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[Plato].[Plato Grape Jam] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[Plato].[Plato Strawberry Jam] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[Super] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[Super].[Super Apple Jam] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[Super].[Super Grape Jam] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[Super].[Super Strawberry Jam] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[BBB Best] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[BBB Best].[BBB Best Apple Jelly] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[BBB Best].[BBB Best Grape Jelly] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[BBB Best].[BBB Best Strawberry Jelly] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[CDR] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[CDR].[CDR Apple Jelly] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[CDR].[CDR Grape Jelly] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[CDR].[CDR Strawberry Jelly] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[Landslide] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[Landslide].[Landslide Apple Jelly] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[Landslide].[Landslide Grape Jelly] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[Landslide].[Landslide Strawberry Jelly] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[Plato] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[Plato].[Plato Apple Jelly] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[Plato].[Plato Grape Jelly] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[Plato].[Plato Strawberry Jelly] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[Super] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[Super].[Super Apple Jelly] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[Super].[Super Grape Jelly] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[Super].[Super Strawberry Jelly] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[BBB Best] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[BBB Best].[BBB Best Chunky Peanut Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[BBB Best].[BBB Best Creamy Peanut Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[BBB Best].[BBB Best Extra Chunky Peanut Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[CDR] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[CDR].[CDR Chunky Peanut Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[CDR].[CDR Creamy Peanut Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[CDR].[CDR Extra Chunky Peanut Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[Landslide] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[Landslide].[Landslide Chunky Peanut Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[Landslide].[Landslide Creamy Peanut Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[Landslide].[Landslide Extra Chunky Peanut Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[Plato] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[Plato].[Plato Chunky Peanut Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[Plato].[Plato Creamy Peanut Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[Plato].[Plato Extra Chunky Peanut Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[Super] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[Super].[Super Chunky Peanut Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[Super].[Super Creamy Peanut Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[Super].[Super Extra Chunky Peanut Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[BBB Best] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[BBB Best].[BBB Best Apple Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[BBB Best].[BBB Best Apple Preserves] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[BBB Best].[BBB Best Grape Preserves] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[BBB Best].[BBB Best Low Fat Apple Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[BBB Best].[BBB Best Strawberry Preserves] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[CDR] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[CDR].[CDR Apple Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[CDR].[CDR Apple Preserves] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[CDR].[CDR Grape Preserves] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[CDR].[CDR Low Fat Apple Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[CDR].[CDR Strawberry Preserves] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Landslide] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Landslide].[Landslide Apple Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Landslide].[Landslide Apple Preserves] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Landslide].[Landslide Grape Preserves] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Landslide].[Landslide Low Fat Apple Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Landslide].[Landslide Strawberry Preserves] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Plato] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Plato].[Plato Apple Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Plato].[Plato Apple Preserves] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Plato].[Plato Grape Preserves] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Plato].[Plato Low Fat Apple Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Plato].[Plato Strawberry Preserves] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Super] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Super].[Super Apple Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Super].[Super Apple Preserves] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Super].[Super Grape Preserves] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Super].[Super Low Fat Apple Butter] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Super].[Super Strawberry Preserves] [Product].[Food].[Breakfast Foods] [Product].[Food].[Breakfast Foods].[Breakfast Foods] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Best] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Best].[Best Corn Puffs] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Best].[Best Grits] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Best].[Best Oatmeal] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Best].[Best Wheat Puffs] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Jeffers] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Jeffers].[Jeffers Corn Puffs] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Jeffers].[Jeffers Grits] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Jeffers].[Jeffers Oatmeal] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Jeffers].[Jeffers Wheat Puffs] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Johnson] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Johnson].[Johnson Corn Puffs] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Johnson].[Johnson Grits] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Johnson].[Johnson Oatmeal] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Johnson].[Johnson Wheat Puffs] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Radius] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Radius].[Radius Corn Puffs] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Radius].[Radius Grits] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Radius].[Radius Oatmeal] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Radius].[Radius Wheat Puffs] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Special] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Special].[Special Corn Puffs] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Special].[Special Grits] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Special].[Special Oatmeal] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Special].[Special Wheat Puffs] [Product].[Food].[Canned Foods] [Product].[Food].[Canned Foods].[Canned Anchovies] [Product].[Food].[Canned Foods].[Canned Anchovies].[Anchovies] [Product].[Food].[Canned Foods].[Canned Anchovies].[Anchovies].[Better] [Product].[Food].[Canned Foods].[Canned Anchovies].[Anchovies].[Better].[Better Fancy Canned Anchovies] [Product].[Food].[Canned Foods].[Canned Anchovies].[Anchovies].[Blue Label] [Product].[Food].[Canned Foods].[Canned Anchovies].[Anchovies].[Blue Label].[Blue Label Fancy Canned Anchovies] [Product].[Food].[Canned Foods].[Canned Anchovies].[Anchovies].[Bravo] [Product].[Food].[Canned Foods].[Canned Anchovies].[Anchovies].[Bravo].[Bravo Fancy Canned Anchovies] [Product].[Food].[Canned Foods].[Canned Anchovies].[Anchovies].[Just Right] [Product].[Food].[Canned Foods].[Canned Anchovies].[Anchovies].[Just Right].[Just Right Fancy Canned Anchovies] [Product].[Food].[Canned Foods].[Canned Anchovies].[Anchovies].[Pleasant] [Product].[Food].[Canned Foods].[Canned Anchovies].[Anchovies].[Pleasant].[Pleasant Fancy Canned Anchovies] [Product].[Food].[Canned Foods].[Canned Clams] [Product].[Food].[Canned Foods].[Canned Clams].[Clams] [Product].[Food].[Canned Foods].[Canned Clams].[Clams].[Better] [Product].[Food].[Canned Foods].[Canned Clams].[Clams].[Better].[Better Fancy Canned Clams] [Product].[Food].[Canned Foods].[Canned Clams].[Clams].[Blue Label] [Product].[Food].[Canned Foods].[Canned Clams].[Clams].[Blue Label].[Blue Label Fancy Canned Clams] [Product].[Food].[Canned Foods].[Canned Clams].[Clams].[Bravo] [Product].[Food].[Canned Foods].[Canned Clams].[Clams].[Bravo].[Bravo Fancy Canned Clams] [Product].[Food].[Canned Foods].[Canned Clams].[Clams].[Just Right] [Product].[Food].[Canned Foods].[Canned Clams].[Clams].[Just Right].[Just Right Fancy Canned Clams] [Product].[Food].[Canned Foods].[Canned Clams].[Clams].[Pleasant] [Product].[Food].[Canned Foods].[Canned Clams].[Clams].[Pleasant].[Pleasant Fancy Canned Clams] [Product].[Food].[Canned Foods].[Canned Oysters] [Product].[Food].[Canned Foods].[Canned Oysters].[Oysters] [Product].[Food].[Canned Foods].[Canned Oysters].[Oysters].[Better] [Product].[Food].[Canned Foods].[Canned Oysters].[Oysters].[Better].[Better Fancy Canned Oysters] [Product].[Food].[Canned Foods].[Canned Oysters].[Oysters].[Blue Label] [Product].[Food].[Canned Foods].[Canned Oysters].[Oysters].[Blue Label].[Blue Label Fancy Canned Oysters] [Product].[Food].[Canned Foods].[Canned Oysters].[Oysters].[Bravo] [Product].[Food].[Canned Foods].[Canned Oysters].[Oysters].[Bravo].[Bravo Fancy Canned Oysters] [Product].[Food].[Canned Foods].[Canned Oysters].[Oysters].[Just Right] [Product].[Food].[Canned Foods].[Canned Oysters].[Oysters].[Just Right].[Just Right Fancy Canned Oysters] [Product].[Food].[Canned Foods].[Canned Oysters].[Oysters].[Pleasant] [Product].[Food].[Canned Foods].[Canned Oysters].[Oysters].[Pleasant].[Pleasant Fancy Canned Oysters] [Product].[Food].[Canned Foods].[Canned Sardines] [Product].[Food].[Canned Foods].[Canned Sardines].[Sardines] [Product].[Food].[Canned Foods].[Canned Sardines].[Sardines].[Better] [Product].[Food].[Canned Foods].[Canned Sardines].[Sardines].[Better].[Better Fancy Canned Sardines] [Product].[Food].[Canned Foods].[Canned Sardines].[Sardines].[Blue Label] [Product].[Food].[Canned Foods].[Canned Sardines].[Sardines].[Blue Label].[Blue Label Fancy Canned Sardines] [Product].[Food].[Canned Foods].[Canned Sardines].[Sardines].[Bravo] [Product].[Food].[Canned Foods].[Canned Sardines].[Sardines].[Bravo].[Bravo Fancy Canned Sardines] [Product].[Food].[Canned Foods].[Canned Sardines].[Sardines].[Just Right] [Product].[Food].[Canned Foods].[Canned Sardines].[Sardines].[Just Right].[Just Right Fancy Canned Sardines] [Product].[Food].[Canned Foods].[Canned Sardines].[Sardines].[Pleasant] [Product].[Food].[Canned Foods].[Canned Sardines].[Sardines].[Pleasant].[Pleasant Fancy Canned Sardines] [Product].[Food].[Canned Foods].[Canned Shrimp] [Product].[Food].[Canned Foods].[Canned Shrimp].[Shrimp] [Product].[Food].[Canned Foods].[Canned Shrimp].[Shrimp].[Better] [Product].[Food].[Canned Foods].[Canned Shrimp].[Shrimp].[Better].[Better Large Canned Shrimp] [Product].[Food].[Canned Foods].[Canned Shrimp].[Shrimp].[Blue Label] [Product].[Food].[Canned Foods].[Canned Shrimp].[Shrimp].[Blue Label].[Blue Label Large Canned Shrimp] [Product].[Food].[Canned Foods].[Canned Shrimp].[Shrimp].[Bravo] [Product].[Food].[Canned Foods].[Canned Shrimp].[Shrimp].[Bravo].[Bravo Large Canned Shrimp] [Product].[Food].[Canned Foods].[Canned Shrimp].[Shrimp].[Just Right] [Product].[Food].[Canned Foods].[Canned Shrimp].[Shrimp].[Just Right].[Just Right Large Canned Shrimp] [Product].[Food].[Canned Foods].[Canned Shrimp].[Shrimp].[Pleasant] [Product].[Food].[Canned Foods].[Canned Shrimp].[Shrimp].[Pleasant].[Pleasant Large Canned Shrimp] [Product].[Food].[Canned Foods].[Canned Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Better] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Better].[Better Beef Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Better].[Better Chicken Noodle Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Better].[Better Chicken Ramen Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Better].[Better Chicken Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Better].[Better Noodle Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Better].[Better Regular Ramen Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Better].[Better Rice Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Better].[Better Turkey Noodle Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Better].[Better Vegetable Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Blue Label] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Blue Label].[Blue Label Beef Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Blue Label].[Blue Label Chicken Noodle Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Blue Label].[Blue Label Chicken Ramen Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Blue Label].[Blue Label Chicken Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Blue Label].[Blue Label Noodle Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Blue Label].[Blue Label Regular Ramen Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Blue Label].[Blue Label Rice Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Blue Label].[Blue Label Turkey Noodle Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Blue Label].[Blue Label Vegetable Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Bravo] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Bravo].[Bravo Beef Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Bravo].[Bravo Chicken Noodle Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Bravo].[Bravo Chicken Ramen Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Bravo].[Bravo Chicken Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Bravo].[Bravo Noodle Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Bravo].[Bravo Regular Ramen Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Bravo].[Bravo Rice Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Bravo].[Bravo Turkey Noodle Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Bravo].[Bravo Vegetable Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Just Right] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Just Right].[Just Right Beef Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Just Right].[Just Right Chicken Noodle Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Just Right].[Just Right Chicken Ramen Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Just Right].[Just Right Chicken Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Just Right].[Just Right Noodle Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Just Right].[Just Right Regular Ramen Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Just Right].[Just Right Rice Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Just Right].[Just Right Turkey Noodle Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Just Right].[Just Right Vegetable Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Pleasant] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Pleasant].[Pleasant Beef Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Pleasant].[Pleasant Chicken Noodle Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Pleasant].[Pleasant Chicken Ramen Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Pleasant].[Pleasant Chicken Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Pleasant].[Pleasant Noodle Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Pleasant].[Pleasant Regular Ramen Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Pleasant].[Pleasant Rice Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Pleasant].[Pleasant Turkey Noodle Soup] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Pleasant].[Pleasant Vegetable Soup] [Product].[Food].[Canned Foods].[Canned Tuna] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Better] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Better].[Better Canned Tuna in Oil] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Better].[Better Canned Tuna in Water] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Blue Label] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Blue Label].[Blue Label Canned Tuna in Oil] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Blue Label].[Blue Label Canned Tuna in Water] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Bravo] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Bravo].[Bravo Canned Tuna in Oil] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Bravo].[Bravo Canned Tuna in Water] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Just Right] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Just Right].[Just Right Canned Tuna in Oil] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Just Right].[Just Right Canned Tuna in Water] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Pleasant] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Pleasant].[Pleasant Canned Tuna in Oil] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Pleasant].[Pleasant Canned Tuna in Water] [Product].[Food].[Canned Foods].[Vegetables] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Better] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Better].[Better Canned Beets] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Better].[Better Canned Peas] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Better].[Better Canned String Beans] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Better].[Better Canned Tomatos] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Better].[Better Canned Yams] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Better].[Better Creamed Corn] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Blue Label] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Blue Label].[Blue Label Canned Beets] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Blue Label].[Blue Label Canned Peas] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Blue Label].[Blue Label Canned String Beans] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Blue Label].[Blue Label Canned Tomatos] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Blue Label].[Blue Label Canned Yams] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Blue Label].[Blue Label Creamed Corn] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Bravo] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Bravo].[Bravo Canned Beets] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Bravo].[Bravo Canned Peas] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Bravo].[Bravo Canned String Beans] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Bravo].[Bravo Canned Tomatos] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Bravo].[Bravo Canned Yams] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Bravo].[Bravo Creamed Corn] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Just Right] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Just Right].[Just Right Canned Beets] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Just Right].[Just Right Canned Peas] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Just Right].[Just Right Canned String Beans] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Just Right].[Just Right Canned Tomatos] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Just Right].[Just Right Canned Yams] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Just Right].[Just Right Creamed Corn] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Pleasant] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Pleasant].[Pleasant Canned Beets] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Pleasant].[Pleasant Canned Peas] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Pleasant].[Pleasant Canned String Beans] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Pleasant].[Pleasant Canned Tomatos] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Pleasant].[Pleasant Canned Yams] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Pleasant].[Pleasant Creamed Corn] [Product].[Food].[Canned Products] [Product].[Food].[Canned Products].[Fruit] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Applause] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Applause].[Applause Canned Mixed Fruit] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Applause].[Applause Canned Peaches] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Big City] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Big City].[Big City Canned Mixed Fruit] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Big City].[Big City Canned Peaches] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Green Ribbon] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Green Ribbon].[Green Ribbon Canned Mixed Fruit] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Green Ribbon].[Green Ribbon Canned Peaches] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Swell] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Swell].[Swell Canned Mixed Fruit] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Swell].[Swell Canned Peaches] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Toucan] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Toucan].[Toucan Canned Mixed Fruit] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Toucan].[Toucan Canned Peaches] [Product].[Food].[Dairy] [Product].[Food].[Dairy].[Dairy] [Product].[Food].[Dairy].[Dairy].[Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Booker] [Product].[Food].[Dairy].[Dairy].[Cheese].[Booker].[Booker Cheese Spread] [Product].[Food].[Dairy].[Dairy].[Cheese].[Booker].[Booker Havarti Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Booker].[Booker Head Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Booker].[Booker Jack Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Booker].[Booker Low Fat String Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Booker].[Booker Mild Cheddar Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Booker].[Booker Muenster Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Booker].[Booker Sharp Cheddar Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Booker].[Booker String Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Carlson] [Product].[Food].[Dairy].[Dairy].[Cheese].[Carlson].[Carlson Cheese Spread] [Product].[Food].[Dairy].[Dairy].[Cheese].[Carlson].[Carlson Havarti Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Carlson].[Carlson Head Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Carlson].[Carlson Jack Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Carlson].[Carlson Low Fat String Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Carlson].[Carlson Mild Cheddar Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Carlson].[Carlson Muenster Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Carlson].[Carlson Sharp Cheddar Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Carlson].[Carlson String Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Club] [Product].[Food].[Dairy].[Dairy].[Cheese].[Club].[Club Cheese Spread] [Product].[Food].[Dairy].[Dairy].[Cheese].[Club].[Club Havarti Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Club].[Club Head Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Club].[Club Jack Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Club].[Club Low Fat String Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Club].[Club Mild Cheddar Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Club].[Club Muenster Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Club].[Club Sharp Cheddar Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Club].[Club String Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Even Better] [Product].[Food].[Dairy].[Dairy].[Cheese].[Even Better].[Even Better Cheese Spread] [Product].[Food].[Dairy].[Dairy].[Cheese].[Even Better].[Even Better Havarti Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Even Better].[Even Better Head Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Even Better].[Even Better Jack Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Even Better].[Even Better Low Fat String Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Even Better].[Even Better Mild Cheddar Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Even Better].[Even Better Muenster Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Even Better].[Even Better Sharp Cheddar Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Even Better].[Even Better String Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Gorilla] [Product].[Food].[Dairy].[Dairy].[Cheese].[Gorilla].[Gorilla Cheese Spread] [Product].[Food].[Dairy].[Dairy].[Cheese].[Gorilla].[Gorilla Havarti Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Gorilla].[Gorilla Head Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Gorilla].[Gorilla Jack Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Gorilla].[Gorilla Low Fat String Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Gorilla].[Gorilla Mild Cheddar Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Gorilla].[Gorilla Muenster Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Gorilla].[Gorilla Sharp Cheddar Cheese] [Product].[Food].[Dairy].[Dairy].[Cheese].[Gorilla].[Gorilla String Cheese] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Booker] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Booker].[Booker Large Curd Cottage Cheese] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Booker].[Booker Low Fat Cottage Cheese] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Carlson] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Carlson].[Carlson Large Curd Cottage Cheese] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Carlson].[Carlson Low Fat Cottage Cheese] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Club] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Club].[Club Large Curd Cottage Cheese] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Club].[Club Low Fat Cottage Cheese] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Even Better] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Even Better].[Even Better Large Curd Cottage Cheese] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Even Better].[Even Better Low Fat Cottage Cheese] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Gorilla] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Gorilla].[Gorilla Large Curd Cottage Cheese] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Gorilla].[Gorilla Low Fat Cottage Cheese] [Product].[Food].[Dairy].[Dairy].[Sour Cream] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Booker] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Booker].[Booker Low Fat Sour Cream] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Booker].[Booker Sour Cream] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Carlson] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Carlson].[Carlson Low Fat Sour Cream] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Carlson].[Carlson Sour Cream] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Club] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Club].[Club Low Fat Sour Cream] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Club].[Club Sour Cream] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Even Better] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Even Better].[Even Better Low Fat Sour Cream] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Even Better].[Even Better Sour Cream] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Gorilla] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Gorilla].[Gorilla Low Fat Sour Cream] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Gorilla].[Gorilla Sour Cream] [Product].[Food].[Dairy].[Dairy].[Yogurt] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Booker] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Booker].[Booker Blueberry Yogurt] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Booker].[Booker Strawberry Yogurt] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Carlson] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Carlson].[Carlson Blueberry Yogurt] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Carlson].[Carlson Strawberry Yogurt] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Club] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Club].[Club Blueberry Yogurt] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Club].[Club Strawberry Yogurt] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Even Better] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Even Better].[Even Better Blueberry Yogurt] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Even Better].[Even Better Strawberry Yogurt] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Gorilla] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Gorilla].[Gorilla Blueberry Yogurt] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Gorilla].[Gorilla Strawberry Yogurt] [Product].[Food].[Deli] [Product].[Food].[Deli].[Meat] [Product].[Food].[Deli].[Meat].[Bologna] [Product].[Food].[Deli].[Meat].[Bologna].[American] [Product].[Food].[Deli].[Meat].[Bologna].[American].[American Beef Bologna] [Product].[Food].[Deli].[Meat].[Bologna].[American].[American Low Fat Bologna] [Product].[Food].[Deli].[Meat].[Bologna].[American].[American Pimento Loaf] [Product].[Food].[Deli].[Meat].[Bologna].[Cutting Edge] [Product].[Food].[Deli].[Meat].[Bologna].[Cutting Edge].[Cutting Edge Beef Bologna] [Product].[Food].[Deli].[Meat].[Bologna].[Cutting Edge].[Cutting Edge Low Fat Bologna] [Product].[Food].[Deli].[Meat].[Bologna].[Cutting Edge].[Cutting Edge Pimento Loaf] [Product].[Food].[Deli].[Meat].[Bologna].[Lake] [Product].[Food].[Deli].[Meat].[Bologna].[Lake].[Lake Beef Bologna] [Product].[Food].[Deli].[Meat].[Bologna].[Lake].[Lake Low Fat Bologna] [Product].[Food].[Deli].[Meat].[Bologna].[Lake].[Lake Pimento Loaf] [Product].[Food].[Deli].[Meat].[Bologna].[Moms] [Product].[Food].[Deli].[Meat].[Bologna].[Moms].[Moms Beef Bologna] [Product].[Food].[Deli].[Meat].[Bologna].[Moms].[Moms Low Fat Bologna] [Product].[Food].[Deli].[Meat].[Bologna].[Moms].[Moms Pimento Loaf] [Product].[Food].[Deli].[Meat].[Bologna].[Red Spade] [Product].[Food].[Deli].[Meat].[Bologna].[Red Spade].[Red Spade Beef Bologna] [Product].[Food].[Deli].[Meat].[Bologna].[Red Spade].[Red Spade Low Fat Bologna] [Product].[Food].[Deli].[Meat].[Bologna].[Red Spade].[Red Spade Pimento Loaf] [Product].[Food].[Deli].[Meat].[Deli Meats] [Product].[Food].[Deli].[Meat].[Deli Meats].[American] [Product].[Food].[Deli].[Meat].[Deli Meats].[American].[American Corned Beef] [Product].[Food].[Deli].[Meat].[Deli Meats].[American].[American Sliced Chicken] [Product].[Food].[Deli].[Meat].[Deli Meats].[American].[American Sliced Ham] [Product].[Food].[Deli].[Meat].[Deli Meats].[American].[American Sliced Turkey] [Product].[Food].[Deli].[Meat].[Deli Meats].[Cutting Edge] [Product].[Food].[Deli].[Meat].[Deli Meats].[Cutting Edge].[Cutting Edge Corned Beef] [Product].[Food].[Deli].[Meat].[Deli Meats].[Cutting Edge].[Cutting Edge Sliced Chicken] [Product].[Food].[Deli].[Meat].[Deli Meats].[Cutting Edge].[Cutting Edge Sliced Ham] [Product].[Food].[Deli].[Meat].[Deli Meats].[Cutting Edge].[Cutting Edge Sliced Turkey] [Product].[Food].[Deli].[Meat].[Deli Meats].[Lake] [Product].[Food].[Deli].[Meat].[Deli Meats].[Lake].[Lake Corned Beef] [Product].[Food].[Deli].[Meat].[Deli Meats].[Lake].[Lake Sliced Chicken] [Product].[Food].[Deli].[Meat].[Deli Meats].[Lake].[Lake Sliced Ham] [Product].[Food].[Deli].[Meat].[Deli Meats].[Lake].[Lake Sliced Turkey] [Product].[Food].[Deli].[Meat].[Deli Meats].[Moms] [Product].[Food].[Deli].[Meat].[Deli Meats].[Moms].[Moms Corned Beef] [Product].[Food].[Deli].[Meat].[Deli Meats].[Moms].[Moms Sliced Chicken] [Product].[Food].[Deli].[Meat].[Deli Meats].[Moms].[Moms Sliced Ham] [Product].[Food].[Deli].[Meat].[Deli Meats].[Moms].[Moms Sliced Turkey] [Product].[Food].[Deli].[Meat].[Deli Meats].[Red Spade] [Product].[Food].[Deli].[Meat].[Deli Meats].[Red Spade].[Red Spade Corned Beef] [Product].[Food].[Deli].[Meat].[Deli Meats].[Red Spade].[Red Spade Sliced Chicken] [Product].[Food].[Deli].[Meat].[Deli Meats].[Red Spade].[Red Spade Sliced Ham] [Product].[Food].[Deli].[Meat].[Deli Meats].[Red Spade].[Red Spade Sliced Turkey] [Product].[Food].[Deli].[Meat].[Fresh Chicken] [Product].[Food].[Deli].[Meat].[Fresh Chicken].[American] [Product].[Food].[Deli].[Meat].[Fresh Chicken].[American].[American Roasted Chicken] [Product].[Food].[Deli].[Meat].[Fresh Chicken].[Cutting Edge] [Product].[Food].[Deli].[Meat].[Fresh Chicken].[Cutting Edge].[Cutting Edge Roasted Chicken] [Product].[Food].[Deli].[Meat].[Fresh Chicken].[Lake] [Product].[Food].[Deli].[Meat].[Fresh Chicken].[Lake].[Lake Roasted Chicken] [Product].[Food].[Deli].[Meat].[Fresh Chicken].[Moms] [Product].[Food].[Deli].[Meat].[Fresh Chicken].[Moms].[Moms Roasted Chicken] [Product].[Food].[Deli].[Meat].[Fresh Chicken].[Red Spade] [Product].[Food].[Deli].[Meat].[Fresh Chicken].[Red Spade].[Red Spade Roasted Chicken] [Product].[Food].[Deli].[Meat].[Hot Dogs] [Product].[Food].[Deli].[Meat].[Hot Dogs].[American] [Product].[Food].[Deli].[Meat].[Hot Dogs].[American].[American Chicken Hot Dogs] [Product].[Food].[Deli].[Meat].[Hot Dogs].[American].[American Foot-Long Hot Dogs] [Product].[Food].[Deli].[Meat].[Hot Dogs].[American].[American Turkey Hot Dogs] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Cutting Edge] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Cutting Edge].[Cutting Edge Chicken Hot Dogs] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Cutting Edge].[Cutting Edge Foot-Long Hot Dogs] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Cutting Edge].[Cutting Edge Turkey Hot Dogs] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Lake] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Lake].[Lake Chicken Hot Dogs] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Lake].[Lake Foot-Long Hot Dogs] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Lake].[Lake Turkey Hot Dogs] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Moms] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Moms].[Moms Chicken Hot Dogs] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Moms].[Moms Foot-Long Hot Dogs] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Moms].[Moms Turkey Hot Dogs] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Red Spade] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Red Spade].[Red Spade Chicken Hot Dogs] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Red Spade].[Red Spade Foot-Long Hot Dogs] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Red Spade].[Red Spade Turkey Hot Dogs] [Product].[Food].[Deli].[Side Dishes] [Product].[Food].[Deli].[Side Dishes].[Deli Salads] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[American] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[American].[American Cole Slaw] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[American].[American Low Fat Cole Slaw] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[American].[American Potato Salad] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Cutting Edge] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Cutting Edge].[Cutting Edge Cole Slaw] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Cutting Edge].[Cutting Edge Low Fat Cole Slaw] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Cutting Edge].[Cutting Edge Potato Salad] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Lake] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Lake].[Lake Cole Slaw] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Lake].[Lake Low Fat Cole Slaw] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Lake].[Lake Potato Salad] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Moms] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Moms].[Moms Cole Slaw] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Moms].[Moms Low Fat Cole Slaw] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Moms].[Moms Potato Salad] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Red Spade] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Red Spade].[Red Spade Cole Slaw] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Red Spade].[Red Spade Low Fat Cole Slaw] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Red Spade].[Red Spade Potato Salad] [Product].[Food].[Eggs] [Product].[Food].[Eggs].[Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[Blue Medal] [Product].[Food].[Eggs].[Eggs].[Eggs].[Blue Medal].[Blue Medal Egg Substitute] [Product].[Food].[Eggs].[Eggs].[Eggs].[Blue Medal].[Blue Medal Large Brown Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[Blue Medal].[Blue Medal Large Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[Blue Medal].[Blue Medal Small Brown Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[Blue Medal].[Blue Medal Small Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[Giant] [Product].[Food].[Eggs].[Eggs].[Eggs].[Giant].[Giant Egg Substitute] [Product].[Food].[Eggs].[Eggs].[Eggs].[Giant].[Giant Large Brown Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[Giant].[Giant Large Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[Giant].[Giant Small Brown Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[Giant].[Giant Small Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[Jumbo] [Product].[Food].[Eggs].[Eggs].[Eggs].[Jumbo].[Jumbo Egg Substitute] [Product].[Food].[Eggs].[Eggs].[Eggs].[Jumbo].[Jumbo Large Brown Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[Jumbo].[Jumbo Large Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[Jumbo].[Jumbo Small Brown Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[Jumbo].[Jumbo Small Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[National] [Product].[Food].[Eggs].[Eggs].[Eggs].[National].[National Egg Substitute] [Product].[Food].[Eggs].[Eggs].[Eggs].[National].[National Large Brown Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[National].[National Large Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[National].[National Small Brown Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[National].[National Small Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[Urban] [Product].[Food].[Eggs].[Eggs].[Eggs].[Urban].[Urban Egg Substitute] [Product].[Food].[Eggs].[Eggs].[Eggs].[Urban].[Urban Large Brown Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[Urban].[Urban Large Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[Urban].[Urban Small Brown Eggs] [Product].[Food].[Eggs].[Eggs].[Eggs].[Urban].[Urban Small Eggs] [Product].[Food].[Frozen Foods] [Product].[Food].[Frozen Foods].[Breakfast Foods] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[Big Time] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[Big Time].[Big Time Pancake Mix] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[Carrington] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[Carrington].[Carrington Pancake Mix] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[Golden] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[Golden].[Golden Pancake Mix] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[Imagine] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[Imagine].[Imagine Pancake Mix] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[PigTail] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[PigTail].[PigTail Pancake Mix] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancakes] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancakes].[Big Time] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancakes].[Big Time].[Big Time Frozen Pancakes] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancakes].[Carrington] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancakes].[Carrington].[Carrington Frozen Pancakes] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancakes].[Golden] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancakes].[Golden].[Golden Frozen Pancakes] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancakes].[Imagine] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancakes].[Imagine].[Imagine Frozen Pancakes] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancakes].[PigTail] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancakes].[PigTail].[PigTail Frozen Pancakes] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Big Time] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Big Time].[Big Time Apple Cinnamon Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Big Time].[Big Time Blueberry Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Big Time].[Big Time Low Fat Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Big Time].[Big Time Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Carrington] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Carrington].[Carrington Apple Cinnamon Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Carrington].[Carrington Blueberry Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Carrington].[Carrington Low Fat Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Carrington].[Carrington Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Golden] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Golden].[Golden Apple Cinnamon Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Golden].[Golden Blueberry Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Golden].[Golden Low Fat Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Golden].[Golden Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Imagine] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Imagine].[Imagine Apple Cinnamon Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Imagine].[Imagine Blueberry Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Imagine].[Imagine Low Fat Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Imagine].[Imagine Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[PigTail] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[PigTail].[PigTail Apple Cinnamon Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[PigTail].[PigTail Blueberry Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[PigTail].[PigTail Low Fat Waffles] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[PigTail].[PigTail Waffles] [Product].[Food].[Frozen Foods].[Frozen Desserts] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Big Time] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Big Time].[Big Time Ice Cream] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Big Time].[Big Time Ice Cream Sandwich] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Big Time].[Big Time Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Carrington] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Carrington].[Carrington Ice Cream] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Carrington].[Carrington Ice Cream Sandwich] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Carrington].[Carrington Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Golden] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Golden].[Golden Ice Cream] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Golden].[Golden Ice Cream Sandwich] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Golden].[Golden Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Imagine] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Imagine].[Imagine Ice Cream] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Imagine].[Imagine Ice Cream Sandwich] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Imagine].[Imagine Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[PigTail] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[PigTail].[PigTail Ice Cream] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[PigTail].[PigTail Ice Cream Sandwich] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[PigTail].[PigTail Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Big Time] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Big Time].[Big Time Grape Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Big Time].[Big Time Lemon Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Big Time].[Big Time Lime Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Big Time].[Big Time Orange Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Carrington] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Carrington].[Carrington Grape Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Carrington].[Carrington Lemon Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Carrington].[Carrington Lime Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Carrington].[Carrington Orange Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Golden] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Golden].[Golden Grape Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Golden].[Golden Lemon Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Golden].[Golden Lime Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Golden].[Golden Orange Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Imagine] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Imagine].[Imagine Grape Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Imagine].[Imagine Lemon Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Imagine].[Imagine Lime Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Imagine].[Imagine Orange Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[PigTail] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[PigTail].[PigTail Grape Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[PigTail].[PigTail Lemon Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[PigTail].[PigTail Lime Popsicles] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[PigTail].[PigTail Orange Popsicles] [Product].[Food].[Frozen Foods].[Frozen Entrees] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Big Time] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Big Time].[Big Time Beef TV Dinner] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Big Time].[Big Time Chicken TV Dinner] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Big Time].[Big Time Turkey TV Dinner] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Carrington] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Carrington].[Carrington Beef TV Dinner] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Carrington].[Carrington Chicken TV Dinner] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Carrington].[Carrington Turkey TV Dinner] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Golden] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Golden].[Golden Beef TV Dinner] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Golden].[Golden Chicken TV Dinner] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Golden].[Golden Turkey TV Dinner] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Imagine] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Imagine].[Imagine Beef TV Dinner] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Imagine].[Imagine Chicken TV Dinner] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Imagine].[Imagine Turkey TV Dinner] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[PigTail] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[PigTail].[PigTail Beef TV Dinner] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[PigTail].[PigTail Chicken TV Dinner] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[PigTail].[PigTail Turkey TV Dinner] [Product].[Food].[Frozen Foods].[Meat] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Big Time] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Big Time].[Big Time Frozen Chicken Breast] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Big Time].[Big Time Frozen Chicken Thighs] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Big Time].[Big Time Frozen Chicken Wings] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Carrington] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Carrington].[Carrington Frozen Chicken Breast] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Carrington].[Carrington Frozen Chicken Thighs] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Carrington].[Carrington Frozen Chicken Wings] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Golden] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Golden].[Golden Frozen Chicken Breast] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Golden].[Golden Frozen Chicken Thighs] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Golden].[Golden Frozen Chicken Wings] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Imagine] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Imagine].[Imagine Frozen Chicken Breast] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Imagine].[Imagine Frozen Chicken Thighs] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Imagine].[Imagine Frozen Chicken Wings] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[PigTail] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[PigTail].[PigTail Frozen Chicken Breast] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[PigTail].[PigTail Frozen Chicken Thighs] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[PigTail].[PigTail Frozen Chicken Wings] [Product].[Food].[Frozen Foods].[Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Big Time] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Big Time].[Big Time Frozen Cheese Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Big Time].[Big Time Frozen Mushroom Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Big Time].[Big Time Frozen Pepperoni Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Big Time].[Big Time Frozen Sausage Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Carrington] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Carrington].[Carrington Frozen Cheese Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Carrington].[Carrington Frozen Mushroom Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Carrington].[Carrington Frozen Pepperoni Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Carrington].[Carrington Frozen Sausage Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Golden] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Golden].[Golden Frozen Cheese Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Golden].[Golden Frozen Mushroom Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Golden].[Golden Frozen Pepperoni Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Golden].[Golden Frozen Sausage Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Imagine] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Imagine].[Imagine Frozen Cheese Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Imagine].[Imagine Frozen Mushroom Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Imagine].[Imagine Frozen Pepperoni Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Imagine].[Imagine Frozen Sausage Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[PigTail] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[PigTail].[PigTail Frozen Cheese Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[PigTail].[PigTail Frozen Mushroom Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[PigTail].[PigTail Frozen Pepperoni Pizza] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[PigTail].[PigTail Frozen Sausage Pizza] [Product].[Food].[Frozen Foods].[Vegetables] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Big Time] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Big Time].[Big Time Fajita French Fries] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Big Time].[Big Time Home Style French Fries] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Big Time].[Big Time Low Fat French Fries] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Carrington] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Carrington].[Carrington Fajita French Fries] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Carrington].[Carrington Home Style French Fries] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Carrington].[Carrington Low Fat French Fries] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Golden] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Golden].[Golden Fajita French Fries] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Golden].[Golden Home Style French Fries] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Golden].[Golden Low Fat French Fries] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Imagine] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Imagine].[Imagine Fajita French Fries] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Imagine].[Imagine Home Style French Fries] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Imagine].[Imagine Low Fat French Fries] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[PigTail] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[PigTail].[PigTail Fajita French Fries] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[PigTail].[PigTail Home Style French Fries] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[PigTail].[PigTail Low Fat French Fries] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Big Time] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Big Time].[Big Time Frozen Broccoli] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Big Time].[Big Time Frozen Carrots] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Big Time].[Big Time Frozen Cauliflower] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Big Time].[Big Time Frozen Corn] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Big Time].[Big Time Frozen Peas] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Carrington] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Carrington].[Carrington Frozen Broccoli] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Carrington].[Carrington Frozen Carrots] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Carrington].[Carrington Frozen Cauliflower] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Carrington].[Carrington Frozen Corn] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Carrington].[Carrington Frozen Peas] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Golden] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Golden].[Golden Frozen Broccoli] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Golden].[Golden Frozen Carrots] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Golden].[Golden Frozen Cauliflower] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Golden].[Golden Frozen Corn] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Golden].[Golden Frozen Peas] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Imagine] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Imagine].[Imagine Frozen Broccoli] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Imagine].[Imagine Frozen Carrots] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Imagine].[Imagine Frozen Cauliflower] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Imagine].[Imagine Frozen Corn] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Imagine].[Imagine Frozen Peas] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[PigTail] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[PigTail].[PigTail Frozen Broccoli] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[PigTail].[PigTail Frozen Carrots] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[PigTail].[PigTail Frozen Cauliflower] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[PigTail].[PigTail Frozen Corn] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[PigTail].[PigTail Frozen Peas] [Product].[Food].[Meat] [Product].[Food].[Meat].[Meat] [Product].[Food].[Meat].[Meat].[Hamburger] [Product].[Food].[Meat].[Meat].[Hamburger].[Footnote] [Product].[Food].[Meat].[Meat].[Hamburger].[Footnote].[Footnote Extra Lean Hamburger] [Product].[Food].[Meat].[Meat].[Hamburger].[Footnote].[Footnote Seasoned Hamburger] [Product].[Food].[Meat].[Meat].[Hamburger].[Genteel] [Product].[Food].[Meat].[Meat].[Hamburger].[Genteel].[Genteel Extra Lean Hamburger] [Product].[Food].[Meat].[Meat].[Hamburger].[Genteel].[Genteel Seasoned Hamburger] [Product].[Food].[Meat].[Meat].[Hamburger].[Gerolli] [Product].[Food].[Meat].[Meat].[Hamburger].[Gerolli].[Gerolli Extra Lean Hamburger] [Product].[Food].[Meat].[Meat].[Hamburger].[Gerolli].[Gerolli Seasoned Hamburger] [Product].[Food].[Meat].[Meat].[Hamburger].[Quick] [Product].[Food].[Meat].[Meat].[Hamburger].[Quick].[Quick Extra Lean Hamburger] [Product].[Food].[Meat].[Meat].[Hamburger].[Quick].[Quick Seasoned Hamburger] [Product].[Food].[Meat].[Meat].[Hamburger].[Ship Shape] [Product].[Food].[Meat].[Meat].[Hamburger].[Ship Shape].[Ship Shape Extra Lean Hamburger] [Product].[Food].[Meat].[Meat].[Hamburger].[Ship Shape].[Ship Shape Seasoned Hamburger] [Product].[Food].[Produce] [Product].[Food].[Produce].[Fruit] [Product].[Food].[Produce].[Fruit].[Fresh Fruit] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Ebony] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Ebony].[Ebony Cantelope] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Ebony].[Ebony Fancy Plums] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Ebony].[Ebony Fuji Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Ebony].[Ebony Golden Delcious Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Ebony].[Ebony Honey Dew] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Ebony].[Ebony Lemons] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Ebony].[Ebony Limes] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Ebony].[Ebony Macintosh Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Ebony].[Ebony Mandarin Oranges] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Ebony].[Ebony Oranges] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Ebony].[Ebony Peaches] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Ebony].[Ebony Plums] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Ebony].[Ebony Red Delcious Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Ebony].[Ebony Tangerines] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Hermanos] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Hermanos].[Hermanos Cantelope] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Hermanos].[Hermanos Fancy Plums] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Hermanos].[Hermanos Fuji Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Hermanos].[Hermanos Golden Delcious Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Hermanos].[Hermanos Honey Dew] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Hermanos].[Hermanos Lemons] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Hermanos].[Hermanos Limes] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Hermanos].[Hermanos Macintosh Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Hermanos].[Hermanos Mandarin Oranges] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Hermanos].[Hermanos Oranges] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Hermanos].[Hermanos Peaches] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Hermanos].[Hermanos Plums] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Hermanos].[Hermanos Red Delcious Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Hermanos].[Hermanos Tangerines] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top].[High Top Cantelope] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top].[High Top Fancy Plums] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top].[High Top Fuji Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top].[High Top Golden Delcious Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top].[High Top Honey Dew] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top].[High Top Lemons] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top].[High Top Limes] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top].[High Top Macintosh Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top].[High Top Mandarin Oranges] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top].[High Top Oranges] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top].[High Top Peaches] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top].[High Top Plums] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top].[High Top Red Delcious Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top].[High Top Tangerines] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tell Tale] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tell Tale].[Tell Tale Cantelope] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tell Tale].[Tell Tale Fancy Plums] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tell Tale].[Tell Tale Fuji Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tell Tale].[Tell Tale Golden Delcious Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tell Tale].[Tell Tale Honey Dew] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tell Tale].[Tell Tale Lemons] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tell Tale].[Tell Tale Limes] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tell Tale].[Tell Tale Macintosh Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tell Tale].[Tell Tale Mandarin Oranges] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tell Tale].[Tell Tale Oranges] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tell Tale].[Tell Tale Peaches] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tell Tale].[Tell Tale Plums] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tell Tale].[Tell Tale Red Delcious Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tell Tale].[Tell Tale Tangerines] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tri-State] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tri-State].[Tri-State Cantelope] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tri-State].[Tri-State Fancy Plums] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tri-State].[Tri-State Fuji Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tri-State].[Tri-State Golden Delcious Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tri-State].[Tri-State Honey Dew] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tri-State].[Tri-State Lemons] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tri-State].[Tri-State Limes] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tri-State].[Tri-State Macintosh Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tri-State].[Tri-State Mandarin Oranges] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tri-State].[Tri-State Oranges] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tri-State].[Tri-State Peaches] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tri-State].[Tri-State Plums] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tri-State].[Tri-State Red Delcious Apples] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tri-State].[Tri-State Tangerines] [Product].[Food].[Produce].[Packaged Vegetables] [Product].[Food].[Produce].[Packaged Vegetables].[Tofu] [Product].[Food].[Produce].[Packaged Vegetables].[Tofu].[Ebony] [Product].[Food].[Produce].[Packaged Vegetables].[Tofu].[Ebony].[Ebony Firm Tofu] [Product].[Food].[Produce].[Packaged Vegetables].[Tofu].[Hermanos] [Product].[Food].[Produce].[Packaged Vegetables].[Tofu].[Hermanos].[Hermanos Firm Tofu] [Product].[Food].[Produce].[Packaged Vegetables].[Tofu].[High Top] [Product].[Food].[Produce].[Packaged Vegetables].[Tofu].[High Top].[High Top Firm Tofu] [Product].[Food].[Produce].[Packaged Vegetables].[Tofu].[Tell Tale] [Product].[Food].[Produce].[Packaged Vegetables].[Tofu].[Tell Tale].[Tell Tale Firm Tofu] [Product].[Food].[Produce].[Packaged Vegetables].[Tofu].[Tri-State] [Product].[Food].[Produce].[Packaged Vegetables].[Tofu].[Tri-State].[Tri-State Firm Tofu] [Product].[Food].[Produce].[Specialty] [Product].[Food].[Produce].[Specialty].[Nuts] [Product].[Food].[Produce].[Specialty].[Nuts].[Ebony] [Product].[Food].[Produce].[Specialty].[Nuts].[Ebony].[Ebony Almonds] [Product].[Food].[Produce].[Specialty].[Nuts].[Ebony].[Ebony Canned Peanuts] [Product].[Food].[Produce].[Specialty].[Nuts].[Ebony].[Ebony Mixed Nuts] [Product].[Food].[Produce].[Specialty].[Nuts].[Ebony].[Ebony Party Nuts] [Product].[Food].[Produce].[Specialty].[Nuts].[Ebony].[Ebony Walnuts] [Product].[Food].[Produce].[Specialty].[Nuts].[Hermanos] [Product].[Food].[Produce].[Specialty].[Nuts].[Hermanos].[Hermanos Almonds] [Product].[Food].[Produce].[Specialty].[Nuts].[Hermanos].[Hermanos Canned Peanuts] [Product].[Food].[Produce].[Specialty].[Nuts].[Hermanos].[Hermanos Mixed Nuts] [Product].[Food].[Produce].[Specialty].[Nuts].[Hermanos].[Hermanos Party Nuts] [Product].[Food].[Produce].[Specialty].[Nuts].[Hermanos].[Hermanos Walnuts] [Product].[Food].[Produce].[Specialty].[Nuts].[High Top] [Product].[Food].[Produce].[Specialty].[Nuts].[High Top].[High Top Almonds] [Product].[Food].[Produce].[Specialty].[Nuts].[High Top].[High Top Canned Peanuts] [Product].[Food].[Produce].[Specialty].[Nuts].[High Top].[High Top Mixed Nuts] [Product].[Food].[Produce].[Specialty].[Nuts].[High Top].[High Top Party Nuts] [Product].[Food].[Produce].[Specialty].[Nuts].[High Top].[High Top Walnuts] [Product].[Food].[Produce].[Specialty].[Nuts].[Tell Tale] [Product].[Food].[Produce].[Specialty].[Nuts].[Tell Tale].[Tell Tale Almonds] [Product].[Food].[Produce].[Specialty].[Nuts].[Tell Tale].[Tell Tale Canned Peanuts] [Product].[Food].[Produce].[Specialty].[Nuts].[Tell Tale].[Tell Tale Mixed Nuts] [Product].[Food].[Produce].[Specialty].[Nuts].[Tell Tale].[Tell Tale Party Nuts] [Product].[Food].[Produce].[Specialty].[Nuts].[Tell Tale].[Tell Tale Walnuts] [Product].[Food].[Produce].[Specialty].[Nuts].[Tri-State] [Product].[Food].[Produce].[Specialty].[Nuts].[Tri-State].[Tri-State Almonds] [Product].[Food].[Produce].[Specialty].[Nuts].[Tri-State].[Tri-State Canned Peanuts] [Product].[Food].[Produce].[Specialty].[Nuts].[Tri-State].[Tri-State Mixed Nuts] [Product].[Food].[Produce].[Specialty].[Nuts].[Tri-State].[Tri-State Party Nuts] [Product].[Food].[Produce].[Specialty].[Nuts].[Tri-State].[Tri-State Walnuts] [Product].[Food].[Produce].[Vegetables] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Asparagus] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Baby Onion] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Beets] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Broccoli] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Cauliflower] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Corn on the Cob] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Dried Mushrooms] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Elephant Garlic] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Fresh Lima Beans] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Garlic] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Green Pepper] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Lettuce] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Mushrooms] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony New Potatos] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Onions] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Potatos] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Prepared Salad] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Red Pepper] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Shitake Mushrooms] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Squash] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Summer Squash] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Sweet Onion] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Sweet Peas] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony].[Ebony Tomatos] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Asparagus] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Baby Onion] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Beets] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Broccoli] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Cauliflower] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Corn on the Cob] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Dried Mushrooms] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Elephant Garlic] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Fresh Lima Beans] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Garlic] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Green Pepper] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Lettuce] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Mushrooms] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos New Potatos] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Onions] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Potatos] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Prepared Salad] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Red Pepper] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Shitake Mushrooms] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Squash] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Summer Squash] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Sweet Onion] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Sweet Peas] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos].[Hermanos Tomatos] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Asparagus] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Baby Onion] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Beets] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Broccoli] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Cauliflower] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Corn on the Cob] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Dried Mushrooms] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Elephant Garlic] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Fresh Lima Beans] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Garlic] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Green Pepper] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Lettuce] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Mushrooms] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top New Potatos] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Onions] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Potatos] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Prepared Salad] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Red Pepper] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Shitake Mushrooms] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Squash] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Summer Squash] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Sweet Onion] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Sweet Peas] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top].[High Top Tomatos] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Asparagus] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Baby Onion] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Beets] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Broccoli] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Cauliflower] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Corn on the Cob] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Dried Mushrooms] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Elephant Garlic] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Fresh Lima Beans] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Garlic] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Green Pepper] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Lettuce] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Mushrooms] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale New Potatos] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Onions] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Potatos] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Prepared Salad] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Red Pepper] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Shitake Mushrooms] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Squash] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Summer Squash] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Sweet Onion] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Sweet Peas] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale].[Tell Tale Tomatos] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Asparagus] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Baby Onion] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Beets] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Broccoli] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Cauliflower] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Corn on the Cob] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Dried Mushrooms] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Elephant Garlic] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Fresh Lima Beans] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Garlic] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Green Pepper] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Lettuce] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Mushrooms] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State New Potatos] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Onions] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Potatos] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Prepared Salad] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Red Pepper] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Shitake Mushrooms] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Squash] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Summer Squash] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Sweet Onion] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Sweet Peas] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State].[Tri-State Tomatos] [Product].[Food].[Seafood] [Product].[Food].[Seafood].[Seafood] [Product].[Food].[Seafood].[Seafood].[Fresh Fish] [Product].[Food].[Seafood].[Seafood].[Fresh Fish].[Amigo] [Product].[Food].[Seafood].[Seafood].[Fresh Fish].[Amigo].[Amigo Lox] [Product].[Food].[Seafood].[Seafood].[Fresh Fish].[Curlew] [Product].[Food].[Seafood].[Seafood].[Fresh Fish].[Curlew].[Curlew Lox] [Product].[Food].[Seafood].[Seafood].[Fresh Fish].[Dual City] [Product].[Food].[Seafood].[Seafood].[Fresh Fish].[Dual City].[Dual City Lox] [Product].[Food].[Seafood].[Seafood].[Fresh Fish].[Kiwi] [Product].[Food].[Seafood].[Seafood].[Fresh Fish].[Kiwi].[Kiwi Lox] [Product].[Food].[Seafood].[Seafood].[Fresh Fish].[Tip Top] [Product].[Food].[Seafood].[Seafood].[Fresh Fish].[Tip Top].[Tip Top Lox] [Product].[Food].[Seafood].[Seafood].[Shellfish] [Product].[Food].[Seafood].[Seafood].[Shellfish].[Amigo] [Product].[Food].[Seafood].[Seafood].[Shellfish].[Amigo].[Amigo Scallops] [Product].[Food].[Seafood].[Seafood].[Shellfish].[Curlew] [Product].[Food].[Seafood].[Seafood].[Shellfish].[Curlew].[Curlew Scallops] [Product].[Food].[Seafood].[Seafood].[Shellfish].[Dual City] [Product].[Food].[Seafood].[Seafood].[Shellfish].[Dual City].[Dual City Scallops] [Product].[Food].[Seafood].[Seafood].[Shellfish].[Kiwi] [Product].[Food].[Seafood].[Seafood].[Shellfish].[Kiwi].[Kiwi Scallops] [Product].[Food].[Seafood].[Seafood].[Shellfish].[Tip Top] [Product].[Food].[Seafood].[Seafood].[Shellfish].[Tip Top].[Tip Top Scallops] [Product].[Food].[Snack Foods] [Product].[Food].[Snack Foods].[Snack Foods] [Product].[Food].[Snack Foods].[Snack Foods].[Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Best Choice].[Best Choice BBQ Potato Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Best Choice].[Best Choice Corn Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Best Choice].[Best Choice Low Fat BBQ Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Best Choice].[Best Choice Low Fat Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Best Choice].[Best Choice Potato Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Fast].[Fast BBQ Potato Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Fast].[Fast Corn Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Fast].[Fast Low Fat BBQ Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Fast].[Fast Low Fat Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Fast].[Fast Potato Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Fort West].[Fort West BBQ Potato Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Fort West].[Fort West Corn Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Fort West].[Fort West Low Fat BBQ Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Fort West].[Fort West Low Fat Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Fort West].[Fort West Potato Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Horatio].[Horatio BBQ Potato Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Horatio].[Horatio Corn Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Horatio].[Horatio Low Fat BBQ Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Horatio].[Horatio Low Fat Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Horatio].[Horatio Potato Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Nationeel].[Nationeel BBQ Potato Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Nationeel].[Nationeel Corn Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Nationeel].[Nationeel Low Fat BBQ Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Nationeel].[Nationeel Low Fat Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Nationeel].[Nationeel Potato Chips] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Best Choice].[Best Choice Chocolate Chip Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Best Choice].[Best Choice Frosted Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Best Choice].[Best Choice Fudge Brownies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Best Choice].[Best Choice Fudge Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Best Choice].[Best Choice Graham Crackers] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Best Choice].[Best Choice Lemon Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Best Choice].[Best Choice Low Fat Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Best Choice].[Best Choice Sugar Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fast].[Fast Chocolate Chip Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fast].[Fast Frosted Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fast].[Fast Fudge Brownies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fast].[Fast Fudge Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fast].[Fast Graham Crackers] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fast].[Fast Lemon Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fast].[Fast Low Fat Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fast].[Fast Sugar Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fort West].[Fort West Chocolate Chip Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fort West].[Fort West Frosted Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fort West].[Fort West Fudge Brownies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fort West].[Fort West Fudge Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fort West].[Fort West Graham Crackers] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fort West].[Fort West Lemon Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fort West].[Fort West Low Fat Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fort West].[Fort West Sugar Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Horatio].[Horatio Chocolate Chip Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Horatio].[Horatio Frosted Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Horatio].[Horatio Fudge Brownies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Horatio].[Horatio Fudge Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Horatio].[Horatio Graham Crackers] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Horatio].[Horatio Lemon Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Horatio].[Horatio Low Fat Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Horatio].[Horatio Sugar Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Nationeel].[Nationeel Chocolate Chip Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Nationeel].[Nationeel Frosted Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Nationeel].[Nationeel Fudge Brownies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Nationeel].[Nationeel Fudge Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Nationeel].[Nationeel Graham Crackers] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Nationeel].[Nationeel Lemon Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Nationeel].[Nationeel Low Fat Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Nationeel].[Nationeel Sugar Cookies] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Best Choice].[Best Choice Cheese Crackers] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Best Choice].[Best Choice Sesame Crackers] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Fast].[Fast Cheese Crackers] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Fast].[Fast Sesame Crackers] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Fort West].[Fort West Cheese Crackers] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Fort West].[Fort West Sesame Crackers] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Horatio].[Horatio Cheese Crackers] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Horatio].[Horatio Sesame Crackers] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Nationeel].[Nationeel Cheese Crackers] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Nationeel].[Nationeel Sesame Crackers] [Product].[Food].[Snack Foods].[Snack Foods].[Dips] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Best Choice].[Best Choice Avocado Dip] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Best Choice].[Best Choice Cheese Dip] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Best Choice].[Best Choice Fondue Mix] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Best Choice].[Best Choice Salsa Dip] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Fast].[Fast Avocado Dip] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Fast].[Fast Cheese Dip] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Fast].[Fast Fondue Mix] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Fast].[Fast Salsa Dip] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Fort West].[Fort West Avocado Dip] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Fort West].[Fort West Cheese Dip] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Fort West].[Fort West Fondue Mix] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Fort West].[Fort West Salsa Dip] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Horatio].[Horatio Avocado Dip] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Horatio].[Horatio Cheese Dip] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Horatio].[Horatio Fondue Mix] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Horatio].[Horatio Salsa Dip] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Nationeel].[Nationeel Avocado Dip] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Nationeel].[Nationeel Cheese Dip] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Nationeel].[Nationeel Fondue Mix] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Nationeel].[Nationeel Salsa Dip] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Best Choice].[Best Choice Chocolate Donuts] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Best Choice].[Best Choice Frosted Donuts] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Best Choice].[Best Choice Mini Donuts] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Fast].[Fast Chocolate Donuts] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Fast].[Fast Frosted Donuts] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Fast].[Fast Mini Donuts] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Fort West].[Fort West Chocolate Donuts] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Fort West].[Fort West Frosted Donuts] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Fort West].[Fort West Mini Donuts] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Horatio].[Horatio Chocolate Donuts] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Horatio].[Horatio Frosted Donuts] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Horatio].[Horatio Mini Donuts] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Nationeel].[Nationeel Chocolate Donuts] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Nationeel].[Nationeel Frosted Donuts] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Nationeel].[Nationeel Mini Donuts] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Best Choice].[Best Choice Apple Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Best Choice].[Best Choice Dried Apples] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Best Choice].[Best Choice Dried Apricots] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Best Choice].[Best Choice Dried Dates] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Best Choice].[Best Choice Golden Raisins] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Best Choice].[Best Choice Grape Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Best Choice].[Best Choice Raisins] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Best Choice].[Best Choice Raspberry Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Best Choice].[Best Choice Strawberry Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fast].[Fast Apple Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fast].[Fast Dried Apples] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fast].[Fast Dried Apricots] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fast].[Fast Dried Dates] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fast].[Fast Golden Raisins] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fast].[Fast Grape Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fast].[Fast Raisins] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fast].[Fast Raspberry Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fast].[Fast Strawberry Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fort West].[Fort West Apple Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fort West].[Fort West Dried Apples] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fort West].[Fort West Dried Apricots] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fort West].[Fort West Dried Dates] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fort West].[Fort West Golden Raisins] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fort West].[Fort West Grape Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fort West].[Fort West Raisins] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fort West].[Fort West Raspberry Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fort West].[Fort West Strawberry Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Horatio].[Horatio Apple Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Horatio].[Horatio Dried Apples] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Horatio].[Horatio Dried Apricots] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Horatio].[Horatio Dried Dates] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Horatio].[Horatio Golden Raisins] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Horatio].[Horatio Grape Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Horatio].[Horatio Raisins] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Horatio].[Horatio Raspberry Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Horatio].[Horatio Strawberry Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Nationeel].[Nationeel Apple Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Nationeel].[Nationeel Dried Apples] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Nationeel].[Nationeel Dried Apricots] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Nationeel].[Nationeel Dried Dates] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Nationeel].[Nationeel Golden Raisins] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Nationeel].[Nationeel Grape Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Nationeel].[Nationeel Raisins] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Nationeel].[Nationeel Raspberry Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Nationeel].[Nationeel Strawberry Fruit Roll] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Meat] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Meat].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Meat].[Best Choice].[Best Choice Beef Jerky] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Meat].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Meat].[Fast].[Fast Beef Jerky] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Meat].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Meat].[Fort West].[Fort West Beef Jerky] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Meat].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Meat].[Horatio].[Horatio Beef Jerky] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Meat].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Meat].[Nationeel].[Nationeel Beef Jerky] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Best Choice].[Best Choice Buttered Popcorn] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Best Choice].[Best Choice Low Fat Popcorn] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Best Choice].[Best Choice No Salt Popcorn] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Fast].[Fast Buttered Popcorn] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Fast].[Fast Low Fat Popcorn] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Fast].[Fast No Salt Popcorn] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Fort West].[Fort West Buttered Popcorn] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Fort West].[Fort West Low Fat Popcorn] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Fort West].[Fort West No Salt Popcorn] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Horatio].[Horatio Buttered Popcorn] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Horatio].[Horatio Low Fat Popcorn] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Horatio].[Horatio No Salt Popcorn] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Nationeel].[Nationeel Buttered Popcorn] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Nationeel].[Nationeel Low Fat Popcorn] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Nationeel].[Nationeel No Salt Popcorn] [Product].[Food].[Snack Foods].[Snack Foods].[Pretzels] [Product].[Food].[Snack Foods].[Snack Foods].[Pretzels].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Pretzels].[Best Choice].[Best Choice Salted Pretzels] [Product].[Food].[Snack Foods].[Snack Foods].[Pretzels].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Pretzels].[Fast].[Fast Salted Pretzels] [Product].[Food].[Snack Foods].[Snack Foods].[Pretzels].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Pretzels].[Fort West].[Fort West Salted Pretzels] [Product].[Food].[Snack Foods].[Snack Foods].[Pretzels].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Pretzels].[Horatio].[Horatio Salted Pretzels] [Product].[Food].[Snack Foods].[Snack Foods].[Pretzels].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Pretzels].[Nationeel].[Nationeel Salted Pretzels] [Product].[Food].[Snacks] [Product].[Food].[Snacks].[Candy] [Product].[Food].[Snacks].[Candy].[Chocolate Candy] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Atomic] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Atomic].[Atomic Malted Milk Balls] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Atomic].[Atomic Mint Chocolate Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Atomic].[Atomic Semi-Sweet Chocolate Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Atomic].[Atomic Tasty Candy Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Atomic].[Atomic White Chocolate Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Choice] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Choice].[Choice Malted Milk Balls] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Choice].[Choice Mint Chocolate Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Choice].[Choice Semi-Sweet Chocolate Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Choice].[Choice Tasty Candy Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Choice].[Choice White Chocolate Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Gulf Coast] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Gulf Coast].[Gulf Coast Malted Milk Balls] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Gulf Coast].[Gulf Coast Mint Chocolate Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Gulf Coast].[Gulf Coast Semi-Sweet Chocolate Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Gulf Coast].[Gulf Coast Tasty Candy Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Gulf Coast].[Gulf Coast White Chocolate Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Musial] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Musial].[Musial Malted Milk Balls] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Musial].[Musial Mint Chocolate Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Musial].[Musial Semi-Sweet Chocolate Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Musial].[Musial Tasty Candy Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Musial].[Musial White Chocolate Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Thresher] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Thresher].[Thresher Malted Milk Balls] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Thresher].[Thresher Mint Chocolate Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Thresher].[Thresher Semi-Sweet Chocolate Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Thresher].[Thresher Tasty Candy Bar] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Thresher].[Thresher White Chocolate Bar] [Product].[Food].[Snacks].[Candy].[Gum] [Product].[Food].[Snacks].[Candy].[Gum].[Atomic] [Product].[Food].[Snacks].[Candy].[Gum].[Atomic].[Atomic Bubble Gum] [Product].[Food].[Snacks].[Candy].[Gum].[Choice] [Product].[Food].[Snacks].[Candy].[Gum].[Choice].[Choice Bubble Gum] [Product].[Food].[Snacks].[Candy].[Gum].[Gulf Coast] [Product].[Food].[Snacks].[Candy].[Gum].[Gulf Coast].[Gulf Coast Bubble Gum] [Product].[Food].[Snacks].[Candy].[Gum].[Musial] [Product].[Food].[Snacks].[Candy].[Gum].[Musial].[Musial Bubble Gum] [Product].[Food].[Snacks].[Candy].[Gum].[Thresher] [Product].[Food].[Snacks].[Candy].[Gum].[Thresher].[Thresher Bubble Gum] [Product].[Food].[Snacks].[Candy].[Hard Candy] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Atomic] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Atomic].[Atomic Mints] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Atomic].[Atomic Spicy Mints] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Choice] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Choice].[Choice Mints] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Choice].[Choice Spicy Mints] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Gulf Coast] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Gulf Coast].[Gulf Coast Mints] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Gulf Coast].[Gulf Coast Spicy Mints] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Musial] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Musial].[Musial Mints] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Musial].[Musial Spicy Mints] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Thresher] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Thresher].[Thresher Mints] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Thresher].[Thresher Spicy Mints] [Product].[Food].[Starchy Foods] [Product].[Food].[Starchy Foods].[Starchy Foods] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Colossal] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Colossal].[Colossal Manicotti] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Colossal].[Colossal Ravioli] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Colossal].[Colossal Spaghetti] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Discover] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Discover].[Discover Manicotti] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Discover].[Discover Ravioli] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Discover].[Discover Spaghetti] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Jardon] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Jardon].[Jardon Manicotti] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Jardon].[Jardon Ravioli] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Jardon].[Jardon Spaghetti] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Medalist] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Medalist].[Medalist Manicotti] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Medalist].[Medalist Ravioli] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Medalist].[Medalist Spaghetti] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Monarch] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Monarch].[Monarch Manicotti] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Monarch].[Monarch Ravioli] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Monarch].[Monarch Spaghetti] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Shady Lake] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Shady Lake].[Shady Lake Manicotti] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Shady Lake].[Shady Lake Ravioli] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Shady Lake].[Shady Lake Spaghetti] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Colossal] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Colossal].[Colossal Rice Medly] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Colossal].[Colossal Thai Rice] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Discover] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Discover].[Discover Rice Medly] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Discover].[Discover Thai Rice] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Jardon] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Jardon].[Jardon Rice Medly] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Jardon].[Jardon Thai Rice] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Medalist] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Medalist].[Medalist Rice Medly] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Medalist].[Medalist Thai Rice] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Monarch] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Monarch].[Monarch Rice Medly] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Monarch].[Monarch Thai Rice] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Shady Lake] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Shady Lake].[Shady Lake Rice Medly] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Shady Lake].[Shady Lake Thai Rice] [Product].[Non-Consumable] [Product].[Non-Consumable].[Carousel] [Product].[Non-Consumable].[Carousel].[Specialty] [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses] [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses].[ADJ] [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses].[ADJ].[ADJ Rosy Sunglasses] [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses].[King] [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses].[King].[King Rosy Sunglasses] [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses].[Prelude] [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses].[Prelude].[Prelude Rosy Sunglasses] [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses].[Symphony] [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses].[Symphony].[Symphony Rosy Sunglasses] [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses].[Toretti] [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses].[Toretti].[Toretti Rosy Sunglasses] [Product].[Non-Consumable].[Checkout] [Product].[Non-Consumable].[Checkout].[Hardware] [Product].[Non-Consumable].[Checkout].[Hardware].[Screwdrivers] [Product].[Non-Consumable].[Checkout].[Hardware].[Screwdrivers].[Akron] [Product].[Non-Consumable].[Checkout].[Hardware].[Screwdrivers].[Akron].[Akron Eyeglass Screwdriver] [Product].[Non-Consumable].[Checkout].[Hardware].[Screwdrivers].[Black Tie] [Product].[Non-Consumable].[Checkout].[Hardware].[Screwdrivers].[Black Tie].[Black Tie Eyeglass Screwdriver] [Product].[Non-Consumable].[Checkout].[Hardware].[Screwdrivers].[Framton] [Product].[Non-Consumable].[Checkout].[Hardware].[Screwdrivers].[Framton].[Framton Eyeglass Screwdriver] [Product].[Non-Consumable].[Checkout].[Hardware].[Screwdrivers].[James Bay] [Product].[Non-Consumable].[Checkout].[Hardware].[Screwdrivers].[James Bay].[James Bay Eyeglass Screwdriver] [Product].[Non-Consumable].[Checkout].[Hardware].[Screwdrivers].[Queen] [Product].[Non-Consumable].[Checkout].[Hardware].[Screwdrivers].[Queen].[Queen Eyeglass Screwdriver] [Product].[Non-Consumable].[Checkout].[Miscellaneous] [Product].[Non-Consumable].[Checkout].[Miscellaneous].[Maps] [Product].[Non-Consumable].[Checkout].[Miscellaneous].[Maps].[Akron] [Product].[Non-Consumable].[Checkout].[Miscellaneous].[Maps].[Akron].[Akron City Map] [Product].[Non-Consumable].[Checkout].[Miscellaneous].[Maps].[Black Tie] [Product].[Non-Consumable].[Checkout].[Miscellaneous].[Maps].[Black Tie].[Black Tie City Map] [Product].[Non-Consumable].[Checkout].[Miscellaneous].[Maps].[Framton] [Product].[Non-Consumable].[Checkout].[Miscellaneous].[Maps].[Framton].[Framton City Map] [Product].[Non-Consumable].[Checkout].[Miscellaneous].[Maps].[James Bay] [Product].[Non-Consumable].[Checkout].[Miscellaneous].[Maps].[James Bay].[James Bay City Map] [Product].[Non-Consumable].[Checkout].[Miscellaneous].[Maps].[Queen] [Product].[Non-Consumable].[Checkout].[Miscellaneous].[Maps].[Queen].[Queen City Map] [Product].[Non-Consumable].[Health and Hygiene] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Conditioner] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Conditioner].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Conditioner].[Bird Call].[Bird Call Silky Smooth Hair Conditioner] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Conditioner].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Conditioner].[Consolidated].[Consolidated Silky Smooth Hair Conditioner] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Conditioner].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Conditioner].[Faux Products].[Faux Products Silky Smooth Hair Conditioner] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Conditioner].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Conditioner].[Hilltop].[Hilltop Silky Smooth Hair Conditioner] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Conditioner].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Conditioner].[Steady].[Steady Silky Smooth Hair Conditioner] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Bird Call].[Bird Call Laundry Detergent] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Bird Call].[Bird Call Mint Mouthwash] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Consolidated].[Consolidated Laundry Detergent] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Consolidated].[Consolidated Mint Mouthwash] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Faux Products].[Faux Products Laundry Detergent] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Faux Products].[Faux Products Mint Mouthwash] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Hilltop].[Hilltop Laundry Detergent] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Hilltop].[Hilltop Mint Mouthwash] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Steady].[Steady Laundry Detergent] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Steady].[Steady Mint Mouthwash] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Bird Call].[Bird Call Apricot Shampoo] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Bird Call].[Bird Call Conditioning Shampoo] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Bird Call].[Bird Call Extra Moisture Shampoo] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Consolidated].[Consolidated Apricot Shampoo] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Consolidated].[Consolidated Conditioning Shampoo] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Consolidated].[Consolidated Extra Moisture Shampoo] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Faux Products].[Faux Products Apricot Shampoo] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Faux Products].[Faux Products Conditioning Shampoo] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Faux Products].[Faux Products Extra Moisture Shampoo] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Hilltop].[Hilltop Apricot Shampoo] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Hilltop].[Hilltop Conditioning Shampoo] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Hilltop].[Hilltop Extra Moisture Shampoo] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Steady].[Steady Apricot Shampoo] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Steady].[Steady Conditioning Shampoo] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Steady].[Steady Extra Moisture Shampoo] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Toothbrushes] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Toothbrushes].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Toothbrushes].[Bird Call].[Bird Call Angled Toothbrush] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Toothbrushes].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Toothbrushes].[Consolidated].[Consolidated Angled Toothbrush] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Toothbrushes].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Toothbrushes].[Faux Products].[Faux Products Angled Toothbrush] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Toothbrushes].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Toothbrushes].[Hilltop].[Hilltop Angled Toothbrush] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Toothbrushes].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Toothbrushes].[Steady].[Steady Angled Toothbrush] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Bird Call].[Bird Call Childrens Cold Remedy] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Bird Call].[Bird Call Multi-Symptom Cold Remedy] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Consolidated].[Consolidated Childrens Cold Remedy] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Consolidated].[Consolidated Multi-Symptom Cold Remedy] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Faux Products].[Faux Products Childrens Cold Remedy] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Faux Products].[Faux Products Multi-Symptom Cold Remedy] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Hilltop].[Hilltop Childrens Cold Remedy] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Hilltop].[Hilltop Multi-Symptom Cold Remedy] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Steady].[Steady Childrens Cold Remedy] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Steady].[Steady Multi-Symptom Cold Remedy] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Bird Call].[Bird Call Dishwasher Detergent] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Bird Call].[Bird Call HCL Nasal Spray] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Consolidated].[Consolidated Dishwasher Detergent] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Consolidated].[Consolidated HCL Nasal Spray] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Faux Products].[Faux Products Dishwasher Detergent] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Faux Products].[Faux Products HCL Nasal Spray] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Hilltop].[Hilltop Dishwasher Detergent] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Hilltop].[Hilltop HCL Nasal Spray] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Steady].[Steady Dishwasher Detergent] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Steady].[Steady HCL Nasal Spray] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Bird Call].[Bird Call Deodorant] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Bird Call].[Bird Call Tartar Control Toothpaste] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Bird Call].[Bird Call Toothpaste] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Bird Call].[Bird Call Whitening Toothpast] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Consolidated].[Consolidated Deodorant] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Consolidated].[Consolidated Tartar Control Toothpaste] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Consolidated].[Consolidated Toothpaste] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Consolidated].[Consolidated Whitening Toothpast] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Faux Products].[Faux Products Deodorant] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Faux Products].[Faux Products Tartar Control Toothpaste] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Faux Products].[Faux Products Toothpaste] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Faux Products].[Faux Products Whitening Toothpast] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Hilltop].[Hilltop Deodorant] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Hilltop].[Hilltop Tartar Control Toothpaste] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Hilltop].[Hilltop Toothpaste] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Hilltop].[Hilltop Whitening Toothpast] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Steady].[Steady Deodorant] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Steady].[Steady Tartar Control Toothpaste] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Steady].[Steady Toothpaste] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Steady].[Steady Whitening Toothpast] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Acetominifen] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Acetominifen].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Acetominifen].[Bird Call].[Bird Call 200 MG Acetominifen] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Acetominifen].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Acetominifen].[Consolidated].[Consolidated 200 MG Acetominifen] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Acetominifen].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Acetominifen].[Faux Products].[Faux Products 200 MG Acetominifen] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Acetominifen].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Acetominifen].[Hilltop].[Hilltop 200 MG Acetominifen] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Acetominifen].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Acetominifen].[Steady].[Steady 200 MG Acetominifen] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Bird Call].[Bird Call Buffered Aspirin] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Bird Call].[Bird Call Childrens Aspirin] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Consolidated].[Consolidated Buffered Aspirin] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Consolidated].[Consolidated Childrens Aspirin] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Faux Products].[Faux Products Buffered Aspirin] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Faux Products].[Faux Products Childrens Aspirin] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Hilltop].[Hilltop Buffered Aspirin] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Hilltop].[Hilltop Childrens Aspirin] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Steady].[Steady Buffered Aspirin] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Steady].[Steady Childrens Aspirin] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Ibuprofen] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Ibuprofen].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Ibuprofen].[Bird Call].[Bird Call 200 MG Ibuprofen] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Ibuprofen].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Ibuprofen].[Consolidated].[Consolidated 200 MG Ibuprofen] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Ibuprofen].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Ibuprofen].[Faux Products].[Faux Products 200 MG Ibuprofen] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Ibuprofen].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Ibuprofen].[Hilltop].[Hilltop 200 MG Ibuprofen] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Ibuprofen].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Ibuprofen].[Steady].[Steady 200 MG Ibuprofen] [Product].[Non-Consumable].[Household] [Product].[Non-Consumable].[Household].[Bathroom Products] [Product].[Non-Consumable].[Household].[Bathroom Products].[Toilet Brushes] [Product].[Non-Consumable].[Household].[Bathroom Products].[Toilet Brushes].[Cormorant] [Product].[Non-Consumable].[Household].[Bathroom Products].[Toilet Brushes].[Cormorant].[Cormorant Economy Toilet Brush] [Product].[Non-Consumable].[Household].[Bathroom Products].[Toilet Brushes].[Denny] [Product].[Non-Consumable].[Household].[Bathroom Products].[Toilet Brushes].[Denny].[Denny Economy Toilet Brush] [Product].[Non-Consumable].[Household].[Bathroom Products].[Toilet Brushes].[High Quality] [Product].[Non-Consumable].[Household].[Bathroom Products].[Toilet Brushes].[High Quality].[High Quality Economy Toilet Brush] [Product].[Non-Consumable].[Household].[Bathroom Products].[Toilet Brushes].[Red Wing] [Product].[Non-Consumable].[Household].[Bathroom Products].[Toilet Brushes].[Red Wing].[Red Wing Economy Toilet Brush] [Product].[Non-Consumable].[Household].[Bathroom Products].[Toilet Brushes].[Sunset] [Product].[Non-Consumable].[Household].[Bathroom Products].[Toilet Brushes].[Sunset].[Sunset Economy Toilet Brush] [Product].[Non-Consumable].[Household].[Candles] [Product].[Non-Consumable].[Household].[Candles].[Candles] [Product].[Non-Consumable].[Household].[Candles].[Candles].[Cormorant] [Product].[Non-Consumable].[Household].[Candles].[Candles].[Cormorant].[Cormorant Bees Wax Candles] [Product].[Non-Consumable].[Household].[Candles].[Candles].[Denny] [Product].[Non-Consumable].[Household].[Candles].[Candles].[Denny].[Denny Bees Wax Candles] [Product].[Non-Consumable].[Household].[Candles].[Candles].[High Quality] [Product].[Non-Consumable].[Household].[Candles].[Candles].[High Quality].[High Quality Bees Wax Candles] [Product].[Non-Consumable].[Household].[Candles].[Candles].[Red Wing] [Product].[Non-Consumable].[Household].[Candles].[Candles].[Red Wing].[Red Wing Bees Wax Candles] [Product].[Non-Consumable].[Household].[Candles].[Candles].[Sunset] [Product].[Non-Consumable].[Household].[Candles].[Candles].[Sunset].[Sunset Bees Wax Candles] [Product].[Non-Consumable].[Household].[Cleaning Supplies] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Cormorant] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Cormorant].[Cormorant Counter Cleaner] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Cormorant].[Cormorant Glass Cleaner] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Cormorant].[Cormorant Toilet Bowl Cleaner] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Denny] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Denny].[Denny Counter Cleaner] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Denny].[Denny Glass Cleaner] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Denny].[Denny Toilet Bowl Cleaner] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[High Quality] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[High Quality].[High Quality Counter Cleaner] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[High Quality].[High Quality Glass Cleaner] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[High Quality].[High Quality Toilet Bowl Cleaner] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Red Wing] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Red Wing].[Red Wing Counter Cleaner] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Red Wing].[Red Wing Glass Cleaner] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Red Wing].[Red Wing Toilet Bowl Cleaner] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Sunset] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Sunset].[Sunset Counter Cleaner] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Sunset].[Sunset Glass Cleaner] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Sunset].[Sunset Toilet Bowl Cleaner] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Deodorizers] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Deodorizers].[Cormorant] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Deodorizers].[Cormorant].[Cormorant Room Freshener] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Deodorizers].[Denny] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Deodorizers].[Denny].[Denny Room Freshener] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Deodorizers].[High Quality] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Deodorizers].[High Quality].[High Quality Room Freshener] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Deodorizers].[Red Wing] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Deodorizers].[Red Wing].[Red Wing Room Freshener] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Deodorizers].[Sunset] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Deodorizers].[Sunset].[Sunset Room Freshener] [Product].[Non-Consumable].[Household].[Electrical] [Product].[Non-Consumable].[Household].[Electrical].[Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Cormorant] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Cormorant].[Cormorant AA-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Cormorant].[Cormorant AAA-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Cormorant].[Cormorant C-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Cormorant].[Cormorant D-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Denny] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Denny].[Denny AA-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Denny].[Denny AAA-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Denny].[Denny C-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Denny].[Denny D-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[High Quality] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[High Quality].[High Quality AA-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[High Quality].[High Quality AAA-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[High Quality].[High Quality C-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[High Quality].[High Quality D-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Red Wing] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Red Wing].[Red Wing AA-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Red Wing].[Red Wing AAA-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Red Wing].[Red Wing C-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Red Wing].[Red Wing D-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Sunset] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Sunset].[Sunset AA-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Sunset].[Sunset AAA-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Sunset].[Sunset C-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Sunset].[Sunset D-Size Batteries] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Cormorant] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Cormorant].[Cormorant 100 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Cormorant].[Cormorant 25 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Cormorant].[Cormorant 60 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Cormorant].[Cormorant 75 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Denny] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Denny].[Denny 100 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Denny].[Denny 25 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Denny].[Denny 60 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Denny].[Denny 75 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[High Quality] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[High Quality].[High Quality 100 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[High Quality].[High Quality 25 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[High Quality].[High Quality 60 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[High Quality].[High Quality 75 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Red Wing] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Red Wing].[Red Wing 100 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Red Wing].[Red Wing 25 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Red Wing].[Red Wing 60 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Red Wing].[Red Wing 75 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Sunset] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Sunset].[Sunset 100 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Sunset].[Sunset 25 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Sunset].[Sunset 60 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Sunset].[Sunset 75 Watt Lightbulb] [Product].[Non-Consumable].[Household].[Hardware] [Product].[Non-Consumable].[Household].[Hardware].[Tools] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[Cormorant] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[Cormorant].[Cormorant Scissors] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[Cormorant].[Cormorant Screw Driver] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[Denny] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[Denny].[Denny Scissors] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[Denny].[Denny Screw Driver] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[High Quality] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[High Quality].[High Quality Scissors] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[High Quality].[High Quality Screw Driver] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[Red Wing] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[Red Wing].[Red Wing Scissors] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[Red Wing].[Red Wing Screw Driver] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[Sunset] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[Sunset].[Sunset Scissors] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[Sunset].[Sunset Screw Driver] [Product].[Non-Consumable].[Household].[Kitchen Products] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[Cormorant] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[Cormorant].[Cormorant Copper Cleaner] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[Cormorant].[Cormorant Silver Cleaner] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[Denny] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[Denny].[Denny Copper Cleaner] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[Denny].[Denny Silver Cleaner] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[High Quality] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[High Quality].[High Quality Copper Cleaner] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[High Quality].[High Quality Silver Cleaner] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[Red Wing] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[Red Wing].[Red Wing Copper Cleaner] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[Red Wing].[Red Wing Silver Cleaner] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[Sunset] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[Sunset].[Sunset Copper Cleaner] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[Sunset].[Sunset Silver Cleaner] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Cormorant] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Cormorant].[Cormorant Copper Pot Scrubber] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Denny] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Denny].[Denny Copper Pot Scrubber] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[High Quality] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[High Quality].[High Quality Copper Pot Scrubber] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Red Wing] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Red Wing].[Red Wing Copper Pot Scrubber] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Sunset] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Sunset].[Sunset Copper Pot Scrubber] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Cormorant] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Cormorant].[Cormorant Frying Pan] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Denny] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Denny].[Denny Frying Pan] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[High Quality] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[High Quality].[High Quality Frying Pan] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Red Wing] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Red Wing].[Red Wing Frying Pan] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Sunset] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Sunset].[Sunset Frying Pan] [Product].[Non-Consumable].[Household].[Kitchen Products].[Sponges] [Product].[Non-Consumable].[Household].[Kitchen Products].[Sponges].[Cormorant] [Product].[Non-Consumable].[Household].[Kitchen Products].[Sponges].[Cormorant].[Cormorant Large Sponge] [Product].[Non-Consumable].[Household].[Kitchen Products].[Sponges].[Denny] [Product].[Non-Consumable].[Household].[Kitchen Products].[Sponges].[Denny].[Denny Large Sponge] [Product].[Non-Consumable].[Household].[Kitchen Products].[Sponges].[High Quality] [Product].[Non-Consumable].[Household].[Kitchen Products].[Sponges].[High Quality].[High Quality Large Sponge] [Product].[Non-Consumable].[Household].[Kitchen Products].[Sponges].[Red Wing] [Product].[Non-Consumable].[Household].[Kitchen Products].[Sponges].[Red Wing].[Red Wing Large Sponge] [Product].[Non-Consumable].[Household].[Kitchen Products].[Sponges].[Sunset] [Product].[Non-Consumable].[Household].[Kitchen Products].[Sponges].[Sunset].[Sunset Large Sponge] [Product].[Non-Consumable].[Household].[Paper Products] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[Cormorant] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[Cormorant].[Cormorant Paper Cups] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[Cormorant].[Cormorant Paper Plates] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[Denny] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[Denny].[Denny Paper Cups] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[Denny].[Denny Paper Plates] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[High Quality] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[High Quality].[High Quality Paper Cups] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[High Quality].[High Quality Paper Plates] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[Red Wing] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[Red Wing].[Red Wing Paper Cups] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[Red Wing].[Red Wing Paper Plates] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[Sunset] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[Sunset].[Sunset Paper Cups] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[Sunset].[Sunset Paper Plates] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Cormorant] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Cormorant].[Cormorant Paper Towels] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Cormorant].[Cormorant Scented Tissue] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Cormorant].[Cormorant Scented Toilet Tissue] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Cormorant].[Cormorant Soft Napkins] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Cormorant].[Cormorant Tissues] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Cormorant].[Cormorant Toilet Paper] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Denny] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Denny].[Denny Paper Towels] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Denny].[Denny Scented Tissue] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Denny].[Denny Scented Toilet Tissue] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Denny].[Denny Soft Napkins] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Denny].[Denny Tissues] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Denny].[Denny Toilet Paper] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[High Quality] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[High Quality].[High Quality Paper Towels] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[High Quality].[High Quality Scented Tissue] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[High Quality].[High Quality Scented Toilet Tissue] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[High Quality].[High Quality Soft Napkins] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[High Quality].[High Quality Tissues] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[High Quality].[High Quality Toilet Paper] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Red Wing] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Red Wing].[Red Wing Paper Towels] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Red Wing].[Red Wing Scented Tissue] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Red Wing].[Red Wing Scented Toilet Tissue] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Red Wing].[Red Wing Soft Napkins] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Red Wing].[Red Wing Tissues] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Red Wing].[Red Wing Toilet Paper] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Sunset] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Sunset].[Sunset Paper Towels] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Sunset].[Sunset Scented Tissue] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Sunset].[Sunset Scented Toilet Tissue] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Sunset].[Sunset Soft Napkins] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Sunset].[Sunset Tissues] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Sunset].[Sunset Toilet Paper] [Product].[Non-Consumable].[Household].[Plastic Products] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Cormorant] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Cormorant].[Cormorant Plastic Forks] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Cormorant].[Cormorant Plastic Knives] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Cormorant].[Cormorant Plastic Spoons] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Denny] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Denny].[Denny Plastic Forks] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Denny].[Denny Plastic Knives] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Denny].[Denny Plastic Spoons] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[High Quality] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[High Quality].[High Quality Plastic Forks] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[High Quality].[High Quality Plastic Knives] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[High Quality].[High Quality Plastic Spoons] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Red Wing] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Red Wing].[Red Wing Plastic Forks] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Red Wing].[Red Wing Plastic Knives] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Red Wing].[Red Wing Plastic Spoons] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Sunset] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Sunset].[Sunset Plastic Forks] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Sunset].[Sunset Plastic Knives] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Sunset].[Sunset Plastic Spoons] [Product].[Non-Consumable].[Periodicals] [Product].[Non-Consumable].[Periodicals].[Magazines] [Product].[Non-Consumable].[Periodicals].[Magazines].[Auto Magazines] [Product].[Non-Consumable].[Periodicals].[Magazines].[Auto Magazines].[Dollar] [Product].[Non-Consumable].[Periodicals].[Magazines].[Auto Magazines].[Dollar].[Dollar Monthly Auto Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Auto Magazines].[Excel] [Product].[Non-Consumable].[Periodicals].[Magazines].[Auto Magazines].[Excel].[Excel Monthly Auto Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Auto Magazines].[Gauss] [Product].[Non-Consumable].[Periodicals].[Magazines].[Auto Magazines].[Gauss].[Gauss Monthly Auto Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Auto Magazines].[Mighty Good] [Product].[Non-Consumable].[Periodicals].[Magazines].[Auto Magazines].[Mighty Good].[Mighty Good Monthly Auto Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Auto Magazines].[Robust] [Product].[Non-Consumable].[Periodicals].[Magazines].[Auto Magazines].[Robust].[Robust Monthly Auto Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Computer Magazines] [Product].[Non-Consumable].[Periodicals].[Magazines].[Computer Magazines].[Dollar] [Product].[Non-Consumable].[Periodicals].[Magazines].[Computer Magazines].[Dollar].[Dollar Monthly Computer Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Computer Magazines].[Excel] [Product].[Non-Consumable].[Periodicals].[Magazines].[Computer Magazines].[Excel].[Excel Monthly Computer Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Computer Magazines].[Gauss] [Product].[Non-Consumable].[Periodicals].[Magazines].[Computer Magazines].[Gauss].[Gauss Monthly Computer Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Computer Magazines].[Mighty Good] [Product].[Non-Consumable].[Periodicals].[Magazines].[Computer Magazines].[Mighty Good].[Mighty Good Monthly Computer Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Computer Magazines].[Robust] [Product].[Non-Consumable].[Periodicals].[Magazines].[Computer Magazines].[Robust].[Robust Monthly Computer Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Fashion Magazines] [Product].[Non-Consumable].[Periodicals].[Magazines].[Fashion Magazines].[Dollar] [Product].[Non-Consumable].[Periodicals].[Magazines].[Fashion Magazines].[Dollar].[Dollar Monthly Fashion Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Fashion Magazines].[Excel] [Product].[Non-Consumable].[Periodicals].[Magazines].[Fashion Magazines].[Excel].[Excel Monthly Fashion Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Fashion Magazines].[Gauss] [Product].[Non-Consumable].[Periodicals].[Magazines].[Fashion Magazines].[Gauss].[Gauss Monthly Fashion Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Fashion Magazines].[Mighty Good] [Product].[Non-Consumable].[Periodicals].[Magazines].[Fashion Magazines].[Mighty Good].[Mighty Good Monthly Fashion Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Fashion Magazines].[Robust] [Product].[Non-Consumable].[Periodicals].[Magazines].[Fashion Magazines].[Robust].[Robust Monthly Fashion Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Home Magazines] [Product].[Non-Consumable].[Periodicals].[Magazines].[Home Magazines].[Dollar] [Product].[Non-Consumable].[Periodicals].[Magazines].[Home Magazines].[Dollar].[Dollar Monthly Home Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Home Magazines].[Excel] [Product].[Non-Consumable].[Periodicals].[Magazines].[Home Magazines].[Excel].[Excel Monthly Home Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Home Magazines].[Gauss] [Product].[Non-Consumable].[Periodicals].[Magazines].[Home Magazines].[Gauss].[Gauss Monthly Home Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Home Magazines].[Mighty Good] [Product].[Non-Consumable].[Periodicals].[Magazines].[Home Magazines].[Mighty Good].[Mighty Good Monthly Home Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Home Magazines].[Robust] [Product].[Non-Consumable].[Periodicals].[Magazines].[Home Magazines].[Robust].[Robust Monthly Home Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Sports Magazines] [Product].[Non-Consumable].[Periodicals].[Magazines].[Sports Magazines].[Dollar] [Product].[Non-Consumable].[Periodicals].[Magazines].[Sports Magazines].[Dollar].[Dollar Monthly Sports Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Sports Magazines].[Excel] [Product].[Non-Consumable].[Periodicals].[Magazines].[Sports Magazines].[Excel].[Excel Monthly Sports Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Sports Magazines].[Gauss] [Product].[Non-Consumable].[Periodicals].[Magazines].[Sports Magazines].[Gauss].[Gauss Monthly Sports Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Sports Magazines].[Mighty Good] [Product].[Non-Consumable].[Periodicals].[Magazines].[Sports Magazines].[Mighty Good].[Mighty Good Monthly Sports Magazine] [Product].[Non-Consumable].[Periodicals].[Magazines].[Sports Magazines].[Robust] [Product].[Non-Consumable].[Periodicals].[Magazines].[Sports Magazines].[Robust].[Robust Monthly Sports Magazine] Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 Not Level 2 mondrian-3.4.1/testsrc/queryFiles/queryTest_b3fa323364166e89.xml0000644000175000017500000000445611735330606023750 0ustar drazzibdrazzib select {[Measures].[Store Invoice], [Measures].[Supply Time]} on columns, descendants([Store].[USA], [Store].[Store City], bEfOrE) on rows from [Warehouse] [Store Size in SQFT] [Store Type] [Time] [Warehouse] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Time].[1997] [Warehouse].[All Warehouses] [Measures] [Measures].[Store Invoice] [Measures].[Supply Time] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[OR] [Store].[USA].[WA] 102278.4089 10425.0 29607.2994 3100.0 20194.0007 2051.0 52477.1088 5274.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_da108686117d1f57.xml0000644000175000017500000030340011735330606023735 0ustar drazzibdrazzib SELECT DISTINCT({FILTER([Product].MEMBERS, [Product].CurrentMember.Level.Name = "Brand Name")}) on AXIS(0) from [Sales] WHERE ([Measures].[Unit Sales]) [Measures] [Time] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Product] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Good] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Pearl] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Portsmouth] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Top Measure] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Beer].[Walrus] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Good] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Pearl] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Portsmouth] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Top Measure] [Product].[Drink].[Alcoholic Beverages].[Beer and Wine].[Wine].[Walrus] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Excellent] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Fabulous] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Skinner] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Token] [Product].[Drink].[Beverages].[Carbonated Beverages].[Soda].[Washington] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Excellent] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Fabulous] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Skinner] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Token] [Product].[Drink].[Beverages].[Drinks].[Flavored Drinks].[Washington] [Product].[Drink].[Beverages].[Hot Beverages].[Chocolate].[BBB Best] [Product].[Drink].[Beverages].[Hot Beverages].[Chocolate].[CDR] [Product].[Drink].[Beverages].[Hot Beverages].[Chocolate].[Landslide] [Product].[Drink].[Beverages].[Hot Beverages].[Chocolate].[Plato] [Product].[Drink].[Beverages].[Hot Beverages].[Chocolate].[Super] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[BBB Best] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[CDR] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Landslide] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Plato] [Product].[Drink].[Beverages].[Hot Beverages].[Coffee].[Super] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Excellent] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Fabulous] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Skinner] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Token] [Product].[Drink].[Beverages].[Pure Juice Beverages].[Juice].[Washington] [Product].[Drink].[Dairy].[Dairy].[Milk].[Booker] [Product].[Drink].[Dairy].[Dairy].[Milk].[Carlson] [Product].[Drink].[Dairy].[Dairy].[Milk].[Club] [Product].[Drink].[Dairy].[Dairy].[Milk].[Even Better] [Product].[Drink].[Dairy].[Dairy].[Milk].[Gorilla] [Product].[Food].[Baked Goods].[Bread].[Bagels].[Colony] [Product].[Food].[Baked Goods].[Bread].[Bagels].[Fantastic] [Product].[Food].[Baked Goods].[Bread].[Bagels].[Great] [Product].[Food].[Baked Goods].[Bread].[Bagels].[Modell] [Product].[Food].[Baked Goods].[Bread].[Bagels].[Sphinx] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Colony] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Fantastic] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Great] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Modell] [Product].[Food].[Baked Goods].[Bread].[Muffins].[Sphinx] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Colony] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Fantastic] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Great] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Modell] [Product].[Food].[Baked Goods].[Bread].[Sliced Bread].[Sphinx] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[BBB Best] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[CDR] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Landslide] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Plato] [Product].[Food].[Baking Goods].[Baking Goods].[Cooking Oil].[Super] [Product].[Food].[Baking Goods].[Baking Goods].[Sauces].[BBB Best] [Product].[Food].[Baking Goods].[Baking Goods].[Sauces].[CDR] [Product].[Food].[Baking Goods].[Baking Goods].[Sauces].[Landslide] [Product].[Food].[Baking Goods].[Baking Goods].[Sauces].[Plato] [Product].[Food].[Baking Goods].[Baking Goods].[Sauces].[Super] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[BBB Best] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[CDR] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[Landslide] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[Plato] [Product].[Food].[Baking Goods].[Baking Goods].[Spices].[Super] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[BBB Best] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[CDR] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[Landslide] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[Plato] [Product].[Food].[Baking Goods].[Baking Goods].[Sugar].[Super] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[BBB Best] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[CDR] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[Landslide] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[Plato] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jam].[Super] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[BBB Best] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[CDR] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[Landslide] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[Plato] [Product].[Food].[Baking Goods].[Jams and Jellies].[Jelly].[Super] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[BBB Best] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[CDR] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[Landslide] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[Plato] [Product].[Food].[Baking Goods].[Jams and Jellies].[Peanut Butter].[Super] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[BBB Best] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[CDR] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Landslide] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Plato] [Product].[Food].[Baking Goods].[Jams and Jellies].[Preserves].[Super] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Best] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Jeffers] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Johnson] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Radius] [Product].[Food].[Breakfast Foods].[Breakfast Foods].[Cereal].[Special] [Product].[Food].[Canned Foods].[Canned Anchovies].[Anchovies].[Better] [Product].[Food].[Canned Foods].[Canned Anchovies].[Anchovies].[Blue Label] [Product].[Food].[Canned Foods].[Canned Anchovies].[Anchovies].[Bravo] [Product].[Food].[Canned Foods].[Canned Anchovies].[Anchovies].[Just Right] [Product].[Food].[Canned Foods].[Canned Anchovies].[Anchovies].[Pleasant] [Product].[Food].[Canned Foods].[Canned Clams].[Clams].[Better] [Product].[Food].[Canned Foods].[Canned Clams].[Clams].[Blue Label] [Product].[Food].[Canned Foods].[Canned Clams].[Clams].[Bravo] [Product].[Food].[Canned Foods].[Canned Clams].[Clams].[Just Right] [Product].[Food].[Canned Foods].[Canned Clams].[Clams].[Pleasant] [Product].[Food].[Canned Foods].[Canned Oysters].[Oysters].[Better] [Product].[Food].[Canned Foods].[Canned Oysters].[Oysters].[Blue Label] [Product].[Food].[Canned Foods].[Canned Oysters].[Oysters].[Bravo] [Product].[Food].[Canned Foods].[Canned Oysters].[Oysters].[Just Right] [Product].[Food].[Canned Foods].[Canned Oysters].[Oysters].[Pleasant] [Product].[Food].[Canned Foods].[Canned Sardines].[Sardines].[Better] [Product].[Food].[Canned Foods].[Canned Sardines].[Sardines].[Blue Label] [Product].[Food].[Canned Foods].[Canned Sardines].[Sardines].[Bravo] [Product].[Food].[Canned Foods].[Canned Sardines].[Sardines].[Just Right] [Product].[Food].[Canned Foods].[Canned Sardines].[Sardines].[Pleasant] [Product].[Food].[Canned Foods].[Canned Shrimp].[Shrimp].[Better] [Product].[Food].[Canned Foods].[Canned Shrimp].[Shrimp].[Blue Label] [Product].[Food].[Canned Foods].[Canned Shrimp].[Shrimp].[Bravo] [Product].[Food].[Canned Foods].[Canned Shrimp].[Shrimp].[Just Right] [Product].[Food].[Canned Foods].[Canned Shrimp].[Shrimp].[Pleasant] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Better] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Blue Label] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Bravo] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Just Right] [Product].[Food].[Canned Foods].[Canned Soup].[Soup].[Pleasant] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Better] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Blue Label] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Bravo] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Just Right] [Product].[Food].[Canned Foods].[Canned Tuna].[Tuna].[Pleasant] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Better] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Blue Label] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Bravo] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Just Right] [Product].[Food].[Canned Foods].[Vegetables].[Canned Vegetables].[Pleasant] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Applause] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Big City] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Green Ribbon] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Swell] [Product].[Food].[Canned Products].[Fruit].[Canned Fruit].[Toucan] [Product].[Food].[Dairy].[Dairy].[Cheese].[Booker] [Product].[Food].[Dairy].[Dairy].[Cheese].[Carlson] [Product].[Food].[Dairy].[Dairy].[Cheese].[Club] [Product].[Food].[Dairy].[Dairy].[Cheese].[Even Better] [Product].[Food].[Dairy].[Dairy].[Cheese].[Gorilla] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Booker] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Carlson] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Club] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Even Better] [Product].[Food].[Dairy].[Dairy].[Cottage Cheese].[Gorilla] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Booker] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Carlson] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Club] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Even Better] [Product].[Food].[Dairy].[Dairy].[Sour Cream].[Gorilla] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Booker] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Carlson] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Club] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Even Better] [Product].[Food].[Dairy].[Dairy].[Yogurt].[Gorilla] [Product].[Food].[Deli].[Meat].[Bologna].[American] [Product].[Food].[Deli].[Meat].[Bologna].[Cutting Edge] [Product].[Food].[Deli].[Meat].[Bologna].[Lake] [Product].[Food].[Deli].[Meat].[Bologna].[Moms] [Product].[Food].[Deli].[Meat].[Bologna].[Red Spade] [Product].[Food].[Deli].[Meat].[Deli Meats].[American] [Product].[Food].[Deli].[Meat].[Deli Meats].[Cutting Edge] [Product].[Food].[Deli].[Meat].[Deli Meats].[Lake] [Product].[Food].[Deli].[Meat].[Deli Meats].[Moms] [Product].[Food].[Deli].[Meat].[Deli Meats].[Red Spade] [Product].[Food].[Deli].[Meat].[Fresh Chicken].[American] [Product].[Food].[Deli].[Meat].[Fresh Chicken].[Cutting Edge] [Product].[Food].[Deli].[Meat].[Fresh Chicken].[Lake] [Product].[Food].[Deli].[Meat].[Fresh Chicken].[Moms] [Product].[Food].[Deli].[Meat].[Fresh Chicken].[Red Spade] [Product].[Food].[Deli].[Meat].[Hot Dogs].[American] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Cutting Edge] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Lake] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Moms] [Product].[Food].[Deli].[Meat].[Hot Dogs].[Red Spade] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[American] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Cutting Edge] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Lake] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Moms] [Product].[Food].[Deli].[Side Dishes].[Deli Salads].[Red Spade] [Product].[Food].[Eggs].[Eggs].[Eggs].[Blue Medal] [Product].[Food].[Eggs].[Eggs].[Eggs].[Giant] [Product].[Food].[Eggs].[Eggs].[Eggs].[Jumbo] [Product].[Food].[Eggs].[Eggs].[Eggs].[National] [Product].[Food].[Eggs].[Eggs].[Eggs].[Urban] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[Big Time] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[Carrington] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[Golden] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[Imagine] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancake Mix].[PigTail] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancakes].[Big Time] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancakes].[Carrington] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancakes].[Golden] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancakes].[Imagine] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Pancakes].[PigTail] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Big Time] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Carrington] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Golden] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[Imagine] [Product].[Food].[Frozen Foods].[Breakfast Foods].[Waffles].[PigTail] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Big Time] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Carrington] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Golden] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[Imagine] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Ice Cream].[PigTail] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Big Time] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Carrington] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Golden] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[Imagine] [Product].[Food].[Frozen Foods].[Frozen Desserts].[Popsicles].[PigTail] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Big Time] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Carrington] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Golden] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[Imagine] [Product].[Food].[Frozen Foods].[Frozen Entrees].[TV Dinner].[PigTail] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Big Time] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Carrington] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Golden] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[Imagine] [Product].[Food].[Frozen Foods].[Meat].[Frozen Chicken].[PigTail] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Big Time] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Carrington] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Golden] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[Imagine] [Product].[Food].[Frozen Foods].[Pizza].[Pizza].[PigTail] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Big Time] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Carrington] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Golden] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[Imagine] [Product].[Food].[Frozen Foods].[Vegetables].[French Fries].[PigTail] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Big Time] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Carrington] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Golden] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[Imagine] [Product].[Food].[Frozen Foods].[Vegetables].[Frozen Vegetables].[PigTail] [Product].[Food].[Meat].[Meat].[Hamburger].[Footnote] [Product].[Food].[Meat].[Meat].[Hamburger].[Genteel] [Product].[Food].[Meat].[Meat].[Hamburger].[Gerolli] [Product].[Food].[Meat].[Meat].[Hamburger].[Quick] [Product].[Food].[Meat].[Meat].[Hamburger].[Ship Shape] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Ebony] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Hermanos] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[High Top] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tell Tale] [Product].[Food].[Produce].[Fruit].[Fresh Fruit].[Tri-State] [Product].[Food].[Produce].[Packaged Vegetables].[Tofu].[Ebony] [Product].[Food].[Produce].[Packaged Vegetables].[Tofu].[Hermanos] [Product].[Food].[Produce].[Packaged Vegetables].[Tofu].[High Top] [Product].[Food].[Produce].[Packaged Vegetables].[Tofu].[Tell Tale] [Product].[Food].[Produce].[Packaged Vegetables].[Tofu].[Tri-State] [Product].[Food].[Produce].[Specialty].[Nuts].[Ebony] [Product].[Food].[Produce].[Specialty].[Nuts].[Hermanos] [Product].[Food].[Produce].[Specialty].[Nuts].[High Top] [Product].[Food].[Produce].[Specialty].[Nuts].[Tell Tale] [Product].[Food].[Produce].[Specialty].[Nuts].[Tri-State] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Ebony] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Hermanos] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[High Top] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tell Tale] [Product].[Food].[Produce].[Vegetables].[Fresh Vegetables].[Tri-State] [Product].[Food].[Seafood].[Seafood].[Fresh Fish].[Amigo] [Product].[Food].[Seafood].[Seafood].[Fresh Fish].[Curlew] [Product].[Food].[Seafood].[Seafood].[Fresh Fish].[Dual City] [Product].[Food].[Seafood].[Seafood].[Fresh Fish].[Kiwi] [Product].[Food].[Seafood].[Seafood].[Fresh Fish].[Tip Top] [Product].[Food].[Seafood].[Seafood].[Shellfish].[Amigo] [Product].[Food].[Seafood].[Seafood].[Shellfish].[Curlew] [Product].[Food].[Seafood].[Seafood].[Shellfish].[Dual City] [Product].[Food].[Seafood].[Seafood].[Shellfish].[Kiwi] [Product].[Food].[Seafood].[Seafood].[Shellfish].[Tip Top] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Chips].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Cookies].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Crackers].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Dips].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Donuts].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Fruit].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Meat].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Meat].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Meat].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Meat].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Dried Meat].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Popcorn].[Nationeel] [Product].[Food].[Snack Foods].[Snack Foods].[Pretzels].[Best Choice] [Product].[Food].[Snack Foods].[Snack Foods].[Pretzels].[Fast] [Product].[Food].[Snack Foods].[Snack Foods].[Pretzels].[Fort West] [Product].[Food].[Snack Foods].[Snack Foods].[Pretzels].[Horatio] [Product].[Food].[Snack Foods].[Snack Foods].[Pretzels].[Nationeel] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Atomic] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Choice] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Gulf Coast] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Musial] [Product].[Food].[Snacks].[Candy].[Chocolate Candy].[Thresher] [Product].[Food].[Snacks].[Candy].[Gum].[Atomic] [Product].[Food].[Snacks].[Candy].[Gum].[Choice] [Product].[Food].[Snacks].[Candy].[Gum].[Gulf Coast] [Product].[Food].[Snacks].[Candy].[Gum].[Musial] [Product].[Food].[Snacks].[Candy].[Gum].[Thresher] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Atomic] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Choice] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Gulf Coast] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Musial] [Product].[Food].[Snacks].[Candy].[Hard Candy].[Thresher] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Colossal] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Discover] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Jardon] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Medalist] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Monarch] [Product].[Food].[Starchy Foods].[Starchy Foods].[Pasta].[Shady Lake] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Colossal] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Discover] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Jardon] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Medalist] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Monarch] [Product].[Food].[Starchy Foods].[Starchy Foods].[Rice].[Shady Lake] [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses].[ADJ] [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses].[King] [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses].[Prelude] [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses].[Symphony] [Product].[Non-Consumable].[Carousel].[Specialty].[Sunglasses].[Toretti] [Product].[Non-Consumable].[Checkout].[Hardware].[Screwdrivers].[Akron] [Product].[Non-Consumable].[Checkout].[Hardware].[Screwdrivers].[Black Tie] [Product].[Non-Consumable].[Checkout].[Hardware].[Screwdrivers].[Framton] [Product].[Non-Consumable].[Checkout].[Hardware].[Screwdrivers].[James Bay] [Product].[Non-Consumable].[Checkout].[Hardware].[Screwdrivers].[Queen] [Product].[Non-Consumable].[Checkout].[Miscellaneous].[Maps].[Akron] [Product].[Non-Consumable].[Checkout].[Miscellaneous].[Maps].[Black Tie] [Product].[Non-Consumable].[Checkout].[Miscellaneous].[Maps].[Framton] [Product].[Non-Consumable].[Checkout].[Miscellaneous].[Maps].[James Bay] [Product].[Non-Consumable].[Checkout].[Miscellaneous].[Maps].[Queen] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Conditioner].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Conditioner].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Conditioner].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Conditioner].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Conditioner].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Mouthwash].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Shampoo].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Toothbrushes].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Toothbrushes].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Toothbrushes].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Toothbrushes].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Bathroom Products].[Toothbrushes].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Cold Remedies].[Cold Remedies].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Decongestants].[Nasal Sprays].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Hygiene].[Personal Hygiene].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Acetominifen].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Acetominifen].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Acetominifen].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Acetominifen].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Acetominifen].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Aspirin].[Steady] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Ibuprofen].[Bird Call] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Ibuprofen].[Consolidated] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Ibuprofen].[Faux Products] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Ibuprofen].[Hilltop] [Product].[Non-Consumable].[Health and Hygiene].[Pain Relievers].[Ibuprofen].[Steady] [Product].[Non-Consumable].[Household].[Bathroom Products].[Toilet Brushes].[Cormorant] [Product].[Non-Consumable].[Household].[Bathroom Products].[Toilet Brushes].[Denny] [Product].[Non-Consumable].[Household].[Bathroom Products].[Toilet Brushes].[High Quality] [Product].[Non-Consumable].[Household].[Bathroom Products].[Toilet Brushes].[Red Wing] [Product].[Non-Consumable].[Household].[Bathroom Products].[Toilet Brushes].[Sunset] [Product].[Non-Consumable].[Household].[Candles].[Candles].[Cormorant] [Product].[Non-Consumable].[Household].[Candles].[Candles].[Denny] [Product].[Non-Consumable].[Household].[Candles].[Candles].[High Quality] [Product].[Non-Consumable].[Household].[Candles].[Candles].[Red Wing] [Product].[Non-Consumable].[Household].[Candles].[Candles].[Sunset] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Cormorant] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Denny] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[High Quality] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Red Wing] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Cleaners].[Sunset] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Deodorizers].[Cormorant] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Deodorizers].[Denny] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Deodorizers].[High Quality] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Deodorizers].[Red Wing] [Product].[Non-Consumable].[Household].[Cleaning Supplies].[Deodorizers].[Sunset] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Cormorant] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Denny] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[High Quality] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Red Wing] [Product].[Non-Consumable].[Household].[Electrical].[Batteries].[Sunset] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Cormorant] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Denny] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[High Quality] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Red Wing] [Product].[Non-Consumable].[Household].[Electrical].[Lightbulbs].[Sunset] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[Cormorant] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[Denny] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[High Quality] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[Red Wing] [Product].[Non-Consumable].[Household].[Hardware].[Tools].[Sunset] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[Cormorant] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[Denny] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[High Quality] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[Red Wing] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Cleaners].[Sunset] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Cormorant] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Denny] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[High Quality] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Red Wing] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pot Scrubbers].[Sunset] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Cormorant] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Denny] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[High Quality] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Red Wing] [Product].[Non-Consumable].[Household].[Kitchen Products].[Pots and Pans].[Sunset] [Product].[Non-Consumable].[Household].[Kitchen Products].[Sponges].[Cormorant] [Product].[Non-Consumable].[Household].[Kitchen Products].[Sponges].[Denny] [Product].[Non-Consumable].[Household].[Kitchen Products].[Sponges].[High Quality] [Product].[Non-Consumable].[Household].[Kitchen Products].[Sponges].[Red Wing] [Product].[Non-Consumable].[Household].[Kitchen Products].[Sponges].[Sunset] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[Cormorant] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[Denny] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[High Quality] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[Red Wing] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Dishes].[Sunset] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Cormorant] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Denny] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[High Quality] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Red Wing] [Product].[Non-Consumable].[Household].[Paper Products].[Paper Wipes].[Sunset] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Cormorant] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Denny] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[High Quality] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Red Wing] [Product].[Non-Consumable].[Household].[Plastic Products].[Plastic Utensils].[Sunset] [Product].[Non-Consumable].[Periodicals].[Magazines].[Auto Magazines].[Dollar] [Product].[Non-Consumable].[Periodicals].[Magazines].[Auto Magazines].[Excel] [Product].[Non-Consumable].[Periodicals].[Magazines].[Auto Magazines].[Gauss] [Product].[Non-Consumable].[Periodicals].[Magazines].[Auto Magazines].[Mighty Good] [Product].[Non-Consumable].[Periodicals].[Magazines].[Auto Magazines].[Robust] [Product].[Non-Consumable].[Periodicals].[Magazines].[Computer Magazines].[Dollar] [Product].[Non-Consumable].[Periodicals].[Magazines].[Computer Magazines].[Excel] [Product].[Non-Consumable].[Periodicals].[Magazines].[Computer Magazines].[Gauss] [Product].[Non-Consumable].[Periodicals].[Magazines].[Computer Magazines].[Mighty Good] [Product].[Non-Consumable].[Periodicals].[Magazines].[Computer Magazines].[Robust] [Product].[Non-Consumable].[Periodicals].[Magazines].[Fashion Magazines].[Dollar] [Product].[Non-Consumable].[Periodicals].[Magazines].[Fashion Magazines].[Excel] [Product].[Non-Consumable].[Periodicals].[Magazines].[Fashion Magazines].[Gauss] [Product].[Non-Consumable].[Periodicals].[Magazines].[Fashion Magazines].[Mighty Good] [Product].[Non-Consumable].[Periodicals].[Magazines].[Fashion Magazines].[Robust] [Product].[Non-Consumable].[Periodicals].[Magazines].[Home Magazines].[Dollar] [Product].[Non-Consumable].[Periodicals].[Magazines].[Home Magazines].[Excel] [Product].[Non-Consumable].[Periodicals].[Magazines].[Home Magazines].[Gauss] [Product].[Non-Consumable].[Periodicals].[Magazines].[Home Magazines].[Mighty Good] [Product].[Non-Consumable].[Periodicals].[Magazines].[Home Magazines].[Robust] [Product].[Non-Consumable].[Periodicals].[Magazines].[Sports Magazines].[Dollar] [Product].[Non-Consumable].[Periodicals].[Magazines].[Sports Magazines].[Excel] [Product].[Non-Consumable].[Periodicals].[Magazines].[Sports Magazines].[Gauss] [Product].[Non-Consumable].[Periodicals].[Magazines].[Sports Magazines].[Mighty Good] [Product].[Non-Consumable].[Periodicals].[Magazines].[Sports Magazines].[Robust] 269.0 385.0 362.0 306.0 361.0 945.0 1206.0 1015.0 951.0 1038.0 738.0 632.0 655.0 735.0 647.0 468.0 469.0 506.0 466.0 560.0 171.0 167.0 155.0 175.0 134.0 774.0 714.0 683.0 718.0 610.0 652.0 861.0 609.0 706.0 568.0 772.0 927.0 767.0 959.0 761.0 163.0 160.0 145.0 165.0 182.0 740.0 798.0 605.0 719.0 635.0 737.0 815.0 638.0 653.0 715.0 690.0 619.0 694.0 654.0 620.0 158.0 129.0 165.0 169.0 160.0 598.0 454.0 627.0 428.0 467.0 379.0 346.0 368.0 337.0 295.0 500.0 549.0 533.0 468.0 506.0 602.0 354.0 596.0 521.0 492.0 556.0 545.0 531.0 520.0 515.0 988.0 706.0 828.0 820.0 758.0 569.0 621.0 617.0 682.0 828.0 198.0 136.0 147.0 205.0 214.0 189.0 165.0 184.0 177.0 167.0 124.0 127.0 119.0 160.0 178.0 166.0 191.0 158.0 164.0 140.0 159.0 138.0 137.0 188.0 182.0 1613.0 1589.0 1535.0 1727.0 1542.0 341.0 336.0 326.0 429.0 278.0 1027.0 911.0 1074.0 1132.0 1053.0 355.0 383.0 315.0 379.0 380.0 1632.0 1605.0 1476.0 1617.0 1488.0 309.0 336.0 335.0 344.0 308.0 308.0 282.0 373.0 390.0 299.0 317.0 372.0 374.0 374.0 346.0 559.0 494.0 498.0 529.0 508.0 719.0 612.0 605.0 764.0 639.0 168.0 160.0 204.0 196.0 150.0 507.0 519.0 524.0 591.0 487.0 501.0 555.0 492.0 525.0 531.0 808.0 789.0 670.0 941.0 924.0 201.0 163.0 110.0 173.0 167.0 160.0 140.0 165.0 149.0 169.0 680.0 736.0 677.0 655.0 659.0 665.0 501.0 484.0 526.0 556.0 826.0 671.0 681.0 632.0 650.0 601.0 478.0 555.0 473.0 478.0 567.0 482.0 476.0 525.0 530.0 803.0 736.0 555.0 551.0 665.0 618.0 465.0 532.0 441.0 513.0 1017.0 896.0 819.0 800.0 883.0 359.0 322.0 374.0 362.0 297.0 2399.0 2614.0 1997.0 2404.0 2353.0 167.0 211.0 172.0 188.0 148.0 873.0 943.0 853.0 906.0 825.0 3999.0 4701.0 3716.0 4379.0 3944.0 175.0 186.0 174.0 181.0 119.0 186.0 178.0 198.0 195.0 172.0 660.0 945.0 826.0 776.0 971.0 1281.0 1325.0 1476.0 1361.0 1357.0 300.0 354.0 365.0 370.0 367.0 563.0 757.0 617.0 673.0 722.0 396.0 570.0 501.0 522.0 503.0 1398.0 1477.0 1655.0 1428.0 1634.0 150.0 258.0 179.0 201.0 219.0 488.0 572.0 522.0 452.0 476.0 165.0 156.0 177.0 191.0 189.0 900.0 846.0 862.0 812.0 911.0 221.0 104.0 192.0 157.0 176.0 368.0 345.0 305.0 312.0 373.0 539.0 469.0 513.0 555.0 577.0 545.0 342.0 339.0 287.0 369.0 340.0 387.0 164.0 165.0 183.0 167.0 162.0 174.0 111.0 160.0 196.0 169.0 177.0 203.0 213.0 177.0 199.0 109.0 171.0 148.0 218.0 167.0 362.0 302.0 328.0 370.0 413.0 513.0 409.0 538.0 493.0 508.0 190.0 136.0 174.0 174.0 162.0 333.0 351.0 285.0 410.0 397.0 351.0 362.0 379.0 330.0 391.0 700.0 611.0 656.0 724.0 865.0 203.0 144.0 142.0 158.0 195.0 317.0 266.0 278.0 347.0 391.0 150.0 174.0 130.0 183.0 176.0 166.0 151.0 137.0 153.0 226.0 163.0 133.0 149.0 195.0 175.0 457.0 458.0 487.0 598.0 514.0 153.0 162.0 207.0 210.0 195.0 624.0 636.0 635.0 716.0 709.0 613.0 669.0 686.0 802.0 692.0 292.0 356.0 300.0 345.0 389.0 346.0 314.0 296.0 366.0 388.0 118.0 129.0 164.0 159.0 164.0 179.0 142.0 178.0 216.0 164.0 157.0 167.0 189.0 163.0 206.0 319.0 298.0 362.0 332.0 372.0 956.0 883.0 1158.0 1063.0 1060.0 407.0 458.0 491.0 548.0 573.0 184.0 154.0 170.0 170.0 131.0 171.0 134.0 202.0 170.0 169.0 202.0 156.0 174.0 120.0 146.0 220.0 211.0 178.0 176.0 147.0 217.0 170.0 200.0 170.0 152.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_55bfd7457ec379e3.xml0000644000175000017500000002755211735330606024126 0ustar drazzibdrazzib with member [Measures].[Test] as'iif ([Promotions].CurrentMember.level.name = "(All)","Top","Not Top")' Select {[Measures].[Test]} on columns, [Promotions].members on rows from [Sales] [Time] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Test] [Promotions] [Promotions].[All Promotions] [Promotions].[Bag Stuffers] [Promotions].[Best Savings] [Promotions].[Big Promo] [Promotions].[Big Time Discounts] [Promotions].[Big Time Savings] [Promotions].[Bye Bye Baby] [Promotions].[Cash Register Lottery] [Promotions].[Coupon Spectacular] [Promotions].[Dimes Off] [Promotions].[Dollar Cutters] [Promotions].[Dollar Days] [Promotions].[Double Down Sale] [Promotions].[Double Your Savings] [Promotions].[Fantastic Discounts] [Promotions].[Free For All] [Promotions].[Go For It] [Promotions].[Green Light Days] [Promotions].[Green Light Special] [Promotions].[High Roller Savings] [Promotions].[I Cant Believe It Sale] [Promotions].[Money Grabbers] [Promotions].[Money Savers] [Promotions].[Mystery Sale] [Promotions].[No Promotion] [Promotions].[One Day Sale] [Promotions].[Pick Your Savings] [Promotions].[Price Cutters] [Promotions].[Price Destroyers] [Promotions].[Price Savers] [Promotions].[Price Slashers] [Promotions].[Price Smashers] [Promotions].[Price Winners] [Promotions].[Sale Winners] [Promotions].[Sales Days] [Promotions].[Sales Galore] [Promotions].[Save-It Sale] [Promotions].[Saving Days] [Promotions].[Savings Galore] [Promotions].[Shelf Clearing Days] [Promotions].[Shelf Emptiers] [Promotions].[Super Duper Savers] [Promotions].[Super Savers] [Promotions].[Super Wallet Savers] [Promotions].[Three for One] [Promotions].[Tip Top Savings] [Promotions].[Two Day Sale] [Promotions].[Two for One] [Promotions].[Unbeatable Price Savers] [Promotions].[Wallet Savers] [Promotions].[Weekend Markdown] [Promotions].[You Save Days] Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top Not Top mondrian-3.4.1/testsrc/queryFiles/queryTest_c1eef8cf112b8551_TOPCOUNT.xml0000644000175000017500000001511611735330606025510 0ustar drazzibdrazzib SELECT {Measures.[Sales Count], Measures.[Store Cost], Measures.[Store Sales], Measures.[Unit Sales], Measures.[Customer Count]} ON COLUMNS, TOPCOUNT({[Store].[Store City].MEMBERS}, 12, Measures.[Sales Count]) ON ROWS FROM [Sales] [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Sales Count] [Measures].[Store Cost] [Measures].[Store Sales] [Measures].[Unit Sales] [Measures].[Customer Count] [Store] [Store].[USA].[OR].[Salem] [Store].[USA].[WA].[Tacoma] [Store].[USA].[OR].[Portland] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Spokane] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[WA].[Yakima] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Walla Walla] 13347.0 34823.5566 87218.28 41580.0 474.0 11184.0 29959.2813 74843.96 35257.0 278.0 8264.0 21948.944 55058.79 26079.0 563.0 8207.0 21771.536 54545.28 25663.0 1147.0 8095.0 21713.5328 54431.14 25635.0 962.0 7956.0 20956.8025 52644.07 25011.0 906.0 7876.0 21121.9631 52896.3 24576.0 179.0 7397.0 19795.491 49634.46 23591.0 84.0 6815.0 18266.4404 45750.24 21333.0 1059.0 3652.0 9713.813 24329.23 11491.0 95.0 1380.0 1896.6174 4739.23 2237.0 190.0 1339.0 1880.3396 4705.97 2203.0 96.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_6d042ea3df09ad4_Item.xml0000644000175000017500000000542011735330606025037 0ustar drazzibdrazzib SELECT {Measures.[Sales Count], Measures.[Store Cost], Measures.[Store Sales], Measures.[Unit Sales], Measures.[Customer Count]} ON COLUMNS FROM [Sales] WHERE( {([Customers].[USA].[OR].[Lebanon].[Mary Frances Christian])}.Item(0).Item(0) ) [Time] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[USA].[OR].[Lebanon].[Mary Frances Christian] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Sales Count] [Measures].[Store Cost] [Measures].[Store Sales] [Measures].[Unit Sales] [Measures].[Customer Count] 16.0 39.8567 102.78 49.0 1.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_2edf32fe74d61e16.xml0000644000175000017500000000503711735330606024163 0ustar drazzibdrazzib select { [Measures].[Customer Count] } on columns, filter(descendants([Store].[USA]), ([Measures].[Customer Count], [Time].[1997].[Q3]) &gt;= 1000 and ([Measures].[Customer Count]) &lt;= 3000) on rows from [Sales] where ([Time].[1997].[Q3]) [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997].[Q3] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Customer Count] [Store] [Store].[USA].[CA] [Store].[USA].[WA] 1196.0 1113.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_nextMember_6be65816fbe41c36.xml0000644000175000017500000000436311735330606026274 0ustar drazzibdrazzib SELECT {[Education Level].[Bachelors Degree].NextMember} ON AXIS(0), {[Time].[1997].[Q1].[1].NextMember} ON AXIS(1) FROM [Sales] [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Education Level] [Education Level].[Graduate Degree] [Time] [Time].[1997].[Q1].[2] 1175.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_parent_7889182ac3a99824.xml0000644000175000017500000000557411735330606025262 0ustar drazzibdrazzib SELECT {Crossjoin({[Store].[Canada].Parent}, {[Time].[1997].Children})} ON AXIS(0), {[Product].[Food].[Eggs].Parent} ON AXIS(1) FROM [Sales] WHERE ([Customers].[USA].[OR].[Salem].Parent, [Gender].[M]) [Measures] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[USA].[OR] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[M] [Store] [Time] [Store].[All Stores] [Time].[1997].[Q1] [Store].[All Stores] [Time].[1997].[Q2] [Store].[All Stores] [Time].[1997].[Q3] [Store].[All Stores] [Time].[1997].[Q4] [Product] [Product].[Food] 7121.0 5470.0 6287.0 6062.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_fdd91d27376831d1.xml0000644000175000017500000001254011735330606024023 0ustar drazzibdrazzib WITH MEMBER [Measures].[Qualified Count] AS 'COUNT(FILTER(DESCENDANTS([Customers].[USA].[CA], [Customers].[City]), ([Measures].[Store Sales]) &gt; 6000 OR ([Measures].[Unit Sales]) &gt; 3000))' MEMBER [Measures].[Qualified Sales] AS 'SUM(FILTER(DESCENDANTS([Customers].[USA].[CA], [Customers].[City]), ([Measures].[Store Sales]) &gt; 6000 OR ([Measures].[Unit Sales]) &gt; 3000), ([Measures].[Store Sales]))' SELECT {[Measures].[Store Sales], [Measures].[Unit Sales], [Measures].[Qualified Count], [Measures].[Qualified Sales]} ON COLUMNS, {FILTER(DESCENDANTS([Customers].[USA].[CA], [Customers].[City]), ([Measures].[Store Sales]) &gt; 6000 OR ([Measures].[Unit Sales]) &gt; 3000)} ON ROWS FROM [Sales] [Time] [Product] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Store Sales] [Measures].[Unit Sales] [Measures].[Qualified Count] [Measures].[Qualified Sales] [Customers] [Customers].[USA].[CA].[Bellflower] [Customers].[USA].[CA].[Beverly Hills] [Customers].[USA].[CA].[Burbank] [Customers].[USA].[CA].[Chula Vista] [Customers].[USA].[CA].[Downey] [Customers].[USA].[CA].[Glendale] [Customers].[USA].[CA].[Long Beach] [Customers].[USA].[CA].[Newport Beach] 6633.97 3106.0 8.0 53258.37 6194.37 2907.0 8.0 53258.37 6577.33 3086.0 8.0 53258.37 6284.3 2999.0 8.0 53258.37 7367.06 3440.0 8.0 53258.37 7082.91 3284.0 8.0 53258.37 6422.37 2973.0 8.0 53258.37 6696.06 3098.0 8.0 53258.37 mondrian-3.4.1/testsrc/queryFiles/queryTest_238a75ca63f7b0da.xml0000644000175000017500000001272111735330606024151 0ustar drazzibdrazzib select {[Measures].[Store Invoice], [Measures].[Supply Time]} on columns, descendants([Store].[USA], [Store].[Store City], Self_and_Before) on rows from [Warehouse] [Store Size in SQFT] [Store Type] [Time] [Warehouse] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Time].[1997] [Warehouse].[All Warehouses] [Measures] [Measures].[Store Invoice] [Measures].[Supply Time] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Francisco] [Store].[USA].[OR] [Store].[USA].[OR].[Portland] [Store].[USA].[OR].[Salem] [Store].[USA].[WA] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Tacoma] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Yakima] 102278.4089 10425.0 29607.2994 3100.0 #Missing #Missing 5132.8974 559.0 11634.9186 1279.0 11850.663 1182.0 988.8204 80.0 20194.0007 2051.0 4042.9596 401.0 16151.0411 1650.0 52477.1088 5274.0 1049.4587 216.0 11659.6249 1130.0 11517.1251 1193.0 5781.9634 546.0 16100.8297 1578.0 1093.7695 83.0 5274.3375 528.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_45f76a3fb635a121.xml0000644000175000017500000000674011735330606024015 0ustar drazzibdrazzib select { [Time].[1997].children } on columns, intersect( {[Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods]}, {[Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods]}) on rows from [Sales] where ( [Measures].[Store Cost] ) [Measures] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Store Cost] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] [Product] [Product].[Food].[Baked Goods] [Product].[Food].[Baking Goods] [Product].[Food].[Breakfast Foods] 1609.8117 1453.4103 1669.9533 1830.9166 3804.7026 3633.6761 3772.9881 4159.2433 680.1564 652.8166 661.5138 762.3098 mondrian-3.4.1/testsrc/queryFiles/queryTest_1662791d39c739_cj_modified.xml0000644000175000017500000001010611735330606025753 0ustar drazzibdrazzib select {[Measures].[Store Sales], [Measures].[Unit Sales]} on columns, {[Time].[Time].children * [Store].children} on rows from [Warehouse and Sales] [Measures] [Measures].[Store Sales] [Measures].[Unit Sales] [Time] [Store] [Time].[1997].[Q1] [Store].[Canada] [Time].[1997].[Q1] [Store].[Mexico] [Time].[1997].[Q1] [Store].[USA] [Time].[1997].[Q2] [Store].[Canada] [Time].[1997].[Q2] [Store].[Mexico] [Time].[1997].[Q2] [Store].[USA] [Time].[1997].[Q3] [Store].[Canada] [Time].[1997].[Q3] [Store].[Mexico] [Time].[1997].[Q3] [Store].[USA] [Time].[1997].[Q4] [Store].[Canada] [Time].[1997].[Q4] [Store].[Mexico] [Time].[1997].[Q4] [Store].[USA] #Missing #Missing #Missing #Missing 139628.35 66291.0 #Missing #Missing #Missing #Missing 132666.27 62610.0 #Missing #Missing #Missing #Missing 140271.89 65848.0 #Missing #Missing #Missing #Missing 152671.62 72024.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_32bf66a6519ec827_TOPPERCENT.xml0000644000175000017500000001150111735330606025564 0ustar drazzibdrazzib SELECT [Store Type].MEMBERS ON COLUMNS, TOPPERCENT({[Store].[Store City].MEMBERS}, 50, Measures.[Sales Count]) ON ROWS FROM [Sales] WHERE (Measures.[Unit Sales]) [Measures] [Time] [Product] [Store Size in SQFT] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store Type] [Store Type].[All Store Types] [Store Type].[Deluxe Supermarket] [Store Type].[Gourmet Supermarket] [Store Type].[HeadQuarters] [Store Type].[Mid-Size Grocery] [Store Type].[Small Grocery] [Store Type].[Supermarket] [Store] [Store].[USA].[OR].[Salem] [Store].[USA].[WA].[Tacoma] [Store].[USA].[OR].[Portland] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] 41580.0 41580.0 #Missing #Missing #Missing #Missing #Missing 35257.0 35257.0 #Missing #Missing #Missing #Missing #Missing 26079.0 #Missing #Missing #Missing #Missing #Missing 26079.0 25663.0 #Missing #Missing #Missing #Missing #Missing 25663.0 25635.0 #Missing #Missing #Missing #Missing #Missing 25635.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_d8ade6381515fa88.xml0000644000175000017500000000562011735330606024110 0ustar drazzibdrazzib with member [Promotions].[Std Dev Store Sales] as 'StDev([Promotions].members, [Measures].[Store Sales])' Select {[Promotions].[Std Dev Store Sales]} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Std Dev Store Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 30067.26769334517 31445.812035886665 32364.046717463083 36081.97459803241 mondrian-3.4.1/testsrc/queryFiles/queryTest_lead_fc7c1724ebcd5e32.xml0000644000175000017500000001003011735330606025210 0ustar drazzibdrazzib SELECT {Store, Store.LastChild, Store.[All Stores].LastChild, Store.[USA].[CA].[San Francisco].LastChild} ON Columns, {Product, Product.LastChild, Product.[Drink].LastChild, Product.[Food].[Deli].[Meat].[Bologna].[American].LastChild } ON Rows FROM [Sales] WHERE([Measures].[Unit Sales], [Time].[1997], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[All Store Types], [Promotions].[You Save Days], [Customers].[All Customers], [Education Level].[All Education Levels], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes], [Promotion Media].[All Media], [Gender].[F]) [Measures] [Time] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[You Save Days] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[F] [Store] [Store].[All Stores] [Store].[USA] [Store].[USA] [Store].[USA].[CA].[San Francisco].[Store 14] [Product] [Product].[All Products] [Product].[Non-Consumable] [Product].[Drink].[Dairy] [Product].[Food].[Deli].[Meat].[Bologna].[American].[American Pimento Loaf] 1634.0 1634.0 1634.0 #Missing 345.0 345.0 345.0 #Missing 36.0 36.0 36.0 #Missing 6.0 6.0 6.0 #Missing mondrian-3.4.1/testsrc/queryFiles/queryTest_lastChild_4c923cd7431fde9.xml0000644000175000017500000000736111735330606026014 0ustar drazzibdrazzib SELECT {Store.[USA].LastSibling, Store.[USA].[CA].LastSibling, Store.[USA].[CA], Store.[USA].[CA].[Los Angeles]} on ROWS, {Time.[1997], Time.[1997].[Q3].LastSibling, Time.[1997].[Q3].[9].LastSibling} on COLUMNS FROM [Sales] WHERE([Measures].[Unit Sales], [Product].[All Products], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[All Store Types], [Promotions].[All Promotions], [Customers].[USA].[CA].[Woodland Hills], [Education Level].[All Education Levels], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes], [Promotion Media].[All Media], [Gender].[All Gender]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[USA].[CA].[Woodland Hills] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997] [Time].[1997].[Q4] [Time].[1997].[Q3].[9] [Store] [Store].[USA] [Store].[USA].[WA] [Store].[USA].[CA] [Store].[USA].[CA].[Los Angeles] 2516.0 704.0 247.0 #Missing #Missing #Missing 2516.0 704.0 247.0 1257.0 371.0 120.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_353e823fecb0474d.xml0000644000175000017500000000725211735330606024101 0ustar drazzibdrazzib select { [Time].[1997].children } on columns, intersect( {[Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods], [Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods]}, {[Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods], [Product].[Food].[Baked Goods], [Product].[Food].[Baking Goods], [Product].[Food].[Breakfast Foods]}) on rows from [Sales] where ( [Measures].[Store Cost] ) [Measures] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Store Cost] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] [Product] [Product].[Food].[Baked Goods] [Product].[Food].[Baking Goods] [Product].[Food].[Breakfast Foods] 1609.8117 1453.4103 1669.9533 1830.9166 3804.7026 3633.6761 3772.9881 4159.2433 680.1564 652.8166 661.5138 762.3098 mondrian-3.4.1/testsrc/queryFiles/queryTest_4e15fa6d6ab1e4.xml0000644000175000017500000001034711735330606024003 0ustar drazzibdrazzib with member [Store].[Sum CA Store Sales] as 'Sum([Store].[USA].[CA].children, [Measures].[Store Sales])' Select { [Store].[Sum CA Store Sales],[Store].[USA].[CA].children} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] WHERE ([Measures].[Store Sales]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Store Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[Sum CA Store Sales] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Francisco] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 36175.2 #Missing 8203.89 13736.97 13297.83 936.51 38396.75 #Missing 12597.15 11684.44 12977.28 1137.88 39394.05 #Missing 10108.72 13868.41 14242.59 1174.33 45201.84 #Missing 14840.48 15255.46 13913.44 1192.46 mondrian-3.4.1/testsrc/queryFiles/queryTest_58e91375c6bbf928.xml0000644000175000017500000000473511735330606024045 0ustar drazzibdrazzib SELECT {[Time].[1997].[Q1]} ON COLUMNS, bottomsum(filter( {[Store].[USA].[CA].children, [Store].[USA].[OR].children, [Store].[USA].[WA].children}, [Measures].[Unit Sales] &gt; 0), 1000, [Measures].[Store Sales]) ON ROWS FROM [Sales] WHERE([Measures].[Unit Sales]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Store] [Store].[USA].[CA].[San Francisco] 439.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_lead_a9edf143dab0885b.xml0000644000175000017500000000624711735330606025230 0ustar drazzibdrazzib SELECT {Store, Store.[Canada], Store.[Canada].Lead(2)} on ROWS, {Time.[1997].[Q2], Time.[1997].[Q2].Lead(-1)} on COLUMNS FROM [Sales] WHERE([Measures].[Unit Sales], [Product].[All Products], [Store Size in SQFT].[All Store Size in SQFTs], [Store Type].[Mid-Size Grocery], [Promotions].[All Promotions], [Customers].[All Customers], [Education Level].[All Education Levels], [Marital Status].[All Marital Status], [Yearly Income].[All Yearly Incomes], [Promotion Media].[All Media], [Gender].[All Gender]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[Mid-Size Grocery] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q2] [Time].[1997].[Q1] [Store] [Store].[All Stores] [Store].[Canada] [Store].[USA] 2860.0 3096.0 #Missing #Missing 2860.0 3096.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_8173fc75888fd39.xml0000644000175000017500000000507011735330606023703 0ustar drazzibdrazzib with member [Measures].[Test Measure] as '[Measures].[Customer Count] * 2 - [Measures].[Sales Count]' select { [Measures].[Test Measure] } on columns, filter(descendants([Store].[USA]), ([Measures].[Test Measure]) &lt; -5000) on rows from [Sales] where ([Time].[1997].[Q2]) [Time] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Time].[1997].[Q2] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Measures] [Measures].[Test Measure] [Store] [Store].[USA] [Store].[USA].[WA] -14422.0 -7500.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_ddea962bb9cb9913_order.xml0000644000175000017500000000455411735330606025445 0ustar drazzibdrazzib Select Order( [Product].[All Products].children,[Measures].[MeasuresLevel].[Unit Sales],DESC) On columns From [Sales] where ([Measures].[Unit Sales]) [Measures] [Time] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Product] [Product].[Food] [Product].[Non-Consumable] [Product].[Drink] 191940.0 50236.0 24597.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_45523f9f92a6e0.xml0000644000175000017500000000637011735330606023600 0ustar drazzibdrazzib select { [Time].[1997].children } on columns, intersect( [Store].[USA].children, descendants([Store].[USA]) ) on rows from [Sales] where ( [Product].[Food] ) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[Food] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] [Store] [Store].[USA].[CA] [Store].[USA].[OR] [Store].[USA].[WA] 12064.0 13074.0 13135.0 15383.0 13737.0 10726.0 12325.0 11749.0 22008.0 21025.0 21980.0 24734.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_6869259e81243129_UniqueName.xml0000644000175000017500000002103711735330606025665 0ustar drazzibdrazzib SELECT [Store Type].MEMBERS ON COLUMNS, FILTER([Store City].MEMBERS , [Store City].CurrentMember.PARENT.PARENT.UniqueName = "[Store].[USA]") ON ROWS FROM [Sales] WHERE (Measures.[Sales Count], [Time].[1997]) [Measures] [Time] [Product] [Store Size in SQFT] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Sales Count] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store Type] [Store Type].[All Store Types] [Store Type].[Deluxe Supermarket] [Store Type].[Gourmet Supermarket] [Store Type].[HeadQuarters] [Store Type].[Mid-Size Grocery] [Store Type].[Small Grocery] [Store Type].[Supermarket] [Store] [Store].[USA].[CA].[Alameda] [Store].[USA].[CA].[Beverly Hills] [Store].[USA].[CA].[Los Angeles] [Store].[USA].[CA].[San Diego] [Store].[USA].[CA].[San Francisco] [Store].[USA].[OR].[Portland] [Store].[USA].[OR].[Salem] [Store].[USA].[WA].[Bellingham] [Store].[USA].[WA].[Bremerton] [Store].[USA].[WA].[Seattle] [Store].[USA].[WA].[Spokane] [Store].[USA].[WA].[Tacoma] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Yakima] #Missing #Missing #Missing #Missing #Missing #Missing #Missing 6815.0 #Missing 6815.0 #Missing #Missing #Missing #Missing 8207.0 #Missing #Missing #Missing #Missing #Missing 8207.0 8095.0 #Missing #Missing #Missing #Missing #Missing 8095.0 1325.0 #Missing #Missing #Missing #Missing 1325.0 #Missing 8264.0 #Missing #Missing #Missing #Missing #Missing 8264.0 13347.0 13347.0 #Missing #Missing #Missing #Missing #Missing 1380.0 #Missing #Missing #Missing #Missing 1380.0 #Missing 7876.0 #Missing #Missing #Missing #Missing #Missing 7876.0 7956.0 #Missing #Missing #Missing #Missing #Missing 7956.0 7397.0 #Missing #Missing #Missing #Missing #Missing 7397.0 11184.0 11184.0 #Missing #Missing #Missing #Missing #Missing 1339.0 #Missing #Missing #Missing #Missing 1339.0 #Missing 3652.0 #Missing #Missing #Missing 3652.0 #Missing #Missing mondrian-3.4.1/testsrc/queryFiles/queryTest_avg_852d15ef306e7178.xml0000644000175000017500000000661111735330606024605 0ustar drazzibdrazzib WITH MEMBER [Promotions].[Average] AS 'AVG({[Bag Stuffers], [Best Savings]}, [Bag Stuffers] * 3)' SELECT {[Promotions].[Bag Stuffers], [Promotions].[Best Savings], [Promotions].[Average]} ON AXIS(0), {[Store].[USA], [Store].[USA].Children} ON AXIS(1) FROM [Sales] [Measures] [Time] [Product] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Bag Stuffers] [Promotions].[Best Savings] [Promotions].[Average] [Store] [Store].[USA] [Store].[USA].[CA] [Store].[USA].[OR] [Store].[USA].[WA] 901.0 2081.0 2703.0 #Missing 1608.0 #Missing 617.0 #Missing 1851.0 284.0 473.0 852.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_c31ffc2bfb11f8.xml0000644000175000017500000000752111735330606024062 0ustar drazzibdrazzib SELECT {[Time].[1997].[Q1], [Time].[1997].[Q2], [Time].[1997].[Q3], [Time].[1997].[Q4]} ON COLUMNS, bottomcount({[Store].[USA].[CA].[Beverly Hills], [Store].[USA].[CA].[Los Angeles], [Store].[USA].[CA].[San Diego], [Store].[USA].[CA].[San Francisco], [Store].[USA].[OR].[Portland], [Store].[All Stores].[USA].[OR].[Salem], [Store].[All Stores].[USA].[WA].[Bellingham], [Store].[All Stores].[USA].[WA].[Bremerton], [Store].[All Stores].[USA].[WA].[Seattle], [Store].[All Stores].[USA].[WA].[Spokane], [Store].[All Stores].[USA].[WA].[Tacoma], [Store].[All Stores].[USA].[WA].[Walla Walla], [Store].[All Stores].[USA].[WA].[Yakima]}, 3, [Measures].[Store Sales]) ON ROWS FROM [Sales] WHERE([Measures].[Unit Sales]) [Measures] [Product] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] [Store] [Store].[USA].[CA].[San Francisco] [Store].[USA].[WA].[Walla Walla] [Store].[USA].[WA].[Bellingham] 439.0 536.0 557.0 585.0 500.0 542.0 509.0 652.0 518.0 510.0 497.0 712.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_3ced6f4514a88a80.xml0000644000175000017500000000542311735330606024102 0ustar drazzibdrazzib select {[Marital Status].[M], [Marital Status].[S]} on columns, {[Product].[All Products].children} on rows from [Sales] [Measures] [Time] [Store] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Marital Status] [Marital Status].[M] [Marital Status].[S] [Product] [Product].[Drink] [Product].[Food] [Product].[Non-Consumable] 12176.0 12421.0 94929.0 97011.0 24691.0 25545.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_count_9697281acf6f69d.xml0000644000175000017500000000772111735330606025175 0ustar drazzibdrazzib WITH MEMBER [Product].[Product Count] AS '{[Product].[All Products], [Product].[All Products].Children}.Count' SELECT {[Store].[All Stores], [Store].[All Stores].Children} ON AXIS(0), {[Product].[All Products], [Product].[All Products].Children, [Product].[Product Count]} ON AXIS(1) FROM [Sales] [Measures] [Time] [Store Size in SQFT] [Store Type] [Promotions] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Time].[1997] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Promotions].[All Promotions] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Store] [Store].[All Stores] [Store].[Canada] [Store].[Mexico] [Store].[USA] [Product] [Product].[All Products] [Product].[Drink] [Product].[Food] [Product].[Non-Consumable] [Product].[Product Count] 266773.0 #Missing #Missing 266773.0 24597.0 #Missing #Missing 24597.0 191940.0 #Missing #Missing 191940.0 50236.0 #Missing #Missing 50236.0 4.0 4.0 4.0 4.0 mondrian-3.4.1/testsrc/queryFiles/queryTest_6744da12d716d072.xml0000644000175000017500000000546711735330606023743 0ustar drazzibdrazzib with member [Promotions].[Min Unit Sales] as 'Median([Promotions].members)' Select { [Promotions].[Min Unit Sales]} on Axis(0), {[Time].[1997].children} on Axis(1) from [Sales] [Measures] [Product] [Store] [Store Size in SQFT] [Store Type] [Customers] [Education Level] [Marital Status] [Yearly Income] [Promotion Media] [Gender] [Measures].[Unit Sales] [Product].[All Products] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Customers].[All Customers] [Education Level].[All Education Levels] [Marital Status].[All Marital Status] [Yearly Income].[All Yearly Incomes] [Promotion Media].[All Media] [Gender].[All Gender] [Promotions] [Promotions].[Min Unit Sales] [Time] [Time].[1997].[Q1] [Time].[1997].[Q2] [Time].[1997].[Q3] [Time].[1997].[Q4] 570.0 536.5 485.5 573.5 mondrian-3.4.1/testsrc/queryFiles/queryTest_5cb1905047f8199a_Cousin.xml0000644000175000017500000000246211735330606025265 0ustar drazzibdrazzib SELECT {Cousin([Time].[1997].[Q1],[Time].[1998])} ON COLUMNS FROM [Warehouse] WHERE ([Measures].[Units Shipped]) [Measures] [Store] [Store Size in SQFT] [Store Type] [Warehouse] [Measures].[Units Shipped] [Store].[All Stores] [Store Size in SQFT].[All Store Size in SQFTs] [Store Type].[All Store Types] [Warehouse].[All Warehouses] [Time] [Time].[1998].[Q1] #Missing mondrian-3.4.1/mondrian.properties0000644000175000017500000011152111744246550017160 0ustar drazzibdrazzib# This software is subject to the terms of the Eclipse Public License v1.0 # Agreement, available at the following URL: # http://www.eclipse.org/legal/epl-v10.html. # You must accept the terms of that agreement to use this software. # # Copyright (C) 2001-2005 Julian Hyde # Copyright (C) 2005-2011 Pentaho and others # All Rights Reserved. ############################################################################### # String property that is the AggRule element's tag value. # # Normally, this property is not set by a user. # #mondrian.rolap.aggregates.rule.tag=default ############################################################################### # String property containing the name of the file which defines the # rules for recognizing an aggregate table. Can be either a resource in # the Mondrian jar or a URL. # # The default value is "/DefaultRules.xml", which is in the # mondrian.rolap.aggmatcher package in Mondrian.jar. # # Normally, this property is not set by a user. # #mondrian.rolap.aggregates.rules=/DefaultRules.xml ############################################################################### # Alerting action to take in case native evaluation of a function is # enabled but not supported for that function's usage in a particular # query. (No alert is ever raised in cases where native evaluation would # definitely have been wasted effort.) # # Recognized actions: # # # * OFF: do nothing (default action, also used if # unrecognized action is specified) # * WARN: log a warning to RolapUtil logger # * ERROR: throw an instance of # NativeEvaluationUnsupportedException # #mondrian.native.unsupported.alert=OFF ############################################################################### # Boolean property that controls whether the MDX parser resolves uses # case-sensitive matching when looking up identifiers. The default is # false. # #mondrian.olap.case.sensitive=false ############################################################################### # Property that contains the URL of the catalog to be used by # mondrian.tui.CmdRunner and XML/A Test. # #mondrian.catalogURL= ############################################################################### # Boolean property that controls whether aggregate tables # are ordered by their volume or row count. # # If true, Mondrian uses the aggregate table with the smallest volume # (number of rows multiplied by number of columns); if false, Mondrian # uses the aggregate table with the fewest rows. # #mondrian.rolap.aggregates.ChooseByVolume=false ############################################################################### # Boolean property that controls whether sibling members are # compared according to order key value fetched from their ordinal # expression. The default is false (only database ORDER BY is used). # #mondrian.rolap.compareSiblingsByOrderKey=false ############################################################################### # Property that defines # when to apply the crossjoin optimization algorithm. # # If a crossjoin input list's size is larger than this property's # value and the axis has the "NON EMPTY" qualifier, then # the crossjoin non-empty optimizer is applied. # Setting this value to '0' means that for all crossjoin # input lists in non-empty axes will have the optimizer applied. # On the other hand, if the value is set larger than any possible # list, say Integer.MAX_VALUE, then the optimizer # will never be applied. # #mondrian.olap.fun.crossjoin.optimizer.size=0 ############################################################################### # Property that defines # the name of the plugin class that resolves data source names to # javax.sql.DataSource objects. The class must implement the # mondrian.spi.DataSourceResolver interface. If not specified, # the default implementation uses JNDI to perform resolution. # # Example: # mondrian.spi.dataSourceResolverClass=mondrian.spi.impl.JndiDataSourceResolver # #mondrian.spi.dataSourceResolverClass= ############################################################################### # Boolean property that controls whether a RolapStar's # aggregate data cache is cleared after each query. # If true, no RolapStar will cache aggregate data from one # query to the next (the cache is cleared after each query). # #mondrian.rolap.star.disableCaching=false ############################################################################### # Property that controls whether aggregation cache hit / miss # counters will be enabled. # # Note that this will affect performance due to existence of sync # blocks. # # @deprecated This property is no longer used, and will be removed in # mondrian-4.0. # #mondrian.rolap.agg.enableCacheHitCounters=false ############################################################################### # If disabled, Mondrian will throw an exception if someone attempts to # perform a drillthrough of any kind. # #mondrian.drillthrough.enable=true ############################################################################### # Boolean property that controls whether to use a cache for frequently # evaluated expressions. With the cache disabled, an expression like # Rank([Product].CurrentMember, # Order([Product].MEMBERS, [Measures].[Unit Sales])) would perform # many redundant sorts. The default is true. # #mondrian.expCache.enable=true ############################################################################### # Property that defines # whether to generate SQL queries using the GROUPING SETS # construct for rollup. By default it is not enabled. # # Ignored on databases which do not support the # GROUPING SETS construct (see # mondrian.spi.Dialect#supportsGroupingSets). # #mondrian.rolap.groupingsets.enable=false ############################################################################### # If enabled some NON EMPTY CrossJoin will be computed in SQL. # #mondrian.native.crossjoin.enable=true ############################################################################### # If enabled some Filter() will be computed in SQL. # #mondrian.native.filter.enable=true ############################################################################### # If enabled some NON EMPTY set operations like member.children, # level.members and member descendants will be computed in SQL. # #mondrian.native.nonempty.enable=true ############################################################################### # If enabled some TopCount will be computed in SQL. # #mondrian.native.topcount.enable=true ############################################################################### # Boolean property that controls whether each query axis implicit has the # NON EMPTY option set. The default is false. # #mondrian.rolap.nonempty=false ############################################################################### # Property that determines whether to cache RolapCubeMember objects, # each of which associates a member of a shared hierarchy with a # particular cube in which it is being used. # # The default is true, that is, use a cache. If you wish to use # the member cache control aspects of mondrian.olap.CacheControl, # you must set this property to false. # # RolapCubeMember has recently become more lightweight to # construct, and we may obsolete this cache and this # property. # #mondrian.rolap.EnableRolapCubeMemberCache=true ############################################################################### # If enabled, first row in the result of an XML/A drill-through request # will be filled with the total count of rows in underlying database. # #mondrian.xmla.drillthroughTotalCount.enable=true ############################################################################### # Boolean property that controls whether to notify the Mondrian system # when a MondrianProperties property value changes. # # This allows objects dependent on Mondrian properties to react (that # is, reload), when a given property changes via, say, # MondrianProperties.instance().populate(null) or # MondrianProperties.instance().QueryLimit.set(50). # #mondrian.olap.triggers.enable=true ############################################################################### # Property that defines # the name of the class used to compile scalar expressions. # # If the value is # non-null, it is used by the ExpCompiler.Factory # to create the implementation. # # To test that for all test MDX queries that all functions can # handle requests for ITERABLE, LIST and MUTABLE_LIST evalutation # results, use the following: # # mondrian.calc.ExpCompiler.class=mondrian.olap.fun.ResultStyleCompiler # #mondrian.calc.ExpCompiler.class= ############################################################################### # If this property is true, when looking for native evaluation of an # expression, Mondrian will expand non-native sub-expressions into # lists of members. # #mondrian.native.ExpandNonNative=false ############################################################################### # Property that defines # whether to generate joins to filter out members in a snowflake # dimension that do not have any children. # # If true (the default), some queries to query members of high # levels snowflake dimensions will be more expensive. If false, and if # there are rows in an outer snowflake table that are not referenced by # a row in an inner snowflake table, then some queries will return members # that have no children. # # Our recommendation, for best performance, is to remove rows outer # snowflake tables are not referenced by any row in an inner snowflake # table, during your ETL process, and to set this property to # false. # #mondrian.rolap.FilterChildlessSnowflakeMembers=true ############################################################################### # Property containing the JDBC URL of the FoodMart database. # The default value is to connect to an ODBC data source called # "MondrianFoodMart". # # To run the test suite, first load the FoodMart data set into the # database of your choice. Then set the driver.classpath, # mondrian.foodmart.jdbcURL and mondrian.jdbcDrivers properties, by # uncommenting and modifying one of the sections below. # Put the JDBC driver jar into mondrian/testlib. # # Here are example property settings for various databases. # # ### Derby: needs user and password ### # # mondrian.foodmart.jdbcURL=jdbc:derby:demo/derby/foodmart # mondrian.foodmart.jdbcUser=sa # mondrian.foodmart.jdbcPassword=sa # mondrian.jdbcDrivers=org.apache.derby.jdbc.EmbeddedDriver # driver.classpath=testlib/derby.jar # # ### FireBirdSQL ### # # mondrian.foodmart.jdbcURL=jdbc:firebirdsql:localhost/3050:/mondrian/foodmart.gdb # mondrian.jdbcDrivers=org.firebirdsql.jdbc.FBDriver # driver.classpath=/jdbc/fb/firebirdsql-full.jar # # ### Greenplum (similar to Postgres) ### # # mondrian.foodmart.jdbcURL=jdbc:postgresql://localhost/foodmart?user=gpadmin&password=xxxx # mondrian.foodmart.jdbcUser=foodmart # mondrian.foodmart.jdbcPassword=foodmart # mondrian.jdbcDrivers=org.postgresql.Driver # driver.classpath=lib/postgresql-8.2-504.jdbc3.jar # # ### LucidDB (see instructions) ### # # mondrian.foodmart.jdbcURL=jdbc:luciddb:http://localhost # mondrian.foodmart.jdbcUser=foodmart # mondrian.jdbcDrivers=org.luciddb.jdbc.LucidDbClientDriver # driver.classpath=/path/to/luciddb/plugin/LucidDbClient.jar # # ### Oracle (needs user and password) ### # # oracle.home=G:/oracle/product/10.1.0/Db_1 # mondrian.foodmart.jdbcURL.oracle=jdbc:oracle:thin:@//host:port/service_name # mondrian.foodmart.jdbcURL=jdbc:oracle:thin:foodmart/foodmart@//stilton:1521/orcl # mondrian.foodmart.jdbcURL=jdbc:oracle:oci8:foodmart/foodmart@orcl # mondrian.foodmart.jdbcUser=FOODMART # mondrian.foodmart.jdbcPassword=oracle # mondrian.jdbcDrivers=oracle.jdbc.OracleDriver # driver.classpath=/home/jhyde/open/mondrian/lib/ojdbc14.jar # # ### ODBC (Microsoft Access) ### # # mondrian.foodmart.jdbcURL=jdbc:odbc:MondrianFoodMart # mondrian.jdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver # driver.classpath= # # ### Hypersonic ### # # mondrian.foodmart.jdbcURL=jdbc:hsqldb:demo/hsql/FoodMart # mondrian.jdbcDrivers=org.hsqldb.jdbcDriver # driver.classpath=xx # # ### MySQL: can have user and password set in JDBC URL ### # # mondrian.foodmart.jdbcURL=jdbc:mysql://localhost/foodmart?user=foodmart&password=foodmart # mondrian.foodmart.jdbcURL=jdbc:mysql://localhost/foodmart # mondrian.foodmart.jdbcUser=foodmart # mondrian.foodmart.jdbcPassword=foodmart # mondrian.jdbcDrivers=com.mysql.jdbc.Driver # driver.classpath=D:/mysql-connector-3.1.12 # # ### Infobright ### # As MySQL. (Infobright uses a MySQL driver, version 5.1 and later.) # # ### Ingres ### # # mondrian.foodmart.jdbcURL=jdbc:ingres://192.168.200.129:II7/MondrianFoodMart;LOOP=on;AUTO=multi;UID=ingres;PWD=sergni # mondrian.jdbcDrivers=com.ingres.jdbc.IngresDriver # driver.classpath=c:/ingres2006/ingres/lib/iijdbc.jar # # ### Postgres: needs user and password ### # # mondrian.foodmart.jdbcURL=jdbc:postgresql://localhost/FM3 # mondrian.foodmart.jdbcUser=postgres # mondrian.foodmart.jdbcPassword=pgAdmin # mondrian.jdbcDrivers=org.postgresql.Driver # # ### Neoview ### # # mondrian.foodmart.jdbcURL=jdbc:hpt4jdbc://localhost:18650/:schema=PENTAHO;serverDataSource=PENTAHO_DataSource # mondrian.foodmart.jdbcUser=user # mondrian.foodmart.jdbcPassword=password # mondrian.jdbcDrivers=com.hp.t4jdbc.HPT4Driver # driver.classpath=/some/path/hpt4jdbc.jar # # ### Netezza: mimics Postgres ### # # mondrian.foodmart.jdbcURL=jdbc:netezza://127.0.1.10/foodmart # mondrian.foodmart.jdbcUser=user # mondrian.foodmart.jdbcPassword=password # mondrian.jdbcDrivers=org.netezza.Driver # driver.classpath=/some/path/nzjdbc.jar # # ### Sybase ### # # mondrian.foodmart.jdbcURL=jdbc:jtds:sybase://xxx.xxx.xxx.xxx:port/dbName # mondrian.foodmart.jdbcUser=user # mondrian.foodmart.jdbcPassword=password # mondrian.jdbcDrivers=net.sourceforge.jtds.jdbc.Driver # driver.classpath=/some/path/jtds-1.2.jar # # ### Teradata ### # # mondrian.foodmart.jdbcURL=jdbc:teradata://DatabaseServerName/DATABASE=FoodMart # mondrian.foodmart.jdbcUser=user # mondrian.foodmart.jdbcPassword=password # mondrian.jdbcDrivers=com.ncr.teradata.TeraDriver # driver.classpath=/some/path/terajdbc/classes/terajdbc4.jar # # ### Vertica ### # # mondrian.foodmart.jdbcURL=jdbc:vertica://xxx.xxx.xxx.xxx:port/dbName # mondrian.foodmart.jdbcUser=user # mondrian.foodmart.jdbcPassword=password # mondrian.jdbcDrivers=com.vertica.Driver # driver.classpath=/some/path/vertica.jar # # ### Vertorwise ### # # mondrian.foodmart.jdbcURL=jdbc:ingres://xxx.xxx.xxx.xxxport/dbName # mondrian.foodmart.jdbcUser=user # mondrian.foodmart.jdbcPassword=password # mondrian.jdbcDrivers=com.ingres.jdbc.IngresDriver # driver.classpath=/some/path/iijdbc.jar # #mondrian.foodmart.jdbcURL=jdbc:odbc:MondrianFoodMart ############################################################################### # Boolean property that controls whether to print the SQL code # generated for aggregate tables. # # If set, then as each aggregate request is processed, both the lost # and collapsed dimension create and insert sql code is printed. # This is for use in the CmdRunner allowing one to create aggregate table # generation sql. # #mondrian.rolap.aggregates.generateSql=false ############################################################################### # Boolean property that controls pretty-print mode. # # If true, the all SqlQuery SQL strings will be generated in # pretty-print mode, formatted for ease of reading. # #mondrian.rolap.generate.formatted.sql=false ############################################################################### # Property that establishes the amount of chunks for querying cells # involving high-cardinality dimensions. # Should prime with #ResultLimit mondrian.result.limit. # #mondrian.result.highCardChunkSize=1 ############################################################################### # Property that defines whether non-existent member errors should be # ignored during schema load. If so, the non-existent member is treated # as a null member. # #mondrian.rolap.ignoreInvalidMembers=false ############################################################################### # Property that defines whether non-existent member errors should be # ignored during query validation. If so, the non-existent member is # treated as a null member. # #mondrian.rolap.ignoreInvalidMembersDuringQuery=false ############################################################################### # Property that defines whether to ignore measure when non joining # dimension is in the tuple during aggregation. # # If there are unrelated dimensions to a measure in context during # aggregation, the measure is ignored in the evaluation context. This # behaviour kicks in only if the cubeusage for this measure has # IgnoreUnrelatedDimensions attribute set to false. # # For example, Gender doesn't join with [Warehouse Sales] measure. # # With mondrian.olap.agg.IgnoreMeasureForNonJoiningDimension=true # Warehouse Sales gets eliminated and is ignored in the aggregate # value. # # [Store Sales] + [Warehouse Sales] # SUM({Product.members * Gender.members}) 7,913,333.82 # # With mondrian.olap.agg.IgnoreMeasureForNonJoiningDimension=false # Warehouse Sales with Gender All level member contributes to the aggregate # value. # # [Store Sales] + [Warehouse Sales] # SUM({Product.members * Gender.members}) 9,290,730.03 # # On a report where Gender M, F and All members exist a user will see a # large aggregated value compared to the aggregated value that can be # arrived at by suming up values against Gender M and F. This can be # confusing to the user. This feature can be used to eliminate such a # situation. # #mondrian.olap.agg.IgnoreMeasureForNonJoiningDimension=false ############################################################################### # Integer property indicating the maximum number of iterations # allowed when iterating over members to compute aggregates. A value of # 0 (the default) indicates no limit. # #mondrian.rolap.iterationLimit=0 ############################################################################### # Not documented. # #mondrian.test.Iterations=1 ############################################################################### # Property containing a list of JDBC drivers to load automatically. # Must be a comma-separated list of class names, and the classes must be # on the class path. # #mondrian.jdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver,org.hsqldb.jdbcDriver,oracle.jdbc.OracleDriver,com.mysql.jdbc.Driver ############################################################################### # Property that defines the JdbcSchema factory class which # determines the list of tables and columns of a specific datasource. # # @see mondrian.rolap.aggmatcher.JdbcSchema # #mondrian.rolap.aggregates.jdbcFactoryClass= ############################################################################### # String property that holds the # name of the class whose resource bundle is to be used to for this # schema. For example, if the class is com.acme.MyResource, # mondrian will look for a resource bundle called # com/acme/MyResource_locale.properties on the class path. # (This property has a confusing name because in a previous release it # actually held a file name.) # # Used for the mondrian.i18n.LocalizingDynamicSchemaProcessor; # see Internationalization # for more details. # # Default value is null. # #mondrian.rolap.localePropFile= ############################################################################### # Set mondrian logging information if not provided by containing application. # # Examples: # # # log4j.configuration=file://full/path/log4j.xml # log4j.configuration=file:log4j.properties # #log4j.configuration= ############################################################################### # Max number of constraints in a single 'IN' SQL clause. # # This value may be variant among database prodcuts and their runtime # settings. Oracle, for example, gives the error "ORA-01795: maximum # number of expressions in a list is 1000". # # Recommended values: # # * Oracle: 1,000 # * DB2: 2,500 # * Other: 10,000 # #mondrian.rolap.maxConstraints=1000 ############################################################################### # Boolean property that defines the maximum number of passes # allowable while evaluating an MDX expression. # # If evaluation exceeds this depth (for example, while evaluating a # very complex calculated member), Mondrian will throw an error. # #mondrian.rolap.evaluate.MaxEvalDepth=10 ############################################################################### # Property that defines # limit on the number of rows returned by XML/A drill through request. # #mondrian.xmla.drillthroughMaxRows=1000 ############################################################################### # Property that defines whether the MemoryMonitor should # be enabled. By default it is disabled; memory monitor is not available # before Java version 1.5. # #mondrian.util.memoryMonitor.enable=false ############################################################################### # Property that defines # the name of the class used as a memory monitor. # # If the value is # non-null, it is used by the MemoryMonitorFactory # to create the implementation. # #mondrian.util.MemoryMonitor.class= ############################################################################### # Property that defines the default MemoryMonitor # percentage threshold. If enabled, when Java's memory monitor detects # that post-garbage collection is above this value, notifications are # generated. # #mondrian.util.memoryMonitor.percentage.threshold=90 ############################################################################### # Property that controls the maximum number of results contained in a # NativizeSet result set. # # If the number of tuples contained in the result set exceeds this # value Mondrian throws a LimitExceededDuringCrossjoin error. # #mondrian.native.NativizeMaxResults=150000 ############################################################################### # Property that controls minimum expected cardinality required in # order for NativizeSet to natively evaluate a query. # # If the expected cardinality falls below this level the query is # executed non-natively. # # It is possible for the actual cardinality to fall below this # threshold even though the expected cardinality falls above this # threshold. In this case the query will be natively evaluated. # #mondrian.native.NativizeMinThreshold=100000 ############################################################################### # Property determines if elements of dimension (levels, hierarchies, # members) need to be prefixed with dimension name in MDX query. # # For example when the property is true, the following queries # will error out. The same queries will work when this property # is set to false. # # select {[M]} on 0 from sales # select {[USA]} on 0 from sales # select {[USA].[CA].[Santa Monica]} on 0 from sales # # When the property is set to true, any query where elements are # prefixed with dimension name as below will work # # select {[Gender].[F]} on 0 from sales # select {[Customers].[Santa Monica]} on 0 from sales # # Please note that this property does not govern the behaviour # wherein # # [Gender].[M] # # is resolved into a fully qualified # # [Gender].[M] # # In a scenario where the schema is very large and dimensions have # large number of members a MDX query that has a invalid member in it will # cause mondrian to to go through all the dimensions, levels, hierarchies, # members and properties trying to resolve the element name. This behavior # consumes considerable time and resources on the server. Setting this # property to true will make it fail fast in a scenario where it is # desirable. # #mondrian.olap.elements.NeedDimensionPrefix=false ############################################################################### # Property that defines # the behavior of division if the denominator evaluates to zero. # # If false (the default), if a division has a non-null numerator and # a null denominator, it evaluates to "Infinity", which conforms to MSAS # behavior. # # If true, the result is null if the denominator is null. Setting to # true enables the old semantics of evaluating this to null; this does # not conform to MSAS, but is useful in some applications. # #mondrian.olap.NullDenominatorProducesNull=false ############################################################################### # Property that determines how a null member value is represented in the # result output. # AS 2000 shows this as empty value # AS 2005 shows this as "(null)" value # #mondrian.olap.NullMemberRepresentation=#null ############################################################################### # Boolean property that determines whether Mondrian optimizes predicates. # # If true, Mondrian may retrieve a little more data than specified in # MDX query and cache it for future use. For example, if you ask for # data on 48 states of the United States for 3 quarters of 2011, # Mondrian will round out to all 50 states and all 4 quarters. If # false, Mondrian still optimizes queries that involve all members of a # dimension. # #mondrian.rolap.aggregates.optimizePredicates=true ############################################################################### # Property that defines the name of the factory class used # to create maps of member properties to their respective values. # # If the value is # non-null, it is used by the PropertyValueFactory # to create the implementation. If unset, # mondrian.rolap.RolapMemberBase.DefaultPropertyValueMapFactory # will be used. # #mondrian.rolap.RolapMember.PropertyValueMapFactory.class= ############################################################################### # Property defining # where the test XML files are. # #mondrian.test.QueryFileDirectory= ############################################################################### # Property that defines # a pattern for which test XML files to run. Pattern has to # match a file name of the form: # querywhatever.xml in the directory. # # Example: # # # mondrian.test.QueryFilePattern=queryTest_fec[A-Za-z0-9_]*.xml # #mondrian.test.QueryFilePattern= ############################################################################### # Maximum number of simultaneous queries the system will allow. # # Oracle fails if you try to run more than the 'processes' parameter in # init.ora, typically 150. The throughput of Oracle and other databases # will probably reduce long before you get to their limit. # #mondrian.query.limit=40 ############################################################################### # Property that defines the timeout value (in seconds) for queries. A # value of 0 (the default), indicates no timeout. # #mondrian.rolap.queryTimeout=0 ############################################################################### # Boolean property that determines whether Mondrian should read # aggregate tables. # # If set to true, then Mondrian scans the database for aggregate tables. # Unless mondrian.rolap.aggregates.Use is set to true, the aggregates # found will not be used. # #mondrian.rolap.aggregates.Read=false ############################################################################### # Integer property that, if set to a value greater than zero, limits the # maximum size of a result set. # #mondrian.result.limit=0 ############################################################################### # Maximum number of user threads per Mondrian server instance. # Defaults to 10. # #mondrian.rolap.maxQueryThreads=10 ############################################################################### # Property that defines the interval value (in miliseconds) between # polling operations performed by the RolapConnection shepherd thread. # This controls query timeouts and calcelation, so a small value # (a few miliseconds) is best. Setting this to a value higher than # mondrian.rolap.queryTimeout will result the timeout not being enforced # as expected. Defaults to 1000ms. # #mondrian.rolap.shepherdThreadPollingInterval=1000 ############################################################################### # Property which defines how many threads are created in order to # analyze possible segment rollup opportunities. This value defaults # to 4, but it can be set to a higher value on big multi-core systems. # #mondrian.rolap.RollupAnalyzerNumberThreads=4 ############################################################################### # Property which defines which SegmentCache implementation to use. # Specify the value as a fully qualified class name, such as # org.example.SegmentCacheImpl where SegmentCacheImpl # is an implementation of mondrian.spi.SegmentCache. # #mondrian.rolap.SegmentCache= ############################################################################### # Property that controls the behavior of # Property#SOLVE_ORDER solve order of calculated members and sets. # # Valid values are "absolute" and "scoped" (the default). See # mondrian.olap.SolveOrderMode for details. # #mondrian.rolap.SolveOrderMode=ABSOLUTE ############################################################################### # Property that, with #SparseSegmentDensityThreshold, determines # whether to choose a sparse or dense representation when storing # collections of cell values in memory. # # When storing collections of cell values, Mondrian has to choose # between a sparse and a dense representation, based upon the # possible and actual number of values. # The density is actual / possible. # # We use a sparse representation if # (possible - # #SparseSegmentCountThreshold countThreshold) * # #SparseSegmentDensityThreshold densityThreshold > # actual # # For example, at the default values # (#SparseSegmentCountThreshold countThreshold = 1000, # #SparseSegmentDensityThreshold = 0.5), # we use a dense representation for # # # * (1000 possible, 0 actual), or # * (2000 possible, 500 actual), or # * (3000 possible, 1000 actual). # # # Any fewer actual values, or any more # possible values, and Mondrian will use a sparse representation. # #mondrian.rolap.SparseSegmentValueThreshold=1000 ############################################################################### # Property that, with #SparseSegmentCountThreshold, # determines whether to choose a sparse or dense representation when # storing collections of cell values in memory. # #mondrian.rolap.SparseSegmentDensityThreshold=0.5 ############################################################################### # Property that defines the name of the class used in SqlMemberSource # to pool common values. # # If the value is non-null, it is used by the # SqlMemberSource.ValueMapFactory # to create the implementation. If it is not set, then # mondrian.rolap.SqlMemberSource.NullValuePoolFactory # will be used, meaning common values will not be pooled. # #mondrian.rolap.SqlMemberSource.ValuePoolFactory.class= ############################################################################### # Property that defines # whether to enable new naming behavior. # # If true, hierarchies are named [Dimension].[Hierarchy]; if false, # [Dimension.Hierarchy]. # #mondrian.olap.SsasCompatibleNaming=false ############################################################################### # String property that determines which test class to run. # # This is the name of the class. It must either implement # junit.framework.Test or have a method # public [static] junit.framework.Test suite(). # # Example: # # mondrian.test.Class=mondrian.test.FoodMartTestCase # # @see #TestName # #mondrian.test.Class= ############################################################################### # Property containing the connect string which regresssion tests should # use to connect to the database. # # Format is specified in Util#parseConnectString(String). # #mondrian.test.connectString= ############################################################################### # Integer property that controls whether to test operators' # dependencies, and how much time to spend doing it. # # If this property is positive, Mondrian's test framework allocates an # expression evaluator which evaluates each expression several times, and # makes sure that the results of the expression are independent of # dimensions which the expression claims to be independent of. # # The default is 0. # #mondrian.test.ExpDependencies=0 ############################################################################### # Property containing a list of dimensions in the Sales cube that should # be treated as high-cardinality dimensions by the testing infrastructure. # This allows us to run the full suite of tests with high-cardinality # functionality enabled. # #mondrian.test.highCardDimensions= ############################################################################### # Property containing the JDBC password of a test database. # The default value is null, to cope with DBMSs that don't need this. # #mondrian.foodmart.jdbcPassword= ############################################################################### # Property containing the JDBC user of a test database. # The default value is null, to cope with DBMSs that don't need this. # #mondrian.foodmart.jdbcUser= ############################################################################### # String property that determines which tests are run. # # This is a regular expression as defined by # java.util.regex.Pattern. # If this property is specified, only tests whose names match the pattern # in its entirety will be run. # # @see #TestClass # #mondrian.test.Name= ############################################################################### # Seed for random number generator used by some of the tests. # # Any value besides 0 or -1 gives deterministic behavior. # The default value is 1234: most users should use this. # Setting the seed to a different value can increase coverage, and # therefore may uncover new bugs. # # If you set the value to 0, the system will generate its own # pseudo-random seed. # # If you set the value to -1, Mondrian uses the next seed from an # internal random-number generator. This is a little more deterministic # than setting the value to 0. # #mondrian.test.random.seed=1234 ############################################################################### # Property that returns the time limit for the test run in seconds. # If the test is running after that time, it is terminated. # #mondrian.test.TimeLimit=0 ############################################################################### # Boolean property that controls whether Mondrian uses aggregate # tables. # # If true, then Mondrian uses aggregate tables. This property is # queried prior to each aggregate query so that changing the value of this # property dynamically (not just at startup) is meaningful. # # Aggregates can be read from the database using the # #ReadAggregates property but will not be used unless this # property is set to true. # #mondrian.rolap.aggregates.Use=false ############################################################################### # Not documented. # #mondrian.test.VUsers=1 ############################################################################### # Property that indicates whether this is a "warmup test". # #mondrian.test.Warmup=false ############################################################################### # Property that controls if warning messages should be printed if a sql # comparison tests do not contain expected sqls for the specified # dialect. The tests are skipped if no expected sqls are # found for the current dialect. # # Possible values are the following: # # # * "NONE": no warning (default) # * "ANY": any dialect # * "ACCESS" # * "DERBY" # * "LUCIDDB" # * "MYSQL" # * ... and any Dialect enum in SqlPattern.Dialect # # # Specific tests can overwrite the default setting. The priority is: # * Settings besides "ANY" in mondrian.properties file # * < Any setting in the test # * < "ANY" # #mondrian.test.WarnIfNoPatternForDialect=NONE ############################################################################### # Connect string for the webapp. (Used by the webapp only.) # # To achieve access control, append say ;Role='California # manager' to the connect string. # #mondrian.webapp.connectString=Provider=mondrian;Jdbc=jdbc:odbc:MondrianFoodMart;Catalog=/WEB-INF/queries/FoodMart.xml;JdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver ############################################################################### # Where mondrian.war will be deployed to. (Used by mondrian's build.xml ant file only.) # # Example: mondrian.webapp.deploy=C:/jboss-4.0.2/server/default/deploy # #mondrian.webapp.deploy= ############################################################################### # Interval, in milliseconds, at which to refresh the # list of XML/A catalogs. (Usually known as the # datasources.xml file.) # # It is not an active process; no threads will be created. It only # serves as a rate limiter. The refresh process is triggered by requests # to the doPost() servlet method. # # Default value is 3000 milliseconds (3 seconds). # # See also # mondrian.xmla.impl.DynamicDatasourceXmlaServlet. # #mondrian.xmla.SchemaRefreshInterval=3000 # End mondrian.properties.template mondrian-3.4.1/VERSION.txt0000644000175000017500000000014611744246512015117 0ustar drazzibdrazzibTitle: mondrian Version: 3.4.1 Vendor: Pentaho Git head SHA: 109f12c54ca4691c1273c9d8e4dbcd4a14d33ab8 mondrian-3.4.1/bin/0000755000175000017500000000000011742752424014002 5ustar drazzibdrazzibmondrian-3.4.1/bin/checkFile.awk0000644000175000017500000005727511735330606016377 0ustar drazzibdrazzib#!/bin/gawk # Checks that a file is valid. function error(fname, linum, msg) { printf "%s:%d: %s\n", fname, linum, msg; if (0) print; # for debug } function _matchFile(fname) { return fname ~ "/mondrian/" \ || fname ~ "/org/olap4j/" \ || fname ~ "/aspen/" \ || fname ~ "/farrago/" \ || fname ~ "/fennel/" \ || fname ~ "/extensions/" \ || fname ~ "/com/sqlstream/" \ || strict > 0; } function _isCpp(fname) { return fname ~ /\.(cpp|h)$/; } function _isJava(fname) { return fname ~ /\.(java|jj)$/; } function _isMondrian(fname) { return fname ~ /mondrian/; } function push(val) { switchStack[switchStackLen++] = val; } function pop() { --switchStackLen val = switchStack[switchStackLen]; delete switchStack[switchStackLen]; return val; } function afterFile() { # Compute basename. If fname="/foo/bar/baz.txt" then basename="baz.txt". basename = fname; gsub(".*/", "", basename); gsub(lf, "", lastNonEmptyLine); terminator = "// End " basename; if (matchFile && (lastNonEmptyLine != terminator)) { error(fname, FNR, sprintf("Last line should be %c%s%c", 39, terminator, 39)); } } # Returns whether there are unmatched open parentheses. # unmatchedOpenParens("if ()") returns false. # unmatchedOpenParens("if (") returns true. # unmatchedOpenParens("if (foo) bar(") returns false function unmatchedOpenParens(s) { i = index(s, "("); if (i == 0) { if (0) print FNR, "unmatchedOpenParens=0"; return 0; } openCount = 1; while (++i <= length(s)) { c = substr(s, i, 1); if (c == "(") { ++openCount; } if (c == ")") { if (--openCount == 0) { if (0) print FNR, "unmatchedOpenParens=0 (b)"; return 0; } } } if (0) print FNR, "unmatchedOpenParens=1"; return 1; } function countLeadingSpaces(str) { i = 0; while (i < length(str) && substr(str, i + 1, 1) == " ") { ++i; } return i; } function startsWith(s, p) { return length(s) > length(p) \ && substr(s, 1, length(p)) == p; } BEGIN { # pre-compute regexp for quotes, linefeed apos = sprintf("%c", 39); quot = sprintf("%c", 34); lf = sprintf("%c", 13); pattern = apos "(\\" apos "|[^" apos "])" apos; if (0) printf "maxLineLength=%s strict=%s\n", maxLineLength, strict; } FNR == 1 { if (fname) { afterFile(); } fname = FILENAME; matchFile = _matchFile(fname); isCpp = _isCpp(fname); isJava = _isJava(fname); mondrian = _isMondrian(fname); prevImport = ""; prevImportGroup = ""; } { if (previousLineEndedInCloseBrace > 0) { --previousLineEndedInCloseBrace; } if (previousLineEndedInOpenBrace > 0) { --previousLineEndedInOpenBrace; } if (previousLineWasEmpty > 0) { --previousLineWasEmpty; } s = $0; # remove DOS linefeeds gsub(lf, "", s); # replace strings gsub(/"(\\"|[^"\\]|\\[^"])*"/, "string", s); # replace single-quoted strings gsub(pattern, "string", s); # replace {: and :} in .cup files if (fname ~ /\.cup$/) { gsub(/{:/, "{", s); gsub(/:}/, "}", s); gsub(/:/, " : ", s); } if (inComment && $0 ~ /\*\//) { # end of multiline comment "*/" inComment = 0; gsub(/^.*\*\//, "/* comment */", s); } else if (inComment) { if (strict > 1 && !inJavadocComment && s ~ /^ *\*/) { error(fname, FNR, "Block comment not allowed; use //"); } s = "/* comment */"; } else if ($0 ~ /\/\*/ && $0 !~ /\/\*.*\*\//) { # beginning of multiline comment "/*" inComment = 1; inJavadocComment = $0 ~ /\/\*\*/; gsub(/\/\*.*$/, "/* comment */", s); } else { # mask out /* */ comments gsub(/\/\*.*\*\//, "/* comment */", s); } if (mondrian && s ~ /\/\/\$NON-NLS/) { error(fname, FNR, "NON-NLS not allowed"); } # mask out // comments gsub(/\/\/.*$/, "// comment", s); # line starts with string or plus? if (s ~ /^ *string/ \ && s !~ /)/) { stringCol = index(s, "string"); } else if (s ~ /^ *[+] string/) { if (stringCol != 0 && index(s, "+") != stringCol) { error(fname, FNR, "String '+' must be aligned with string on line above"); } } else if (s ~ /comment/) { # in comment; string target carries forward } else { stringCol = 0; } # Is the line indented as expected? if (nextIndent > 0) { indent = countLeadingSpaces(s); if (indent != nextIndent) { error(fname, FNR, "Incorrect indent for first line of arg list"); } } nextIndent = -1; } / $/ { error(fname, FNR, "Line ends in space"); } /[\t]/ { if (matchFile) { error(fname, FNR, "Tab character"); } } /[\r]/ { if (matchFile) { error(fname, FNR, "Carriage return character (file is in DOS format?)"); } } /./ { lastNonEmptyLine = $0; } { # Rules beyond this point only apply to Java and C++. if (!isCpp && !isJava) { next; } } /^package / { thisPackage = $2; } /^package / && previousLineWasEmpty { error(fname, FNR, "'package' declaration must not occur after empty line"); } /^import / { if (previousLineWasEmpty) { prevImport = ""; } else { if (!prevImport) { error(fname, FNR, "Expected blank line before first import"); } } thisImport = $2; gsub(/;/, "", thisImport); gsub(/\*/, "", thisImport); if (thisPackage ~ /^mondrian.*/ && thisImport ~ /^mondrian.*/ \ || thisPackage ~ /^org.olap4j.*/ && thisImport ~ /^org.olap4j.*/) { importGroup = "a"; } else if (thisImport ~ /^static/) { importGroup = "z"; } else if (thisImport ~ /^java.*/) { importGroup = "y"; } else if (thisImport ~ /^junit.*/) { importGroup = "b"; } else if (thisImport ~ /^mondrian.*/) { importGroup = "bb"; } else if (thisImport ~ /^org.apache.*/) { importGroup = "c"; } else if (thisImport ~ /^org.eigenbase.*/) { importGroup = "d"; } else if (thisImport ~ /^org.olap4j.*/) { importGroup = "e"; } else { importGroup = "f"; } if (importGroup != prevImportGroup \ && prevImportGroup) { if (!previousLineWasEmpty) { error(fname, FNR, "Expected blank line between import groups"); } else if (prevImportGroup > importGroup) { error(fname, FNR, "Import group out of sequence (should precede " prevImportGroup ")"); } } else if (prevImport \ && prevImport > thisImport \ && !startsWith(prevImport, thisImport) \ && !startsWith(thisImport, prevImport)) { error(fname, FNR, "Import out of sorted order"); } prevImport = thisImport; prevImportGroup = importGroup; } /^\/\/ Copyright .* Pentaho/ && strict > 1 { if ($0 !~ /-2012/) { error(fname, FNR, "copyright is not current"); } } /(static|public|private|protected|final|abstract)/ && !/import/ && strict > 1 { # Order of qualifiers: "public/private/protected static final abstract class ..." s2 = s; gsub(/\(.*$/, "", s2); if (s2 ~ /abstract .*final/) { error(fname, FNR, "'final' must come before 'abstract'"); } if (s2 ~ /final .*static/) { error(fname, FNR, "'static' must come before 'final'"); } if (s2 ~ /abstract .*static/) { error(fname, FNR, "'static' must come before 'abstract'"); } if (s2 ~ /static .*(public|protected|private)/) { error(fname, FNR, "'public/private/protected' must come before 'static'"); } if (s2 ~ /final .*(public|protected|private)/) { error(fname, FNR, "'public/private/protected' must come before 'final'"); } if (s2 ~ /abstract .*(public|protected|private)/) { error(fname, FNR, "'public/private/protected' must come before 'abstract'"); } } /^$/ { if (matchFile && previousLineEndedInOpenBrace) { error(fname, FNR, "Empty line following open brace"); } } /^ +}( catch| finally| while|[;,)])/ || /^ +}$/ { if (matchFile && previousLineWasEmpty) { error(fname, FNR - 1, "Empty line before close brace"); } } s ~ /\.*;$/ { if (!matchFile) {} else { error(fname, FNR, "if followed by statement on same line"); } } s ~ /\<(if) *\(/ { if (!matchFile) { } else if (s !~ /\<(if) /) { error(fname, FNR, "if must be followed by space"); } else if (s ~ / else if /) { } else if (s ~ /^#if /) { } else if (s !~ /^( )*(if)/) { error(fname, FNR, "if must be correctly indented"); } } s ~ /\<(while) *\(/ { if (!matchFile) { } else if (s !~ /\<(while) /) { error(fname, FNR, "while must be followed by space"); } else if (s ~ /} while /) { } else if (s !~ /^( )+(while)/) { error(fname, FNR, "while must be correctly indented"); } } s ~ /\<(for|switch|synchronized|} catch) *\(/ { if (!matchFile) {} else if (s !~ /^( )*(for|switch|synchronized|} catch)/) { error(fname, FNR, "for/switch/synchronized/catch must be correctly indented"); } else if (s !~ /\<(for|switch|synchronized|} catch) /) { error(fname, FNR, "for/switch/synchronized/catch must be followed by space"); } } s ~ /\<(if|while|for|switch|catch)\>/ { # Check single-line if statements, such as # if (condition) return; # We recognize such statements because there are equal numbers of open and # close parentheses. opens = s; gsub(/[^(]/, "", opens); closes = s; gsub(/[^)]/, "", closes); if (!matchFile) { } else if (s ~ /{( *\/\/ comment)?$/) { # lines which end with { and optional comment are ok } else if (s ~ /{.*\\$/ && isCpp) { # lines which end with backslash are ok in c++ macros } else if (s ~ /} while/) { # lines like "} while (foo);" are ok } else if (s ~ /^#/) { # lines like "#if 0" are ok } else if (s ~ /if \(true|false\)/) { # allow "if (true)" and "if (false)" because they are # used for commenting } else if (!unmatchedOpenParens(s) \ && length($0) != 79 \ && length($0) != 80) { error(fname, FNR, "single-line if/while/for/switch/catch must end in {"); } } s ~ /[[:alnum:]]\(/ && s !~ /\<(if|while|for|switch|assert)\>/ { ss = s; while (match(ss, /[[:alnum:]]\(/)) { ss = substr(ss, RSTART + RLENGTH - 1); parens = ss; gsub(/[^()]/, "", parens); while (substr(parens, 1, 2) == "()") { parens = substr(parens, 3); } opens = parens; gsub(/[^(]/, "", opens); closes = parens; gsub(/[^)]/, "", closes); if (length(opens) > length(closes)) { if (ss ~ /,$/) { bras = ss; gsub(/[^<]/, "", bras); kets = ss; gsub(/->/, "", kets); gsub(/[^>]/, "", kets); if (length(bras) > length(kets)) { # Ignore case like 'for (Map.Entry entry : ...' } else if (s ~ / for /) { # Ignore case like 'for (int i = 1,{nl} j = 2; i < j; ...' } else { error( \ fname, FNR, \ "multi-line parameter list should start with newline"); break; } } else if (s ~ /[;(]( *\\)?$/) { # If open paren is at end of line (with optional backslash # for macros), we're fine. } else if (s ~ /@.*\({/) { # Ignore Java annotations. } else { error( \ fname, FNR, \ "Open parenthesis should be at end of line (function call spans several lines)"); break; } } ss = substr(ss, 2); # remove initial "(" } } s ~ /\/ { push(switchCol); switchCol = index($0, "switch"); } s ~ /{/ { braceCol = index($0, "{"); if (braceCol == switchCol) { push(switchCol); } } s ~ /}/ { braceCol = index($0, "}"); if (braceCol == switchCol) { switchCol = pop(); } } s ~ /\<(case|default)\>/ { caseDefaultCol = match($0, /case|default/); if (!matchFile) {} else if (caseDefaultCol != switchCol) { error(fname, FNR, "case/default must be aligned with switch"); } } s ~ /\/ { if (!matchFile) {} else if (isCpp) {} # rule only applies to java else if (s !~ /^( )+(assert)/) { error(fname, FNR, "assert must be correctly indented"); } else if (s !~ /\/ { if (!matchFile) {} else if (isCpp && s ~ /^#/) { # ignore macros } else if (s !~ /^( )+(return)/) { error(fname, FNR, "return must be correctly indented"); } else if (s !~ /\/ { if (!matchFile) {} else if (isCpp) { # cannot yet handle C++ cases like 'void foo() throw(int)' } else if (s !~ /^( )+(throw)/) { error(fname, FNR, "throw must be correctly indented"); } else if (s !~ /\/ { if (!matchFile) {} else if (isCpp && s ~ /^# *else$/) {} # ignore "#else" else if (s !~ /^( )+} else (if |{$|{ *\/\/|{ *\/\*)/) { error(fname, FNR, "else must be preceded by } and followed by { or if and correctly indented"); } } s ~ /\/ { if (!matchFile) {} else if (s !~ /^( )*do {/) { error(fname, FNR, "do must be followed by space {, and correctly indented"); } } s ~ /\/ { if (!matchFile) {} else if (s !~ /^( )+try {/) { error(fname, FNR, "try must be followed by space {, and correctly indented"); } } s ~ /\/ { if (!matchFile) {} else if (s !~ /^( )+} catch /) { error(fname, FNR, "catch must be preceded by }, followed by space, and correctly indented"); } } s ~ /\/ { if (!matchFile) {} else if (s !~ /^( )+} finally {/) { error(fname, FNR, "finally must be preceded by }, followed by space {, and correctly indented"); } } s ~ /\($/ { nextIndent = countLeadingSpaces(s) + 4; if (s ~ / (if|while) .*\(.*\(/) { nextIndent += 4; } } match(s, /([]A-Za-z0-9()])(+|-|\*|\^|\/|%|=|==|+=|-=|\*=|\/=|>=|<=|!=|&|&&|\||\|\||^|\?|:) *[A-Za-z0-9(]/, a) { # < and > are not handled here - they have special treatment below if (!matchFile) {} # else if (s ~ /<.*>/) {} # ignore templates else if (a[2] == "-" && s ~ /\(-/) {} # ignore case "foo(-1)" else if (a[2] == "-" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e-5 else if (a[2] == "+" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e+5 else if (a[2] == ":" && s ~ /(case.*|default):$/) {} # ignore e.g. "case 5:" else if (isCpp && s ~ /[^ ][*&]/) {} # ignore e.g. "Foo* p;" in c++ - debatable else if (isCpp && s ~ /\" in c++ else { error(fname, FNR, "operator '" a[2] "' must be preceded by space"); } } match(s, /([]A-Za-z0-9() ] *)(+|-|\*|\^|\/|%|=|==|+=|-=|\*=|\/=|>=|<=|!=|&|&&|\||\|\||^|\?|:|,)[A-Za-z0-9(]/, a) { if (!matchFile) {} # else if (s ~ /<.*>/) {} # ignore templates else if (a[2] == "-" && s ~ /(\(|return |case |= )-/) {} # ignore prefix - else if (a[2] == ":" && s ~ /(case.*|default):$/) {} # ignore e.g. "case 5:" else if (s ~ /, *-/) {} # ignore case "foo(x, -1)" else if (s ~ /-[^ ]/ && s ~ /[^A-Za-z0-9] -/) {} # ignore case "x + -1" but not "x -1" or "3 -1" else if (a[2] == "-" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e-5 else if (a[2] == "+" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e+5 else if (a[2] == "*" && isCpp && s ~ /\*[^ ]/) {} # ignore e.g. "Foo *p;" in c++ else if (a[2] == "&" && isCpp && s ~ /&[^ ]/) {} # ignore case "foo(&x)" in c++ else if (isCpp && s ~ /\" in c++ else if (strict < 2 && fname ~ /(fennel)/ && a[1] = ",") {} # not enabled yet else { error(fname, FNR, "operator '" a[2] "' must be followed by space"); } } match(s, /( )(,)/, a) { # (, < and > are not handled here - they have special treatment below if (!matchFile) {} else { error(fname, FNR, "operator '" a[2] "' must not be preceded by space"); } } match(s, / (+|-|\*|\/|==|>=|<=|!=|<<|<<<|>>|&|&&|\|\||\?|:)$/, a) || \ match(s, /(\.|->)$/, a) { if (strict < 2 && fname ~ /(aspen)/ && a[1] != ":") {} # not enabled yet else if (strict < 2 && fname ~ /(fennel|farrago|aspen)/ && a[1] = "+") {} # not enabled yet else if (a[1] == ":" && s ~ /(case.*|default):$/) { # ignore e.g. "case 5:" } else if ((a[1] == "*" || a[1] == "&") && isCpp && s ~ /^[[:alnum:]:_ ]* [*&]$/) { # ignore e.g. "const int *\nClass::Subclass2::method(int x)" } else { error(fname, FNR, "operator '" a[1] "' must not be at end of line"); } } match(s, /^ *(=) /, a) { error(fname, FNR, "operator '" a[1] "' must not be at start of line"); } match(s, /([[:alnum:]~]+)( )([(])/, a) { # (, < and > are not handled here - they have special treatment below if (!matchFile) {} else if (isJava && a[1] ~ /\<(if|while|for|catch|switch|case|return|throw|synchronized|assert)\>/) {} else if (isCpp && a[1] ~ /\<(if|while|for|catch|switch|case|return|throw|operator|void|PBuffer)\>/) {} else if (isCpp && s ~ /^#define /) {} else { error(fname, FNR, "there must be no space before '" a[3] "' in fun call or fun decl"); } } s ~ /\<[[:digit:][:lower:]][[:alnum:]_]*' could be a template else { error(fname, FNR, "operator '<' must be preceded by space"); } } s ~ /\<[[:digit:][:lower:]][[:alnum:]_]*>/ { # E.g. "g>" but not "String>" as in "List" if (!matchFile) {} else if (isCpp) {} # in C++ 'xyz' could be a template else { error(fname, FNR, "operator '>' must be preceded by space"); } } match(s, /<([[:digit:][:lower:]][[:alnum:].]*)\>/, a) { if (!matchFile) {} else if (isCpp) { # in C++, template and include generate too many false positives } else if (isJava && a[1] ~ /(int|char|long|boolean|byte|double|float)/) { # Allow e.g. 'List' } else if (isJava && a[1] ~ /^[[:lower:]]+\./) { # Allow e.g. 'List' } else { error(fname, FNR, "operator '<' must be followed by space"); } } match(s, /^(.*[^-])>([[:digit:][:lower:]][[:alnum:]]*)\>/, a) { if (!matchFile) {} else if (isJava && a[1] ~ /.*\.<.*/) { # Ignore 'Collections.member' } else { error(fname, FNR, "operator '>' must be followed by space"); } } s ~ /[[(] / { if (!matchFile) {} else if (s ~ /[[(] +\\$/) {} # ignore '#define foo( \' else { error(fname, FNR, "( or [ must not be followed by space"); } } s ~ / [])]/ { if (!matchFile) {} else if (s ~ /^ *\)/ && previousLineEndedInCloseBrace) {} # ignore "bar(new Foo() { } );" else { error(fname, FNR, ") or ] must not be followed by space"); } } s ~ /}/ { if (!matchFile) {} else if (s !~ /}( |;|,|$|\))/) { error(fname, FNR, "} must be followed by space"); } else if (s !~ /( )*}/) { error(fname, FNR, "} must be at start of line and correctly indented"); } } s ~ /{/ { if (!matchFile) {} else if (s ~ /(\]\)?|=) *{/) {} # ignore e.g. "(int[]) {1, 2}" or "int[] x = {1, 2}" else if (s ~ /\({/) {} # ignore e.g. @SuppressWarnings({"unchecked"}) else if (s ~ /{ *(\/\/|\/\*)/) {} # ignore e.g. "do { // a comment" else if (s ~ / \{\}$/) {} # ignore e.g. "Constructor() {}" else if (s ~ / },$/) {} # ignore e.g. "{ yada }," else if (s ~ / };$/) {} # ignore e.g. "{ yada };" else if (s ~ / \{\};$/) {} # ignore e.g. "template <> class Foo {};" else if (s ~ / },? *\/\/.*$/) {} # ignore e.g. "{ yada }, // comment" else if (s ~ /\\$/) {} # ignore multiline macros else if (s ~ /{}/) { # e.g. "Constructor(){}" error(fname, FNR, "{} must be preceded by space and at end of line"); } else if (isCpp && s ~ /{ *\\$/) { # ignore - "{" can be followed by "\" in c macro } else if (s !~ /{$/) { error(fname, FNR, "{ must be at end of line"); } else if (s !~ /(^| ){/) { error(fname, FNR, "{ must be preceded by space or at start of line"); } else { opens = s; gsub(/[^(]/, "", opens); closes = s; gsub(/[^)]/, "", closes); if (0 && strict < 2 && fname ~ /aspen/) {} # not enabled else if (length(closes) > length(opens)) { error(fname, FNR, "Open brace should be on new line (function call/decl spans several lines)"); } } } s ~ /(^| )(class|interface|enum) / || s ~ /(^| )namespace / && isCpp { if (isCpp && s ~ /;$/) {} # ignore type declaration else { classDeclStartLine = FNR; t = s; gsub(/.*(class|interface|enum|namespace) /, "", t); gsub(/ .*$/, "", t); if (s ~ /template/) { # ignore case "template static void foo()" classDeclStartLine = 0; } else if (t ~ /[[:upper:]][[:upper:]][[:upper:]][[:upper:]]/ \ && t !~ /LRU/ \ && t !~ /WAL/ \ && t !~ /classUUID/ \ && t !~ /classSQLException/ \ && t !~ /BBRC/ \ && t !~ /_/ \ && t !~ /EncodedSqlInterval/) { error(fname, FNR, "Class name " t " has consecutive uppercase letters"); } } } s ~ / throws\>/ { if (s ~ /\(/) { funDeclStartLine = FNR; } else { funDeclStartLine = FNR - 1; } } length($0) > maxLineLength \ && $0 !~ /@(throws|see|link)/ \ && $0 !~ /[$]Id: / \ && $0 !~ /^import / \ && $0 !~ /http:/ \ && $0 !~ /\/\/ Expect "/ \ && s !~ /^ *(\+ |<< )?string\)?[;,]?$/ { error( \ fname, \ FNR, \ "Line length (" length($0) ") exceeds " maxLineLength " chars"); } /}$/ { previousLineEndedInCloseBrace = 2; } /;$/ { funDeclStartLine = 0; } /{$/ { # Ignore open brace if it is part of class or interface declaration. if (classDeclStartLine) { if (classDeclStartLine < FNR \ && $0 !~ /^ *{$/) { error(fname, FNR, "Open brace should be on new line (class decl spans several lines)"); } classDeclStartLine = 0; } else { previousLineEndedInOpenBrace = 2; } if (funDeclStartLine) { if (funDeclStartLine < FNR \ && $0 !~ /^ *{$/) { if (strict < 2 && fname ~ /aspen/) {} # not enabled else error(fname, FNR, "Open brace should be on new line (function decl spans several lines)"); } funDeclStartLine = 0; } } /^$/ { previousLineWasEmpty = 2; } { next; } END { afterFile(); } # End checkFile.awk mondrian-3.4.1/bin/cmdrunner.cmd0000644000175000017500000000033711735330604016461 0ustar drazzibdrazzib@echo off rem rem rem cmdrunner.cmd rem rem Must set location of the cmdrunner.jar rem rem $Id: $ rem set JAVA_COMMAND=%JAVA_HOME%\bin\java set CMD_RUNNER_JAR=..\lib\cmdrunner.jar %JAVA_COMMAND% -jar %CMD_RUNNER_JAR% %* mondrian-3.4.1/bin/cmdrunner.sh0000644000175000017500000000121711735330604016326 0ustar drazzibdrazzib#!/bin/bash # do not use ksh, use bash ##################################################################### # # cmdrunner.sh # # Must set location of the cmdrunner.jar # # $Id$ ##################################################################### declare -r DIR=$(dirname $0) # # attempt to find jar # if [[ -e "$DIR"/../lib/cmdrunner.jar ]] ; then CMD_RUNNER_JAR="$DIR"/../lib/cmdrunner.jar elif [[ -e "$DIR"/lib/cmdrunner.jar ]] ; then CMD_RUNNER_JAR="$DIR"/lib/cmdrunner.jar elif [[ -e ./cmdrunner.jar ]] ; then CMD_RUNNER_JAR=./cmdrunner.jar else echo "Can not locate cmdrunner.jar" exit 1 fi java -jar "$CMD_RUNNER_JAR" "$@" mondrian-3.4.1/bin/checkFile.sh0000644000175000017500000002067711735330604016221 0ustar drazzibdrazzib#!/bin/bash # $Id$ # Checks that a file is valid. # Used by perforce submit trigger, via runTrigger. # The file is deemed to be valid if this command produces no output. # # Usage: # checkFile [ --depotPath ] # # runTrigger uses first form, with a temporary file, e.g. # checkFile --depotPath //depot/src/foo/Bar.java /tmp/foo.txt # # The second form is useful for checking files in the client before you # try to submit them: # checkFile src/foo/Bar.java # usage() { echo "checkFile [ ] --depotPath " echo " Checks a temporary file. depotPath is the full path of" echo " the file stored in perforce, for error reporting; file" echo " holds the actual file contents." echo "checkFile [ ] ..." echo " Checks a list of files." echo "checkFile [ ] --opened" echo " Checks all files that are opened for edit in the current" echo " perforce client." echo "checkFile [ ] --under " echo " Recursively checks all files under a given directory." echo "checkFile --help" echo " Prints this help." echo echo "Options:" echo "--lenient" echo " Does not apply rules to components which are not known to" echo " be in compliance. The perforce trigger uses this option." echo "--strict" echo " Stricter than usual; the opposite of lenient." } doCheck() { filePath="$1" file="$2" maxLineLength="$3" # CHECKFILE_IGNORE is an environment variable that contains a callback # to decide whether to check this file. The command or function should # succeed (that is, return 0) if checkFile is to ignore the file, fail # (that is, return 1 or other non-zero value) otherwise. if [ "$CHECKFILE_IGNORE" ]; then if eval $CHECKFILE_IGNORE "$filePath"; then return fi fi # Exceptions for mondrian case "$filePath" in */mondrian/util/Base64.java| \ */mondrian/olap/MondrianDef.java| \ */mondrian/gui/MondrianGuiDef.java| \ */mondrian/xmla/DataSourcesConfig.java| \ */mondrian/rolap/aggmatcher/DefaultDef.java| \ */mondrian/resource/MondrianResource*.java| \ */mondrian/olap/MondrianProperties.java| \ */mondrian/olap/Parser.java| \ */mondrian/olap/ParserSym.java| \ */mondrian/parser/ParseException.java| \ */mondrian/parser/Token.java| \ */mondrian/parser/TokenMgrError.java| \ */mondrian/parser/SimpleCharStream.java| \ */mondrian/parser/MdxParserImplConstants.java| \ */mondrian/parser/MdxParserImplTokenManager.java| \ */mondrian/parser/MdxParserImpl.java) # mondrian.util.Base64 is checked in as is, so don't check it # Other files above are generated. return ;; # Exceptions for olap4j */org/olap4j/mdx/parser/impl/*.java| \ */org/olap4j/impl/Base64.java) return ;; # Exceptions for farrago */farrago/classes/* | \ */farrago/catalog/* | \ */farrago/examples/rng/src/net/sf/farrago/rng/parserimpl/*.java | \ */farrago/examples/rng/src/net/sf/farrago/rng/resource/FarragoRngResource*.java | \ */farrago/examples/rng/catalog/net/sf/farrago/rng/rngmodel/* | \ */farrago/examples/rng/catalog/java/* | \ */farrago/jdbc4/*.java | \ */farrago/src/net/sf/farrago/FarragoMetadataFactoryImpl.java | \ */farrago/src/net/sf/farrago/FarragoMetadataFactory.java | \ */farrago/src/net/sf/farrago/parser/impl/*.java | \ */farrago/src/net/sf/farrago/resource/FarragoResource*.java | \ */farrago/src/net/sf/farrago/resource/FarragoInternalQuery*.java | \ */farrago/src/net/sf/farrago/test/FarragoSqlTestWrapper.java | \ */farrago/src/org/eigenbase/jdbc4/*.java | \ */farrago/src/org/eigenbase/lurql/parser/*.java | \ */farrago/src/com/lucidera/lurql/parser/*.java | \ */farrago/src/org/eigenbase/resource/EigenbaseResource*.java | \ */farrago/src/org/eigenbase/sql/parser/impl/*.java) return ;; # Exceptions for fennel */fennel/CMakeFiles/CompilerIdCXX/CMakeCXXCompilerId.cpp | \ */fennel/disruptivetech/calc/CalcGrammar.tab.cpp | \ */fennel/disruptivetech/calc/CalcGrammar.cpp | \ */fennel/disruptivetech/calc/CalcGrammar.h | \ */fennel/disruptivetech/calc/CalcLexer.cpp | \ */fennel/disruptivetech/calc/CalcLexer.h | \ */fennel/calculator/CalcGrammar.tab.cpp | \ */fennel/calculator/CalcGrammar.cpp | \ */fennel/calculator/CalcGrammar.h | \ */fennel/calculator/CalcLexer.cpp | \ */fennel/calculator/CalcLexer.h | \ */fennel/common/FemGeneratedEnums.h | \ */fennel/common/FennelResource.cpp | \ */fennel/common/FennelResource.h | \ */fennel/config.h | \ */fennel/farrago/FemGeneratedClasses.h | \ */fennel/farrago/FemGeneratedMethods.h | \ */fennel/farrago/NativeMethods.h) return ;; # Skip our own test files, unless we are testing. */util/test/CheckFile1.*) if [ ! "$test" ]; then return fi ;; # Only validate .java and parser files at present. *.java|*.cup|*.h|*.cpp) ;; *) return ;; esac # Set maxLineLength if it is not already set. ('checkFile --opened' # sets it to the strictest value, 80). if [ ! "$maxLineLength" ]; then maxLineLength=80 fi # Check whether there are tabs, or lines end with spaces # todo: check for ' ;' # todo: check that every file has copyright/license header # todo: check that every class has javadoc # todo: check that every top-level class has @author and @version # todo: check c++ files if test "$deferred" ; then echo "$file" >> "${deferred_file}" else gawk -f "$CHECKFILE_AWK" \ -v fname="$filePath" \ -v strict="$strict" \ -v maxLineLength="$maxLineLength" \ "$file" fi } doCheckDeferred() { if [ -s "${deferred_file}" ]; then maxLineLength=80 cat "${deferred_file}" | xargs -P $(expr ${CORE_COUNT} \* 2) -n 100 gawk -f "$CHECKFILE_AWK" \ -v strict="$strict" \ -v maxLineLength="$maxLineLength" fi rm -f "${deferred_file}" } function guessCoreCount() { if [ -f /proc/cpuinfo ]; then cat /proc/cpuinfo | awk '$1 == "processor"' | wc -l else # File doe not exist on Darwin or cygwin echo 2 fi } export CORE_COUNT=$(guessCoreCount) export deferred=true # 'test' is an undocumented flag, overriding the default behavior which is # to ignore our own test files test= if [ "$1" == --test ]; then test=true shift fi strict=1 if [ "$1" == --lenient ]; then strict=0 shift fi if [ "$1" == --help ]; then usage exit 0 fi if [ "$1" == --strict ]; then strict=2 shift fi depotPath= if [ "$1" == --depotPath ]; then depotPath="$2" deferred= shift 2 fi opened= if [ "$1" == --opened ]; then opened=true deferred= shift fi under= if [ "$1" == --under ]; then if [ "$opened" ]; then echo "Cannot specify both --under and --opened" exit 1 fi if [ ! -d "$2" ]; then echo "--under requires a directory; '$2' not found" exit 1 fi under="$2" shift 2 fi if [ "$1" == --opened ]; then echo "Cannot specify both --under and --opened" exit 1 fi if [ ! -f "$CHECKFILE_AWK" ] then case $(uname) in (Darwin) export CHECKFILE_AWK="$(cd $(dirname $0); pwd -P)/checkFile.awk";; (*) export CHECKFILE_AWK="$(dirname $(readlink -f $0))/checkFile.awk";; esac fi export deferred_file=/tmp/checkFile_deferred_$$.txt rm -f "${deferred_file}" ( if [ "$under" ]; then find "$under" -type f | while read file; do filePath="$file" if [ "$depotPath" ]; then filePath="$depotPath" fi doCheck "$filePath" "$file" "" done elif [ "$opened" ]; then p4 opened | gawk -F'#' '$2 !~ / - delete/ {print $1}' | while read line; do file=$(p4 where "$line" | gawk '{print $3}' | tr \\\\ /) doCheck "$file" "$file" "80" done else for file in "$@"; do filePath="$file" if [ "$depotPath" ]; then filePath="$depotPath" fi doCheck "$filePath" "$file" "" done fi if test "$deferred"; then doCheckDeferred fi ) | tee /tmp/checkFile_output_$$.txt status=0 if [ -s /tmp/checkFile_output_$$.txt ]; then status=1 fi rm -f /tmp/checkFile_output_$$.txt exit $status # End checkFile mondrian-3.4.1/bin/run.sh0000644000175000017500000000257011735330604015140 0ustar drazzibdrazzib#!/bin/bash # do not use ksh, use bash ##################################################################### # # runcmd.sh # # Must set location of the cmdrunner.jar # # $Id$ ##################################################################### declare -r DIR=$(dirname $0) LOCATION="$PWD/$DIR" MONDRIAN_HOME="$LOCATION/.." MONDRIAN_LIB="$MONDRIAN_HOME/lib" MONDRIAN_TEST_LIB="$MONDRIAN_HOME/testlib" CLASSPATH="$MONDRIAN_LIB/mondrian.jar" CLASSPATH="$CLASSPATH:$MONDRIAN_LIB/commons-dbcp.jar" CLASSPATH="$CLASSPATH:$MONDRIAN_LIB/commons-collections.jar" CLASSPATH="$CLASSPATH:$MONDRIAN_LIB/commons-pool.jar" CLASSPATH="$CLASSPATH:$MONDRIAN_LIB/eigenbase-resgen.jar" CLASSPATH="$CLASSPATH:$MONDRIAN_LIB/eigenbase-xom.jar" CLASSPATH="$CLASSPATH:$MONDRIAN_LIB/javacup.jar" CLASSPATH="$CLASSPATH:$MONDRIAN_LIB/log4j.jar" CLASSPATH="$CLASSPATH:$MONDRIAN_LIB/servlet-api.jar" CLASSPATH="$CLASSPATH:$MONDRIAN_LIB/eigenbase-properties.jar" CLASSPATH="$CLASSPATH:$MONDRIAN_LIB/commons-math.jar" CLASSPATH="$CLASSPATH:$MONDRIAN_LIB/commons-vfs.jar" CLASSPATH="$CLASSPATH:$MONDRIAN_LIB/commons-logging.jar" CLASSPATH="$CLASSPATH:$MONDRIAN_LIB/derby.jar" # now pick up jdbc jars for j in $MONDRIAN_TEST_LIB/*.jar do CLASSPATH="$CLASSPATH:$j" done export CLASSPATH MAIN=mondrian.tui.CmdRunner java \ -Dlog4j.configuration=file://$MONDRIAN_HOME/log4j.properties \ -cp $CLASSPATH $MAIN "$@" mondrian-3.4.1/build.xml0000644000175000017500000020724211741476754015072 0ustar drazzibdrazzib You must specify a specific project target when using the ANT build. Targets are one of the following: - all Cleans, compiles all classes, and runs a simple test. - compile Compiles functional classes only. - clean Deletes build output. - test Runs a simple test. - info shows configuration info ============================================================== | Mondrian configuration info | ============================================================== project.location = ${project.location} jdk.home = ${env.JAVA_HOME} log4j.configuration = ${log4j.configuration} mondrian.foodmart.catalogURL = ${mondrian.foodmart.catalogURL} mondrian.foodmart.jdbcURL = ${mondrian.foodmart.jdbcURL} mondrian.jdbcDrivers = ${mondrian.jdbcDrivers} driver.classpath (additions) = ${driver.classpath} ============================================================== Actual JVM Version (${java.runtime.version}) does not match requested (${requested.java.version}); skipping compile for this JDK. If you wish to build for this JDK, modify ${unix.script}. Compiling on JVM Version: ${java.runtime.version} Connecting to ${mondrian.foodmart.jdbcURL} Connect String: ${mondrian.test.connectString} Title: ${name} Version: ${project.revision} Vendor: ${vendor} Git head SHA: ${git.head.sha} /* * Project version information. Generated - do not modify. */ package mondrian.olap4j; /** * Version information for the Mondrian olap4j driver. (Generated.) */ class MondrianOlap4jDriverVersion { static final String NAME = "${driver.name}"; static final String VERSION = "${driver.version}"; static final String GIT_HEAD_SHA = "${git.head.sha}"; static final int MAJOR_VERSION = ${driver.version.major}; static final int MINOR_VERSION = ${driver.version.minor}; } // End MondrianOlap4jDriverVersion.java
mondrian-3.4.1/webapp/0000755000175000017500000000000011735330606014504 5ustar drazzibdrazzibmondrian-3.4.1/webapp/busy.jsp0000644000175000017500000000102411735330606016201 0ustar drazzibdrazzib<%@ page session="true" contentType="text/html; charset=ISO-8859-1" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> JPivot is busy ... ">

JPivot is busy ...

Please wait until your results are computed. Click ">here if your browser does not support redirects. mondrian-3.4.1/webapp/xmla.jsp0000644000175000017500000000146511735330606016171 0ustar drazzibdrazzib<%@ page import="mondrian.xmla.test.XmlaTestServletRequestWrapper" language="java" contentType="text/xml" %><% // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde, Sherman Wood // Copyright (C) 2005-2006 Pentaho // All Rights Reserved. // // Julian Hyde, 3 June, 2003 final ServletRequest requestWrapper = new XmlaTestServletRequestWrapper(request); final ServletContext servletContext = config.getServletContext(); final RequestDispatcher dispatcher = servletContext.getNamedDispatcher("MondrianXmlaServlet"); dispatcher.forward(requestWrapper, response); %>mondrian-3.4.1/webapp/pivot.jsp0000644000175000017500000000206511735330606016366 0ustar drazzibdrazzib<%@ page language="java" %> <%-- // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde and others // All Rights Reserved. // // Julian Hyde, June 20, 2002 --%> <%@ taglib uri="/WEB-INF/mdxtable.tld" prefix="mdx" %> Mondrian Pivot Table select {[Measures].[Unit Sales], [Measures].[Store Sales]} on columns, Order([Product].children, [Measures].[Store Sales], DESC) on rows from Sales where ([Time].[1997], [Gender].[M])

Mondrian Pivot Table

[Morph]

Query:


mondrian-3.4.1/webapp/error.jsp0000644000175000017500000000112511735330606016352 0ustar drazzibdrazzib<%@ page session="true" contentType="text/html; charset=ISO-8859-1" %> <%@ page import="java.io.PrintWriter"%> JPivot had an error ...

JPivot had an error ...

<% Throwable e = (Throwable) request.getAttribute("javax.servlet.jsp.jspException"); while (e != null) { %>

<%= e.toString() %>

<%
        e.printStackTrace(new PrintWriter(out));
%>
        
<% Throwable prev = e; e = e.getCause(); if (e == prev) break; } %> mondrian-3.4.1/webapp/morph_pivot.jsp0000644000175000017500000002762211735330606017601 0ustar drazzibdrazzib<%-- // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde and others // All Rights Reserved. // // Julian Hyde, June 20, 2002 --%> <%@ page language="java" import="mondrian.web.taglib.ResultCache, mondrian.olap.*, java.io.PrintWriter, java.util.ArrayList, java.util.List, java.util.Iterator, java.util.LinkedList, java.io.IOException"%> <%@ taglib uri="/WEB-INF/mdxtable.tld" prefix="mdx" %> <%! static final String queryName = "pivotQuery"; static final String redirect = "pivot.jsp"; String actionURL(String operation) { return "mdxquery?query=" + queryName + "&" + operation + "&redirect=" + redirect; } String removeHierarchyUrl(Hierarchy hierarchy) { return actionURL( "operation=remove_hierarchy&hierarchy=" + hierarchy.getUniqueName()); } String moveHierarchyUpURL(Hierarchy hierarchy) { return actionURL( "operation=move_hierarchy_up&hierarchy=" + hierarchy.getUniqueName()); } String moveHierarchyDownURL(Hierarchy hierarchy) { return actionURL( "operation=move_hierarchy_down&hierarchy=" + hierarchy.getUniqueName()); } String moveHierarchyToAxisURL(Hierarchy hierarchy, int targetAxis) { return actionURL( "operation=move_hierarchy_to_axis&hierarchy=" + hierarchy.getUniqueName() + "&to_axis=" + targetAxis); } String replaceHierarchyURL(Hierarchy hierarchy, String expression) { return actionURL( "operation=replace_hierarchy&hierarchy=" + hierarchy.getUniqueName() + "&expression=" + expression); } String setSlicerURL(Member member) { return actionURL( "operation=set_slicer&member=" + member.getUniqueName()); } String doubleQuote(String s) { return Util.quoteForMdx(s); } String makeHref(String url, String text) { if (url == null) { return "" + text + ""; } else { return "" + text + ""; } } ArrayList getMembersOnAxis(Result result, int axisOffset, int offset) { ArrayList list = new ArrayList(); Position[] positions = getAxis(result,axisOffset).positions; for (int i = 0; i < positions.length; i++) { Position position = positions[i]; if (!list.contains(position.members[offset])) { list.add(position.members[offset]); } } return list; } /** * Returns all of a member's ancestors, including itself. */ Member[] getAncestors(Member member) { LinkedList list = new LinkedList(); for (Member m = member; m != null; m = m.getParentMember()) { list.addFirst(m); } return (Member[]) list.toArray(new Member[list.size()]); } /** * Returns all of a member's ancestors, including itself, and their * siblings, and its children, in prefix order. */ Member[] getAncestorsAndSiblings(Member member) { LinkedList list = new LinkedList(); Member[] children = getSchemaReader(member).getMemberChildren(member); if (children != null && children.length > 0) { member = children[0]; } getAncestorsAndSiblings(member,list); return (Member[]) list.toArray(new Member[list.size()]); } void getAncestorsAndSiblings(Member member, List list) { // member's parent // older siblings // member // member's children // younger siblings Member parent = member.getParentMember(); Member[] siblings; int parentPos; if (parent != null) { getAncestorsAndSiblings(parent, list); parentPos = list.indexOf(parent); siblings = getSchemaReader(member).getMemberChildren(parent); } else { parentPos = -1; final Hierarchy hierarchy = member.getHierarchy(); final Schema schema = hierarchy.getDimension().getSchema(); siblings = schema.getSchemaReader().getHierarchyRootMembers(hierarchy); } for (int i = 0; i < siblings.length; i++) { Member sibling = siblings[i]; list.add(parentPos + 1 + i, sibling); } } void generateAxis(Result result, int axis, JspWriter out) throws IOException { Hierarchy[] hierarchies = getHierarchiesOnAxis(result, axis); out.write("

" + getAxisName(axis) + "

"); // e.g. "

Columns:

" out.write("
    "); for (int i = 0; i < hierarchies.length; i++) { Hierarchy hierarchy = hierarchies[i]; out.write("
  • "); out.write(hierarchy.getName()); out.write(" [" + makeHref(removeHierarchyUrl(hierarchy), "remove") + "]"); out.write(" [" + makeHref(i == 0 ? null : moveHierarchyUpURL(hierarchy), "move up") + "]"); out.write(" [" + makeHref(i == hierarchies.length - 1 ? null : moveHierarchyDownURL(hierarchy), "move down") + "]"); for (int j = -1; j < result.getAxes().length; j++) { if (j != axis) { out.write(" [" + makeHref(moveHierarchyToAxisURL(hierarchy,j), "move to " + getAxisName(j)) + "]"); } } if (axis == SLICER_AXIS) { // Want to generate something like the following. //
  • Time [remove] [move to columns] [move to rows]
    //

/// // // //
1997
//   Q1
//   Q2
//   Q3 (current)
//     July
//     August
//     September
//   Q4
// 1998
// 1999
// out.write("

"); Member member = result.getSlicerAxis().positions[0].members[i]; Member[] ancestors = getAncestorsAndSiblings(member); for (int j = 0; j < ancestors.length; j++) { if (j > 0) { out.write("
"); out.newLine(); } Member ancestor = ancestors[j]; for (int k = 0, depth = ancestor.getLevel().getDepth(); k < depth; k++) { out.write("   "); } if (ancestor == member) { out.write(ancestor.getName() + " (current)"); } else { out.write(makeHref(setSlicerURL(ancestor), ancestor.getName())); } } out.write("
"); } out.write(""); out.newLine(); if (hierarchy.getDimension().isMeasures()) { out.write("
    "); List membersList = getMembersOnAxis(result, axis, i); for (Iterator iterator = membersList.iterator(); iterator.hasNext();) { Member member = (Member) iterator.next(); out.write("
  • " + member.getName()); out.write(" [" + makeHref(replaceHierarchyURL(hierarchy,"{todo: [Measures].[A],[Measures].[B], [Measures].[C]}"),"move up") + "]"); out.write(" [" + makeHref(replaceHierarchyURL(hierarchy,"{todo: [Measures].[A],[Measures].[B], [Measures].[C]}"),"move down") + "]"); out.write(" [" + makeHref(replaceHierarchyURL(hierarchy,"{todo: [Measures].[A],[Measures].[B], [Measures].[C]}"),"remove") + "]"); out.write("
  • "); out.newLine(); } final SchemaReader schemaReader = hierarchy.getDimension().getSchema().getSchemaReader(); Member[] allMeasures = schemaReader.getHierarchyRootMembers(hierarchy); for (int j = 0; j < allMeasures.length; j++) { Member measure = allMeasures[j]; if (membersList.contains(measure)) { continue; } out.write("
  • " + measure.getName()); out.write(" [" + makeHref(replaceHierarchyURL(hierarchy,"{todo: [Measures].[A],[Measures].[B], [Measures].[C]}"),"add") + "]"); out.write("
  • "); out.newLine(); } out.write("
"); out.newLine(); } } out.write(""); out.newLine(); } SchemaReader getSchemaReader(OlapElement element) { return null; } Hierarchy[] getHierarchiesOnAxis(Result result, int axis) { Axis resultAxis = getAxis(result,axis); Position position = resultAxis.positions[0]; int count = position.members.length; Hierarchy[] hierarchies = new Hierarchy[count]; for (int i = 0; i < hierarchies.length; i++) { hierarchies[i] = position.members[i].getHierarchy(); } return hierarchies; } boolean isHierarchyUsed(Result result, Hierarchy hierarchy) { for (int i = -1, axisCount = result.getAxes().length; i < axisCount; i++) { Axis axis = getAxis(result, i); for (int j = 0; j < axis.positions[0].members.length; j++) { Member member = axis.positions[0].members[j]; if (member.getHierarchy() == hierarchy) { return true; } } } return false; } Axis getAxis(Result result, int axis) { if (axis < 0) { return result.getSlicerAxis(); } else { return result.getAxes()[axis]; } } private static final int SLICER_AXIS = -1; private static final int COLUMNS_AXIS = 0; private static final int ROWS_AXIS = 1; String getAxisName(int axis) { switch (axis) { case -1: return "slicer"; case 0: return "columns"; case 1: return "rows"; default: return "axis#" + axis; } } %> Morph Pivot Table <% ResultCache rc = ResultCache.getInstance( pageContext.getSession(), pageContext.getServletContext(), queryName); Query query = rc.getQuery(); QueryAxis[] axes = query.axes; Result result = rc.getResult(); %>

Organize hierarchies

<% generateAxis(result, COLUMNS_AXIS, out); generateAxis(result, ROWS_AXIS, out); generateAxis(result, SLICER_AXIS, out); %>

Hierarchies not shown:

    <% Cube cube = query.getCube(); for (int i = 0; i < cube.getDimensions().length; i++) { Dimension dimension = cube.getDimensions()[i]; for (int j = 0; j < dimension.getHierarchies().length; j++) { Hierarchy hierarchy = dimension.getHierarchies()[j]; if (isHierarchyUsed(result, hierarchy)) { continue; } out.write("
  • " + hierarchy.getName()); for (int k = -1; k < result.getAxes().length; k++) { out.write(" [" + makeHref(moveHierarchyToAxisURL(hierarchy,k), "add to " + getAxisName(k)) + "]"); } out.write("
  • "); out.newLine(); } } %>

[OK] <%-- End morph_pivot.jsp --%>mondrian-3.4.1/webapp/adhoc.jsp0000644000175000017500000002473311735330606016311 0ustar drazzibdrazzib<%@page contentType="text/html"%> <%-- // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde and others // All Rights Reserved. // // jhyde, 6 August, 2001 --%> <% final String nl = System.getProperty("line.separator"); String[] queries = new String[] { // #0 "select {[Measures].[Unit Sales]} on columns" + nl + " from Sales", // mdx sample #1 "select" + nl + " {[Measures].[Unit Sales]} on columns," + nl + " order(except([Promotion Media].[Media Type].members,{[Promotion Media].[Media Type].[No Media]}),[Measures].[Unit Sales],DESC) on rows" + nl + "from Sales", // mdx sample #2 "select" + nl + " { [Measures].[Units Shipped], [Measures].[Units Ordered] } on columns," + nl + " NON EMPTY [Store].[Store Name].members on rows" + nl + "from Warehouse", // mdx sample #3 "with member [Measures].[Store Sales Last Period] as '([Measures].[Store Sales], Time.PrevMember)'" + nl + "select" + nl + " {[Measures].[Store Sales Last Period]} on columns," + nl + " {TopCount([Product].[Product Department].members,5, [Measures].[Store Sales Last Period])} on rows" + nl + "from Sales" + nl + "where ([Time].[1998])", // mdx sample #4 "with member [Measures].[Total Store Sales] as 'Sum(YTD(),[Measures].[Store Sales])'" + nl + "select" + nl + " {[Measures].[Total Store Sales]} on columns," + nl + " {TopCount([Product].[Product Department].members,5, [Measures].[Total Store Sales])} on rows" + nl + "from Sales" + nl + "where ([Time].[1997].[Q2].[4])", // mdx sample #5 "with member [Measures].[Store Profit Rate] as '([Measures].[Store Sales]-[Measures].[Store Cost])/[Measures].[Store Cost]', format = '#.00%'" + nl + "select" + nl + " {[Measures].[Store Cost],[Measures].[Store Sales],[Measures].[Store Profit Rate]} on columns," + nl + " Order([Product].[Product Department].members, [Measures].[Store Profit Rate], BDESC) on rows" + nl + "from Sales" + nl + "where ([Time].[1997])", // mdx sample #6 "with" + nl + " member [Product].[All Products].[Drink].[Percent of Alcoholic Drinks] as '[Product].[All Products].[Drink].[Alcoholic Beverages]/[Product].[All Products].[Drink]', format = '#.00%'" + nl + "select" + nl + " { [Product].[All Products].[Drink].[Percent of Alcoholic Drinks] } on columns," + nl + " order([Customers].[All Customers].[USA].[WA].Children, [Product].[All Products].[Drink].[Percent of Alcoholic Drinks],BDESC ) on rows" + nl + "from Sales" + nl + "where ( [Measures].[Unit Sales] )", // mdx sample #7 "with member [Measures].[Accumulated Sales] as 'Sum(YTD(),[Measures].[Store Sales])'" + nl + "select" + nl + " {[Measures].[Store Sales],[Measures].[Accumulated Sales]} on columns," + nl + " {Descendants([Time].[1997],[Time].[Month])} on rows" + nl + "from Sales", // #8 "select" + nl + " {[Measures].[Unit Sales]} on columns," + nl + " [Gender].members on rows" + nl + "from Sales", /* // #9 "with" + nl + " member [Product].[Non dairy] as '[Product].[All Products] - [Product].[Food].[Dairy]'" + nl + " member [Measures].[Dairy ever] as 'sum([Time].members, ([Measures].[Unit Sales],[Product].[Food].[Dairy]))'" + nl + " set [Customers who never bought dairy] as 'filter([Customers].members, [Measures].[Dairy ever] = 0)'" + nl + "select" + nl + " {[Measures].[Unit Sales], [Measures].[Dairy ever]} on columns," + nl + " [Customers who never bought dairy] on rows" + nl + "from Sales\r\n", */ // #10 "WITH" + nl + " MEMBER [Measures].[StoreType] AS " + nl + " '[Store].CurrentMember.Properties(\"Store Type\")'," + nl + " SOLVE_ORDER = 2" + nl + " MEMBER [Measures].[ProfitPct] AS " + nl + " '(Measures.[Store Sales] - Measures.[Store Cost]) / Measures.[Store Sales]'," + nl + " SOLVE_ORDER = 1, FORMAT_STRING = 'Percent'" + nl + "SELECT" + nl + " { [Store].[Store Name].Members} ON COLUMNS," + nl + " { [Measures].[Store Sales], [Measures].[Store Cost], [Measures].[StoreType]," + nl + " [Measures].[ProfitPct] } ON ROWS" + nl + "FROM Sales", // #11 "WITH" + nl + " MEMBER [Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[BigSeller] AS" + nl + " 'IIf([Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine] > 100, \"Yes\",\"No\")'" + nl + "SELECT" + nl + " {[Product].[All Products].[Drink].[Alcoholic Beverages].[Beer and Wine].[BigSeller]} ON COLUMNS," + nl + " {Store.[Store Name].Members} ON ROWS" + nl + "FROM Sales", // #12 "WITH" + nl + " MEMBER [Measures].[ProfitPct] AS " + nl + " '([Measures].[Store Sales] - [Measures].[Store Cost]) / [Measures].[Store Sales]'," + nl + " SOLVE_ORDER = 1, FORMAT_STRING = 'Percent'" + nl + " MEMBER [Measures].[ProfitValue] AS " + nl + " '[Measures].[Store Sales] * [Measures].[ProfitPct]'," + nl + " SOLVE_ORDER = 2, FORMAT_STRING = 'Currency'" + nl + "SELECT" + nl + " { [Store].[Store Name].Members} ON COLUMNS," + nl + " { [Measures].[Store Sales], [Measures].[Store Cost], [Measures].[ProfitValue]," + nl + " [Measures].[ProfitPct] } ON ROWS" + nl + "FROM Sales", /* // #13: cyclical calculated members "WITH" + nl + " MEMBER [Product].[X] AS '[Product].[Y]'" + nl + " MEMBER [Product].[Y] AS '[Product].[X]'" + nl + "SELECT" + nl + " {[Product].[X]} ON COLUMNS," + nl + " {Store.[Store Name].Members} ON ROWS" + nl + "FROM Sales", */ // #14 "WITH MEMBER MEASURES.ProfitPercent AS" + nl + " '([Measures].[Store Sales]-[Measures].[Store Cost])/([Measures].[Store Cost])'," + nl + " FORMAT_STRING = '#.00%', SOLVE_ORDER = 1" + nl + " MEMBER [Time].[First Half 97] AS '[Time].[1997].[Q1] + [Time].[1997].[Q2]'" + nl + " MEMBER [Time].[Second Half 97] AS '[Time].[1997].[Q3] + [Time].[1997].[Q4]'" + nl + " SELECT {[Time].[First Half 97]," + nl + " [Time].[Second Half 97]," + nl + " [Time].[1997].CHILDREN} ON COLUMNS," + nl + " {[Store].[Store Country].[USA].CHILDREN} ON ROWS" + nl + " FROM [Sales]" + nl + " WHERE ([Measures].[ProfitPercent])", // #15 (= mdx sample #7, but uses virtual cube) "with member [Measures].[Accumulated Sales] as 'Sum(YTD(),[Measures].[Store Sales])'" + nl + "select" + nl + " {[Measures].[Store Sales],[Measures].[Accumulated Sales]} on columns," + nl + " {Descendants([Time].[1997],[Time].[Month])} on rows" + nl + "from [Warehouse and Sales]", // #16 Virtual cube. Note that Unit Sales is independent of Warehouse. "select CrossJoin(\r\n"+ " {[Warehouse].DefaultMember, [Warehouse].[USA].children}," + nl + " {[Measures].[Unit Sales], [Measures].[Units Shipped]}) on columns," + nl + " [Time].children on rows" + nl + "from [Warehouse and Sales]", // #17 crossjoins on rows and columns, and a slicer "select" + nl + " CrossJoin(" + nl + " {[Measures].[Unit Sales], [Measures].[Store Sales]}," + nl + " {[Time].[1997].[Q2].children}) on columns, " + nl + " CrossJoin(" + nl + " CrossJoin(" + nl + " [Gender].members," + nl + " [Marital Status].members)," + nl + " {[Store], [Store].children}) on rows" + nl + "from [Sales]" + nl + "where (" + nl + " [Product].[Food]," + nl + " [Education Level].[High School Degree]," + nl + " [Promotions].DefaultMember)", }; %> JSP Page back to index

<% if (request.getAttribute("result") != null) { %> <% } %>
Results: <% out.println(request.getAttribute("result")); %>
back to index

mondrian-3.4.1/webapp/testpage.jsp0000644000175000017500000001466511735330606017052 0ustar drazzibdrazzib<%@ page session="true" contentType="text/html; charset=ISO-8859-1" %> <%@ taglib uri="http://www.tonbeller.com/jpivot" prefix="jp" %> <%@ taglib uri="http://www.tonbeller.com/wcf" prefix="wcf" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> <%-- JPivot / WCF comes with its own "expression language", which simply is a path of properties. E.g. #{customer.address.name} is translated into: session.getAttribute("customer").getAddress().getName() WCF uses jakarta commons beanutils to do so, for an exact syntax see its documentation. With JSP 2.0 you should use #{} notation to define expressions for WCF attributes and \${} to define JSP EL expressions. JSP EL expressions can not be used with WCF tags currently, all tag attributes have their rtexprvalue set to false. There may be a twin library supporting JSP EL expressions in the future (similar to the twin libraries in JSTL, e.g. core and core_rt). Check out the WCF distribution which contains many examples on how to use the WCF tags (like tree, form, table etc). --%> Mondrian/JPivot Test Page

<%-- include query and title, so this jsp may be used with different queries --%> <%-- define table, navigator and forms --%>

<%-- define a toolbar --%> <%-- render toolbar --%>

<%-- if there was an overflow, show error message --%>

Resultset overflow occured

<%-- render navigator --%> <%-- edit mdx --%>

MDX Query Editor

<%-- sort properties --%> <%-- chart properties --%> <%-- print properties --%>

Slicer:

back to index

mondrian-3.4.1/webapp/WEB-INF/0000755000175000017500000000000011735330606015533 5ustar drazzibdrazzibmondrian-3.4.1/webapp/WEB-INF/queries/0000755000175000017500000000000011735330606017210 5ustar drazzibdrazzibmondrian-3.4.1/webapp/WEB-INF/queries/xmla.jsp0000644000175000017500000000144611735330606020674 0ustar drazzibdrazzib<%@ page session="true" pageEncoding="UTF-8" contentType="text/html; charset=ISO-8859-1" %> <%@ taglib uri="http://www.tonbeller.com/jpivot" prefix="jp" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> select {[Measures].[Unit Sales], [Measures].[Store Cost], [Measures].[Store Sales]} on columns, {([Promotion Media].[All Media], [Product].[All Products])} ON rows from Sales where ([Time].[1997]) Accessing Mondrian By XMLA mondrian-3.4.1/webapp/WEB-INF/queries/testrole.jsp0000644000175000017500000000241011735330606021564 0ustar drazzibdrazzib<%@ page session="true" contentType="text/html; charset=ISO-8859-1" %> <%@ taglib uri="http://www.tonbeller.com/jpivot" prefix="jp" %> <%@ taglib prefix="c" uri="http://java.sun.com/jstl/core" %> <%-- uses a dataSource --%> <%-- jp:mondrianQuery id="query01" dataSource="jdbc/MondrianFoodmart" catalogUri="/WEB-INF/demo/FoodMart.xml" --%> <%-- uses mysql --%> <%-- jp:mondrianQuery id="query01" jdbcDriver="com.mysql.jdbc.Driver" jdbcUrl="jdbc:mysql://localhost/foodmart" catalogUri="/WEB-INF/queries/FoodMart.xml"--%> <%-- uses a role defined in FoodMart.xml --%> <%-- jp:mondrianQuery role="California manager" id="query01" jdbcDriver="sun.jdbc.odbc.JdbcOdbcDriver" jdbcUrl="jdbc:odbc:MondrianFoodMart" catalogUri="/WEB-INF/queries/FoodMart.xml" --%> select {[Measures].[Unit Sales], [Measures].[Store Cost], [Measures].[Store Sales]} on columns, {([Marital Status].[All Marital Status], [Customers], [Product].[All Products])} on rows from Sales where ([Time].[1997]) Test query uses Mondrian OLAP, using role 'California manager' mondrian-3.4.1/webapp/WEB-INF/mdxtable.tld0000644000175000017500000000336311735330606020045 0ustar drazzibdrazzib 0.1 1.2 mdxtable Mondrian MDX Table Tag Library Mondrian MDX Table Tag Library query mondrian.web.taglib.QueryTag tagdependent name true false resultCache false false conrols wether the query is executed on every request (good for development) transform mondrian.web.taglib.TransformTag EMPTY query true false xsltURI true false xsltCache false false mondrian-3.4.1/webapp/WEB-INF/mdxslicer.xsl0000644000175000017500000000147611735330606020265 0ustar drazzibdrazzib , mondrian-3.4.1/webapp/WEB-INF/web-jpivot.xml0000644000175000017500000001147611735330606020354 0ustar drazzibdrazzib Mondrian contextFactory com.tonbeller.wcf.controller.RequestContextFactoryImpl connectString @mondrian.webapp.connectString@ JPivotController com.tonbeller.wcf.controller.RequestFilter errorJSP /error.jsp URI of error page busyJSP /busy.jsp This page is displayed if a the user clicks on a query before the previous query has finished JPivotController /testpage.jsp mondrian.web.taglib.Listener com.tonbeller.tbutils.res.ResourcesFactoryContextListener MDXQueryServlet mondrian.web.servlet.MdxQueryServlet connectString @mondrian.webapp.connectString@ MondrianXmlaServlet mondrian.xmla.impl.DynamicDatasourceXmlaServlet DataSourcesConfig /WEB-INF/datasources.xml DisplayChart org.jfree.chart.servlet.DisplayChart GetChart GetChart Default configuration created for servlet. com.tonbeller.jpivot.chart.GetChart Print Print Default configuration created for servlet. com.tonbeller.jpivot.print.PrintServlet DisplayChart /DisplayChart Print /Print GetChart /GetChart MDXQueryServlet /mdxquery MondrianXmlaServlet /xmla index.html http://www.tonbeller.com/wcf /WEB-INF/wcf/wcf-tags.tld http://www.tonbeller.com/jpivot /WEB-INF/jpivot/jpivot-tags.tld mondrian-3.4.1/webapp/WEB-INF/mdxpivot.xsl0000644000175000017500000000643211735330606020142 0ustar drazzibdrazzib ]>
 
mondrian-3.4.1/webapp/WEB-INF/web.xml0000644000175000017500000000413111735330606017031 0ustar drazzibdrazzib JPivot connectString @mondrian.webapp.connectString@ mondrian.web.taglib.Listener MDXQueryServlet mondrian.web.servlet.MdxQueryServlet connectString @mondrian.webapp.connectString@ MondrianXmlaServlet mondrian.xmla.impl.DynamicDatasourceXmlaServlet DataSourcesConfig /WEB-INF/datasources.xml MDXQueryServlet /mdxquery MondrianXmlaServlet /xmla 30 index.html mondrian-3.4.1/webapp/WEB-INF/datasources.xml0000644000175000017500000000677711735330606020613 0ustar drazzibdrazzib Provider=Mondrian;DataSource=MondrianFoodMart; Mondrian FoodMart Data Warehouse http://localhost:8888/mondrian/xmla Provider=mondrian;Jdbc=jdbc:oracle:thin:foodmart/foodmart@//marmalade.hydromatic.net:1521/XE;JdbcUser=foodmart;JdbcPassword=foodmart;JdbcDrivers=oracle.jdbc.OracleDriver;Catalog=/WEB-INF/queries/FoodMart.xml Mondrian MDP Unauthenticated /WEB-INF/queries/FoodMart.xml mondrian-3.4.1/webapp/WEB-INF/mdxvalue.xsl0000644000175000017500000000047611735330606020117 0ustar drazzibdrazzib mondrian-3.4.1/webapp/WEB-INF/mdxtable.xsl0000644000175000017500000000614311735330606020067 0ustar drazzibdrazzib ]>
 
mondrian-3.4.1/webapp/WEB-INF/mdxquery.xsl0000644000175000017500000000136111735330606020142 0ustar drazzibdrazzib ]> mondrian-3.4.1/webapp/taglib.jsp0000644000175000017500000000771311735330606016474 0ustar drazzibdrazzib<%@ page language="java" %> <%-- // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde and others. // All Rights Reserved. // // Andreas Voss, 27 March, 2002 --%> <%@ taglib uri="/WEB-INF/mdxtable.tld" prefix="mdx" %> Mondrian MDX Table Taglib Demo back to index

select {[Measures].[Unit Sales], [Measures].[Store Cost], [Measures].[Store Sales]} on columns, CrossJoin( { [Promotion Media].[All Media].[Radio], [Promotion Media].[All Media].[TV], [Promotion Media].[All Media].[Sunday Paper], [Promotion Media].[All Media].[Street Handout] }, [Product].[All Products].[Drink].children) on rows from Sales where ([Time].[1997]) select [Product].[All Products].[Drink].children on rows, CrossJoin( {[Measures].[Unit Sales], [Measures].[Store Sales]}, { [Promotion Media].[All Media].[Radio], [Promotion Media].[All Media].[TV], [Promotion Media].[All Media].[Sunday Paper], [Promotion Media].[All Media].[Street Handout] } ) on columns from Sales where ([Time].[1997]) select {[Measures].[Unit Sales], [Measures].[Store Sales]} on columns, Order([Product].[Product Department].members, [Measures].[Store Sales], DESC) on rows from Sales select [Product].[All Products].[Drink].children on columns from Sales where ([Measures].[Unit Sales], [Promotion Media].[All Media].[Street Handout], [Time].[1997]) select NON EMPTY CrossJoin([Product].[All Products].[Drink].children, [Customers].[All Customers].[USA].[WA].Children) on rows, CrossJoin( {[Measures].[Unit Sales], [Measures].[Store Sales]}, { [Promotion Media].[All Media].[Radio], [Promotion Media].[All Media].[TV], [Promotion Media].[All Media].[Sunday Paper], [Promotion Media].[All Media].[Street Handout] } ) on columns from Sales where ([Time].[1997]) select from Sales where ([Measures].[Store Sales], [Time].[1997], [Promotion Media].[All Media].[TV])

Mondrian Taglib Examples

CrossJoin Example 1

The current slicer is .

CrossJoin Example 2

The same thing the other way round. The current slicer is .

Simple Table

1-dim Table

A One dimensional table. Slicer is ""

CrossJoin on both axes

Here is the slicer: ""

0-dim example

0-dim "tables" may be useful, if you want to display calculated numbers in a form or text. For example, the Store Sales were in 1997 for TV promoted products.

back to index

mondrian-3.4.1/webapp/xmlaTest.jsp0000644000175000017500000000705511735330606017032 0ustar drazzibdrazzib<%@ page import="mondrian.olap.Util, org.w3c.dom.Element, org.eigenbase.xom.XMLUtil, java.io.PrintWriter, java.io.StringWriter, mondrian.xmla.test.XmlaTestContext, org.apache.log4j.Logger"%> <%@ page language="java" %> <%-- // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2007 Pentaho // All Rights Reserved. // // Julian Hyde, 3 June, 2003 --%> <%! private static final Logger LOGGER = Logger.getLogger("mondrian.jsp.xmlaTest"); /** * XML-encodes a string. */ private static String wrap(String s) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); XMLUtil.stringEncodeXML(s, pw); pw.flush(); return sw.toString(); } %> Mondrian XML for Analysis Tester back to index

<% XmlaTestContext context = new XmlaTestContext(); String[][] requests = context.defaultRequests(); int whichRequest = 0; if (request.getParameter("whichrequest") != null) { whichRequest = Integer.valueOf(request.getParameter("whichrequest")).intValue(); } if (whichRequest >= requests.length) { whichRequest = requests.length - 1; } String[] defaultPair = requests[whichRequest]; String defaultRequest = defaultPair[1]; String postURL = request.getParameter("postURL"); if (postURL == null) { postURL = "xmla.jsp"; } %>

Request
 

 

Target URL: <%= postURL %>
New target URL:
back to index

mondrian-3.4.1/webapp/index.jsp0000644000175000017500000000352211735330606016333 0ustar drazzibdrazzib<%@page contentType="text/html"%> <%-- // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho and others // All Rights Reserved. // // jhyde, 6 August, 2001 --%> Mondrian OLAP Server

Mondrian examples:

Other links:

mondrian-3.4.1/webapp/zero.jsp0000644000175000017500000005111211735330606016201 0ustar drazzibdrazzib<%@ page import="mondrian.olap.*" %> <%@ page import="java.util.*" %> <% // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2007 Pentaho // All Rights Reserved. %> <%! private static final String ACTION_PARAM = "action"; private static final String ACTION_UNLIMIT_MEMBERS = "unlimitMembers"; private static final String ACTION_DESELECT_MEMBERS = "deselectMembers"; private static final String ACTION_SELECT_MEMBER = "selectMember"; private static final String ACTION_SET_MEMBER = "setMember"; private static final String OLAP_URL = "Provider=mondrian; Jdbc=jdbc:odbc:MondrianFoodMart; Catalog=file:/c:/open/mondrian/demo/FoodMart.xml"; private Cube lookup(Cube[] cubes, String cubeName) { for (Cube cube : cubes) { if (cube.getName().equals(cubeName)) { return cube; } } return null; } private ModelMember lookup(List members, String memberName) { for (ModelMember member : members) { if (member.member.getName().equals(memberName)) { return member; } } return null; } private ModelDimension lookup(List dimensions, String dimensionName) { for (ModelDimension dimension : dimensions) { if (dimension.dimension.getName().equals(dimensionName)) { return dimension; } } return null; } static class Pane { private final List dimensions = new ArrayList(); } static class ModelDimension { private final Model model; private final Dimension dimension; private Member currentMember; private boolean currentMemberSelected; private final List members = new ArrayList(); private int maxMemberCount; private boolean needToRecomputeMembers = true; private boolean hasMoreMembersThanShown; public ModelDimension(Model model, Dimension dimension) { this.model = model; this.dimension = dimension; this.maxMemberCount = model.getDefaultMaxMemberCount(); setParentMember(dimension.getHierarchy().getDefaultMember()); } /** * Sets the current embmer of this dimension. * *

After calling this method, you * need to call bar(, true) to populate the members. If you are * setting several dimensions at once, defer the calls until all have * been set. * * @param member */ private void setParentMember(Member member) { this.currentMember = member; this.members.clear(); this.needToRecomputeMembers = true; } public List getMembers() { return members; } public List getNonZeroMembers() { final ArrayList list = new ArrayList(); for (ModelMember member : members) { if (member.count > 0) { list.add(member); } } // Sort by count descending Collections.sort( list, new Comparator() { public int compare(ModelMember o1, ModelMember o2) { if (o1.count != o2.count) { return o2.count - o1.count; } return o1.member.getUniqueName().compareTo( o2.member.getUniqueName()); } } ); if (maxMemberCount >= 0 && list.size() > maxMemberCount) { return list.subList(0, maxMemberCount); } return list; } public String getCaption() { return dimension.getCaption(); } public String urlToRemove() { return "zero.jsp?action=" + ACTION_DESELECT_MEMBERS + "&dimension=" + dimension.getName(); } public String urlToRemoveLimit() { return "zero.jsp?action=" + ACTION_UNLIMIT_MEMBERS + "&dimension=" + dimension.getName(); } public boolean existsSelected() { if (needToRecomputeMembers) { return false; } if (currentMemberSelected) { return true; } for (ModelMember member : members) { if (member.selected) { return true; } } return false; } public String urlToSetMember(Member member) { assert member.getDimension() == dimension; return "zero.jsp?action=" + ACTION_SET_MEMBER + "&dimension=" + dimension.getName() + "&member=" + member.getUniqueName(); } } static class ModelMember { final Member member; boolean selected; private int count; ModelMember(Member member) { System.out.println("create member " + member); this.member = member; } public String urlToAdd() { return "zero.jsp?action=" + ACTION_SELECT_MEMBER + "&dimension=" + member.getDimension().getName() + "&member=" + member.getName(); } public String getCaption() { return member.getCaption(); } public int getCount() { return count; } } static class Model { private final Connection connection; private final Cube cube; private List dimensions = new ArrayList(); public Model(Connection connection, Cube cube) { this.connection = connection; this.cube = cube; for (Dimension dimension : cube.getDimensions()) { if (!dimension.isMeasures()) { dimensions.add(new ModelDimension(this, dimension)); } } foo(null); } /** * After the selection of a given dimension has changed, recomputes the * cardinality of the members in all other dimensions. * * @param modelDimension Dimension which changed, or null to update * everything */ void foo(ModelDimension modelDimension) { // Are there any dimensions which need recomputing? boolean exist = true; // todo: for (ModelDimension dimension : dimensions) { if (dimension.needToRecomputeMembers) { exist = true; } } // If any dimensions need recomputing, all dimensions with no // selected members float up to their 'all' members. They will // float down again. if (exist) { for (ModelDimension dimension : dimensions) { if (!dimension.needToRecomputeMembers && !dimension.existsSelected()) { dimension.setParentMember( dimension.dimension.getHierarchy().getDefaultMember()); } } } for (ModelDimension dimension : dimensions) { bar(dimension); } } /** * Recomputes the cardinality of the members of a given dimension. * *

If the dimension has only one member with non-zero cardinality, * that member becomes the current member. * *

If the dimension has no non-zero members, the dimension is * flagged invisible. (TODO:) * * @param modelDimension Dimension whose cardinality compute */ void bar(ModelDimension modelDimension) { String axis; if (modelDimension.needToRecomputeMembers) { axis = "{" + modelDimension.currentMember.getUniqueName() + ".Children}"; if (modelDimension.maxMemberCount >= 0) { axis = "TopCount(" + axis + ", " + (modelDimension.maxMemberCount + 1) + ")"; } } else { StringBuilder buf = new StringBuilder("{"); int k = -1; for (ModelMember modelMember : modelDimension.getMembers()) { if (++k > 0) { buf.append(",\n"); } buf.append(modelMember.member.getUniqueName()); } buf.append("}"); axis = buf.toString(); } StringBuilder withSet = new StringBuilder(); StringBuilder slicer = new StringBuilder(); for (ModelDimension dimension : dimensions) { if (dimension == modelDimension) { continue; } String theMember; if (dimension.existsSelected()) { if (withSet.length() == 0) { withSet.append("WITH "); } theMember = dimension.dimension.getUniqueName() + ".[Foo]"; withSet.append("MEMBER ") .append(theMember) .append(" AS 'Aggregate({"); int k = -1; for (ModelMember modelMember : dimension.getMembers()) { if (!modelMember.selected) { continue; } ++k; if (k > 0) { withSet.append(", "); } withSet.append(modelMember.member.getUniqueName()); } withSet.append("})'\n"); } else { theMember = dimension.currentMember.getUniqueName(); } if (slicer.length() == 0) { slicer.append("\nWHERE ("); } else { slicer.append(", "); } slicer.append(theMember); } if (slicer.length() > 0) { slicer.append(")"); } String mdx = withSet + "SELECT NON EMPTY " + axis + " ON COLUMNS\n" + "FROM " + cube.getUniqueName() + slicer; System.out.println("MDX:\n " + mdx); Query query = connection.parseQuery(mdx); Result result = connection.execute(query); int nonZeroMembers = 0; if (modelDimension.needToRecomputeMembers) { modelDimension.members.clear(); int i = -1; modelDimension.hasMoreMembersThanShown = false; for (Position position : result.getAxes()[0].getPositions()) { ++i; if (modelDimension.maxMemberCount >= 0 && i > modelDimension.maxMemberCount - 1) { modelDimension.hasMoreMembersThanShown = true; } final Cell cell = result.getCell(new int[]{i}); final int count = ((Number) cell.getValue()).intValue(); if (count > 0) { ++nonZeroMembers; } ModelMember modelMember = new ModelMember(position.get(0)); modelMember.count = count; modelDimension.members.add(modelMember); } modelDimension.needToRecomputeMembers = false; } else { int i = -1; Map memberCounts = new HashMap(); for (Position position : result.getAxes()[0].getPositions()) { ++i; final Cell cell = result.getCell(new int[]{i}); memberCounts.put( position.get(0), ((Number) cell.getValue()).intValue()); } for (ModelMember modelMember : modelDimension.getMembers()) { final Integer integer = memberCounts.get(modelMember.member); final int count; if (integer == null) { count = 0; } else { count = integer; assert count > 0; ++nonZeroMembers; } modelMember.count = count; } } if (nonZeroMembers == 1 && modelDimension.currentMember.getLevel().getDepth() < modelDimension.currentMember.getHierarchy().getLevels().length - 1) { // Drill down, making the sole remaining member the parent. for (ModelMember modelMember : modelDimension.getMembers()) { if (modelMember.count > 0) { modelDimension.setParentMember(modelMember.member); // recursive call bar(modelDimension); return; } } } } /** * Returns the maximum number of members to display in a dimension. * Dimensions are created with this setting, and reset to this on * drillup or drilldown. * Return -1 if you want to always show all members. * * @return maximum number of members to display in a dimension, or -1 * to always show all members */ public int getDefaultMaxMemberCount() { return 7; } } %> <% Model model; String cubeName = request.getParameter("cubeName"); if (cubeName != null) { model = null; } else { model = (Model) session.getAttribute("ctx"); } if (model == null) { if (cubeName == null) { cubeName = "Sales"; } Connection connection = DriverManager.getConnection(OLAP_URL, null); Cube cube = lookup(connection.getSchema().getCubes(), cubeName); if (cube == null) { %> Cube <%= cubeName %> not found.<% return; } model = new Model(connection, cube); session.setAttribute("ctx", model); } final String action = request.getParameter(ACTION_PARAM); if (action == null) { // nothing } else if (action.equals(ACTION_SELECT_MEMBER)) { String dimensionName = request.getParameter("dimension"); String memberName = request.getParameter("member"); ModelDimension modelDimension = lookup(model.dimensions, dimensionName); if (modelDimension == null) { throw new IllegalArgumentException(); } ModelMember modelMember = lookup(modelDimension.getMembers(), memberName); if (modelMember == null) { throw new IllegalArgumentException(); } System.out.println("select " + modelMember.member); modelDimension.currentMemberSelected = false; modelMember.selected = true; model.foo(modelDimension); } else if (action.equals(ACTION_DESELECT_MEMBERS)) { String dimensionName = request.getParameter("dimension"); ModelDimension modelDimension = lookup(model.dimensions, dimensionName); if (modelDimension == null) { throw new IllegalArgumentException(); } for (ModelMember modelMember : modelDimension.getMembers()) { modelMember.selected = false; } modelDimension.currentMemberSelected = false; modelDimension.maxMemberCount = model.getDefaultMaxMemberCount(); model.foo(modelDimension); } else if (action.equals(ACTION_UNLIMIT_MEMBERS)) { String dimensionName = request.getParameter("dimension"); ModelDimension modelDimension = lookup(model.dimensions, dimensionName); if (modelDimension == null) { throw new IllegalArgumentException(); } modelDimension.hasMoreMembersThanShown = false; modelDimension.maxMemberCount = -1; } else if (action.equals(ACTION_SET_MEMBER)) { String dimensionName = request.getParameter("dimension"); ModelDimension modelDimension = lookup(model.dimensions, dimensionName); if (modelDimension == null) { throw new IllegalArgumentException(); } String memberUniqueName = request.getParameter("member"); final Member member = (Member) model.cube.getSchema().getSchemaReader().lookupCompound( model.cube, Util.parseIdentifier(memberUniqueName), true, Category.Member); modelDimension.setParentMember(member); modelDimension.currentMemberSelected = true; model.foo(modelDimension); } else { throw new RuntimeException("Unknown action '" + action + "'"); } // Assign dimensions to panes List paneList = new ArrayList(); int paneCount = 3; for (int i = 0; i < paneCount; ++i) { Pane pane = new Pane(); paneList.add(pane); } int i = -1; for (ModelDimension modelDimension : model.dimensions) { ++i; int paneIndex = i % paneCount; paneList.get(paneIndex).dimensions.add(modelDimension); } %>
<% for (Pane pane : paneList) { %> <% } %>
<% for (ModelDimension dimension : pane.dimensions) { %>

<%= dimension.getCaption() %> <% Member m = dimension.currentMember; if (m != null && !m.isAll()) { List ancestors = new LinkedList(); while (m != null && !m.isAll()) { ancestors.add(0, m); m = m.getParentMember(); } for (Member ancestor : ancestors) { if (dimension.currentMemberSelected && ancestor == dimension.currentMember) { %> <%= ancestor.getCaption() %> <% } else { %> <%= ancestor.getCaption() %> <% } } } %> [x]<% if (dimension.currentMember.getLevel().getChildLevel() != null) { %>:<% } %>

<% if (dimension.currentMember.getLevel().getChildLevel() != null) { %>
<% int k = -1; for (ModelMember member : dimension.getNonZeroMembers()) { ++k; if (k > 0) { %>, <% } if (member.selected) { %> <%= member.getCaption() %> (<%= member.getCount() %>) <% } else { %> <%= member.getCaption() %> (<%= member.getCount() %>)<% } } if (dimension.hasMoreMembersThanShown) { if (k > 0) { %>, <% } %>...<% } %>
<% } %> <% } %>
mondrian-3.4.1/workbench/0000755000175000017500000000000011735330606015210 5ustar drazzibdrazzibmondrian-3.4.1/workbench/workbench.sh0000644000175000017500000000601711735330606017532 0ustar drazzibdrazzib#!/bin/bash # # This software is subject to the terms of the Eclipse Public License v1.0 # Agreement, available at the following URL: # http://www.eclipse.org/legal/epl-v10.html. # You must accept the terms of that agreement to use this software. # # Copyright (C) 2007-2010 Pentaho and others # All Rights Reserved. # # Launch Mondrian Schema Workbench on Linux, UNIX or Cygwin # Platform specific path-separator. # first look in directory of the script for lib, then # look up one folder if lib does not exist MONDRIAN_HOME=`cd \`dirname $0\`; pwd` if test ! -d "$MONDRIAN_HOME/lib"; then MONDRIAN_HOME="`cd \`dirname $0\`/..; pwd`" fi case `uname` in Windows_NT|CYGWIN*) export PS=";" export MONDRIAN_HOME=`cygpath -m $MONDRIAN_HOME` ;; *) export PS=":" ;; esac CP="${MONDRIAN_HOME}/lib/commons-collections.jar" CP="${CP}${PS}${MONDRIAN_HOME}/lib/commons-pool.jar" CP="${CP}${PS}${MONDRIAN_HOME}/lib/commons-dbcp.jar" CP="${CP}${PS}${MONDRIAN_HOME}/lib/commons-io.jar" CP="${CP}${PS}${MONDRIAN_HOME}/lib/commons-lang.jar" CP="${CP}${PS}${MONDRIAN_HOME}/lib/eigenbase-properties.jar" CP="${CP}${PS}${MONDRIAN_HOME}/lib/eigenbase-resgen.jar" CP="${CP}${PS}${MONDRIAN_HOME}/lib/eigenbase-xom.jar" CP="${CP}${PS}${MONDRIAN_HOME}/lib/javacup.jar" CP="${CP}${PS}${MONDRIAN_HOME}/lib/log4j.jar" CP="${CP}${PS}${MONDRIAN_HOME}/lib/mondrian.jar" CP="${CP}${PS}${MONDRIAN_HOME}/lib/olap4j.jar" CP="${CP}${PS}${MONDRIAN_HOME}/lib/jlfgr.jar" CP="${CP}${PS}${MONDRIAN_HOME}/lib/commons-math.jar" CP="${CP}${PS}${MONDRIAN_HOME}/lib/commons-vfs.jar" CP="${CP}${PS}${MONDRIAN_HOME}/lib/commons-logging.jar" # Workbench GUI code and resources CP="${CP}${PS}${MONDRIAN_HOME}/lib/workbench.jar" # local directory is ~/.schemaWorkbench if test ! -d ${HOME}/.schemaWorkbench; then mkdir ${HOME}/.schemaWorkbench fi # copy mondrian.properties and log4j.xml if necessary if test ! -e ${HOME}/.schemaWorkbench/mondrian.properties; then cp mondrian.properties ${HOME}/.schemaWorkbench/mondrian.properties fi if test ! -e ${HOME}/.schemaWorkbench/log4j.xml; then cp log4j.xml ${HOME}/.schemaWorkbench/log4j.xml fi CP="${CP}${PS}${HOME}/.schemaWorkbench" # or # set the log4j.properties system property # "-Dlog4j.properties=path to <.properties or .xml file>" # in the java command below to adjust workbench logging # add all needed JDBC drivers to the classpath for i in `ls ${MONDRIAN_HOME}/drivers/*.jar 2> /dev/null`; do CP="${CP}${PS}${i}" done # add all needed plugins to the classpath for i in `ls ${MONDRIAN_HOME}/plugins/*.jar 2> /dev/null`; do CP="${CP}${PS}${i}" done #echo $CP JAVA_FLAGS="-Xms100m -Xmx500m" #JAVA_FLAGS="-verbose $JAVA_FLAGS" # Standard pentaho environment. Script lives in workbench directory in a # development environment, MONDRIAN_HOME otherwise. if test -x "$MONDRIAN_HOME/workbench/set-pentaho-env.sh"; then . "$MONDRIAN_HOME/workbench/set-pentaho-env.sh" else . "$MONDRIAN_HOME/set-pentaho-env.sh" fi setPentahoEnv exec "$_PENTAHO_JAVA" $JAVA_FLAGS -cp "$CP" mondrian.gui.Workbench # End workbench.sh mondrian-3.4.1/workbench/mondrian.properties0000644000175000017500000002635111735330606021144 0ustar drazzibdrazzib# This software is subject to the terms of the Eclipse Public License v1.0 # Agreement, available at the following URL: # http://www.eclipse.org/legal/epl-v10.html. # You must accept the terms of that agreement to use this software. # # Copyright (C) 2001-2005 Julian Hyde # Copyright (C) 2005-2009 Pentaho and others # All Rights Reserved. # # jhyde, 31 October, 2001 ############################################################################### # Below are lots of examples of setting up database connections for Mondrian. # # JDBC Driver class name, URL, user name and password need to be set in # the Workbench: Tools > Preferences # # Driver JARs can be placed in the drivers directory, where they will be # picked up automatically by the workbench. # # Derby: needs user and password #mondrian.foodmart.jdbcURL=jdbc:derby:demo/derby/foodmart #mondrian.foodmart.jdbcUser=sa #mondrian.foodmart.jdbcPassword=sa #mondrian.jdbcDrivers=org.apache.derby.jdbc.EmbeddedDriver #driver.classpath=testlib/derby.jar # FireBirdSQL #mondrian.foodmart.jdbcURL=jdbc:firebirdsql:localhost/3050:/mondrian/foodmart.gdb #mondrian.jdbcDrivers=org.firebirdsql.jdbc.FBDriver #driver.classpath=/jdbc/fb/firebirdsql-full.jar # LucidDB # (see http://docs.eigenbase.org/LucidDbOlap) #mondrian.foodmart.jdbcURL=jdbc:luciddb:http://localhost #mondrian.foodmart.jdbcUser=foodmart #mondrian.jdbcDrivers=org.luciddb.jdbc.LucidDbClientDriver #driver.classpath=/path/to/luciddb/plugin/LucidDbClient.jar # Oracle (needs user and password) #oracle.home=G:/oracle/product/10.1.0/Db_1 #mondrian.foodmart.jdbcURL.oracle=jdbc:oracle:thin:@//:/ #mondrian.foodmart.jdbcURL=jdbc:oracle:thin:foodmart/foodmart@//stilton:1521/orcl #mondrian.foodmart.jdbcURL=jdbc:oracle:oci8:foodmart/foodmart@orcl #mondrian.foodmart.jdbcUser=FOODMART #mondrian.foodmart.jdbcPassword=oracle #mondrian.jdbcDrivers=oracle.jdbc.OracleDriver #driver.classpath=/home/jhyde/open/mondrian/lib/ojdbc14.jar # ODBC (Microsoft Access) #mondrian.foodmart.jdbcURL=jdbc:odbc:MondrianFoodMart #mondrian.jdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver #driver.classpath= # Hypersonic #mondrian.foodmart.jdbcURL=jdbc:hsqldb:demo/hsql/FoodMart #mondrian.jdbcDrivers=org.hsqldb.jdbcDriver #driver.classpath=xx # MySQL: needs user and password set in JDBC URL mondrian.foodmart.jdbcURL=jdbc:mysql://localhost:3307/foodmart3?user=root&password=mypassword mondrian.jdbcDrivers=com.mysql.jdbc.Driver #driver.classpath=D:/mysql-connector-3.1.12 # Ingres #mondrian.foodmart.jdbcURL=jdbc:ingres://192.168.200.129:II7/MondrianFoodMart;LOOP=on;AUTO=multi;UID=ingres;PWD=sergni #mondrian.jdbcDrivers=com.ingres.jdbc.IngresDriver #driver.classpath=c:/ingres2006/ingres/lib/iijdbc.jar # Postgres: needs user and password #mondrian.foodmart.jdbcURL=jdbc:postgresql://localhost/foodmart #mondrian.foodmart.jdbcUser=postgres #mondrian.foodmart.jdbcPassword=pgroot #mondrian.jdbcDrivers=org.postgresql.Driver # Sybase #mondrian.foodmart.jdbcURL=jdbc:jtds:sybase://xxx.xxx.xxx.xxx:port/dbName #mondrian.foodmart.jdbcUser= #mondrian.foodmart.jdbcPassword= #mondrian.jdbcDrivers=net.sourceforge.jtds.jdbc.Driver #driver.classpath=/jtds-1.2.jar # Teradata #mondrian.foodmart.jdbcURL=jdbc:teradata://DatabaseServerName/DATABASE=FoodMart #mondrian.foodmart.jdbcUser= #mondrian.foodmart.jdbcPassword= #mondrian.jdbcDrivers=com.ncr.teradata.TeraDriver #driver.classpath=/terajdbc/classes/terajdbc4.jar # DB2 #mondrian.foodmart.jdbcURL=jdbc:db2://172.17.3.177:50000/foodmart #mondrian.test.jdbcUser=db2admin #mondrian.test.jdbcPassword=jasper #mondrian.jdbcDrivers=com.ibm.db2.jcc.DB2Driver ############################################################################### # Diagnostics & tuning ############################################################################### # Property which controls the amount of tracing displayed. # # If trace level is above 0, SQL tracing will be enabled and logged as # per the "mondrian.debug.out.file" below. This is separate from Log4j # logging. #mondrian.trace.level=0 ############################################################################### # Property containing the name of the file to which tracing is to be # written. If empty (the default), prints to stdout. #mondrian.debug.out.file= ############################################################################### # Maximum number of simultaneous queries the system will allow. # # Oracle fails if you try to run more than the 'processes' parameter in # init.ora, typically 150. The throughput of Oracle and other databases # will probably reduce long before you get to their limit. #mondrian.query.limit=40 ############################################################################### # Max number of constraints in a single `IN' SQL clause. # # This value may be variant among database prodcuts and their runtime settings. # Oracle, for example, gives the error "ORA-01795: maximum number of expressions # in a list is 1000". # # Recommended values: # * Oracle: 1000 # * DB2: 2500 # * Other: 10000 #mondrian.rolap.maxConstraints=1000 ############################################################################### # Boolean property that controls whether Mondrian uses aggregate tables. # # If true, then Mondrian uses aggregate tables. This property is # queried prior to each aggregate query so that changing the value of this # property dynamically (not just at startup) is meaningful. # # Aggregates can be read from the database using the # ReadAggregates property but will not be used unless this # property is set to true. #mondrian.rolap.aggregates.Use=false ############################################################################### # Boolean property which determines whether Mondrian should read aggregate # tables. # # If set to true, then Mondrian scans the database for aggregate tables. # Unless mondrian.rolap.aggregates.Use is set to true, the aggregates # found will not be used. #mondrian.rolap.aggregates.Read=false ############################################################################### # Boolean property which controls pretty-print mode. # If set to true, the all SqlQuery SQL strings # will be generated in pretty-print mode, formatted for ease of reading. #mondrian.rolap.generate.formatted.sql=true ############################################################################### # Integer property which controls whether to test operators' dependencies, # and how much time to spend doing it. # # If this property is positive, Mondrian's test framework allocates an # expression evaluator which evaluates each expression several times, and # makes sure that the results of the expression are independent of # dimensions which the expression claims to be independent of. #mondrian.test.ExpDependencies=0 ############################################################################### # Seed for random number generator used by some of the tests. # # Any value besides 0 or -1 gives deterministic behavior. # The default value is 1234: most users should use this. # Setting the seed to a different value can increase coverage, and # therefore may uncover new bugs. # # If you set the value to 0, the system will generate its own # pseudo-random seed. # # If you set the value to -1, Mondrian uses the next seed from an # internal random-number generator. This is a little more deterministic # than setting the value to 0. #mondrian.test.random.seed=1234 ############################################################################### # Boolean property which controls whether to use a cache for frequently # evaluated expressions. With the cache disabled, an expression like # Rank([Product].CurrentMember, # Order([Product].MEMBERS, [Measures].[Unit Sales])) would perform # many redundant sorts. The default is true. #mondrian.expCache.enable=true ############################################################################### # Boolean property which controls whether each query axis implicit has the # NON EMPTY option set. The default is false. #mondrian.rolap.nonempty=false ############################################################################### # String property which controls alerting behavior in case native # evaluation of a function is enabled but not supported for that # function's usage in a particular query. (No alert is ever raised in # cases where native evaluation would definitely have been wasted # effort.) Values recognized are { OFF, WARN, ERROR }. #mondrian.native.unsupported.alert=OFF ############################################################################### # Boolean property which controls whether sibling members are # compared according to order key value fetched from their ordinal # expression. The default is false (only database ORDER BY is used). #mondrian.rolap.compareSiblingsByOrderKey=false ############################################################################### # Integer property indicating timeout value, in seconds, for queries. # Default of 0 indicates no timeout #mondrian.rolap.queryTimeout=0 ############################################################################### # Boolean property indicating whether errors related to non-existent members # should be ignored during schema load. If so, the non-existent member is # treated as a null member. mondrian.rolap.ignoreInvalidMembers=false ############################################################################### # Integer property indicating the maximum number of iterations allowed when # iterating over members to compute aggregates. Default of 0 indicates no # limit. mondrian.rolap.iterationLimit=0 ############################################################################### # Whether the MemoryMonitor should be enabled. By # default for Java5 and above it is enabled. #mondrian.util.memoryMonitor.enable=false ############################################################################### # The default MemoryMonitor percentage threshold. # If enabled, when Java5 memory monitor detects that post-garbage # collection is above this value, notifications are generated. #mondrian.util.memoryMonitor.percentage.threshold=90 ############################################################################### # Property for overriding default MemoryMonitor implementation. #mondrian.util.MemoryMonitor.class=null ############################################################################### # Property for overriding default ExpCompiler implementation. # only for Java5 and above # # To test that for all test MDX queries that all functions can # handle requests for ITERABLE, LIST and MUTABLE_LIST evalutation # results, the following can be used: # mondrian.calc.ExpCompiler.class=mondrian.olap.fun.ResultStyleCompiler # #mondrian.calc.ExpCompiler.class=null ############################################################################### # Controls when a non-empty crossjoin input list will have the # optimiztion applied base upon the list's size. # The size of the list must be greater than the property value size # for optimiztion. # mondrian.olap.fun.crossjoin.optimizer.size=0 ############################################################################### # If true, the the RolapResult will apply its implicit member # fix by merging axes when a non-empty crossjoin uses the incorrect # memebers. # mondrian.rolap.RolapResult.useImplicitMembers=true # End mondrian.properties mondrian-3.4.1/workbench/readme.txt0000644000175000017500000000143611735330606017212 0ustar drazzibdrazzib# This software is subject to the terms of the Eclipse Public License v1.0 # Agreement, available at the following URL: # http://www.eclipse.org/legal/epl-v10.html. # You must accept the terms of that agreement to use this software. # # Copyright (C) 2007-2008 Cincom Inc, JasperSoft, Pentaho, and others # All Rights Reserved. Welcome to the Mondrian Schema Workbench. The Mondrian Schema Workbench allows you to visually create and test Mondrian OLAP cube schemas. See the docs directory for documentation on the Workbench and Mondrian. Technical Prerequisites for the Workbench * Java 1.5. * JDBC driver for your database in the drivers directory. Start the Workbench by running "workbench.bat" (Windows) or "workbench.sh" (for Unix/Linux) where you installed. Sherman Wood JasperSoft mondrian-3.4.1/workbench/plugins/0000755000175000017500000000000011744246536016701 5ustar drazzibdrazzibmondrian-3.4.1/workbench/plugins/readme.txt0000644000175000017500000000005111735330606020663 0ustar drazzibdrazzibplace workbench plugins in this directorymondrian-3.4.1/workbench/ivy.xml0000644000175000017500000000566711735330606016557 0ustar drazzibdrazzib Mondrian workbench. mondrian-3.4.1/workbench/cpappend.bat0000644000175000017500000000001611735330606017467 0ustar drazzibdrazzibset CP=%CP%;%1mondrian-3.4.1/workbench/log4j.xml0000644000175000017500000000601711735330606016755 0ustar drazzibdrazzib mondrian-3.4.1/workbench/drivers/0000755000175000017500000000000011735330606016666 5ustar drazzibdrazzibmondrian-3.4.1/workbench/drivers/readme.txt0000644000175000017500000000004411735330606020662 0ustar drazzibdrazzibplace jdbc drivers in this directorymondrian-3.4.1/workbench/workbench.bat0000644000175000017500000000333411735330606017665 0ustar drazzibdrazzib@echo off cd /D %~dp0 rem Schema Workbench launch script rem base Mondrian JARs need to be included set CP=lib/commons-dbcp.jar set CP=%CP%;lib/commons-io.jar set CP=%CP%;lib/commons-lang.jar set CP=%CP%;lib/commons-collections.jar set CP=%CP%;lib/commons-pool.jar set CP=%CP%;lib/eigenbase-properties.jar set CP=%CP%;lib/eigenbase-resgen.jar set CP=%CP%;lib/eigenbase-xom.jar set CP=%CP%;lib/javacup.jar set CP=%CP%;lib/log4j.jar set CP=%CP%;lib/mondrian.jar set CP=%CP%;lib/olap4j.jar set CP=%CP%;lib/jlfgr.jar set CP=%CP%;lib/commons-math.jar set CP=%CP%;lib/commons-vfs.jar set CP=%CP%;lib/commons-logging.jar rem Workbench GUI code and resources set CP=%CP%;lib/workbench.jar rem Have a .schemaWorkbench directory for local for /F "delims=/" %%i in ('echo %USERPROFILE%') do set ROOT=%%~si if not exist %ROOT%\.schemaWorkbench mkdir %ROOT%\.schemaWorkbench if not exist %ROOT%\.schemaWorkbench\log4j.xml copy log4j.xml %ROOT%\.schemaWorkbench if not exist %ROOT%\.schemaWorkbench\mondrian.properties copy mondrian.properties %ROOT%\.schemaWorkbench rem put mondrian.properties on the classpath for it to be picked up set CP=%ROOT%/.schemaWorkbench;%CP% rem or rem set the log4j.properties system property rem "-Dlog4j.properties=path to <.properties or .xml file>" rem in the java command below to adjust workbench logging rem add all needed JDBC drivers to the classpath for %%i in ("drivers\*.jar") do call cpappend %%i rem add all needed plugin jars to the classpath for %%i in ("plugins\*.jar") do call cpappend %%i set PENTAHO_JAVA=java call "%~dp0set-pentaho-env.bat" "%_PENTAHO_JAVA%" -Xms100m -Xmx500m -cp "%CP%" -Dlog4j.configuration=file:///%ROOT%\.schemaWorkbench\log4j.xml mondrian.gui.Workbench rem End workbench.bat mondrian-3.4.1/ivy.xml0000644000175000017500000001417611742766610014576 0ustar drazzibdrazzib Mondrian is an OLAP (online analytical processing) engine written in Java. It reads from JDBC data sources, aggregates data in a memory cache, and implements the MDX language and the olap4j and XML/A APIs. mondrian-3.4.1/misc/0000755000175000017500000000000011735330606014161 5ustar drazzibdrazzibmondrian-3.4.1/misc/workbench-manifest.mf0000644000175000017500000000012411735330606020270 0ustar drazzibdrazzibManifest-Version: 1.0 Main-Class: mondrian.gui.Workbench Name: Mondrian Workbench mondrian-3.4.1/misc/Meta.xsl0000644000175000017500000004567011735330606015613 0ustar drazzibdrazzib

Mining Meta Model Instance

DTD Name:
Class Name: http://code/bb/main/mining/Broadbase/mining/xml/.java
Root Element: #
Version:

Overview

Imports

Element Summary

Classes

Element

Class

#

Attributes

AttributeTypeDefault Description
String none none
none

Content

ElementJava NameConstraintsDescription
# Required Array [ .. ] Array Optional none
Any Text empty
; keep DOM node

Plugin

Class

#

Attributes

AttributeTypeDefault Description
String none none
defPackageString Broadbase.mining.xml The defPackage attribute, available to all Plugins, specifies the package of the Java Class used to parse all plugin contents.
defClassString Broadbase.mining.xml The defClass attribute, available to all Plugins, specifies the class name of the Java Class used to parse all plugin contents.

Content

Any from defClass

Class

Superclass

#

Attributes

AttributeTypeDefault Description
String none none
none

Content

ElementJava NameConstraintsDescription
# Required Array [ .. ] Array Optional none
Any Text empty

StringElement

Attributes

none

Content

Text

Import

Package:
Class:
DTD:

mondrian-3.4.1/lib/0000755000175000017500000000000011744246600013774 5ustar drazzibdrazzibmondrian-3.4.1/lib/mondrian.xsd0000644000175000017500000034356011735330606016336 0ustar drazzibdrazzib A schema is a collection of cubes and virtual cubes. It can also contain shared dimensions (for use by those cubes), named sets, roles, and declarations of user-defined functions. A Parameter defines a schema parameter. It can be referenced from an MDX statement using the ParamRef function and, if not final, its value can be overridden. Name of this parameter. Description of this parameter. Indicates the type of this parameter: String, Numeric, Integer, Boolean, Date, Time, Timestamp, or Member. If false, statement cannot change the value of this parameter; the parameter becomes effectively constant (provided that its default value expression always returns the same value). Default is true. Expression for the default value of this parameter. Definition of a cube. The fact table is the source of all measures in this cube. If this is a Table and the schema name is not present, table name is left unqualified. The SQL expression used to calculate a measure. Must be specified if a source column is not specified. Name of this measure. Column which is source of this measure's values. If not specified, a measure expression must be specified. The datatype of this measure: String, Numeric, Integer, Boolean, Date, Time or Timestamp. The default datatype of a measure is 'Integer' if the measure's aggregator is 'Count', otherwise it is 'Numeric'. Format string with which to format cells of this measure. For more details, see the mondrian.util.Format class. Aggregation function. Allowed values are "sum", "count", "min", "max", "avg", and "distinct-count". ("distinct count" is allowed for backwards compatibility, but is deprecated because XML enumerated attributes in a DTD cannot legally contain spaces.) Name of a formatter class for the appropriate cell being displayed. The class must implement the mondrian.olap.CellFormatter interface. A string being displayed instead of the name. Can be localized from Properties file using #{propertyname}. Description of this measure. Can be localized from Properties file using #{propertyname}. Whether this member is visible in the user-interface. Default true. Calculated members in this cube. Named sets in this virtual cube. Name of this cube. A string being displayed instead of the cube's name. Can be localized from Properties file using #{propertyname}. Description of this cube. Can be localized from Properties file using #{propertyname}. The name of the measure that would be taken as the default measure of the cube. Should the Fact table data for this Cube be cached by Mondrian or not. The default action is to cache the data. Whether element is enabled - if true, then the Cube is realized otherwise it is ignored. Virtual cubes in this schema. A VirtualCube is a set of dimensions and measures gleaned from other cubes. List of base cubes used by the virtual cube. Name of the cube which the virtualCube uses. Unrelated dimensions to measures in this cube will be pushed to top level member. A VirtualCubeDimension is a usage of a Dimension in a VirtualCube. Name of the cube which the dimension belongs to, or unspecified if the dimension is shared. Name of the dimension. A VirtualCubeMeasure is a usage of a Measure in a VirtualCube. Name of the cube which the measure belongs to. Unique name of the measure within its cube. Whether this member is visible in the user-interface. Default true. Calculated members that belong to this virtual cube. (Calculated members inherited from other cubes should not be in this list). Named sets in this virtual cube. Whether this element is enabled - if true, then the Virtual Cube is realized otherwise it is ignored. The name of the measure that would be taken as the default measure of the cube. A string being displayed instead of the cube's name. Can be localized from Properties file using #{propertyname}. Description of this virtual cube. Can be localized from Properties file using #{propertyname}. Named sets in this schema. A role defines an access-control profile. It has a series of grants (or denials) for schema elements. Grants (or denies) this role access to this schema. access may be "all", "all_dimensions", or "none". If access is "all_dimensions", the role has access to all dimensions but still needs explicit access to cubes. See mondrian.olap.Role#grant(mondrian.olap.Schema,int). Grants (or denies) this role access to a cube. access may be "all" or "none". Grants (or denies) this role access to a dimension. access may be "all" or "none". Note that a role is implicitly given access to a dimension when it is given acess to a cube. See also the "all_dimensions" option of the "SchemaGrant" element. The unique name of the dimension Values correspond to Access. Grants (or denies) this role access to a hierarchy. access may be "all", "custom" or "none". If access is "custom", you may also specify the attributes topLevel, bottomLevel, and the member grants. Grants (or denies) this role access to a member. The children of this member inherit that access. You can implicitly see a member if you can see any of its children. The unique name of the member Values correspond to Access. The unique name of the hierarchy Values correspond to Access. Unique name of the highest level of the hierarchy from which this role is allowed to see members. May only be specified if the HierarchyGrant.access is "custom". If not specified, role can see members up to the top level. Unique name of the lowest level of the hierarchy from which this role is allowed to see members. May only be specified if the HierarchyGrant.access is "custom". If not specified, role can see members down to the leaf level. Policy which determines how cell values are calculated if not all of the children of the current cell are visible to the current role. Allowable values are 'full' (the default), 'partial', and 'hidden'. The unique name of the cube Values correspond to Access. Values correspond to Access. Body of a Role definition which defines a Role to be the union of several Roles. The RoleUsage elements must refer to Roles that have been declared earlier in this schema file. Usage of a Role in a union Role. Declarations of user-defined functions in this schema. A UserDefinedFunction is a function which extends the MDX language. It must be implemented by a Java class which implements the interface mondrian.spi.UserDefinedFunction. Name with which the user-defined function will be referenced in MDX expressions. Name of the class which implemenets this user-defined function. Must implement the mondrian.spi.UserDefinedFunction interface. Name of this schema Description of this schema. Label for the measures dimension. Can be localized from Properties file using #{propertyname}. The name of the default role for connections to this schema Contains values of user-defined properties. Shared dimensions in this schema. A Dimension is a collection of hierarchies. There are two kinds: a public dimension belongs to a Schema, and be used by several cubes; a private dimension belongs to a Cube. The foreignKey field is only applicable to private dimensions. Name of this dimension The dimension's type may be one of "Standard" or "Time". A time dimension will allow the use of the MDX time functions (WTD, YTD, QTD, etc.). Use a standard dimension if the dimension is not a time-related dimension. The default value is "Standard". A string being displayed instead of the dimensions's name. Can be localized from Properties file using #{propertyname}. Description of this dimension. Can be localized from Properties file using #{propertyname}. Private dimensions in the cube. A Dimension is a collection of hierarchies. There are two kinds: a public dimension belongs to a Schema, and be used by several cubes; a private dimension belongs to a Cube. The foreignKey field is only applicable to private dimensions. Name of this dimension The dimension's type may be one of "Standard" or "Time". A time dimension will allow the use of the MDX time functions (WTD, YTD, QTD, etc.). Use a standard dimension if the dimension is not a time-related dimension. The default value is "Standard". A string being displayed instead of the dimensions's name. Can be localized from Properties file using #{propertyname}. Description of this dimension. Can be localized from Properties file using #{propertyname}. The name of the column in the fact table which joins to the leaf level of this dimension. Required in a private Dimension or a DimensionUsage, but not in a public Dimension. Flag to mark this dimension as a high cardinality one and avoid caching. A DimensionUsage is usage of a shared Dimension within the context of a cube. Name of the dimension. Must be a dimension in this schema. Case-sensitive. Name of the source dimension. Must be a dimension in this schema. Case-sensitive. Name of the level to join to. If not specified, joins to the lowest level of the dimension. If present, then this is prepended to the Dimension column names during the building of collapse dimension aggregates allowing 1) different dimension usages to be disambiguated during aggregate table recognition and 2) multiple shared dimensions that have common column names to be disambiguated. The name of the column in the fact table which joins to the leaf level of this dimension. Required in a private Dimension or a DimensionUsage, but not in a public Dimension. Flag to mark this dimension as a high cardinality one and avoid caching. or memberReaderClass. If you specify none, the hierarchy is assumed to come from the same fact table of the current cube. ]]> The Table, Join (set of tables), View (SQL statement), or InlineTable which populates this hierarchy. The SQL expression used to populate this level's key. The SQL expression used to populate this level's name. If not specified, the level's key is used. Expression which forms the caption of members. If not specified, the level name is used. The SQL expression used to populate this level's ordinal. The SQL expression used to join to the parent member in a parent-child hierarchy. Specifies the transitive closure of a parent-child hierarchy. Optional, but recommended for better performance. The closure is provided as a set of (parent/child) pairs: since it is the transitive closure these are actually (ancestor/descendant) pairs. Member property. Data type of this property: String, Numeric, Integer, Boolean, Date, Time or Timestamp. Name of a formatter class for the appropriate property value being displayed. The class must implement the mondrian.olap.PropertyFormatter interface. A string being displayed instead of the name. Can be localized from Properties file using #{propertyname}. Description of this member property. Can be localized from Properties file using #{propertyname}. Should be set to true if the value of the property is functionally dependent on the level value. This permits the associated property column to be omitted from the GROUP BY clause (if the database permits columns in the SELECT that are not in the GROUP BY). This can be a significant performance enhancement on some databases, such as MySQL. The estimated number of members in this level. Setting this property improves the performance of MDSCHEMA_LEVELS, MDSCHEMA_HIERARCHIES and MDSCHEMA_DIMENSIONS XMLA requests. The name of the table that the column comes from. If this hierarchy is based upon just one table, defaults to the name of that table; otherwise, it is required. Can be localized from Properties file using #{propertyname}. The name of the column which holds the unique identifier of this level. The name of the column which holds the user identifier of this level. The name of the column which holds member ordinals. If this column is not specified, the key column is used for ordering. The name of the column which references the parent member in a parent-child hierarchy. Value which identifies null parents in a parent-child hierarchy. Typical values are 'NULL' and '0'. Indicates the type of this level's key column: String, Numeric, Integer, Boolean, Date, Time or Timestamp. When generating SQL statements, Mondrian encloses values for String columns in quotation marks, but leaves values for Integer and Numeric columns un-quoted. Date, Time, and Timestamp values are quoted according to the SQL dialect. For a SQL-compliant dialect, the values appear prefixed by their typename, for example, "DATE '2006-06-01'". Whether members are unique across all parents. For example, zipcodes are unique across all states. The first level's members are always unique. Whether this is a regular or a time-related level. The value makes a difference to time-related functions such as YTD (year-to-date). Condition which determines whether a member of this level is hidden. If a hierarchy has one or more levels with hidden members, then it is possible that not all leaf members are the same distance from the root, and it is termed a ragged hierarchy. Allowable values are: Never (a member always appears; the default); IfBlankName (a member doesn't appear if its name is null or empty); and IfParentsName (a member appears unless its name matches the parent's. Name of a formatter class for the member labels being displayed. The class must implement the mondrian.olap.MemberFormatter interface. A string being displayed instead of the level's name. Can be localized from Properties file using #{propertyname}. Description of this level. Can be localized from Properties file using #{propertyname}. The name of the column which holds the caption for members. Not used Name of the hierarchy. If this is not specified, the hierarchy has the same name as its dimension. Whether this hierarchy has an 'all' member. Name of the 'all' member. If this attribute is not specified, the all member is named 'All hierarchyName', for example, 'All Store'. A string being displayed instead as the all member's name. Can be localized from Properties file using #{propertyname}. Name of the 'all' level. If this attribute is not specified, the all member is named '(All)'. Can be localized from Properties file using #{propertyname}. The name of the column which identifies members, and which is referenced by rows in the fact table. If not specified, the key of the lowest level is used. The name of the table which contains primaryKey. If the hierarchy has only one table, defaults to that; otherwise it is required. Default member of this hierarchy. Name of the custom member reader class. Must implement the mondrian.rolap.MemberReader interface. A string to be displayed in the user interface. If not specified, the hierarchy's name is used. Can be localized from Properties file using #{propertyname}. Description of this hierarchy. Can be localized from Properties file using #{propertyname}. Should be set to the level (if such a level exists) at which depth it is known that all members have entirely unique rows, allowing SQL GROUP BY clauses to be completely eliminated from the query. Defaults to left's alias if left is a table, otherwise required. Defaults to right's alias if right is a table, otherwise required. Holder for an array of ColumnDef elements Column definition for an inline table. Name of the column. Type of the column: String, Numeric, Integer, Boolean, Date, Time or Timestamp. Holder for an array of Row elements Row definition for an inline table. Must have one Column for each ColumnDef in the InlineTable. Column value for an inline table. The CDATA holds the value of the column. Name of the column. A definition of an aggregate table for a base fact table. This aggregate table must be in the same schema as the base fact table. The name of the column mapping from base fact table foreign key to aggregate table foreign key. The name of the base fact table foreign key. The name of the aggregate table foreign key. The name of the column mapping to the measure name. The name of the Cube measure. The name of the column mapping to the measure name. The name of the Cube measure. The Table name of a Specific aggregate table. Whether or not the match should ignore case. The name of the column mapping from base fact table foreign key to aggregate table foreign key. The name of the base fact table foreign key. The name of the aggregate table foreign key. The name of the column mapping to the measure name. The name of the Cube measure. The name of the column mapping to the measure name. The name of the Cube measure. A Table pattern not to be matched. The Table name not to be matched. Whether or not the match should ignore case. A Table pattern used to define a set of aggregate tables. Whether or not the match should ignore case. Dialect of SQL the view is intended for. Valid values include, but are not limited to: * generic * access * db2 * derby * firebird * hsqldb * mssql * mysql * oracle * postgres * sybase * teradata * ingres * infobright * luciddb The SQL WHERE clause expression to be appended to any select statement A Table pattern not to be matched. The Table name not to be matched. Whether or not the match should ignore case. A definition of an aggregate table for a base fact table. This aggregate table must be in the same schema as the base fact table. Dialect-specific table optimization hints. Type of hint, interpreted and applied on a per-dialect basis. Optional qualifier for table. Alias to be used with this table when it is used to form queries. If not specified, defaults to the table name, but in any case, must be unique within the schema. (You can use the same table in different hierarchies, but it must have different aliases.) A collection of SQL statements, one per dialect. MDX expression which gives the value of this member. Name of this calculated member. Format string with which to format cells of this member. A string being displayed instead of the name. Can be localized from Properties file using #{propertyname}. Description of this calculated member. Can be localized from Properties file using #{propertyname}. MDX expression which gives the value of this member. Equivalent to the Formula sub-element. Name of the dimension which this member belongs to. Whether this member is visible in the user-interface. Default true. Property of a calculated member defined against a cube. It must have either an expression or a value. Name of this member property. A string being displayed instead of the Properties's name. Can be localized from Properties file using #{propertyname}. Description of this calculated member property. Can be localized from Properties file using #{propertyname}. MDX expression which defines the value of this property. If the expression is a constant string, you could enclose it in quotes, or just specify the 'value' attribute instead. Value of this property. If the value is not constant, specify the 'expression' attribute instead. Defines a named set which can be used in queries in the same way as a set defined using a WITH SET clause. A named set can be defined against a particular cube, or can be global to a schema. If it is defined against a cube, it is only available to queries which use that cube. A named set defined against a cube is not inherited by a virtual cubes defined against that cube. (But you can define a named set against a virtual cube). A named set defined against a schema is available in all cubes and virtual cubes in that schema. However, it is only valid if the cube contains dimensions with the names required to make the formula valid. MDX expression which gives the value of this set. Name of this named set. Caption of this named set. Can be localized from Properties file using #{propertyname}. Description of this named set. Can be localized from Properties file using #{propertyname}. MDX expression which gives the value of this set. Equivalent to the Formula sub-element. mondrian-3.4.1/README.txt0000644000175000017500000000072411735330604014725 0ustar drazzibdrazzibThis is a source, binary or data distribution of Mondrian, an OLAP Engine written in Java. This code is released under the terms of the Eclipse Public License v1.0 (EPL); see LICENSE.html. For installation instructions, see doc/install.html (http://mondrian.pentaho.com/documentation/installation.php). The version is described in VERSION.txt. Home page: http://mondrian.pentaho.com Project home: http://sourceforge.net/projects/mondrian/ Email: jhyde@pentaho.com mondrian-3.4.1/demo/0000755000175000017500000000000011744246510014152 5ustar drazzibdrazzibmondrian-3.4.1/demo/FoodMart.xml0000644000175000017500000010351611735330604016413 0ustar drazzibdrazzib
Verkaufen Ventes Cube des ventes Cube Verkaufen Cube den Verkaufen
"fname" || ' ' || "lname" `customer`.`fullname` "fname" || ' ' || "lname" fname + ' ' + lname "fname" || ' ' || "lname" CONCAT(`customer`.`fname`, ' ', `customer`.`lname`) fname + ' ' + lname "customer"."fullname" CONCAT(CONCAT("customer"."fname", ' '), "customer"."lname") "fname" || ' ' || "lname" "customer"."fullname" "fname" || ' ' || "lname" fullname "fname" || ' ' || "lname" "fname" || ' ' || "lname" fname + ' ' + lname "fname" || ' ' || "lname" CONCAT(`customer`.`fname`, ' ', `customer`.`lname`) fname + ' ' + lname "customer"."fullname" "customer"."fullname" CONCAT(CONCAT("customer"."fname", ' '), "customer"."lname") "fname" || ' ' || "lname" fullname
Iif("sales_fact_1997"."promotion_id" = 0, 0, "sales_fact_1997"."store_sales") (case when "sales_fact_1997"."promotion_id" = 0 then 0 else "sales_fact_1997"."store_sales" end) (case when "sales_fact_1997"."promotion_id" = 0 then 0 else "sales_fact_1997"."store_sales" end) (case when "sales_fact_1997"."promotion_id" = 0 then 0 else "sales_fact_1997"."store_sales" end) (case when `sales_fact_1997`.`promotion_id` = 0 then 0 else `sales_fact_1997`.`store_sales` end) (case when "sales_fact_1997"."promotion_id" = 0 then 0 else "sales_fact_1997"."store_sales" end) `sales_fact_1997`.`store_sales` (case when "sales_fact_1997"."promotion_id" = 0 then 0 else "sales_fact_1997"."store_sales" end) (case when "sales_fact_1997"."promotion_id" = 0 then 0 else "sales_fact_1997"."store_sales" end) (case when "sales_fact_1997"."promotion_id" = 0 then 0 else "sales_fact_1997"."store_sales" end) (case when sales_fact_1997.promotion_id = 0 then 0 else sales_fact_1997.store_sales end) [Measures].[Store Sales] - [Measures].[Store Cost]
`warehouse_sales` - `inventory_fact_1997`.`warehouse_cost` `warehouse_sales` - `inventory_fact_1997`.`warehouse_cost` "warehouse_sales" - "inventory_fact_1997"."warehouse_cost" [Measures].[Warehouse Sales] / [Measures].[Warehouse Cost] TopCount([Warehouse].[Warehouse Name].MEMBERS, 5, [Measures].[Warehouse Sales])
"fname" || ' ' || "lname" "fname" || ' ' || "lname" fname + ' ' + lname "fname" || ' ' || "lname" CONCAT(`customer`.`fname`, ' ', `customer`.`lname`) fname + ' ' + lname "customer"."fullname" CONCAT(CONCAT("customer"."fname", ' '), "customer"."lname") "fname" || ' ' || "lname" "customer"."fullname" fullname
[Measures].[Store Sales] - [Measures].[Store Cost] [Measures].[Profit] / [Measures].[Units Shipped] mondrian-3.4.1/buildOnJdk.bat0000755000175000017500000000344211735330604015747 0ustar drazzibdrazzib@echo off rem $Id$ rem **** This program takes 1 argument and a series of other arguments to pass to Ant. rem **** - buildOnJdk.bat [jdk version] [ant arguments] rem **** The value of [jdk version] must be one of: rem **** - jdk1.5 rem **** - jdk1.6 rem **** - jdk1.7 rem **** It assumes the following environment variables are set. rem **** - JAVA_HOME_15: Home directory of a JDK 1.5.X. rem **** - JAVA_HOME_16: Home directory of a JDK 1.6.X. rem **** - JAVA_HOME_17: Home directory of a JDK 1.7.X. rem **** It also assumes that Ant is on the classpath. rem ============================================================================= rem ===== You can set some environment variables right here if needed =========== rem Change the following line to point to your JDK 1.5 home. set JAVA_HOME_15=C:\apps\java\jdk1.5.0_22 rem Change the following line to point to your JDK 1.6 home. set JAVA_HOME_16=C:\apps\java\jdk1.6.0_27 rem Change the following line to point to your JDK 1.7 home. set JAVA_HOME_17=C:\apps\java\jdk1.7.0_01 rem Change the following line to point to your ant home. rem set ANT_HOME=C:\apps\ant\1.7.1 rem set ANT_HOME=C:\apps\ant\1.8.1 rem ====================================================== rem ===== Don't touch anything below this line =========== if %1==jdk1.5 ( set JAVA_HOME=%JAVA_HOME_15% ) if %1==jdk1.6 ( set JAVA_HOME=%JAVA_HOME_16% ) if %1==jdk1.7 ( set JAVA_HOME=%JAVA_HOME_17% ) set ANT_ARGUMENTS=-Drequested.java.version=%1 for %%A in (%*) do ( set ANT_ARGUMENTS=%ANT_ARGUMENTS% %%A ) rem We set JAVACMD for the benefit of Ant. set JAVACMD=%JAVA_HOME%\bin\java.exe rem Some debug info echo Using ANT_HOME: %ANT_HOME% echo Using JAVA_HOME: %JAVA_HOME% echo Using JAVACMD: %JAVACMD% echo Using Ant arguments: %ANT_ARGUMENTS% ant %ANT_ARGUMENTS% rem End buildOnJdk.bat mondrian-3.4.1/ivysettings.xml0000644000175000017500000000261511735330606016344 0ustar drazzibdrazzib mondrian-3.4.1/build.bat0000644000175000017500000000414211735330604015014 0ustar drazzibdrazzib@echo off setlocal rem This software is subject to the terms of the Eclipse Public License v1.0 rem Agreement, available at the following URL: rem http://www.eclipse.org/legal/epl-v10.html. rem You must accept the terms of that agreement to use this software. rem rem Copyright (C) 2001-2005 Julian Hyde and others rem All Rights Reserved. set SRCROOT=%~dp0 if exist "%HOME_DRIVE%" goto homeDriveOk set HOME_DRIVE=E :homeDriveOk if exist "%JAVA_HOME%" goto javaHomeOk set JAVA_HOME=%HOME_DRIVE%:/j2sdk1.4.1_01 if exist "%JAVA_HOME%" goto javaHomeOk echo JAVA_HOME (%JAVA_HOME%) does not exist goto end :javaHomeOk echo Using JAVA_HOME %JAVA_HOME% set PATH=%JAVA_HOME%/bin;%PATH% if exist "%ANT_HOME%" goto antHomeOk set ANT_HOME=%HOME_DRIVE%:/apache-ant-1.6.1 if exist "%ANT_HOME%" goto antHomeOk echo ANT_HOME (%ANT_HOME%) does not exist goto end :antHomeOk if exist "%JUNIT_HOME%" goto junitHomeOk set JUNIT_HOME=%HOME_DRIVE%:/junit3.8.1 if exist "%JUNIT_HOME%" goto junitHomeOk echo JUNIT_HOME (%JUNIT_HOME%) does not exist goto end :junitHomeOk if exist "%CATALINA_HOME%" goto catalinaHomeOk set CATALINA_HOME=%HOME_DRIVE%:/jakarta-tomcat-5.0.25 if exist "%CATALINA_HOME%" goto catalinaHomeOk echo CATALINA_HOME (%CATALINA_HOME%) does not exist goto end :catalinaHomeOk set CLASSPATH=%SRCROOT%classes;%SRCROOT%lib/javacup.jar;%SRCROOT%lib/eigenbase-xom.jar;%SRCROOT%lib/eigenbase-resgen.jar rem To use Oracle, uncomment the next line and modify appropriately rem set ORACLE_HOME=%HOME_DRIVE%:/oracle/ora81 if "%ORACLE_HOME%" == "" goto oracleHomeNotSet if exist "%ORACLE_HOME%" goto oracleHomeOk echo ORACLE_HOME (%ORACLE_HOME%) does not exist goto end :oracleHomeOk set CLASSPATH=%CLASSPATH%;%ORACLE_HOME%/jdbc/lib/classes12.zip :oracleHomeNotSet rem To use MySQL, uncomment the next 2 lines and modify appropriately rem set MYSQL_HOME=%HOME_DRIVE%:/MySQL rem set CLASSPATH=%CLASSPATH%;%MYSQL_HOME%/lib/mm.mysql-2.0.4-bin.jar rem To use Weblogic, uncomment the next line and modify appropriately. rem set WEBLOGIC_HOME=%HOME_DRIVE%:/bea/wlserver6.1 %ANT_HOME%\bin\ant %1 %2 %3 %4 %5 %6 %7 %8 %9 :end endlocal rem end build.bat mondrian-3.4.1/log4j.properties0000644000175000017500000000172511735330606016370 0ustar drazzibdrazzib# # Example log4j properties file # # $Id$ # # Set root logger level to DEBUG and its only appender to MONDRIAN. #log4j.rootLogger=WARN, MONDRIAN # MONDRIAN is set to be a ConsoleAppender. log4j.appender.MONDRIAN=org.apache.log4j.ConsoleAppender # MONDRIAN uses PatternLayout. log4j.appender.MONDRIAN.layout=org.apache.log4j.PatternLayout log4j.appender.MONDRIAN.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n # Example of setting on a class basis #log4j.category.mondrian.rolap.RolapCube=DEBUG, MONDRIAN #log4j.category.mondrian.rolap.RolapSchema=DEBUG, MONDRIAN #log4j.category.mondrian.rolap.agg.AggregationManager=DEBUG, MONDRIAN # Trace MDX statements #log4j.category.mondrian.mdx=DEBUG, MONDRIAN # Trace SQL statements #log4j.category.mondrian.sql=DEBUG, MONDRIAN # Statement profiling #log4j.category.mondrian.profile=DEBUG, MONDRIAN # Performance test is disabled by default. log4j.category.mondrian.test.PerformanceTest=OFF, MONDRIAN # End log4j.properties mondrian-3.4.1/buildOnJdk.sh0000755000175000017500000000605711742762234015626 0ustar drazzibdrazzib#!/bin/bash # $Id$ # Called recursively from 'ant release' to build the files which can only be # built under a particular JDK version. # # Usage: # buildOnJdk.sh # # For example: # buildOnJdk.sh jdk1.6 compile.java jdkVersion=$1 shift # Version number without jdk prefix. E.g. '1.6'. versionSansJdk=`echo "${jdkVersion}" | sed 's/jdk\([0-9]\.[0-9]\)/\1/'` # Chooses a JAVA_HOME to match the required JDK version. # # If you are building Mondrian for a single JDK (most likely the case), it # doesn't matter if this method cannot find a valid JAVA_HOME for the JDK # version. It will keep the existing JAVA_HOME, ant will report that the JDK # version is not the one required, and will not compile any files. You will # still end up with a valid build for all the classes needed by your JDK # version. # # If you are building a release, you must compile different parts of Mondrian's # source code under different JDKs. You will need to do one of the following: # # 1. Specify environment variables JAVA_HOME_15, JAVA_HOME_16 and JAVA_HOME_17 # before invoking 'ant'. # # 2. Install a JDK in (or create a symbolic link as) one of the standard # locations: /usr/lib/jvm/jdk1.x on Linux/Unix/Cygwin; or # /System/Library/Frameworks/JavaVM.framework/Versions/1.x/Home on MacOS. # # 3. Customize this method with the correct JDK location. # chooseJavaHome() { # If JAVA_HOME_xx is specified in the environment, use it. case "$jdkVersion" in (jdk1.5) if [ -d "$JAVA_HOME_15" ]; then export JAVA_HOME="$JAVA_HOME_15" return fi ;; (jdk1.6) if [ -d "$JAVA_HOME_16" ]; then export JAVA_HOME="$JAVA_HOME_16" return fi ;; (jdk1.7) if [ -d "$JAVA_HOME_17" ]; then export JAVA_HOME="$JAVA_HOME_17" return fi ;; esac # 2. Look in default location based on the operating system and JDK # version. If the directory exists, we presume that it is a valid JDK, and # override JAVA_HOME. defaultJavaHome= case $(uname) in (Darwin) defaultJavaHome=/System/Library/Frameworks/JavaVM.framework/Versions/${versionSansJdk}/Home;; (*) defaultJavaHome=`readlink -f /usr/lib/jvm/${jdkVersion}`;; esac if [ -d "$defaultJavaHome" ]; then export JAVA_HOME="$defaultJavaHome" return fi # 3. If JDK is installed in a non-standard location, customize here. # #case ${jdkVersion} in #(jdk1.5) export JAVA_HOME=...; return ;; #(jdk1.6) export JAVA_HOME=...; return ;; #(jdk1.7) export JAVA_HOME=...; return ;; #esac # 4. Leave JAVA_HOME unchanged. If it does not match the required version, # ant will no-op. } chooseJavaHome if [ ! -d "$JAVA_HOME" ]; then echo "$0: Invalid JAVA_HOME $JAVA_HOME; skipping compile." exit 1 fi export PATH=$JAVA_HOME/bin:$PATH echo Using JAVA_HOME: $JAVA_HOME echo Using Ant arguments: -Drequested.java.version="$jdkVersion" $@ ant -Drequested.java.version="$jdkVersion" "$@" # End buildOnJdk.sh mondrian-3.4.1/build.properties0000644000175000017500000000235411742752424016453 0ustar drazzibdrazzib# This software is subject to the terms of the Eclipse Public License v1.0 # Agreement, available at the following URL: # http://www.eclipse.org/legal/epl-v10.html. # You must accept the terms of that agreement to use this software. # # Copyright (C) 2002-2005 Julian Hyde # Copyright (C) 2005-2012 Pentaho and others # All Rights Reserved. # # Modify this file to override build settings. It is read by ant's build.xml. Name=Mondrian name=mondrian vendor=Pentaho project.revision=3.4.1 project.revision.major=3 project.revision.minor=41 ivy.artifact.id=mondrian ivy.artifact.group=pentaho impl.title=mondrian driver.name=Mondrian olap4j driver driver.version=3.4.1 driver.version.major=3 driver.version.minor=41 #dependency for olap4j dependency.olap4j-core.revision=1.0.1.500 dependency.olap4j.revision=1.0.1.500 dependency.olap4j-tck.revision=1.0.1.500 #dependency properties: used during resolve step of workbench build dependency.kettle.revision=4.3.0-GA dependency.pentaho-xul.revision=3.3.4 dependency.pentaho-launcher.revision=1.0.2 # Uncomment to use yDoc doclet for enhanced javadoc (requires commercial # license). #ydoc.home=/usr/local/ydoc-3.0-jdk1.5 # Uncomment to skip the test suite run. # mondrian.tests.skip=true # End build.properties mondrian-3.4.1/LICENSE.html0000644000175000017500000003031211735330604015173 0ustar drazzibdrazzib Eclipse Public License - Version 1.0

Eclipse Public License - v 1.0

THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.

1. DEFINITIONS

"Contribution" means:

a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and

b) in the case of each subsequent Contributor:

i) changes to the Program, and

ii) additions to the Program;

where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program.

"Contributor" means any person or entity that distributes the Program.

"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program.

"Program" means the Contributions distributed in accordance with this Agreement.

"Recipient" means anyone who receives the Program under this Agreement, including all Contributors.

2. GRANT OF RIGHTS

a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form.

b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder.

c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program.

d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement.

3. REQUIREMENTS

A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that:

a) it complies with the terms and conditions of this Agreement; and

b) its license agreement:

i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose;

ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits;

iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and

iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange.

When the Program is made available in source code form:

a) it must be made available under this Agreement; and

b) a copy of this Agreement must be included with each copy of the Program.

Contributors may not remove or alter any copyright notices contained within the Program.

Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution.

4. COMMERCIAL DISTRIBUTION

Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense.

For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages.

5. NO WARRANTY

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations.

6. DISCLAIMER OF LIABILITY

EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

7. GENERAL

If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.

If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed.

All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive.

Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved.

This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation.

mondrian-3.4.1/src/0000755000175000017500000000000011735330606014015 5ustar drazzibdrazzibmondrian-3.4.1/src/main/0000755000175000017500000000000011735330606014741 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/0000755000175000017500000000000011735330606016550 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/rolap/0000755000175000017500000000000011735330606017665 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/rolap/SqlStatement.java0000644000175000017500000005263211735330606023164 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.Util; import mondrian.server.Execution; import mondrian.server.Locus; import mondrian.server.monitor.*; import mondrian.util.DelegatingInvocationHandler; import java.lang.reflect.Proxy; import java.sql.*; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import javax.sql.DataSource; /** * SqlStatement contains a SQL statement and associated resources throughout * its lifetime. * *

The goal of SqlStatement is to make tracing, error-handling and * resource-management easier. None of the methods throws a SQLException; * if an error occurs in one of the methods, the method wraps the exception * in a {@link RuntimeException} describing the high-level operation, logs * that the operation failed, and throws that RuntimeException. * *

If methods succeed, the method generates lifecycle logging such as * the elapsed time and number of rows fetched. * *

There are a few obligations on the caller. The caller must:

    *
  • call the {@link #handle(Throwable)} method if one of the contained * objects (say the {@link java.sql.ResultSet}) gives an error; *
  • call the {@link #close()} method if all operations complete * successfully. *
  • increment the {@link #rowCount} field each time a row is fetched. *
* *

The {@link #close()} method is idempotent. You are welcome to call it * more than once. * *

SqlStatement is not thread-safe. * * @author jhyde * @since 2.3 */ public class SqlStatement { private static final String TIMING_NAME = "SqlStatement-"; // used for SQL logging, allows for a SQL Statement UID private static final AtomicLong ID_GENERATOR = new AtomicLong(); private static final RolapUtil.Semaphore querySemaphore = RolapUtil.getQuerySemaphore(); private final DataSource dataSource; private Connection jdbcConnection; private ResultSet resultSet; private final String sql; private final List types; private final int maxRows; private final int firstRowOrdinal; private final Locus locus; private final int resultSetType; private final int resultSetConcurrency; private boolean haveSemaphore; public int rowCount; private long startTimeNanos; private long startTimeMillis; private final List accessors = new ArrayList(); private State state = State.FRESH; private final long id; /** * Creates a SqlStatement. * * @param dataSource Data source * @param sql SQL * @param types Suggested types of columns, or null; * if present, must have one element for each SQL column; * each not-null entry overrides deduced JDBC type of the column * @param maxRows Maximum rows; <= 0 means no maximum * @param firstRowOrdinal Ordinal of first row to skip to; <= 0 do not skip * @param locus Execution context of this statement * @param resultSetType Result set type * @param resultSetConcurrency Result set concurrency */ public SqlStatement( DataSource dataSource, String sql, List types, int maxRows, int firstRowOrdinal, Locus locus, int resultSetType, int resultSetConcurrency) { this.id = ID_GENERATOR.getAndIncrement(); this.dataSource = dataSource; this.sql = sql; this.types = types; this.maxRows = maxRows; this.firstRowOrdinal = firstRowOrdinal; this.locus = locus; this.resultSetType = resultSetType; this.resultSetConcurrency = resultSetConcurrency; } /** * Executes the current statement, and handles any SQLException. */ public void execute() { assert state == State.FRESH : "cannot re-execute"; state = State.ACTIVE; String status = "failed"; Statement statement = null; try { this.jdbcConnection = dataSource.getConnection(); querySemaphore.enter(); haveSemaphore = true; // Trace start of execution. if (RolapUtil.SQL_LOGGER.isDebugEnabled()) { StringBuilder sqllog = new StringBuilder(); sqllog.append(id) .append(": ") .append(locus.component) .append(": executing sql ["); if (sql.indexOf('\n') >= 0) { // SQL appears to be formatted as multiple lines. Make it // start on its own line. sqllog.append("\n"); } sqllog.append(sql); sqllog.append(']'); RolapUtil.SQL_LOGGER.debug(sqllog.toString()); } // Execute hook. RolapUtil.ExecuteQueryHook hook = RolapUtil.getHook(); if (hook != null) { hook.onExecuteQuery(sql); } startTimeNanos = System.nanoTime(); startTimeMillis = System.currentTimeMillis(); if (resultSetType < 0 || resultSetConcurrency < 0) { statement = jdbcConnection.createStatement(); } else { statement = jdbcConnection.createStatement( resultSetType, resultSetConcurrency); } if (maxRows > 0) { statement.setMaxRows(maxRows); } // First make sure to register with the execution instance. locus.execution.registerStatement(locus, statement); locus.getServer().getMonitor().sendEvent( new SqlStatementStartEvent( startTimeMillis, id, locus, sql, getPurpose(), getCellRequestCount())); this.resultSet = statement.executeQuery(sql); // skip to first row specified in request this.state = State.ACTIVE; if (firstRowOrdinal > 0) { if (resultSetType == ResultSet.TYPE_FORWARD_ONLY) { for (int i = 0; i < firstRowOrdinal; ++i) { if (!this.resultSet.next()) { this.state = State.DONE; break; } } } else { if (!this.resultSet.absolute(firstRowOrdinal)) { this.state = State.DONE; } } } long timeMillis = System.currentTimeMillis(); long timeNanos = System.nanoTime(); final long executeNanos = timeNanos - startTimeNanos; final long executeMillis = executeNanos / 1000000; Util.addDatabaseTime(executeMillis); status = ", exec " + executeMillis + " ms"; locus.getServer().getMonitor().sendEvent( new SqlStatementExecuteEvent( timeMillis, id, locus, sql, getPurpose(), executeNanos)); // Compute accessors. They ensure that we use the most efficient // method (e.g. getInt, getDouble, getObject) for the type of the // column. Even if you are going to box the result into an object, // it is better to use getInt than getObject; the latter might // return something daft like a BigDecimal (does, on the Oracle JDBC // driver). accessors.clear(); for (Type type : guessTypes()) { accessors.add(createAccessor(accessors.size(), type)); } } catch (Throwable e) { status = ", failed (" + e + ")"; try { if (statement != null) { statement.close(); } } catch (SQLException e2) { // ignore } if (haveSemaphore) { haveSemaphore = false; querySemaphore.leave(); } if (e instanceof Error) { throw (Error) e; } else { throw handle(e); } } finally { RolapUtil.SQL_LOGGER.debug(id + ": " + status); if (RolapUtil.LOGGER.isDebugEnabled()) { RolapUtil.LOGGER.debug( locus.component + ": executing sql [" + sql + "]" + status); } } } /** * Closes all resources (statement, result set) held by this * SqlStatement. * *

If any of them fails, wraps them in a * {@link RuntimeException} describing the high-level operation which * this statement was performing. No further error-handling is required * to produce a descriptive stack trace, unless you want to absorb the * error. */ public void close() { if (haveSemaphore) { haveSemaphore = false; querySemaphore.leave(); } // According to the JDBC spec, closing a statement automatically closes // its result sets, and closing a connection automatically closes its // statements. But let's be conservative and close everything // explicitly. Statement statement = null; if (resultSet != null) { try { statement = resultSet.getStatement(); resultSet.close(); } catch (SQLException e) { throw Util.newError(locus.message + "; sql=[" + sql + "]"); } finally { resultSet = null; } } if (statement != null) { try { statement.close(); } catch (SQLException e) { throw Util.newError(locus.message + "; sql=[" + sql + "]"); } } if (jdbcConnection != null) { try { jdbcConnection.close(); } catch (SQLException e) { throw Util.newError(locus.message + "; sql=[" + sql + "]"); } finally { jdbcConnection = null; } } long endTime = System.currentTimeMillis(); long totalMs = endTime - startTimeMillis; String status = ", exec+fetch " + totalMs + " ms, " + rowCount + " rows"; locus.execution.getQueryTiming().markFull( TIMING_NAME + locus.component, totalMs); RolapUtil.SQL_LOGGER.debug(id + ": " + status); if (RolapUtil.LOGGER.isDebugEnabled()) { RolapUtil.LOGGER.debug( locus.component + ": done executing sql [" + sql + "]" + status); } locus.getServer().getMonitor().sendEvent( new SqlStatementEndEvent( endTime, id, locus, sql, getPurpose(), rowCount, false, null)); } public ResultSet getResultSet() { return resultSet; } /** * Handles an exception thrown from the ResultSet, implicitly calls * {@link #close}, and returns an exception which includes the full * stack, including a description of the high-level operation. * * @param e Exception * @return Runtime exception */ public RuntimeException handle(Throwable e) { RuntimeException runtimeException = Util.newError(e, locus.message + "; sql=[" + sql + "]"); try { close(); } catch (RuntimeException re) { // ignore } return runtimeException; } private static Type getDecimalType(int precision, int scale) { if ((scale == 0 || scale == -127) && (precision <= 9 || precision == 38)) { // An int (up to 2^31 = 2.1B) can hold any NUMBER(10, 0) value // (up to 10^9 = 1B). NUMBER(38, 0) is conventionally used in // Oracle for integers of unspecified precision, so let's be // bold and assume that they can fit into an int. // // Oracle also seems to sometimes represent integers as // (type=NUMERIC, precision=0, scale=-127) for reasons unknown. return Type.INT; } else { return Type.DOUBLE; } } /** * Chooses the most appropriate type for accessing the values of a * column in a result set. * *

NOTE: It is possible that this method is driver-dependent. If this is * the case, move it to {@link mondrian.spi.Dialect}. * * @param suggestedType Type suggested by Level.internalType attribute * @param metaData Result set metadata * @param i Column ordinal (0-based) * @return Best client type * @throws SQLException on error */ public static Type guessType( Type suggestedType, ResultSetMetaData metaData, int i) throws SQLException { if (suggestedType != null) { return suggestedType; } final String typeName = metaData.getColumnTypeName(i + 1); final int columnType = metaData.getColumnType(i + 1); int precision; int scale; switch (columnType) { case Types.SMALLINT: case Types.INTEGER: case Types.BOOLEAN: return Type.INT; case Types.NUMERIC: precision = metaData.getPrecision(i + 1); scale = metaData.getScale(i + 1); if (precision == 0 && (scale == 0 || scale == -127) && (typeName.equalsIgnoreCase("NUMBER") || (typeName.equalsIgnoreCase("NUMERIC")))) { // In Oracle and Greenplum the NUMBER/NUMERIC datatype with no // precision or scale (not NUMBER(p) or NUMBER(p, s)) means // floating point. Some drivers represent this with scale 0, // others scale -127. // // There is a further problem. In GROUPING SETS queries, Oracle // loosens the type of columns compared to mere GROUP BY // queries. We need integer GROUP BY columns to remain integers, // otherwise the segments won't be found; but if we convert // measure (whose column names are like "m0", "m1") to integers, // data loss will occur. final String columnName = metaData.getColumnName(i + 1); if (columnName.startsWith("m")) { return Type.OBJECT; } else { return Type.INT; } } return getDecimalType(precision, scale); case Types.DECIMAL: precision = metaData.getPrecision(i + 1); scale = metaData.getScale(i + 1); return getDecimalType(precision, scale); case Types.DOUBLE: case Types.FLOAT: return Type.DOUBLE; default: return Type.OBJECT; } } private Accessor createAccessor(int column, Type type) { final int columnPlusOne = column + 1; switch (type) { case OBJECT: return new Accessor() { public Object get() throws SQLException { return resultSet.getObject(columnPlusOne); } }; case STRING: return new Accessor() { public Object get() throws SQLException { return resultSet.getString(columnPlusOne); } }; case INT: return new Accessor() { public Object get() throws SQLException { final int val = resultSet.getInt(columnPlusOne); if (val == 0 && resultSet.wasNull()) { return null; } return val; } }; case LONG: return new Accessor() { public Object get() throws SQLException { final long val = resultSet.getLong(columnPlusOne); if (val == 0 && resultSet.wasNull()) { return null; } return val; } }; case DOUBLE: return new Accessor() { public Object get() throws SQLException { final double val = resultSet.getDouble(columnPlusOne); if (val == 0 && resultSet.wasNull()) { return null; } return val; } }; default: throw Util.unexpected(type); } } public List guessTypes() throws SQLException { final ResultSetMetaData metaData = resultSet.getMetaData(); final int columnCount = metaData.getColumnCount(); assert this.types == null || this.types.size() == columnCount; List types = new ArrayList(); for (int i = 0; i < columnCount; i++) { final Type suggestedType = this.types == null ? null : this.types.get(i); types.add(guessType(suggestedType, metaData, i)); } return types; } public List getAccessors() throws SQLException { return accessors; } /** * Returns the result set in a proxy which automatically closes this * SqlStatement (and hence also the statement and result set) when the * result set is closed. * *

This helps to prevent connection leaks. The caller still has to * remember to call ResultSet.close(), of course. * * @return Wrapped result set */ public ResultSet getWrappedResultSet() { return (ResultSet) Proxy.newProxyInstance( null, new Class[] {ResultSet.class}, new MyDelegatingInvocationHandler(this)); } private SqlStatementEvent.Purpose getPurpose() { if (locus instanceof StatementLocus) { return ((StatementLocus) locus).purpose; } else { return SqlStatementEvent.Purpose.OTHER; } } private int getCellRequestCount() { if (locus instanceof StatementLocus) { return ((StatementLocus) locus).cellRequestCount; } else { return 0; } } /** * The approximate JDBC type of a column. * *

This type affects which {@link ResultSet} method we use to get values * of this column: the default is {@link java.sql.ResultSet#getObject(int)}, * but we'd prefer to use native values {@code getInt} and {@code getDouble} * if possible. */ public enum Type { OBJECT, DOUBLE, INT, LONG, STRING; public Object get(ResultSet resultSet, int column) throws SQLException { switch (this) { case OBJECT: return resultSet.getObject(column + 1); case STRING: return resultSet.getString(column + 1); case INT: return resultSet.getInt(column + 1); case LONG: return resultSet.getLong(column + 1); case DOUBLE: return resultSet.getDouble(column + 1); default: throw Util.unexpected(this); } } } public interface Accessor { Object get() throws SQLException; } /** * Reflectively implements the {@link ResultSet} interface by routing method * calls to the result set inside a {@link mondrian.rolap.SqlStatement}. * When the result set is closed, so is the SqlStatement, and hence the * JDBC connection and statement also. */ // must be public for reflection to work public static class MyDelegatingInvocationHandler extends DelegatingInvocationHandler { private final SqlStatement sqlStatement; /** * Creates a MyDelegatingInvocationHandler. * * @param sqlStatement SQL statement */ MyDelegatingInvocationHandler(SqlStatement sqlStatement) { this.sqlStatement = sqlStatement; } protected Object getTarget() { return sqlStatement.getResultSet(); } /** * Helper method to implement {@link java.sql.ResultSet#close()}. * * @throws SQLException on error */ public void close() throws SQLException { sqlStatement.close(); } } private enum State { FRESH, ACTIVE, DONE } public static class StatementLocus extends Locus { private final SqlStatementEvent.Purpose purpose; private final int cellRequestCount; public StatementLocus( Execution execution, String component, String message, SqlStatementEvent.Purpose purpose, int cellRequestCount) { super( execution, component, message); this.purpose = purpose; this.cellRequestCount = cellRequestCount; } } } // End SqlStatement.java mondrian-3.4.1/src/main/mondrian/rolap/SqlMemberSource.java0000644000175000017500000014641611735330606023614 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. // // jhyde, 21 December, 2001 */ package mondrian.rolap; import mondrian.calc.TupleList; import mondrian.olap.*; import mondrian.resource.MondrianResource; import mondrian.rolap.agg.AggregationManager; import mondrian.rolap.agg.CellRequest; import mondrian.rolap.aggmatcher.AggStar; import mondrian.rolap.sql.*; import mondrian.server.Locus; import mondrian.server.monitor.SqlStatementEvent; import mondrian.spi.Dialect; import mondrian.util.*; import org.eigenbase.util.property.StringProperty; import java.sql.*; import java.util.*; import javax.sql.DataSource; /** * A SqlMemberSource reads members from a SQL database. * *

It's a good idea to put a {@link CacheMemberReader} on top of this. * * @author jhyde * @since 21 December, 2001 */ class SqlMemberSource implements MemberReader, SqlTupleReader.MemberBuilder { private final SqlConstraintFactory sqlConstraintFactory = SqlConstraintFactory.instance(); private final RolapHierarchy hierarchy; private final DataSource dataSource; private MemberCache cache; private int lastOrdinal = 0; private boolean assignOrderKeys; private Map valuePool; SqlMemberSource(RolapHierarchy hierarchy) { this.hierarchy = hierarchy; this.dataSource = hierarchy.getRolapSchema().getInternalConnection().getDataSource(); assignOrderKeys = MondrianProperties.instance().CompareSiblingsByOrderKey.get(); valuePool = ValuePoolFactoryFactory.getValuePoolFactory().create(this); } // implement MemberSource public RolapHierarchy getHierarchy() { return hierarchy; } // implement MemberSource public boolean setCache(MemberCache cache) { this.cache = cache; return true; // yes, we support cache writeback } // implement MemberSource public int getMemberCount() { RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels(); int count = 0; for (RolapLevel level : levels) { count += getLevelMemberCount(level); } return count; } public RolapMember substitute(RolapMember member) { return member; } public RolapMember desubstitute(RolapMember member) { return member; } public RolapMember lookupMember( List uniqueNameParts, boolean failIfNotFound) { throw new UnsupportedOperationException(); } public int getLevelMemberCount(RolapLevel level) { if (level.isAll()) { return 1; } return getMemberCount(level, dataSource); } private int getMemberCount(RolapLevel level, DataSource dataSource) { boolean[] mustCount = new boolean[1]; String sql = makeLevelMemberCountSql(level, dataSource, mustCount); final SqlStatement stmt = RolapUtil.executeQuery( dataSource, sql, new Locus( Locus.peek().execution, "SqlMemberSource.getLevelMemberCount", "while counting members of level '" + level)); try { ResultSet resultSet = stmt.getResultSet(); int count; if (! mustCount[0]) { Util.assertTrue(resultSet.next()); ++stmt.rowCount; count = resultSet.getInt(1); } else { // count distinct "manually" ResultSetMetaData rmd = resultSet.getMetaData(); int nColumns = rmd.getColumnCount(); String[] colStrings = new String[nColumns]; count = 0; while (resultSet.next()) { ++stmt.rowCount; boolean isEqual = true; for (int i = 0; i < nColumns; i++) { String colStr = resultSet.getString(i + 1); if (!Util.equals(colStr, colStrings[i])) { isEqual = false; } colStrings[i] = colStr; } if (!isEqual) { count++; } } } return count; } catch (SQLException e) { throw stmt.handle(e); } finally { stmt.close(); } } /** * Generates the SQL statement to count the members in * level. For example,

* *
SELECT count(*) FROM (
     *   SELECT DISTINCT "country", "state_province"
     *   FROM "customer") AS "init"
* *
counts the non-leaf "state_province" level. MySQL * doesn't allow SELECT-in-FROM, so we use the syntax
* *
SELECT count(DISTINCT "country", "state_province")
     * FROM "customer"
* *
. The leaf level requires a different query:
* *
SELECT count(*) FROM "customer"
* *
counts the leaf "name" level of the "customer" hierarchy. */ private String makeLevelMemberCountSql( RolapLevel level, DataSource dataSource, boolean[] mustCount) { mustCount[0] = false; SqlQuery sqlQuery = SqlQuery.newQuery( dataSource, "while generating query to count members in level " + level); int levelDepth = level.getDepth(); RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels(); if (levelDepth == levels.length) { // "select count(*) from schema.customer" sqlQuery.addSelect("count(*)", null); hierarchy.addToFrom(sqlQuery, level.getKeyExp()); return sqlQuery.toString(); } if (!sqlQuery.getDialect().allowsFromQuery()) { List columnList = new ArrayList(); int columnCount = 0; for (int i = levelDepth; i >= 0; i--) { RolapLevel level2 = levels[i]; if (level2.isAll()) { continue; } if (columnCount > 0) { if (sqlQuery.getDialect().allowsCompoundCountDistinct()) { // no op. } else if (true) { // for databases where both SELECT-in-FROM and // COUNT DISTINCT do not work, we do not // generate any count and do the count // distinct "manually". mustCount[0] = true; } } hierarchy.addToFrom(sqlQuery, level2.getKeyExp()); String keyExp = level2.getKeyExp().getExpression(sqlQuery); if (columnCount > 0 && !sqlQuery.getDialect().allowsCompoundCountDistinct() && sqlQuery.getDialect().getDatabaseProduct() == Dialect.DatabaseProduct.SYBASE) { keyExp = "convert(varchar, " + columnList + ")"; } columnList.add(keyExp); if (level2.isUnique()) { break; // no further qualification needed } ++columnCount; } if (mustCount[0]) { for (String colDef : columnList) { final String exp = sqlQuery.getDialect().generateCountExpression(colDef); sqlQuery.addSelect(exp, null); sqlQuery.addOrderBy(exp, true, false, true); } } else { int i = 0; StringBuilder sb = new StringBuilder(); for (String colDef : columnList) { if (i > 0) { sb.append(", "); } sb.append( sqlQuery.getDialect() .generateCountExpression(colDef)); } sqlQuery.addSelect( "count(DISTINCT " + sb.toString() + ")", null); } return sqlQuery.toString(); } else { sqlQuery.setDistinct(true); for (int i = levelDepth; i >= 0; i--) { RolapLevel level2 = levels[i]; if (level2.isAll()) { continue; } MondrianDef.Expression keyExp = level2.getKeyExp(); hierarchy.addToFrom(sqlQuery, keyExp); sqlQuery.addSelect(keyExp.getExpression(sqlQuery), null); if (level2.isUnique()) { break; // no further qualification needed } } SqlQuery outerQuery = SqlQuery.newQuery( dataSource, "while generating query to count members in level " + level); outerQuery.addSelect("count(*)", null); // Note: the "init" is for Postgres, which requires // FROM-queries to have an alias boolean failIfExists = true; outerQuery.addFrom(sqlQuery, "init", failIfExists); return outerQuery.toString(); } } public List getMembers() { return getMembers(dataSource); } private List getMembers(DataSource dataSource) { Pair> pair = makeKeysSql(dataSource); final String sql = pair.left; List types = pair.right; RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels(); SqlStatement stmt = RolapUtil.executeQuery( dataSource, sql, types, 0, 0, new SqlStatement.StatementLocus( null, "SqlMemberSource.getMembers", "while building member cache", SqlStatementEvent.Purpose.TUPLES, 0), -1, -1); try { final List accessors = stmt.getAccessors(); List list = new ArrayList(); Map map = new HashMap(); RolapMember root = null; if (hierarchy.hasAll()) { root = hierarchy.getAllMember(); list.add(root); } int limit = MondrianProperties.instance().ResultLimit.get(); ResultSet resultSet = stmt.getResultSet(); while (resultSet.next()) { ++stmt.rowCount; if (limit > 0 && limit < stmt.rowCount) { // result limit exceeded, throw an exception throw stmt.handle( MondrianResource.instance().MemberFetchLimitExceeded.ex( limit)); } int column = 0; RolapMember member = root; for (RolapLevel level : levels) { if (level.isAll()) { continue; } Object value = accessors.get(column).get(); if (value == null) { value = RolapUtil.sqlNullValue; } RolapMember parent = member; MemberKey key = new MemberKey(parent, value); member = map.get(key); if (member == null) { RolapMemberBase memberBase = new RolapMemberBase(parent, level, value); memberBase.setOrdinal(lastOrdinal++); member = memberBase; /* RME is this right if (level.getOrdinalExp() != level.getKeyExp()) { member.setOrdinal(lastOrdinal++); } */ if (value == RolapUtil.sqlNullValue) { addAsOldestSibling(list, member); } else { list.add(member); } map.put(key, member); } column++; // REVIEW jvs 20-Feb-2007: What about caption? if (!level.getOrdinalExp().equals(level.getKeyExp())) { if (assignOrderKeys) { Object orderKey = accessors.get(column).get(); setOrderKey((RolapMemberBase) member, orderKey); } column++; } Property[] properties = level.getProperties(); for (Property property : properties) { /* REVIEW emcdermid 9-Jul-2009: * Should we also look up the value in the * pool here, rather than setting it directly? * Presumably the value is already in the pool * as a result of makeMember(). */ member.setProperty( property.getName(), accessors.get(column).get()); column++; } } } return list; } catch (SQLException e) { throw stmt.handle(e); } finally { stmt.close(); } } private void setOrderKey(RolapMemberBase member, Object orderKey) { if ((orderKey != null) && !(orderKey instanceof Comparable)) { orderKey = orderKey.toString(); } member.setOrderKey((Comparable) orderKey); } /** * Adds member just before the first element in * list which has the same parent. */ private void addAsOldestSibling( List list, RolapMember member) { int i = list.size(); while (--i >= 0) { RolapMember sibling = list.get(i); if (sibling.getParentMember() != member.getParentMember()) { break; } } list.add(i + 1, member); } private Pair> makeKeysSql( DataSource dataSource) { SqlQuery sqlQuery = SqlQuery.newQuery( dataSource, "while generating query to retrieve members of " + hierarchy); RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels(); for (RolapLevel level : levels) { if (level.isAll()) { continue; } MondrianDef.Expression exp = level.getKeyExp(); hierarchy.addToFrom(sqlQuery, exp); String expString = exp.getExpression(sqlQuery); sqlQuery.addSelectGroupBy(expString, null); exp = level.getOrdinalExp(); hierarchy.addToFrom(sqlQuery, exp); expString = exp.getExpression(sqlQuery); sqlQuery.addOrderBy(expString, true, false, true); if (!exp.equals(level.getKeyExp())) { sqlQuery.addSelect(expString, null); } RolapProperty[] properties = level.getProperties(); for (RolapProperty property : properties) { exp = property.getExp(); hierarchy.addToFrom(sqlQuery, exp); expString = exp.getExpression(sqlQuery); String alias = sqlQuery.addSelect(expString, null); // Some dialects allow us to eliminate properties from the // group by that are functionally dependent on the level value if (!sqlQuery.getDialect().allowsSelectNotInGroupBy() || !property.dependsOnLevelValue()) { sqlQuery.addGroupBy(expString, alias); } } } return sqlQuery.toSqlAndTypes(); } // implement MemberReader public List getMembersInLevel( RolapLevel level, int startOrdinal, int endOrdinal) { TupleConstraint constraint = sqlConstraintFactory.getLevelMembersConstraint(null); return getMembersInLevel(level, startOrdinal, endOrdinal, constraint); } public List getMembersInLevel( RolapLevel level, int startOrdinal, int endOrdinal, TupleConstraint constraint) { if (level.isAll()) { final List list = new ArrayList(); list.add(hierarchy.getAllMember()); //return Collections.singletonList(hierarchy.getAllMember()); return list; } return getMembersInLevel(level, constraint); } private List getMembersInLevel( RolapLevel level, TupleConstraint constraint) { final TupleReader tupleReader = level.getDimension().isHighCardinality() ? new HighCardSqlTupleReader(constraint) : new SqlTupleReader(constraint); tupleReader.addLevelMembers(level, this, null); final TupleList tupleList = tupleReader.readTuples(dataSource, null, null); assert tupleList.getArity() == 1; return Util.cast(tupleList.slice(0)); } public MemberCache getMemberCache() { return cache; } public Object getMemberCacheLock() { return cache; } // implement MemberSource public List getRootMembers() { return getMembersInLevel( (RolapLevel) hierarchy.getLevels()[0], 0, Integer.MAX_VALUE); } /** * Generates the SQL statement to access the children of * member. For example,
* *
SELECT "city"
     * FROM "customer"
     * WHERE "country" = 'USA'
     * AND "state_province" = 'BC'
     * GROUP BY "city"
*
retrieves the children of the member * [Canada].[BC]. *

Note that this method is never called in the context of * virtual cubes, it is only called on regular cubes. * *

See also {@link SqlTupleReader#makeLevelMembersSql}. */ Pair> makeChildMemberSql( RolapMember member, DataSource dataSource, MemberChildrenConstraint constraint) { SqlQuery sqlQuery = SqlQuery.newQuery( dataSource, "while generating query to retrieve children of member " + member); // If this is a non-empty constraint, it is more efficient to join to // an aggregate table than to the fact table. See whether a suitable // aggregate table exists. AggStar aggStar = chooseAggStar(constraint, member); // Create the condition, which is either the parent member or // the full context (non empty). constraint.addMemberConstraint(sqlQuery, null, aggStar, member); RolapLevel level = (RolapLevel) member.getLevel().getChildLevel(); boolean levelCollapsed = (aggStar != null) && isLevelCollapsed(aggStar, (RolapCubeLevel)level); boolean multipleCols = SqlMemberSource.levelContainsMultipleColumns(level); if (levelCollapsed && !multipleCols) { // if this is a single column collapsed level, there is // no need to join it with dimension tables RolapStar.Column starColumn = ((RolapCubeLevel) level).getStarKeyColumn(); int bitPos = starColumn.getBitPosition(); AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); String q = aggColumn.generateExprString(sqlQuery); sqlQuery.addSelectGroupBy(q, starColumn.getInternalType()); sqlQuery.addOrderBy(q, true, false, true); aggColumn.getTable().addToFrom(sqlQuery, false, true); return sqlQuery.toSqlAndTypes(); } hierarchy.addToFrom(sqlQuery, level.getKeyExp()); String q = level.getKeyExp().getExpression(sqlQuery); sqlQuery.addSelectGroupBy(q, level.getInternalType()); // in non empty mode the level table must be joined to the fact // table constraint.addLevelConstraint(sqlQuery, null, aggStar, level); if (levelCollapsed) { // if this is a collapsed level, add a join between key and aggstar RolapStar.Column starColumn = ((RolapCubeLevel) level).getStarKeyColumn(); int bitPos = starColumn.getBitPosition(); AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); RolapStar.Condition condition = new RolapStar.Condition( level.getKeyExp(), aggColumn.getExpression()); sqlQuery.addWhere(condition.toString(sqlQuery)); hierarchy.addToFromInverse(sqlQuery, level.getKeyExp()); // also may need to join parent levels to make selection unique RolapCubeLevel parentLevel = (RolapCubeLevel)level.getParentLevel(); boolean isUnique = level.isUnique(); while (parentLevel != null && !parentLevel.isAll() && !isUnique) { hierarchy.addToFromInverse(sqlQuery, parentLevel.getKeyExp()); starColumn = parentLevel.getStarKeyColumn(); bitPos = starColumn.getBitPosition(); aggColumn = aggStar.lookupColumn(bitPos); condition = new RolapStar.Condition( parentLevel.getKeyExp(), aggColumn.getExpression()); sqlQuery.addWhere(condition.toString(sqlQuery)); parentLevel = parentLevel.getParentLevel(); } } if (level.hasCaptionColumn()) { MondrianDef.Expression captionExp = level.getCaptionExp(); if (!levelCollapsed) { hierarchy.addToFrom(sqlQuery, captionExp); } String captionSql = captionExp.getExpression(sqlQuery); sqlQuery.addSelectGroupBy(captionSql, null); } if (!levelCollapsed) { hierarchy.addToFrom(sqlQuery, level.getOrdinalExp()); } String orderBy = level.getOrdinalExp().getExpression(sqlQuery); sqlQuery.addOrderBy(orderBy, true, false, true); if (!orderBy.equals(q)) { sqlQuery.addSelectGroupBy(orderBy, null); } RolapProperty[] properties = level.getProperties(); for (RolapProperty property : properties) { final MondrianDef.Expression exp = property.getExp(); if (!levelCollapsed) { hierarchy.addToFrom(sqlQuery, exp); } final String s = exp.getExpression(sqlQuery); String alias = sqlQuery.addSelect(s, null); // Some dialects allow us to eliminate properties from the // group by that are functionally dependent on the level value if (!sqlQuery.getDialect().allowsSelectNotInGroupBy() || !property.dependsOnLevelValue()) { sqlQuery.addGroupBy(s, alias); } } return sqlQuery.toSqlAndTypes(); } private static AggStar chooseAggStar( MemberChildrenConstraint constraint, RolapMember member) { if (!MondrianProperties.instance().UseAggregates.get() || !(constraint instanceof SqlContextConstraint)) { return null; } SqlContextConstraint contextConstraint = (SqlContextConstraint) constraint; Evaluator evaluator = contextConstraint.getEvaluator(); RolapCube cube = (RolapCube) evaluator.getCube(); RolapStar star = cube.getStar(); final int starColumnCount = star.getColumnCount(); BitKey measureBitKey = BitKey.Factory.makeBitKey(starColumnCount); BitKey levelBitKey = BitKey.Factory.makeBitKey(starColumnCount); // Convert global ordinal to cube based ordinal (the 0th dimension // is always [Measures]) final Member[] members = evaluator.getNonAllMembers(); // if measure is calculated, we can't continue if (!(members[0] instanceof RolapBaseCubeMeasure)) { return null; } RolapBaseCubeMeasure measure = (RolapBaseCubeMeasure)members[0]; // we need to do more than this! we need the rolap star ordinal, not // the rolap cube int bitPosition = ((RolapStar.Measure)measure.getStarMeasure()).getBitPosition(); int ordinal = measure.getOrdinal(); // childLevel will always end up being a RolapCubeLevel, but the API // calls into this method can be both shared RolapMembers and // RolapCubeMembers so this cast is necessary for now. Also note that // this method will never be called in the context of a virtual cube // so baseCube isn't necessary for retrieving the correct column // get the level using the current depth RolapCubeLevel childLevel = (RolapCubeLevel) member.getLevel().getChildLevel(); RolapStar.Column column = childLevel.getStarKeyColumn(); // set a bit for each level which is constrained in the context final CellRequest request = RolapAggregationManager.makeRequest(members); if (request == null) { // One or more calculated members. Cannot use agg table. return null; } // TODO: RME why is this using the array of constrained columns // from the CellRequest rather than just the constrained columns // BitKey (method getConstrainedColumnsBitKey)? RolapStar.Column[] columns = request.getConstrainedColumns(); for (RolapStar.Column column1 : columns) { levelBitKey.set(column1.getBitPosition()); } // set the masks levelBitKey.set(column.getBitPosition()); measureBitKey.set(bitPosition); // find the aggstar using the masks final AggregationManager aggMgr = cube.getSchema().getInternalConnection().getServer() .getAggregationManager(); return aggMgr.findAgg( star, levelBitKey, measureBitKey, new boolean[] {false}); } /** * Determine if a level contains more than a single column for its * data, such as an ordinal column or property column * * @param level the level to check * @return true if multiple relational columns are involved in this level */ public static boolean levelContainsMultipleColumns(RolapLevel level) { if (level.isAll()) { return false; } MondrianDef.Expression keyExp = level.getKeyExp(); MondrianDef.Expression ordinalExp = level.getOrdinalExp(); MondrianDef.Expression captionExp = level.getCaptionExp(); if (!keyExp.equals(ordinalExp)) { return true; } if (captionExp != null && !keyExp.equals(captionExp)) { return true; } RolapProperty[] properties = level.getProperties(); for (RolapProperty property : properties) { if (!property.getExp().equals(keyExp)) { return true; } } return false; } /** * Determine if the given aggregate table has the dimension level * specified within in (AggStar.FactTable) it, aka collapsed, * or associated with foreign keys (AggStar.DimTable) * * @param aggStar aggregate star if exists * @param level level * @return true if agg table has level or not */ public static boolean isLevelCollapsed( AggStar aggStar, RolapCubeLevel level) { boolean levelCollapsed = false; if (level.isAll()) { return levelCollapsed; } RolapStar.Column starColumn = level.getStarKeyColumn(); int bitPos = starColumn.getBitPosition(); AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); if (aggColumn.getTable() instanceof AggStar.FactTable) { levelCollapsed = true; } return levelCollapsed; } public void getMemberChildren( List parentMembers, List children) { MemberChildrenConstraint constraint = sqlConstraintFactory.getMemberChildrenConstraint(null); getMemberChildren(parentMembers, children, constraint); } public void getMemberChildren( List parentMembers, List children, MemberChildrenConstraint mcc) { // try to fetch all children at once RolapLevel childLevel = getCommonChildLevelForDescendants(parentMembers); if (childLevel != null) { TupleConstraint lmc = sqlConstraintFactory.getDescendantsConstraint( parentMembers, mcc); List list = getMembersInLevel(childLevel, 0, Integer.MAX_VALUE, lmc); children.addAll(list); return; } // fetch them one by one for (RolapMember parentMember : parentMembers) { getMemberChildren(parentMember, children, mcc); } } public void getMemberChildren( RolapMember parentMember, List children) { MemberChildrenConstraint constraint = sqlConstraintFactory.getMemberChildrenConstraint(null); getMemberChildren(parentMember, children, constraint); } public void getMemberChildren( RolapMember parentMember, List children, MemberChildrenConstraint constraint) { // allow parent child calculated members through // this fixes the non closure parent child hierarchy bug if (!parentMember.isAll() && parentMember.isCalculated() && !parentMember.getLevel().isParentChild()) { return; } getMemberChildren2(parentMember, children, constraint); } /** * If all parents belong to the same level and no parent/child is involved, * returns that level; this indicates that all member children can be * fetched at once. Otherwise returns null. */ private RolapLevel getCommonChildLevelForDescendants( List parents) { // at least two members required if (parents.size() < 2) { return null; } RolapLevel parentLevel = null; RolapLevel childLevel = null; for (RolapMember member : parents) { // we can not fetch children of calc members if (member.isCalculated()) { return null; } // first round? if (parentLevel == null) { parentLevel = member.getLevel(); // check for parent/child if (parentLevel.isParentChild()) { return null; } childLevel = (RolapLevel) parentLevel.getChildLevel(); if (childLevel == null) { return null; } if (childLevel.isParentChild()) { return null; } } else if (parentLevel != member.getLevel()) { return null; } } return childLevel; } private void getMemberChildren2( RolapMember parentMember, List children, MemberChildrenConstraint constraint) { Pair> pair; boolean parentChild; final RolapLevel parentLevel = parentMember.getLevel(); RolapLevel childLevel; if (parentLevel.isParentChild()) { pair = makeChildMemberSqlPC(parentMember); parentChild = true; childLevel = parentLevel; } else { childLevel = (RolapLevel) parentLevel.getChildLevel(); if (childLevel == null) { // member is at last level, so can have no children return; } if (childLevel.isParentChild()) { pair = makeChildMemberSql_PCRoot(parentMember); parentChild = true; } else { pair = makeChildMemberSql(parentMember, dataSource, constraint); parentChild = false; } } final String sql = pair.left; final List types = pair.right; SqlStatement stmt = RolapUtil.executeQuery( dataSource, sql, types, 0, 0, new SqlStatement.StatementLocus( Locus.peek().execution, "SqlMemberSource.getMemberChildren", "while building member cache", SqlStatementEvent.Purpose.TUPLES, 0), -1, -1); try { int limit = MondrianProperties.instance().ResultLimit.get(); boolean checkCacheStatus = true; final List accessors = stmt.getAccessors(); ResultSet resultSet = stmt.getResultSet(); RolapMember parentMember2 = RolapUtil.strip(parentMember); while (resultSet.next()) { ++stmt.rowCount; if (limit > 0 && limit < stmt.rowCount) { // result limit exceeded, throw an exception throw MondrianResource.instance().MemberFetchLimitExceeded .ex(limit); } Object value = accessors.get(0).get(); if (value == null) { value = RolapUtil.sqlNullValue; } Object captionValue; int columnOffset = 1; if (childLevel.hasCaptionColumn()) { // The columnOffset needs to take into account // the caption column if one exists captionValue = accessors.get(columnOffset++).get(); } else { captionValue = null; } Object key = cache.makeKey(parentMember2, value); RolapMember member = cache.getMember(key, checkCacheStatus); checkCacheStatus = false; /* Only check the first time */ if (member == null) { member = makeMember( parentMember2, childLevel, value, captionValue, parentChild, stmt, key, columnOffset); } if (value == RolapUtil.sqlNullValue) { children.toArray(); addAsOldestSibling(children, member); } else { children.add(member); } } } catch (SQLException e) { throw stmt.handle(e); } finally { stmt.close(); } } public RolapMember makeMember( RolapMember parentMember, RolapLevel childLevel, Object value, Object captionValue, boolean parentChild, SqlStatement stmt, Object key, int columnOffset) throws SQLException { final RolapLevel rolapChildLevel; if (childLevel instanceof RolapCubeLevel) { rolapChildLevel = ((RolapCubeLevel) childLevel).getRolapLevel(); } else { rolapChildLevel = childLevel; } RolapMemberBase member = new RolapMemberBase(parentMember, rolapChildLevel, value); if (!childLevel.getOrdinalExp().equals(childLevel.getKeyExp())) { member.setOrdinal(lastOrdinal++); } if (captionValue != null) { member.setCaption(captionValue.toString()); } if (parentChild) { // Create a 'public' and a 'data' member. The public member is // calculated, and its value is the aggregation of the data member // and all of the children. The children and the data member belong // to the parent member; the data member does not have any // children. member = childLevel.hasClosedPeer() ? new RolapParentChildMember( parentMember, rolapChildLevel, value, member) : new RolapParentChildMemberNoClosure( parentMember, rolapChildLevel, value, member); } Property[] properties = childLevel.getProperties(); final List accessors = stmt.getAccessors(); if (!childLevel.getOrdinalExp().equals(childLevel.getKeyExp())) { if (assignOrderKeys) { Object orderKey = accessors.get(columnOffset).get(); setOrderKey(member, orderKey); } ++columnOffset; } for (int j = 0; j < properties.length; j++) { Property property = properties[j]; member.setProperty( property.getName(), getPooledValue(accessors.get(columnOffset + j).get())); } cache.putMember(key, member); return member; } public RolapMember allMember() { final RolapHierarchy rolapHierarchy = hierarchy instanceof RolapCubeHierarchy ? ((RolapCubeHierarchy) hierarchy).getRolapHierarchy() : hierarchy; return rolapHierarchy.getAllMember(); } /** *

Looks up an object (and if needed, stores it) in a cached value pool. * This permits us to reuse references to an existing object rather than * create new references to what are essentially duplicates. The intent * is to allow the duplicate object to be garbage collected earlier, thus * keeping overall memory requirements down.

* *

If * {@link mondrian.olap.MondrianProperties#SqlMemberSourceValuePoolFactoryClass} * is not set, then valuePool will be null and no attempt to cache the * value will be made. The method will simply return the incoming * object reference.

* * @param incoming An object to look up. Must be immutable in usage, * even if not declared as such. * @return a reference to a cached object equal to the incoming object, * or to the incoming object if either no cached object was found, * or caching is disabled. */ private Object getPooledValue(Object incoming) { if (valuePool == null) { return incoming; } else { Object ret = this.valuePool.get(incoming); if (ret != null) { return ret; } else { this.valuePool.put(incoming, incoming); return incoming; } } } /** * Generates the SQL to find all root members of a parent-child hierarchy. * For example,
* *
SELECT "employee_id"
     * FROM "employee"
     * WHERE "supervisor_id" IS NULL
     * GROUP BY "employee_id"
*
retrieves the root members of the [Employee] * hierarchy. * *

Currently, parent-child hierarchies may have only one level (plus the * 'All' level). */ private Pair> makeChildMemberSql_PCRoot( RolapMember member) { SqlQuery sqlQuery = SqlQuery.newQuery( dataSource, "while generating query to retrieve children of parent/child " + "hierarchy member " + member); Util.assertTrue( member.isAll(), "In the current implementation, parent/child hierarchies must " + "have only one level (plus the 'All' level)."); RolapLevel level = (RolapLevel) member.getLevel().getChildLevel(); Util.assertTrue(!level.isAll(), "all level cannot be parent-child"); Util.assertTrue( level.isUnique(), "parent-child level '" + level + "' must be unique"); hierarchy.addToFrom(sqlQuery, level.getParentExp()); String parentId = level.getParentExp().getExpression(sqlQuery); StringBuilder condition = new StringBuilder(64); condition.append(parentId); if (level.getNullParentValue() == null || level.getNullParentValue().equalsIgnoreCase("NULL")) { condition.append(" IS NULL"); } else { // Quote the value if it doesn't seem to be a number. try { Util.discard(Double.parseDouble(level.getNullParentValue())); condition.append(" = "); condition.append(level.getNullParentValue()); } catch (NumberFormatException e) { condition.append(" = "); Util.singleQuoteString(level.getNullParentValue(), condition); } } sqlQuery.addWhere(condition.toString()); hierarchy.addToFrom(sqlQuery, level.getKeyExp()); String childId = level.getKeyExp().getExpression(sqlQuery); sqlQuery.addSelectGroupBy(childId, level.getInternalType()); hierarchy.addToFrom(sqlQuery, level.getOrdinalExp()); String orderBy = level.getOrdinalExp().getExpression(sqlQuery); sqlQuery.addOrderBy(orderBy, true, false, true); if (!orderBy.equals(childId)) { sqlQuery.addSelectGroupBy(orderBy, null); } RolapProperty[] properties = level.getProperties(); for (RolapProperty property : properties) { final MondrianDef.Expression exp = property.getExp(); hierarchy.addToFrom(sqlQuery, exp); final String s = exp.getExpression(sqlQuery); String alias = sqlQuery.addSelect(s, null); // Some dialects allow us to eliminate properties from the group by // that are functionally dependent on the level value if (!sqlQuery.getDialect().allowsSelectNotInGroupBy() || !property.dependsOnLevelValue()) { sqlQuery.addGroupBy(s, alias); } } return sqlQuery.toSqlAndTypes(); } /** * Generates the SQL statement to access the children of * member in a parent-child hierarchy. For example, *

* *
SELECT "employee_id"
     * FROM "employee"
     * WHERE "supervisor_id" = 5
*
retrieves the children of the member * [Employee].[5]. * *

See also {@link SqlTupleReader#makeLevelMembersSql}. */ private Pair> makeChildMemberSqlPC( RolapMember member) { SqlQuery sqlQuery = SqlQuery.newQuery( dataSource, "while generating query to retrieve children of " + "parent/child hierarchy member " + member); RolapLevel level = member.getLevel(); Util.assertTrue(!level.isAll(), "all level cannot be parent-child"); Util.assertTrue( level.isUnique(), "parent-child level '" + level + "' must be " + "unique"); hierarchy.addToFrom(sqlQuery, level.getParentExp()); String parentId = level.getParentExp().getExpression(sqlQuery); StringBuilder buf = new StringBuilder(); sqlQuery.getDialect().quote(buf, member.getKey(), level.getDatatype()); sqlQuery.addWhere(parentId, " = ", buf.toString()); hierarchy.addToFrom(sqlQuery, level.getKeyExp()); String childId = level.getKeyExp().getExpression(sqlQuery); sqlQuery.addSelectGroupBy(childId, level.getInternalType()); hierarchy.addToFrom(sqlQuery, level.getOrdinalExp()); String orderBy = level.getOrdinalExp().getExpression(sqlQuery); sqlQuery.addOrderBy(orderBy, true, false, true); if (!orderBy.equals(childId)) { sqlQuery.addSelectGroupBy(orderBy, null); } RolapProperty[] properties = level.getProperties(); for (RolapProperty property : properties) { final MondrianDef.Expression exp = property.getExp(); hierarchy.addToFrom(sqlQuery, exp); final String s = exp.getExpression(sqlQuery); String alias = sqlQuery.addSelect(s, null); // Some dialects allow us to eliminate properties from the group by // that are functionally dependent on the level value if (!sqlQuery.getDialect().allowsSelectNotInGroupBy() || !property.dependsOnLevelValue()) { sqlQuery.addGroupBy(s, alias); } } return sqlQuery.toSqlAndTypes(); } // implement MemberReader public RolapMember getLeadMember(RolapMember member, int n) { throw new UnsupportedOperationException(); } public void getMemberRange( RolapLevel level, RolapMember startMember, RolapMember endMember, List memberList) { throw new UnsupportedOperationException(); } public int compare( RolapMember m1, RolapMember m2, boolean siblingsAreEqual) { throw new UnsupportedOperationException(); } public TupleReader.MemberBuilder getMemberBuilder() { return this; } public RolapMember getDefaultMember() { // we expected the CacheMemberReader to implement this throw new UnsupportedOperationException(); } public RolapMember getMemberParent(RolapMember member) { throw new UnsupportedOperationException(); } // ~ -- Inner classes ------------------------------------------------------ /** * Member of a parent-child dimension which has a closure table. * *

When looking up cells, this member will automatically be converted * to a corresponding member of the auxiliary dimension which maps onto * the closure table. */ private static class RolapParentChildMember extends RolapMemberBase { private final RolapMember dataMember; private int depth = 0; public RolapParentChildMember( RolapMember parentMember, RolapLevel childLevel, Object value, RolapMember dataMember) { super(parentMember, childLevel, value); this.dataMember = dataMember; this.depth = (parentMember != null) ? parentMember.getDepth() + 1 : 0; } public Member getDataMember() { return dataMember; } /** * @return the members's depth * @see mondrian.olap.Member#getDepth() */ public int getDepth() { return depth; } public int getOrdinal() { return dataMember.getOrdinal(); } } /** * Member of a parent-child dimension which has no closure table. * *

This member is calculated. When you ask for its value, it returns * an expression which aggregates the values of its child members. * This calculation is very inefficient, and we can only support * aggregatable measures ("count distinct" is non-aggregatable). * Unfortunately it's the best we can do without a closure table. */ private static class RolapParentChildMemberNoClosure extends RolapParentChildMember { public RolapParentChildMemberNoClosure( RolapMember parentMember, RolapLevel childLevel, Object value, RolapMember dataMember) { super(parentMember, childLevel, value, dataMember); } protected boolean computeCalculated(final MemberType memberType) { return true; } public Exp getExpression() { return getHierarchy().getAggregateChildrenExpression(); } } /** *

Interface definition for the pluggable factory used to decide * which implementation of {@link java.util.Map} to use to pool * reusable values.

*/ public interface ValuePoolFactory { /** *

Create a new {@link java.util.Map} to be used to pool values. * The value pool permits us to reuse references to existing objects * rather than create new references to what are essentially duplicates * of the same object. The intent is to allow the duplicate object * to be garbage collected earlier, thus keeping overall memory * requirements down.

* * @param source The {@link SqlMemberSource} in which values are * being pooled. * @return a new value pool map */ Map create(SqlMemberSource source); } /** * Default {@link mondrian.rolap.SqlMemberSource.ValuePoolFactory} * implementation, used if * {@link mondrian.olap.MondrianProperties#SqlMemberSourceValuePoolFactoryClass} * is not set. */ public static final class NullValuePoolFactory implements ValuePoolFactory { /** * {@inheritDoc} *

This version returns null, meaning that * by default values will not be pooled.

* * @param source {@inheritDoc} * @return {@inheritDoc} */ public Map create(SqlMemberSource source) { return null; } } /** *

Creates the ValuePoolFactory which is in turn used * to create property-value maps for member properties.

* *

The name of the ValuePoolFactory is drawn from * {@link mondrian.olap.MondrianProperties#SqlMemberSourceValuePoolFactoryClass} * in mondrian.properties. If unset, it defaults to * {@link mondrian.rolap.SqlMemberSource.NullValuePoolFactory}.

*/ public static final class ValuePoolFactoryFactory extends ObjectFactory.Singleton { /** * Single instance of the ValuePoolFactoryFactory. */ private static final ValuePoolFactoryFactory factory; static { factory = new ValuePoolFactoryFactory(); } /** * Access the ValuePoolFactory instance. * * @return the Map. */ public static ValuePoolFactory getValuePoolFactory() { return factory.getObject(); } /** * The constructor for the ValuePoolFactoryFactory. * This passes the ValuePoolFactory class to the * ObjectFactory base class. */ private ValuePoolFactoryFactory() { super(ValuePoolFactory.class); } protected StringProperty getStringProperty() { return MondrianProperties.instance() .SqlMemberSourceValuePoolFactoryClass; } protected ValuePoolFactory getDefault( Class[] parameterTypes, Object[] parameterValues) throws CreationException { return new NullValuePoolFactory(); } } } // End SqlMemberSource.java mondrian-3.4.1/src/main/mondrian/rolap/RolapCacheRegion.java0000644000175000017500000001020011735330606023666 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2012 Pentaho // All Rights Reserved. */ package mondrian.rolap; import java.util.*; /** * A RolapCacheRegion represents a region of multidimensional space * in the cache. * *

The region is represented in terms of the columns of a given * {@link mondrian.rolap.RolapStar}, and constraints on those columns. * *

Compare with {@link mondrian.olap.CacheControl.CellRegion}: a * CellRegion is in terms of {@link mondrian.olap.Member} objects * (logical); whereas a RolapCacheRegion is in terms of columns * (physical). */ public class RolapCacheRegion { private final BitKey bitKey; private final Map columnPredicates = new HashMap(); private final Map columnPredicatesByName = new HashMap(); private Map, StarPredicate> predicates = new HashMap, StarPredicate>(); public RolapCacheRegion( RolapStar star, List starMeasureList) { bitKey = BitKey.Factory.makeBitKey(star.getColumnCount()); for (RolapStar.Measure measure : starMeasureList) { bitKey.set(measure.getBitPosition()); } } public BitKey getConstrainedColumnsBitKey() { return bitKey; } /** * Adds a predicate which applies to a single column. * * @param column Constrained column * @param predicate Predicate */ public void addPredicate( RolapStar.Column column, StarColumnPredicate predicate) { int bitPosition = column.getBitPosition(); assert !bitKey.get(bitPosition); bitKey.set(bitPosition); columnPredicates.put(bitPosition, predicate); columnPredicatesByName.put( column.getExpression().getGenericExpression(), predicate); } /** * Returns the predicate associated with the * columnOrdinalth column. * * @param columnOrdinal Column ordinal * @return Predicate, or null if not constrained */ public StarColumnPredicate getPredicate(int columnOrdinal) { return columnPredicates.get(columnOrdinal); } /** * Returns the predicate associated with the * columnName, where column name is * the generic SQL expression in the form of: * *

    table.column * * @param columnName Column name * @return Predicate, or null if not constrained */ public StarColumnPredicate getPredicate(String columnName) { return columnPredicatesByName.get(columnName); } /** * Adds a predicate which applies to multiple columns. * *

The typical example of a multi-column predicate is a member * constraint. For example, the constraint "m between 1997.Q3 and * 1998.Q2" translates into "year = 1997 and quarter >= Q3 or year = * 1998 and quarter <= Q2". * * @param predicate Predicate */ public void addPredicate(StarPredicate predicate) { final List columnList = predicate.getConstrainedColumnList(); predicates.put( new ArrayList(columnList), predicate); for (RolapStar.Column column : columnList) { bitKey.set(column.getBitPosition()); } } /** * Returns a collection of all multi-column predicates. * * @return Collection of all multi-column constraints */ public Collection getPredicates() { return predicates.values(); } /** * Returns the list of all column predicates. */ public Collection getColumnPredicates() { return columnPredicates.values(); } } // End RolapCacheRegion.java mondrian-3.4.1/src/main/mondrian/rolap/RolapNamedSetEvaluator.java0000644000175000017500000001274311735330606025120 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2008-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.*; import mondrian.olap.*; import java.util.List; /** * Evaluation context for a particular named set. * * @author jhyde * @since November 11, 2008 */ class RolapNamedSetEvaluator implements Evaluator.NamedSetEvaluator, TupleList.PositionCallback { private final RolapResult.RolapResultEvaluatorRoot rrer; private final NamedSet namedSet; /** Value of this named set; set on first use. */ private TupleList list; /** * Dummy list used as a marker to detect re-entrant calls to * {@link #ensureList}. */ private static final TupleList DUMMY_LIST = TupleCollections.createList(1); /** * Ordinal of current iteration through the named set. Used to implement * the <Named Set>.CurrentOrdinal and <Named Set>.Current * functions. */ private int currentOrdinal; /** * Creates a RolapNamedSetEvaluator. * * @param rrer Evaluation root context * @param namedSet Named set */ public RolapNamedSetEvaluator( RolapResult.RolapResultEvaluatorRoot rrer, NamedSet namedSet) { this.rrer = rrer; this.namedSet = namedSet; } public TupleIterable evaluateTupleIterable() { ensureList(); return list; } /** * Evaluates and saves the value of this named set, if it has not been * evaluated already. */ private void ensureList() { if (list != null) { if (list == DUMMY_LIST) { throw rrer.result.slicerEvaluator.newEvalException( null, "Illegal attempt to reference value of named set '" + namedSet.getName() + "' while evaluating itself"); } return; } if (RolapResult.LOGGER.isDebugEnabled()) { RolapResult.LOGGER.debug( "Named set " + namedSet.getName() + ": starting evaluation"); } list = DUMMY_LIST; // recursion detection try { final Calc calc = rrer.getCompiled( namedSet.getExp(), false, ResultStyle.ITERABLE); TupleIterable iterable = (TupleIterable) rrer.result.evaluateExp( calc, rrer.result.slicerEvaluator); // Axes can be in two forms: list or iterable. If iterable, we // need to materialize it, to ensure that all cell values are in // cache. final TupleList rawList; if (iterable instanceof TupleList) { rawList = (TupleList) iterable; } else { rawList = TupleCollections.createList(iterable.getArity()); TupleCursor cursor = iterable.tupleCursor(); while (cursor.forward()) { rawList.addCurrent(cursor); } } if (RolapResult.LOGGER.isDebugEnabled()) { RolapResult.LOGGER.debug(generateDebugMessage(calc, rawList)); } // Wrap list so that currentOrdinal is updated whenever the list // is accessed. The list is immutable, because we don't override // AbstractList.set(int, Object). this.list = rawList.withPositionCallback(this); } finally { if (this.list == DUMMY_LIST) { this.list = null; } } } private String generateDebugMessage(Calc calc, TupleList rawList) { final StringBuilder buf = new StringBuilder(); buf.append(this); buf.append(": "); buf.append("Named set "); buf.append(namedSet.getName()); buf.append(" evaluated to:"); buf.append(Util.nl); int arity = calc.getType().getArity(); int rowCount = 0; final int maxRowCount = 100; if (arity == 1) { for (Member t : rawList.slice(0)) { if (rowCount++ > maxRowCount) { buf.append("..."); buf.append(Util.nl); break; } buf.append(t); buf.append(Util.nl); } } else { for (List t : rawList) { if (rowCount++ > maxRowCount) { buf.append("..."); buf.append(Util.nl); break; } int k = 0; for (Member member : t) { if (k++ > 0) { buf.append(", "); } buf.append(member); } buf.append(Util.nl); } } return buf.toString(); } public int currentOrdinal() { return currentOrdinal; } public void onPosition(int index) { this.currentOrdinal = index; } public Member[] currentTuple() { final List tuple = list.get(currentOrdinal); return tuple.toArray(new Member[tuple.size()]); } public Member currentMember() { return list.get(0, currentOrdinal); } } // End RolapNamedSetEvaluator.java mondrian-3.4.1/src/main/mondrian/rolap/agg/0000755000175000017500000000000011744246504020426 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/rolap/agg/DenseNativeSegmentDataset.java0000644000175000017500000000317311735330606026330 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. // // jhyde, 21 March, 2002 */ package mondrian.rolap.agg; import mondrian.rolap.CellKey; import java.util.BitSet; /** * Implementation of {@link DenseSegmentDataset} that stores * values of type {@code double}. * * @author jhyde */ abstract class DenseNativeSegmentDataset extends DenseSegmentDataset { protected final BitSet nullIndicators; /** * Creates a DenseNativeSegmentDataset. * * @param axes Segment axes, containing actual column values * @param nullIndicators Null indicators */ DenseNativeSegmentDataset( SegmentAxis[] axes, BitSet nullIndicators) { super(axes); this.nullIndicators = nullIndicators; } public boolean isNull(CellKey key) { int offset = key.getOffset(axisMultipliers); return isNull(offset); } /** * Returns whether the value at the given offset is null. * *

The native value at this offset will also be 0. You only need to * call this method if the {@link #getInt getXxx} method has returned 0. * * @param offset Cell offset * @return Whether the cell at this offset is null */ protected final boolean isNull(int offset) { return !nullIndicators.get(offset); } } // End DenseNativeSegmentDataset.java mondrian-3.4.1/src/main/mondrian/rolap/agg/DenseIntSegmentBody.java0000644000175000017500000000333211735330606025141 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.util.Pair; import java.util.*; /** * Implementation of a segment body which stores the data inside * a dense primitive array of integers. * * @author LBoudreau */ class DenseIntSegmentBody extends AbstractSegmentBody { private static final long serialVersionUID = 5391233622968115488L; private final int[] values; private final BitSet nullIndicators; /** * Creates a DenseIntSegmentBody. * *

Stores the given array of cell values and null indicators; caller must * not modify them afterwards.

* * @param nullIndicators Null indicators * @param values Cell values * @param axes Axes */ DenseIntSegmentBody( BitSet nullIndicators, int[] values, List, Boolean>> axes) { super(axes); this.values = values; this.nullIndicators = nullIndicators; } @Override public Object getValueArray() { return values; } @Override public BitSet getIndicators() { return nullIndicators; } protected int getSize() { return values.length - nullIndicators.cardinality(); } protected Object getObject(int i) { int value = values[i]; if (value == 0 && nullIndicators.get(i)) { return null; } return value; } } // End DenseIntSegmentBody.java mondrian-3.4.1/src/main/mondrian/rolap/agg/DenseObjectSegmentDataset.java0000644000175000017500000000523311735330606026307 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.CellKey; import mondrian.rolap.SqlStatement; import mondrian.spi.SegmentBody; import mondrian.util.Pair; import java.util.List; import java.util.SortedSet; /** * Implementation of {@link mondrian.rolap.agg.DenseSegmentDataset} that stores * values of type {@link Object}. * *

The storage requirements are as follows. Table requires 1 word per * cell.

* * @author jhyde * @since 21 March, 2002 */ class DenseObjectSegmentDataset extends DenseSegmentDataset { final Object[] values; // length == m[0] * ... * m[axes.length-1] /** * Creates a DenseSegmentDataset. * * @param axes Segment axes, containing actual column values * @param size Number of coordinates */ DenseObjectSegmentDataset(SegmentAxis[] axes, int size) { this(axes, new Object[size]); } /** * Creates and populates a DenseSegmentDataset. The data set is not copied. * * @param axes Axes * @param values Data set */ DenseObjectSegmentDataset(SegmentAxis[] axes, Object[] values) { super(axes); this.values = values; } public Object getObject(CellKey key) { int offset = key.getOffset(axisMultipliers); return values[offset]; } public boolean isNull(CellKey pos) { return getObject(pos) != null; } public boolean exists(CellKey pos) { return getObject(pos) != null; } public void populateFrom(int[] pos, SegmentDataset data, CellKey key) { values[getOffset(pos)] = data.getObject(key); } public void populateFrom( int[] pos, SegmentLoader.RowList rowList, int column) { int offset = getOffset(pos); values[offset] = rowList.getObject(column); } public SqlStatement.Type getType() { return SqlStatement.Type.OBJECT; } public void put(CellKey key, Object value) { int offset = key.getOffset(axisMultipliers); values[offset] = value; } protected Object getObject(int i) { return values[i]; } protected int getSize() { return values.length; } public SegmentBody createSegmentBody( List, Boolean>> axes) { return new DenseObjectSegmentBody( values, axes); } } // End DenseObjectSegmentDataset.java mondrian-3.4.1/src/main/mondrian/rolap/agg/package.html0000644000175000017500000000012511735330606022702 0ustar drazzibdrazzib Manages a cache of aggregates containing cell values. mondrian-3.4.1/src/main/mondrian/rolap/agg/MinusStarPredicate.java0000644000175000017500000001233111735330606025034 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.*; import java.util.*; /** * A StarPredicate which evaluates to true if its * first child evaluates to true and its second child evaluates to false. * * @author jhyde * @since Nov 6, 2006 */ public class MinusStarPredicate extends AbstractColumnPredicate { private final StarColumnPredicate plus; private final StarColumnPredicate minus; /** * Creates a MinusStarPredicate. * * @param plus Positive predicate * @param minus Negative predicate * @pre plus != null * @pre minus != null */ public MinusStarPredicate( StarColumnPredicate plus, StarColumnPredicate minus) { super(plus.getConstrainedColumn()); assert minus != null; this.plus = plus; this.minus = minus; } public boolean equals(Object obj) { if (obj instanceof MinusStarPredicate) { MinusStarPredicate that = (MinusStarPredicate) obj; return this.plus.equals(that.plus) && this.minus.equals(that.minus); } else { return false; } } public int hashCode() { return plus.hashCode() * 31 + minus.hashCode(); } public RolapStar.Column getConstrainedColumn() { return plus.getConstrainedColumn(); } public void values(Collection collection) { Set plusValues = new HashSet(); plus.values(plusValues); List minusValues = new ArrayList(); minus.values(minusValues); plusValues.removeAll(minusValues); collection.addAll(plusValues); } public boolean evaluate(Object value) { return plus.evaluate(value) && !minus.evaluate(value); } public void describe(StringBuilder buf) { buf.append("(").append(plus).append(" - ").append(minus).append(")"); } public Overlap intersect(StarColumnPredicate predicate) { throw new UnsupportedOperationException(); } public boolean mightIntersect(StarPredicate other) { // Approximately, this constraint might intersect if it intersects // with the 'plus' side. It's possible that the 'minus' side might // wipe out all of those intersections, but we don't consider that. return plus.mightIntersect(other); } public StarColumnPredicate minus(StarPredicate predicate) { assert predicate != null; if (predicate instanceof ValueColumnPredicate) { ValueColumnPredicate valuePredicate = (ValueColumnPredicate) predicate; if (!evaluate(valuePredicate.getValue())) { // Case 3: 'minus' is a list, 'constraint' is a value // which is not matched by this return this; } } if (minus instanceof ListColumnPredicate) { ListColumnPredicate minusList = (ListColumnPredicate) minus; RolapStar.Column column = plus.getConstrainedColumn(); if (predicate instanceof ListColumnPredicate) { // Case 1: 'minus' and 'constraint' are both lists. ListColumnPredicate list = (ListColumnPredicate) predicate; List unionList = new ArrayList(); unionList.addAll(minusList.getPredicates()); unionList.addAll(list.getPredicates()); return new MinusStarPredicate( plus, new ListColumnPredicate( column, unionList)); } if (predicate instanceof ValueColumnPredicate) { ValueColumnPredicate valuePredicate = (ValueColumnPredicate) predicate; if (!evaluate(valuePredicate.getValue())) { // Case 3: 'minus' is a list, 'constraint' is a value // which is not matched by this return this; } // Case 2: 'minus' is a list, 'constraint' is a value. List unionList = new ArrayList(); unionList.addAll(minusList.getPredicates()); unionList.add( new ValueColumnPredicate( column, valuePredicate.getValue())); return new MinusStarPredicate( plus, new ListColumnPredicate(column, unionList)); } } return new MinusStarPredicate( this, (StarColumnPredicate) predicate); } public StarColumnPredicate cloneWithColumn(RolapStar.Column column) { return new MinusStarPredicate( plus.cloneWithColumn(column), minus.cloneWithColumn(column)); } } // End MinusStarPredicate.java mondrian-3.4.1/src/main/mondrian/rolap/agg/DrillThroughQuerySpec.java0000644000175000017500000001406711735330606025546 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.olap.Exp; import mondrian.olap.Id; import mondrian.olap.Member; import mondrian.olap.MondrianDef; import mondrian.olap.SchemaReader; import mondrian.olap.Util; import mondrian.rolap.*; import mondrian.rolap.sql.SqlQuery; import mondrian.util.Pair; import java.util.*; /** * Provides the information necessary to generate SQL for a drill-through * request. * * @author jhyde * @author Richard M. Emberson */ class DrillThroughQuerySpec extends AbstractQuerySpec { private final DrillThroughCellRequest request; private final List listOfStarPredicates; private final List columnNames; private final int maxColumnNameLength; public DrillThroughQuerySpec( DrillThroughCellRequest request, StarPredicate starPredicateSlicer, boolean countOnly) { super(request.getMeasure().getStar(), countOnly); this.request = request; if (starPredicateSlicer != null) { this.listOfStarPredicates = Collections.singletonList(starPredicateSlicer); } else { this.listOfStarPredicates = Collections.emptyList(); } int tmpMaxColumnNameLength = getStar().getSqlQueryDialect().getMaxColumnNameLength(); if (tmpMaxColumnNameLength == 0) { // From java.sql.DatabaseMetaData: "a result of zero means that // there is no limit or the limit is not known" maxColumnNameLength = Integer.MAX_VALUE; } else { maxColumnNameLength = tmpMaxColumnNameLength; } this.columnNames = computeDistinctColumnNames(); } private List computeDistinctColumnNames() { final List columnNames = new ArrayList(); final Set columnNameSet = new HashSet(); final RolapStar.Column[] columns = getColumns(); for (RolapStar.Column column : columns) { addColumnName(column, columnNames, columnNameSet); } addColumnName(request.getMeasure(), columnNames, columnNameSet); return columnNames; } private void addColumnName( final RolapStar.Column column, final List columnNames, final Set columnNameSet) { String columnName = column.getName(); if (columnName != null) { // nothing } else if (column.getExpression() instanceof MondrianDef.Column) { columnName = ((MondrianDef.Column) column.getExpression()).name; } else { columnName = "c" + Integer.toString(columnNames.size()); } // Register the column name, and if it's not unique, append numeric // suffixes until it is. Also make sure that it is within the // range allowed by this SQL dialect. String originalColumnName = columnName; if (columnName.length() > maxColumnNameLength) { columnName = columnName.substring(0, maxColumnNameLength); } for (int j = 0; !columnNameSet.add(columnName); j++) { final String suffix = "_" + Integer.toString(j); columnName = originalColumnName; if (originalColumnName.length() + suffix.length() > maxColumnNameLength) { columnName = originalColumnName.substring( 0, maxColumnNameLength - suffix.length()); } columnName += suffix; } columnNames.add(columnName); } @Override protected boolean isPartOfSelect(RolapStar.Column col) { return request.includeInSelect(col); } @Override protected boolean isPartOfSelect(RolapStar.Measure measure) { return request.includeInSelect(measure); } public int getMeasureCount() { return request.getDrillThroughMeasures().size() > 0 ? request.getDrillThroughMeasures().size() : 1; } public RolapStar.Measure getMeasure(final int i) { return request.getDrillThroughMeasures().size() > 0 ? request.getDrillThroughMeasures().get(i) : request.getMeasure(); } public String getMeasureAlias(final int i) { return request.getDrillThroughMeasures().size() > 0 ? request.getDrillThroughMeasures().get(i).getName() : columnNames.get(columnNames.size() - 1); } public RolapStar.Column[] getColumns() { return request.getConstrainedColumns(); } public String getColumnAlias(final int i) { return columnNames.get(i); } public StarColumnPredicate getColumnPredicate(final int i) { final StarColumnPredicate constr = request.getValueAt(i); return (constr == null) ? LiteralStarPredicate.TRUE : constr; } public Pair> generateSqlQuery() { SqlQuery sqlQuery = newSqlQuery(); nonDistinctGenerateSql(sqlQuery); return sqlQuery.toSqlAndTypes(); } protected void addMeasure(final int i, final SqlQuery sqlQuery) { RolapStar.Measure measure = getMeasure(i); if (!isPartOfSelect(measure)) { return; } Util.assertTrue(measure.getTable() == getStar().getFactTable()); measure.getTable().addToFrom(sqlQuery, false, true); if (!countOnly) { String expr = measure.generateExprString(sqlQuery); sqlQuery.addSelect(expr, null, getMeasureAlias(i)); } } protected boolean isAggregate() { return false; } protected boolean isOrdered() { return true; } protected List getPredicateList() { return listOfStarPredicates; } } // End DrillThroughQuerySpec.java mondrian-3.4.1/src/main/mondrian/rolap/agg/DenseDoubleSegmentDataset.java0000644000175000017500000000601411735330606026311 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.CellKey; import mondrian.rolap.SqlStatement; import mondrian.spi.SegmentBody; import mondrian.util.Pair; import java.util.*; /** * Implementation of {@link mondrian.rolap.agg.DenseSegmentDataset} that stores * values of type {@code double}. * * @author jhyde */ class DenseDoubleSegmentDataset extends DenseNativeSegmentDataset { final double[] values; // length == m[0] * ... * m[axes.length-1] /** * Creates a DenseDoubleSegmentDataset. * * @param axes Segment axes, containing actual column values * @param size Number of coordinates */ DenseDoubleSegmentDataset(SegmentAxis[] axes, int size) { this(axes, new double[size], new BitSet(size)); } /** * Creates a populated DenseDoubleSegmentDataset. * * @param axes Segment axes, containing actual column values * @param values Cell values; not copied * @param nullIndicators Null indicators */ DenseDoubleSegmentDataset( SegmentAxis[] axes, double[] values, BitSet nullIndicators) { super(axes, nullIndicators); this.values = values; } public double getDouble(CellKey key) { int offset = key.getOffset(axisMultipliers); return values[offset]; } public Object getObject(CellKey pos) { int offset = pos.getOffset(axisMultipliers); return getObject(offset); } public Double getObject(int offset) { final double value = values[offset]; if (value == 0 && isNull(offset)) { return null; } return value; } public boolean exists(CellKey pos) { return true; } public void populateFrom(int[] pos, SegmentDataset data, CellKey key) { final int offset = getOffset(pos); double value = values[offset] = data.getDouble(key); if (value == 0) { nullIndicators.set(offset, !data.isNull(key)); } } public void populateFrom( int[] pos, SegmentLoader.RowList rowList, int column) { int offset = getOffset(pos); double d = values[offset] = rowList.getDouble(column); if (d == 0) { nullIndicators.set(offset, !rowList.isNull(column)); } } public SqlStatement.Type getType() { return SqlStatement.Type.DOUBLE; } void set(int k, double d) { values[k] = d; } protected int getSize() { return values.length; } public SegmentBody createSegmentBody( List, Boolean>> axes) { return new DenseDoubleSegmentBody( nullIndicators, values, axes); } } // End DenseDoubleSegmentDataset.java mondrian-3.4.1/src/main/mondrian/rolap/agg/OrPredicate.java0000644000175000017500000002220411735330606023467 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.*; import mondrian.rolap.sql.SqlQuery; import java.util.*; /** * Predicate which is the union of a list of predicates. It evaluates to * true if any of the predicates evaluates to true. * * @see OrPredicate * * @author jhyde */ public class OrPredicate extends ListPredicate { public OrPredicate(List predicateList) { super(predicateList); } public boolean evaluate(List valueList) { // NOTE: If we know that every predicate in the list is a // ValueColumnPredicate, we could optimize the evaluate method by // building a value list at construction time. But it's a tradeoff, // considering the extra time and space required. for (StarPredicate childPredicate : children) { if (childPredicate.evaluate(valueList)) { return true; } } return false; } public StarPredicate or(StarPredicate predicate) { if (predicate instanceof OrPredicate && predicate.getConstrainedColumnBitKey().equals( getConstrainedColumnBitKey())) { // Do not collapse OrPredicates with different number of columns. // Keeping them separate helps the SQL translation to IN-list. ListPredicate that = (ListPredicate) predicate; final List list = new ArrayList(children); list.addAll(that.children); return new OrPredicate(list); } else { final List list = new ArrayList(children); list.add(predicate); return new OrPredicate(list); } } public StarPredicate and(StarPredicate predicate) { List list = new ArrayList(); list.add(this); list.add(predicate); return new AndPredicate(list); } /** * Checks whether a predicate can be translated using an IN list, and groups * predicates based on how many columns can be translated using IN list. If * none of the columns can be made part of IN, the entire predicate will be * translated using AND/OR. This method identifies all the columns that can * be part of IN and and categorizes this predicate based on number of * column values to use in the IN list. * * @param predicate predicate to analyze * @param sqlQuery Query * @param predicateMap the map containing predicates analyzed so far */ private void checkInListForPredicate( StarPredicate predicate, SqlQuery sqlQuery, Map> predicateMap) { BitKey inListRhsBitKey; BitKey columnBitKey = getConstrainedColumnBitKey(); if (predicate instanceof ValueColumnPredicate) { // OR of column values from the same column inListRhsBitKey = ((ValueColumnPredicate) predicate).checkInList(columnBitKey); } else if (predicate instanceof AndPredicate) { // OR of ANDs over a set of values over the same column set inListRhsBitKey = ((AndPredicate) predicate).checkInList(sqlQuery, columnBitKey); } else { inListRhsBitKey = columnBitKey.emptyCopy(); } List predicateGroup = predicateMap.get(inListRhsBitKey); if (predicateGroup == null) { predicateGroup = new ArrayList (); predicateMap.put(inListRhsBitKey, predicateGroup); } predicateGroup.add(predicate); } private void checkInList( SqlQuery sqlQuery, Map> predicateMap) { for (StarPredicate predicate : children) { checkInListForPredicate(predicate, sqlQuery, predicateMap); } } /** * Translates a list of predicates over the same set of columns into sql * using IN list where possible. * * @param sqlQuery Query * @param buf buffer to build sql * @param inListRhsBitKey which column positions are included in * the IN predicate; the non included positions corresponde to * columns that are nulls * @param predicateList the list of predicates to translate. */ private void toInListSql( SqlQuery sqlQuery, StringBuilder buf, BitKey inListRhsBitKey, List predicateList) { // Make a col position to column map to aid search. Map columnMap = new HashMap(); for (RolapStar.Column column : columns) { columnMap.put(column.getBitPosition(), column); } buf.append("("); // First generate nulls for the columns which will not be included // in the IN list boolean firstNullColumnPredicate = true; for (Integer colPos : getConstrainedColumnBitKey().andNot(inListRhsBitKey)) { if (firstNullColumnPredicate) { firstNullColumnPredicate = false; } else { buf.append(" and "); } String expr = columnMap.get(colPos).generateExprString(sqlQuery); buf.append(expr); buf.append(" is null"); } // Now the IN list part if (inListRhsBitKey.isEmpty()) { return; } if (firstNullColumnPredicate) { firstNullColumnPredicate = false; } else { buf.append(" and "); } // First add the column names; boolean multiInList = inListRhsBitKey.toBitSet().cardinality() > 1; if (multiInList) { // Multi-IN list buf.append("("); } boolean firstColumn = true; for (Integer colPos : inListRhsBitKey) { if (firstColumn) { firstColumn = false; } else { buf.append(", "); } String expr = columnMap.get(colPos).generateExprString(sqlQuery); buf.append(expr); } if (multiInList) { // Multi-IN list buf.append(")"); } buf.append(" in ("); boolean firstPredicate = true; for (StarPredicate predicate : predicateList) { if (firstPredicate) { firstPredicate = false; } else { buf.append(", "); } if (predicate instanceof AndPredicate) { ((AndPredicate) predicate).toInListSql( sqlQuery, buf, inListRhsBitKey); } else { assert predicate instanceof ValueColumnPredicate; ((ValueColumnPredicate) predicate).toInListSql(sqlQuery, buf); } } buf.append(")"); buf.append(")"); } public void toSql(SqlQuery sqlQuery, StringBuilder buf) { // // If possible, translate the predicate using IN lists. // // Two possibilities: // (1) top-level can be directly tranlated to IN-list // examples: // (country IN (USA, Canada)) // // ((country, satte) in ((USA, CA), (USA, OR))) // // (2) child level can be translated to IN list: this allows IN list // predicates generated such as: // (country, state) IN ((USA, CA), (USA, OR)) // OR // (country, state, city) IN ((USA, CA, SF), (USA, OR, Portland)) // // The second case is handled by calling toSql on the children in // super.toSql(). // final Map> predicateMap = new LinkedHashMap> (); boolean first = true; checkInList(sqlQuery, predicateMap); buf.append("("); for (BitKey columnKey : predicateMap.keySet()) { List predList = predicateMap.get(columnKey); if (columnKey.isEmpty() || predList.size() <= 1) { // Not possible to have IN list, or only one predicate // in the group. for (StarPredicate pred : predList) { if (first) { first = false; } else { buf.append(" or "); } pred.toSql(sqlQuery, buf); } } else { // Translate the rest if (first) { first = false; } else { buf.append(" or "); } toInListSql(sqlQuery, buf, columnKey, predList); } } buf.append(")"); } protected String getOp() { return "or"; } } // End OrPredicate.java mondrian-3.4.1/src/main/mondrian/rolap/agg/AbstractSegmentBody.java0000644000175000017500000000772211735330606025202 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.CellKey; import mondrian.spi.SegmentBody; import mondrian.util.Pair; import java.util.*; /** * Abstract implementation of a SegmentBody. * * @author LBoudreau */ abstract class AbstractSegmentBody implements SegmentBody { private static final long serialVersionUID = -7094121704771005640L; protected final SortedSet[] axisValueSets; private final boolean[] nullAxisFlags; public AbstractSegmentBody( List, Boolean>> axes) { super(); //noinspection unchecked this.axisValueSets = new SortedSet[axes.size()]; this.nullAxisFlags = new boolean[axes.size()]; for (int i = 0; i < axes.size(); i++) { Pair, Boolean> pair = axes.get(i); axisValueSets[i] = pair.left; nullAxisFlags[i] = pair.right; } } public SortedSet[] getAxisValueSets() { return axisValueSets; } public boolean[] getNullAxisFlags() { return nullAxisFlags; } public Map getValueMap() { return new AbstractMap() { public Set> entrySet() { return new AbstractSet>() { public Iterator> iterator() { return new SegmentBodyIterator(); } public int size() { return getSize(); } }; } }; } public Object getValueArray() { throw new UnsupportedOperationException( "This method is only supported for dense segments"); } public BitSet getIndicators() { throw new UnsupportedOperationException( "This method is only supported for dense segments " + "of native values"); } protected abstract int getSize(); protected abstract Object getObject(int i); /** * Iterator over all (cellkey, value) pairs in this data set. */ private class SegmentBodyIterator implements Iterator> { private int i = -1; private final int[] ordinals; private final int size = getSize(); private boolean hasNext = true; private Object next; SegmentBodyIterator() { ordinals = new int[axisValueSets.length]; ordinals[ordinals.length - 1] = -1; moveToNext(); } public boolean hasNext() { return hasNext; } public Map.Entry next() { Pair o = Pair.of(CellKey.Generator.newCellKey(ordinals), next); moveToNext(); return o; } private void moveToNext() { for (;;) { ++i; if (i >= size) { hasNext = false; return; } int k = ordinals.length - 1; while (k >= 0) { if (ordinals[k] < axisValueSets[k].size() - 1) { ++ordinals[k]; break; } else { ordinals[k] = 0; --k; } } next = getObject(i); if (next != null) { return; } } } public void remove() { throw new UnsupportedOperationException(); } } } // End AbstractSegmentBody.java mondrian-3.4.1/src/main/mondrian/rolap/agg/RangeColumnPredicate.java0000644000175000017500000001250111735330606025320 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.*; import java.util.Collection; /** * Predicate constraining a column to be greater than or less than a given * bound, or between a pair of bounds. * * @author jhyde * @since Nov 26, 2006 */ public class RangeColumnPredicate extends AbstractColumnPredicate { private final boolean lowerInclusive; private final ValueColumnPredicate lowerBound; private final boolean upperInclusive; private final ValueColumnPredicate upperBound; /** * Creates a RangeColumnPredicate. * * @param column Constrained column * @param lowerInclusive Whether range includes the lower bound; * must be false if not bounded below * @param lowerBound Lower bound, or null if not bounded below * @param upperInclusive Whether range includes the upper bound; * must be false if not bounded above * @param upperBound Upper bound, or null if not bounded above */ public RangeColumnPredicate( RolapStar.Column column, boolean lowerInclusive, ValueColumnPredicate lowerBound, boolean upperInclusive, ValueColumnPredicate upperBound) { super(column); assert lowerBound == null || lowerBound.getConstrainedColumn() == column; assert !(lowerBound == null && lowerInclusive); assert upperBound == null || upperBound.getConstrainedColumn() == column; assert !(upperBound == null && upperInclusive); this.lowerInclusive = lowerInclusive; this.lowerBound = lowerBound; this.upperInclusive = upperInclusive; this.upperBound = upperBound; } public int hashCode() { int h = lowerInclusive ? 2 : 1; h = 31 * h + lowerBound.hashCode(); h = 31 * h + (upperInclusive ? 2 : 1); h = 31 * h + upperBound.hashCode(); return h; } public boolean equals(Object obj) { if (obj instanceof RangeColumnPredicate) { RangeColumnPredicate that = (RangeColumnPredicate) obj; return this.lowerInclusive == that.lowerInclusive && this.lowerBound.equals(that.lowerBound) && this.upperInclusive == that.upperInclusive && this.upperBound.equals(that.upperBound); } else { return false; } } public void values(Collection collection) { // Besides the end points, don't know what values may be in the range. // FIXME: values() is only a half-useful method. Replace it? throw new UnsupportedOperationException(); } public boolean evaluate(Object value) { if (lowerBound != null) { int c = ((Comparable) lowerBound.getValue()).compareTo(value); if (lowerInclusive ? c > 0 : c >= 0) { return false; } } if (upperBound != null) { int c = ((Comparable) upperBound.getValue()).compareTo(value); if (upperInclusive ? c < 0 : c <= 0) { return false; } } return true; } public void describe(StringBuilder buf) { buf.append("Range("); if (lowerBound == null) { buf.append("unbounded"); } else { lowerBound.describe(buf); if (lowerInclusive) { buf.append(" inclusive"); } } buf.append(" to "); if (upperBound == null) { buf.append("unbounded"); } else { upperBound.describe(buf); if (upperInclusive) { buf.append(" inclusive"); } } buf.append(")"); } public Overlap intersect(StarColumnPredicate predicate) { throw new UnsupportedOperationException(); } public boolean mightIntersect(StarPredicate other) { if (other instanceof ValueColumnPredicate) { return evaluate(((ValueColumnPredicate) other).getValue()); } else { // It MIGHT intersect. (Might not.) // todo: Handle case 'other instanceof RangeColumnPredicate' return true; } } public StarColumnPredicate minus(StarPredicate predicate) { assert predicate != null; // todo: Implement some common cases, such as Range minus Range, and // Range minus true/false return new MinusStarPredicate( this, (StarColumnPredicate) predicate); } public StarColumnPredicate cloneWithColumn(RolapStar.Column column) { return new RangeColumnPredicate( column, lowerInclusive, lowerBound, upperInclusive, upperBound); } public ValueColumnPredicate getLowerBound() { return lowerBound; } public boolean getLowerInclusive() { return lowerInclusive; } public ValueColumnPredicate getUpperBound() { return upperBound; } public boolean getUpperInclusive() { return upperInclusive; } } // End RangeColumnPredicate.java mondrian-3.4.1/src/main/mondrian/rolap/agg/CellRequestQuantumExceededException.java0000644000175000017500000000320311735330606030375 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; /** * Signals that there are enough outstanding cell requests that it is * worth terminating this phase of execution and asking the segment cache * for all of the cells that have been asked for. * *

Not really an exception, just a way of aborting a process so that we can * do some work and restart the process. Any code that handles this exception * is typically in a loop that calls {@link mondrian.rolap.RolapResult#phase}. *

* *

There are several advantages to this:

*
    *
  • If the query has been run before, the cells will be in the * cache already, and this is an opportunity to copy them into the * local cache.
  • *
  • If cell requests are for the same or similar cells, it gives * opportunity to fetch these cells. Then the requests can be answered * from local cache, and we don't need to bother the cache manager with * similar requests.
  • *
  • Prevents memory from filling up with cell requests.
  • *
*/ public final class CellRequestQuantumExceededException extends RuntimeException { public static final CellRequestQuantumExceededException INSTANCE = new CellRequestQuantumExceededException(); private CellRequestQuantumExceededException() { } } // End CellRequestQuantumExceededException.java mondrian-3.4.1/src/main/mondrian/rolap/agg/GroupingSet.java0000644000175000017500000000447411735330606023545 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.*; import java.util.List; /** *

A collection * of {@link mondrian.rolap.agg.Segment}s that can be represented * as a GROUP BY GROUPING SET in a SQL query.

* * @author Thiyagu * @since 05-Jun-2007 */ public class GroupingSet { private final List segments; final Segment segment0; private final BitKey levelBitKey; private final BitKey measureBitKey; private final StarColumnPredicate[] predicates; private final SegmentAxis[] axes; private final RolapStar.Column[] columns; /** * Creates a GroupingSet. * * @param segments Constituent segments * @param levelBitKey Levels * @param measureBitKey Measures * @param predicates Predicates * @param columns Columns */ public GroupingSet( List segments, BitKey levelBitKey, BitKey measureBitKey, StarColumnPredicate[] predicates, RolapStar.Column[] columns) { this.segments = segments; this.segment0 = segments.get(0); this.levelBitKey = levelBitKey; this.measureBitKey = measureBitKey; this.predicates = predicates; this.axes = new SegmentAxis[predicates.length]; this.columns = columns; } public List getSegments() { return segments; } public BitKey getLevelBitKey() { return levelBitKey; } public BitKey getMeasureBitKey() { return measureBitKey; } public SegmentAxis[] getAxes() { return axes; } public StarColumnPredicate[] getPredicates() { return predicates; } public RolapStar.Column[] getColumns() { return columns; } /** * Sets all the segments which are in loading state as failed */ public void setSegmentsFailed() { for (Segment segment : segments) { // TODO: segment.setFailIfStillLoading(); } } } // End GroupingSet.java mondrian-3.4.1/src/main/mondrian/rolap/agg/SegmentDataset.java0000644000175000017500000000547111735330606024205 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. // // jhyde, 21 March, 2002 */ package mondrian.rolap.agg; import mondrian.rolap.CellKey; import mondrian.rolap.SqlStatement; import mondrian.spi.SegmentBody; import mondrian.util.Pair; import java.util.*; /** * A SegmentDataset holds the values in a segment. * * @author jhyde * @since 21 March, 2002 */ public interface SegmentDataset extends Iterable> { /** * Returns the value at a given coordinate, as an {@link Object}. * * @param pos Coordinate position * @return Value */ Object getObject(CellKey pos); /** * Returns the value at a given coordinate, as an {@code int}. * * @param pos Coordinate position * @return Value */ int getInt(CellKey pos); /** * Returns the value at a given coordinate, as a {@code double}. * * @param pos Coordinate position * @return Value */ double getDouble(CellKey pos); /** * Returns whether the cell at a given coordinate is null. * * @param pos Coordinate position * @return Whether cell value is null */ boolean isNull(CellKey pos); /** * Returns whether there is a value at a given coordinate. * * @param pos Coordinate position * @return Whether there is a value */ boolean exists(CellKey pos); /** * Returns the number of bytes occupied by this dataset. * * @return number of bytes */ double getBytes(); void populateFrom(int[] pos, SegmentDataset data, CellKey key); /** * Sets the value a given ordinal. * * @param pos Ordinal * @param rowList Row list * @param column Column of row list */ void populateFrom( int[] pos, SegmentLoader.RowList rowList, int column); /** * Returns the SQL type of the data contained in this dataset. * @return A value of SqlStatement.Type */ SqlStatement.Type getType(); /** * Return an immutable, final and serializable implementation * of a SegmentBody in order to cache this dataset. * * @param axes An array with, for each axis, the set of axis values, sorted * in natural order, and a flag saying whether the null value is also * present. * This is supplied by the {@link SegmentLoader}. * * @return A {@link SegmentBody}. */ SegmentBody createSegmentBody( List, Boolean>> axes); } // End SegmentDataset.java mondrian-3.4.1/src/main/mondrian/rolap/agg/SegmentCacheManager.java0000644000175000017500000016133311735330606025116 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.olap.*; import mondrian.olap.CacheControl.CellRegion; import mondrian.rolap.*; import mondrian.rolap.cache.*; import mondrian.server.Execution; import mondrian.server.Locus; import mondrian.server.monitor.*; import mondrian.spi.*; import mondrian.util.ByteString; import mondrian.util.Pair; import org.apache.commons.collections.map.ReferenceMap; import org.apache.log4j.Logger; import java.io.PrintWriter; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.*; /** * Active object that maintains the "global cache" (in JVM, but shared between * connections using a particular schema) and "external cache" (as implemented * by a {@link mondrian.spi.SegmentCache}. * *

Segment states

* *
* * *
StateMeaning
LocalInitial state of a segment
* *

Decisions to be reviewed

* *

1. Create variant of actor that processes all requests synchronously, * and does not need a thread. This would be a more 'embedded' mode of operation * (albeit with worse scale-out).

* *

2. Move functionality into AggregationManager?

* *

3. Delete {@link mondrian.rolap.RolapStar#lookupOrCreateAggregation} * and {@link mondrian.rolap.RolapStar#lookupSegment} * and {@link mondrian.rolap.RolapStar}.lookupAggregationShared * (formerly RolapStar.lookupAggregation).

* * * * *

Moved methods

* *

(Keeping track of where methods came from will make it easier to merge * to the mondrian-4 code line.)

* *

1. {@link mondrian.rolap.RolapStar#getCellFromCache} moved from * {@link Aggregation}.getCellValue

* * * *

Done

* *

1. Obsolete CountingAggregationManager, and property * mondrian.rolap.agg.enableCacheHitCounters.

* *

2. AggregationManager becomes non-singleton.

* *

3. SegmentCacheWorker methods and segmentCache field become * non-static. initCache() is called on construction. SegmentCache is passed * into constructor (therefore move ServiceDiscovery into * client). AggregationManager (or maybe MondrianServer) is another constructor * parameter.

* *

5. Move SegmentHeader, SegmentBody, ConstrainedColumn into * mondrian.spi. Leave behind dependencies on mondrian.rolap.agg. In particular, * put code that converts Segment + SegmentWithData to and from SegmentHeader * + SegmentBody (e.g. {@link SegmentHeader}#forSegment) into a utility class. * (Do this as CLEANUP, after functionality is complete?)

* *

6. Move functionality Aggregation to Segment. Long-term, Aggregation * should not be used as a 'gatekeeper' to Segment. Remove Aggregation fields * columns and axes.

* *

9. Obsolete {@link RolapStar#cacheAggregations}. Similar effect will be * achieved by removing the 'jvm cache' from the chain of caches.

* *

10. Rename Aggregation.Axis to SegmentAxis.

* *

11. Remove Segment.setData and instead split out subclass * SegmentWithData. Now segment is immutable. You don't have to wait for its * state to change. You wait for a Future<SegmentWithData> to become * ready.

* *

12. Remove methods: RolapCube.checkAggregateModifications, * RolapStar.checkAggregateModifications, * RolapSchema.checkAggregateModifications, * RolapStar.pushAggregateModificationsToGlobalCache, * RolapSchema.pushAggregateModificationsToGlobalCache, * RolapCube.pushAggregateModificationsToGlobalCache.

* *

13. Add new implementations of Future: CompletedFuture and SlotFuture.

* *

14. Remove methods:

*

    * *
  • Remove {@link SegmentLoader}.loadSegmentsFromCache - creates a * {@link SegmentHeader} that has PRECISELY same specification as the * requested segment, very unlikely to have a hit
  • * *
  • Remove {@link SegmentLoader}.loadSegmentFromCacheRollup
  • * *
  • Break up {@link SegmentLoader}.cacheSegmentData, and * place code that is called after a segment has arrived
  • * *
* *

13. Fix flush. Obsolete {@link Aggregation}.flush, and * {@link RolapStar}.flush, which called it.

* *

18. {@code SegmentCacheManager#locateHeaderBody} (and maybe other * methods) call {@link SegmentCacheWorker#get}, and that's a slow blocking * call. Make waits for segment futures should be called from a worker or * client, not an agent.

* * *

Ideas and tasks

* *

7. RolapStar.localAggregations and .sharedAggregations. Obsolete * sharedAggregations.

* *

8. Longer term. Move {@link mondrian.rolap.RolapStar.Bar}.segmentRefs to * {@link mondrian.server.Execution}. Would it still be thread-local?

* *

10. Call * {@link mondrian.spi.DataSourceChangeListener#isAggregationChanged}. * Previously called from * {@link RolapStar}.checkAggregateModifications, now never called.

* *

12. We can quickly identify segments affected by a flush using * {@link SegmentCacheIndex#intersectRegion}. But then what? Options:

* *
    * *
  1. Option #1. Pull them in, trim them, write them out? But: causes * a lot of I/O, and we may never use these * segments. Easiest.
  2. * *
  3. Option #2. Mark the segments in the index as needing to be trimmed; trim * them when read, and write out again. But: doesn't propagate to other * nodes.
  4. * *
  5. Option #3. (Best?) Write a mapping SegmentHeader->Restrictions into the * cache. Less I/O than #1. Method * "SegmentCache.addRestriction(SegmentHeader, CacheRegion)"
  6. * *
* *

14. Move {@link AggregationManager#getCellFromCache} somewhere else. * It's concerned with local segments, not the global/external cache.

* *

15. Method to convert SegmentHeader + SegmentBody to Segment + * SegmentWithData is imperfect. Cannot parse predicates, compound predicates. * Need mapping in star to do it properly and efficiently? * {@link mondrian.rolap.agg.SegmentBuilder.SegmentConverter} is a hack that * can be removed when this is fixed. * See {@link SegmentBuilder#toSegment}. Also see #20.

* *

17. Revisit the strategy for finding segments that can be copied from * global and external cache into local cache. The strategy of sending N * {@link CellRequest}s at a time, then executing SQL to fill in the gaps, is * flawed. We need to maximize N in order to reduce segment fragmentation, but * if too high, we blow memory. BasicQueryTest.testAnalysis is an example of * this. Instead, we should send cell-requests in batches (is ~1000 the right * size?), identify those that can be answered from global or external cache, * return those segments, but not execute SQL until the end of the phase. * If so, {@link CellRequestQuantumExceededException} be obsoleted.

* *

19. Tracing. * a. Remove or re-purpose {@link FastBatchingCellReader#pendingCount}; * b. Add counter to measure requests satisfied by calling * {@link mondrian.rolap.agg.SegmentCacheManager#peek}.

* *

20. Obsolete {@link SegmentDataset} and its implementing classes. * {@link SegmentWithData} can use {@link SegmentBody} instead. Will save * copying.

* *

21. Obsolete {@link mondrian.util.CombiningGenerator}.

* *

22. {@link SegmentHeader#constrain(mondrian.spi.SegmentColumn[])} is * broken for N-dimensional regions where N > 1. Each call currently * creates N more 1-dimensional regions, but should create 1 more N-dimensional * region. {@link SegmentHeader#excludedRegions} should be a list of * {@link SegmentColumn} arrays.

* *

23. All code that calls {@link Future#get} should probably handle * {@link CancellationException}.

* *

24. Obsolete {@link #handler}. Indirection doesn't win anything.

* * * @author jhyde */ public class SegmentCacheManager { private final Handler handler = new Handler(); private final Actor ACTOR; public final Thread thread; /** * Executor with which to send requests to external caches. */ public final ExecutorService cacheExecutor = Util.getExecutorService( 10, 0, 1, -1, "mondrian.rolap.agg.SegmentCacheManager$cacheExecutor"); /** * Executor with which to execute SQL requests. * *

TODO: create using factory and/or configuration parameters. Executor * should be shared within MondrianServer or target JDBC database. */ public final ExecutorService sqlExecutor = Util.getExecutorService( 10, 0, 1, 10, "mondrian.rolap.agg.SegmentCacheManager$sqlExecutor"); // NOTE: This list is only mutable for testing purposes. Would rather it // were immutable. public final List segmentCacheWorkers = new CopyOnWriteArrayList(); public final SegmentCache compositeCache; private final SegmentCacheIndexRegistry indexRegistry; private static final Logger LOGGER = Logger.getLogger(AggregationManager.class); private final MondrianServer server; public SegmentCacheManager(MondrianServer server) { this.server = server; ACTOR = new Actor(); thread = new Thread( ACTOR, "mondrian.rolap.agg.SegmentCacheManager$ACTOR"); thread.setDaemon(true); thread.start(); // Create the index registry. this.indexRegistry = new SegmentCacheIndexRegistry(); // Add a local cache, if needed. if (!MondrianProperties.instance().DisableCaching.get()) { final MemorySegmentCache cache = new MemorySegmentCache(); segmentCacheWorkers.add( new SegmentCacheWorker(cache, thread)); } // Add an external cache, if configured. final List externalCache = SegmentCacheWorker.initCache(); for (SegmentCache cache : externalCache) { // Create a worker for this external cache segmentCacheWorkers.add( new SegmentCacheWorker(cache, thread)); // Hook up a listener so it can update // the segment index. cache.addListener( new AsyncCacheListener(this, server)); } compositeCache = new CompositeSegmentCache(segmentCacheWorkers); } public T execute(Command command) { return ACTOR.execute(handler, command); } public SegmentCacheIndexRegistry getIndexRegistry() { return indexRegistry; } /** * Adds a segment to segment index. * *

Called when a SQL statement has finished loading a segment.

* *

Does not add the segment to the external cache. That is a potentially * long-duration operation, better carried out by a worker.

* * @param header segment header * @param body segment body */ public void loadSucceeded( RolapStar star, SegmentHeader header, SegmentBody body) { final Locus locus = Locus.peek(); ACTOR.event( handler, new SegmentLoadSucceededEvent( System.currentTimeMillis(), locus.getServer().getMonitor(), locus.getServer().getId(), locus.execution.getMondrianStatement() .getMondrianConnection().getId(), locus.execution.getMondrianStatement().getId(), locus.execution.getId(), star, header, body)); } /** * Informs cache manager that a segment load failed. * *

Called when a SQL statement receives an error while loading a * segment.

* * @param header segment header * @param throwable Error */ public void loadFailed( RolapStar star, SegmentHeader header, Throwable throwable) { final Locus locus = Locus.peek(); ACTOR.event( handler, new SegmentLoadFailedEvent( System.currentTimeMillis(), locus.getServer().getMonitor(), locus.getServer().getId(), locus.execution.getMondrianStatement() .getMondrianConnection().getId(), locus.execution.getMondrianStatement().getId(), locus.execution.getId(), star, header, throwable)); } /** * Removes a segment from segment index. * *

Call is asynchronous. It comes back immediately.

* *

Does not remove it from the external cache.

* * @param header segment header */ public void remove( RolapStar star, SegmentHeader header) { final Locus locus = Locus.peek(); ACTOR.event( handler, new SegmentRemoveEvent( System.currentTimeMillis(), locus.getServer().getMonitor(), locus.getServer().getId(), locus.execution.getMondrianStatement() .getMondrianConnection().getId(), locus.execution.getMondrianStatement().getId(), locus.execution.getId(), this, star, header)); } /** * Tells the cache that a segment is newly available in an external cache. */ public void externalSegmentCreated( SegmentHeader header, MondrianServer server) { ACTOR.event( handler, new ExternalSegmentCreatedEvent( System.currentTimeMillis(), server.getMonitor(), server.getId(), 0, 0, 0, this, header)); } /** * Tells the cache that a segment is no longer available in an external * cache. */ public void externalSegmentDeleted( SegmentHeader header, MondrianServer server) { ACTOR.event( handler, new ExternalSegmentDeletedEvent( System.currentTimeMillis(), server.getMonitor(), server.getId(), 0, 0, 0, this, header)); } public void printCacheState( CellRegion region, PrintWriter pw, Locus locus) { ACTOR.execute( handler, new PrintCacheStateCommand(region, pw, locus)); } /** * Shuts down this cache manager and all active threads and indexes. */ public void shutdown() { execute(new ShutdownCommand()); cacheExecutor.shutdown(); sqlExecutor.shutdown(); } public SegmentBuilder.SegmentConverter getConverter( RolapStar star, SegmentHeader header) { return indexRegistry.getIndex(star) .getConverter( header.schemaName, header.schemaChecksum, header.cubeName, header.rolapStarFactTableName, header.measureName, header.compoundPredicates); } /** * Makes a quick request to the aggregation manager to see whether the * cell value required by a particular cell request is in external cache. * *

'Quick' is relative. It is an asynchronous request (due to * the aggregation manager being an actor) and therefore somewhat slow. If * the segment is in cache, will save batching up future requests and * re-executing the query. Win should be particularly noticeable for queries * running on a populated cache. Without this feature, every query would * require at least two iterations.

* *

Request does not issue SQL to populate the segment. Nor does it * try to find existing segments for rollup. Those operations can wait until * next phase.

* *

Client is responsible for adding the segment to its private cache.

* * @param request Cell request * @return Segment with data, or null if not in cache */ public SegmentWithData peek(final CellRequest request) { final SegmentCacheManager.PeekResponse response = execute( new PeekCommand(request, Locus.peek())); for (SegmentHeader header : response.headerMap.keySet()) { final SegmentBody body = compositeCache.get(header); if (body != null) { final SegmentBuilder.SegmentConverter converter = response.converterMap.get( SegmentCacheIndexImpl.makeConverterKey(header)); if (converter != null) { return converter.convert(header, body); } } } for (Map.Entry> entry : response.headerMap.entrySet()) { final Future bodyFuture = entry.getValue(); if (bodyFuture != null) { final SegmentBody body = Util.safeGet( bodyFuture, "Waiting for segment to load"); final SegmentHeader header = entry.getKey(); final SegmentBuilder.SegmentConverter converter = response.converterMap.get( SegmentCacheIndexImpl.makeConverterKey(header)); if (converter != null) { return converter.convert(header, body); } } } return null; } /** * Visitor for messages (commands and events). */ public interface Visitor { void visit(SegmentLoadSucceededEvent event); void visit(SegmentLoadFailedEvent event); void visit(SegmentRemoveEvent event); void visit(ExternalSegmentCreatedEvent event); void visit(ExternalSegmentDeletedEvent event); } private class Handler implements Visitor { public void visit(SegmentLoadSucceededEvent event) { indexRegistry.getIndex(event.star) .loadSucceeded( event.header, event.body); event.monitor.sendEvent( new CellCacheSegmentCreateEvent( event.timestamp, event.serverId, event.connectionId, event.statementId, event.executionId, event.header.getConstrainedColumns().size(), event.body == null ? 0 : event.body.getValueMap().size(), CellCacheSegmentCreateEvent.Source.SQL)); } public void visit(SegmentLoadFailedEvent event) { indexRegistry.getIndex(event.star) .loadFailed( event.header, event.throwable); } public void visit(final SegmentRemoveEvent event) { indexRegistry.getIndex(event.star) .remove(event.header); event.monitor.sendEvent( new CellCacheSegmentDeleteEvent( event.timestamp, event.serverId, event.connectionId, event.statementId, event.executionId, event.header.getConstrainedColumns().size(), CellCacheEvent.Source.CACHE_CONTROL)); // Remove the segment from external caches. Use an executor, because // it may take some time. We discard the future, because we don't // care too much if it fails. final Future future = event.cacheMgr.cacheExecutor.submit( new Runnable() { public void run() { try { // Note that the SegmentCache API doesn't require // us to verify that the segment exists (by calling // "contains") before we call "remove". event.cacheMgr.compositeCache.remove(event.header); } catch (Throwable e) { LOGGER.warn( "remove header failed: " + event.header, e); } } } ); Util.discard(future); } public void visit(ExternalSegmentCreatedEvent event) { final SegmentCacheIndex index = event.cacheMgr.indexRegistry.getIndex(event.header); if (index != null) { index.add(event.header, false, null); event.monitor.sendEvent( new CellCacheSegmentCreateEvent( event.timestamp, event.serverId, event.connectionId, event.statementId, event.executionId, event.header.getConstrainedColumns().size(), 0, CellCacheEvent.Source.EXTERNAL)); } } public void visit(ExternalSegmentDeletedEvent event) { final SegmentCacheIndex index = event.cacheMgr.indexRegistry.getIndex(event.header); if (index != null) { index.remove(event.header); event.monitor.sendEvent( new CellCacheSegmentDeleteEvent( event.timestamp, event.serverId, event.connectionId, event.statementId, event.executionId, event.header.getConstrainedColumns().size(), CellCacheEvent.Source.EXTERNAL)); } } } interface Message { } public static interface Command extends Message, Callable { Locus getLocus(); } /** * Command to flush a particular region from cache. */ public static final class FlushCommand implements Command { private final CellRegion region; private final CacheControlImpl cacheControlImpl; private final Locus locus; private final SegmentCacheManager cacheMgr; public FlushCommand( Locus locus, SegmentCacheManager mgr, CellRegion region, CacheControlImpl cacheControlImpl) { this.locus = locus; this.cacheMgr = mgr; this.region = region; this.cacheControlImpl = cacheControlImpl; } public Locus getLocus() { return locus; } public FlushResult call() throws Exception { // For each measure and each star, ask the index // which headers intersect. final List headers = new ArrayList(); final List measures = CacheControlImpl.findMeasures(region); final SegmentColumn[] flushRegion = CacheControlImpl.findAxisValues(region); final List starList = CacheControlImpl.getStarList(region); for (Member member : measures) { if (!(member instanceof RolapStoredMeasure)) { continue; } final RolapStoredMeasure storedMeasure = (RolapStoredMeasure) member; final RolapStar star = storedMeasure.getCube().getStar(); final SegmentCacheIndex index = cacheMgr.indexRegistry.getIndex(star); headers.addAll( index.intersectRegion( member.getDimension().getSchema().getName(), ((RolapSchema) member.getDimension().getSchema()) .getChecksum(), storedMeasure.getCube().getName(), storedMeasure.getName(), storedMeasure.getCube().getStar() .getFactTable().getAlias(), flushRegion)); } // If flushRegion is empty, this means we must clear all // segments for the region's measures. if (flushRegion.length == 0) { for (final SegmentHeader header : headers) { for (RolapStar star : starList) { cacheMgr.indexRegistry.getIndex(star).remove(header); } // Remove the segment from external caches. Use an // executor, because it may take some time. We discard // the future, because we don't care too much if it fails. Util.discard(cacheMgr.cacheExecutor.submit( new Runnable() { public void run() { try { // Note that the SegmentCache API doesn't // require us to verify that the segment // exists (by calling "contains") before we // call "remove". cacheMgr.compositeCache.remove(header); } catch (Throwable e) { LOGGER.warn( "remove header failed: " + header, e); } } })); } return new FlushResult( Collections.>emptyList()); } // Now we know which headers intersect. For each of them, // we append an excluded region. // // TODO: Optimize the logic here. If a segment is mostly // empty, we should trash it completely. final List> callableList = new ArrayList>(); for (final SegmentHeader header : headers) { if (!header.canConstrain(flushRegion)) { // We have to delete that segment altogether. cacheControlImpl.trace( "discard segment - it cannot be constrained and maintain consistency:\n" + header.getDescription()); for (RolapStar star : starList) { cacheMgr.indexRegistry.getIndex(star).remove(header); } continue; } final SegmentHeader newHeader = header.constrain(flushRegion); for (final SegmentCacheWorker worker : cacheMgr.segmentCacheWorkers) { callableList.add( new Callable() { public Boolean call() throws Exception { boolean existed; if (worker.supportsRichIndex()) { final SegmentBody sb = worker.get(header); existed = worker.remove(header); if (sb != null) { worker.put(newHeader, sb); } } else { // The cache doesn't support rich index. We // have to clear the segment entirely. existed = worker.remove(header); } return existed; } }); } for (RolapStar star : starList) { SegmentCacheIndex index = cacheMgr.indexRegistry.getIndex(star); index.remove(header); index.add(newHeader, false, null); } } // Done return new FlushResult(callableList); } } private class PrintCacheStateCommand implements SegmentCacheManager.Command { private final PrintWriter pw; private final Locus locus; private final CellRegion region; public PrintCacheStateCommand( CellRegion region, PrintWriter pw, Locus locus) { this.region = region; this.pw = pw; this.locus = locus; } public Void call() { final List starList = CacheControlImpl.getStarList(region); Collections.sort( starList, new Comparator() { public int compare(RolapStar o1, RolapStar o2) { return o1.getFactTable().getAlias().compareTo( o2.getFactTable().getAlias()); } }); for (RolapStar star : starList) { indexRegistry.getIndex(star) .printCacheState(pw); } return null; } public Locus getLocus() { return locus; } } /** * Result of a {@link FlushCommand}. Contains a list of tasks that must * be executed by the caller (or by an executor) to flush segments from the * external cache(s). */ public static class FlushResult { public final List> tasks; public FlushResult(List> tasks) { this.tasks = tasks; } } /** * Special exception, thrown only by {@link ShutdownCommand}, telling * the actor to shut down. */ private static class PleaseShutdownException extends RuntimeException { private PleaseShutdownException() { } } private static class ShutdownCommand implements Command { public ShutdownCommand() { } public String call() throws Exception { throw new PleaseShutdownException(); } public Locus getLocus() { return null; } } private static abstract class Event implements Message { /** * Dispatches a call to the appropriate {@code visit} method on * {@link mondrian.server.monitor.Visitor}. * * @param visitor Visitor */ public abstract void acceptWithoutResponse(Visitor visitor); } /** * Point for various clients in a request-response pattern to receive the * response to their requests. * *

The key type should test for object identity using the == operator, * like {@link java.util.WeakHashMap}. This allows responses to be automatically * removed if the request (key) is garbage collected.

* *

Thread safety. {@link #queue} is a thread-safe data structure; * a thread can safely call {@link #put} while another thread calls * {@link #take}. The {@link #taken} map is not thread safe, so you must * lock the ResponseQueue before reading or writing it.

* *

If requests are processed out of order, this queue is not ideal: until * request #1 has received its response, requests #2, #3 etc. will not * receive their response. However, this is not a problem for the monitor, * which uses an actor model, processing requests in strict order.

* *

REVIEW: This class is copy-pasted from * {@link mondrian.server.monitor.Monitor}. Consider * abstracting common code.

* * @param request (key) type * @param response (value) type */ private static class ResponseQueue { private final BlockingQueue> queue; /** * Entries that have been removed from the queue. If the request * is garbage-collected, the map entry is removed. */ private final Map taken = new WeakHashMap(); /** * Creates a ResponseQueue with given capacity. * * @param capacity Capacity */ public ResponseQueue(int capacity) { queue = new ArrayBlockingQueue>(capacity); } /** * Places a (request, response) pair onto the queue. * * @param k Request * @param v Response * @throws InterruptedException if interrupted while waiting */ public void put(K k, V v) throws InterruptedException { queue.put(Pair.of(k, v)); } /** * Retrieves the response from the queue matching the given key, * blocking until it is received. * * @param k Response * @return Response * @throws InterruptedException if interrupted while waiting */ public synchronized V take(K k) throws InterruptedException { final V v = taken.remove(k); if (v != null) { return v; } // Take the laundry out of the machine. If it's ours, leave with it. // If it's someone else's, fold it neatly and put it on the pile. for (;;) { final Pair pair = queue.take(); if (pair.left.equals(k)) { return pair.right; } else { taken.put(pair.left, pair.right); } } } } /** * Copy-pasted from {@link mondrian.server.monitor.Monitor}. Consider * abstracting common code. */ private static class Actor implements Runnable { private final BlockingQueue> eventQueue = new ArrayBlockingQueue>(1000); private final ResponseQueue, Pair> responseQueue = new ResponseQueue, Pair>(1000); public void run() { try { for (;;) { final Pair entry = eventQueue.take(); final Handler handler = entry.left; final Message message = entry.right; try { // A message is either a command or an event. // A command returns a value that must be read by // the caller. if (message instanceof Command) { Command command = (Command) message; try { Locus.push(command.getLocus()); Object result = command.call(); responseQueue.put( command, Pair.of(result, (Throwable) null)); } catch (PleaseShutdownException e) { responseQueue.put( command, Pair.of(null, (Throwable) null)); return; // exit event loop } catch (Throwable e) { responseQueue.put( command, Pair.of(null, e)); } finally { Locus.pop(command.getLocus()); } } else { Event event = (Event) message; event.acceptWithoutResponse(handler); // Broadcast the event to anyone who is interested. RolapUtil.MONITOR_LOGGER.debug(message); } } catch (Throwable e) { // REVIEW: Somewhere better to send it? e.printStackTrace(); } } } catch (InterruptedException e) { // REVIEW: Somewhere better to send it? e.printStackTrace(); } catch (Throwable e) { e.printStackTrace(); } } T execute(Handler handler, Command command) { try { eventQueue.put(Pair.of(handler, command)); } catch (InterruptedException e) { throw Util.newError(e, "Exception while executing " + command); } try { final Pair pair = responseQueue.take(command); if (pair.right != null) { if (pair.right instanceof RuntimeException) { throw (RuntimeException) pair.right; } else if (pair.right instanceof Error) { throw (Error) pair.right; } else { throw new RuntimeException(pair.right); } } else { return (T) pair.left; } } catch (InterruptedException e) { throw Util.newError(e, "Exception while executing " + command); } } public void event(Handler handler, Event event) { try { eventQueue.put(Pair.of(handler, event)); } catch (InterruptedException e) { throw Util.newError(e, "Exception while executing " + event); } } } private static class SegmentLoadSucceededEvent extends Event { private final SegmentHeader header; private final SegmentBody body; private final long timestamp; private final RolapStar star; private final int serverId; private final int connectionId; private final long statementId; private final long executionId; private final Monitor monitor; public SegmentLoadSucceededEvent( long timestamp, Monitor monitor, int serverId, int connectionId, long statementId, long executionId, RolapStar star, SegmentHeader header, SegmentBody body) { this.timestamp = timestamp; this.monitor = monitor; this.serverId = serverId; this.connectionId = connectionId; this.statementId = statementId; this.executionId = executionId; assert header != null; assert star != null; this.star = star; this.header = header; this.body = body; // may be null } public void acceptWithoutResponse(Visitor visitor) { visitor.visit(this); } } private static class SegmentLoadFailedEvent extends Event { private final SegmentHeader header; private final Throwable throwable; private final long timestamp; private final RolapStar star; private final Monitor monitor; private final int serverId; private final int connectionId; private final long statementId; private final long executionId; public SegmentLoadFailedEvent( long timestamp, Monitor monitor, int serverId, int connectionId, long statementId, long executionId, RolapStar star, SegmentHeader header, Throwable throwable) { this.timestamp = timestamp; this.monitor = monitor; this.serverId = serverId; this.connectionId = connectionId; this.statementId = statementId; this.executionId = executionId; this.star = star; this.throwable = throwable; assert header != null; this.header = header; } public void acceptWithoutResponse(Visitor visitor) { visitor.visit(this); } } private static class SegmentRemoveEvent extends Event { private final SegmentHeader header; private final long timestamp; private final Monitor monitor; private final int serverId; private final int connectionId; private final long statementId; private final long executionId; private final RolapStar star; private final SegmentCacheManager cacheMgr; public SegmentRemoveEvent( long timestamp, Monitor monitor, int serverId, int connectionId, long statementId, long executionId, SegmentCacheManager cacheMgr, RolapStar star, SegmentHeader header) { this.timestamp = timestamp; this.monitor = monitor; this.serverId = serverId; this.connectionId = connectionId; this.statementId = statementId; this.executionId = executionId; this.cacheMgr = cacheMgr; this.star = star; assert header != null; this.header = header; } public void acceptWithoutResponse(Visitor visitor) { visitor.visit(this); } } private static class ExternalSegmentCreatedEvent extends Event { private final SegmentCacheManager cacheMgr; private final SegmentHeader header; private final long timestamp; private final Monitor monitor; private final int serverId; private final int connectionId; private final long statementId; private final long executionId; public ExternalSegmentCreatedEvent( long timestamp, Monitor monitor, int serverId, int connectionId, long statementId, long executionId, SegmentCacheManager cacheMgr, SegmentHeader header) { this.timestamp = timestamp; this.monitor = monitor; this.serverId = serverId; this.connectionId = connectionId; this.statementId = statementId; this.executionId = executionId; assert header != null; assert cacheMgr != null; this.cacheMgr = cacheMgr; this.header = header; } public void acceptWithoutResponse(Visitor visitor) { visitor.visit(this); } } private static class ExternalSegmentDeletedEvent extends Event { private final SegmentCacheManager cacheMgr; private final SegmentHeader header; private final long timestamp; private final Monitor monitor; private final int serverId; private final int connectionId; private final long statementId; private final long executionId; public ExternalSegmentDeletedEvent( long timestamp, Monitor monitor, int serverId, int connectionId, long statementId, long executionId, SegmentCacheManager cacheMgr, SegmentHeader header) { this.timestamp = timestamp; this.monitor = monitor; this.serverId = serverId; this.connectionId = connectionId; this.statementId = statementId; this.executionId = executionId; assert header != null; assert cacheMgr != null; this.cacheMgr = cacheMgr; this.header = header; } public void acceptWithoutResponse(Visitor visitor) { visitor.visit(this); } } /** * Implementation of SegmentCacheListener that updates the * segment index of its aggregation manager instance when it receives * events from its assigned SegmentCache implementation. */ private static class AsyncCacheListener implements SegmentCache.SegmentCacheListener { private final SegmentCacheManager cacheMgr; private final MondrianServer server; public AsyncCacheListener( SegmentCacheManager cacheMgr, MondrianServer server) { this.cacheMgr = cacheMgr; this.server = server; } public void handle(final SegmentCacheEvent e) { if (e.isLocal()) { return; } Locus.execute( Execution.NONE, "AsyncCacheListener.handle", new Locus.Action() { public Void execute() { final SegmentCacheManager.Command command; final Locus locus = Locus.peek(); switch (e.getEventType()) { case ENTRY_CREATED: command = new Command() { public Void call() { cacheMgr.externalSegmentCreated( e.getSource(), server); return null; } public Locus getLocus() { return locus; } }; break; case ENTRY_DELETED: command = new Command() { public Void call() { cacheMgr.externalSegmentDeleted( e.getSource(), server); return null; } public Locus getLocus() { return locus; } }; break; default: throw new UnsupportedOperationException(); } cacheMgr.execute(command); return null; } }); } } /** * Makes a collection of {@link SegmentCacheWorker} objects (each of which * is backed by a {@link SegmentCache} appear to be a SegmentCache. * *

For most operations, it is easier to operate on a single cache. * It is usually clear whether operations should quit when finding the first * match, or to operate on all workers. (For example, {@link #remove} tries * to remove the segment header from all workers, and returns whether it * was removed from any of them.) This class just does what seems * most typical. If you want another behavior for a particular operation, * operate on the workers directly.

*/ private static class CompositeSegmentCache implements SegmentCache { private final List workers; public CompositeSegmentCache(List workers) { this.workers = workers; } public SegmentBody get(SegmentHeader header) { for (SegmentCacheWorker worker : workers) { final SegmentBody body = worker.get(header); if (body != null) { return body; } } return null; } public boolean contains(SegmentHeader header) { for (SegmentCacheWorker worker : workers) { if (worker.contains(header)) { return true; } } return false; } public List getSegmentHeaders() { // Special case 0 and 1 workers, for which the 'union' operation // is trivial. switch (workers.size()) { case 0: return Collections.emptyList(); case 1: return workers.get(0).getSegmentHeaders(); default: final List list = new ArrayList(); final Set set = new HashSet(); for (SegmentCacheWorker worker : workers) { for (SegmentHeader header : worker.getSegmentHeaders()) { if (set.add(header)) { list.add(header); } } } return list; } } public boolean put(SegmentHeader header, SegmentBody body) { for (SegmentCacheWorker worker : workers) { worker.put(header, body); } return true; } public boolean remove(SegmentHeader header) { boolean result = false; for (SegmentCacheWorker worker : workers) { if (worker.remove(header)) { result = true; } } return result; } public void tearDown() { // nothing } public void addListener(SegmentCacheListener listener) { // nothing } public void removeListener(SegmentCacheListener listener) { // nothing } public boolean supportsRichIndex() { return false; } } /** * Locates segments in the cache that satisfy a given request. * *

The result consists of (a) a list of segment headers, (b) a list * of futures for segment bodies that are currently being loaded, (c) * converters to convert headers into {@link SegmentWithData}.

* *

For (a), the client should call the cache to get the body for each * segment header; it is possible that there is no body in the cache. * For (b), the client will have to wait for the segment to arrive.

*/ private class PeekCommand implements SegmentCacheManager.Command { private final CellRequest request; private final Locus locus; /** * Creates a PeekCommand. * * @param request Cell request * @param locus Locus */ public PeekCommand( CellRequest request, Locus locus) { this.request = request; this.locus = locus; } public PeekResponse call() { final RolapStar.Measure measure = request.getMeasure(); final RolapStar star = measure.getStar(); final RolapSchema schema = star.getSchema(); final AggregationKey key = new AggregationKey(request); final List headers = indexRegistry.getIndex(star) .locate( schema.getName(), schema.getChecksum(), measure.getCubeName(), measure.getName(), star.getFactTable().getAlias(), request.getConstrainedColumnsBitKey(), request.getMappedCellValues(), AggregationKey.getCompoundPredicateStringList( star, key.getCompoundPredicateList())); final Map> headerMap = new HashMap>(); final Map converterMap = new HashMap(); // Is there a pending segment? (A segment that has been created and // is loading via SQL.) for (final SegmentHeader header : headers) { final Future bodyFuture = indexRegistry.getIndex(star) .getFuture(header); if (bodyFuture != null) { // Check if the DataSourceChangeListener wants us to clear // the current segment if (star.getChangeListener() != null && star.getChangeListener().isAggregationChanged(key)) { /* * We can't satisfy this request, and we must clear the * data from our cache. We clear it from the index * first, then queue up a job in the background * to remove the data from all the caches. */ indexRegistry.getIndex(star).remove(header); Util.discard(cacheExecutor.submit( new Runnable() { public void run() { try { compositeCache.remove(header); } catch (Throwable e) { LOGGER.warn( "remove header failed: " + header, e); } } })); } converterMap.put( SegmentCacheIndexImpl.makeConverterKey(header), getConverter(star, header)); headerMap.put( header, bodyFuture); } } return new PeekResponse(headerMap, converterMap); } public Locus getLocus() { return locus; } } private static class PeekResponse { public final Map> headerMap; public final Map converterMap; public PeekResponse( Map> headerMap, Map converterMap) { this.headerMap = headerMap; this.converterMap = converterMap; } } /** * Registry of all the indexes that were created for this * cache manager, per {@link RolapStar}. */ public class SegmentCacheIndexRegistry { private final Map indexes = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.SOFT); /** * Returns the {@link SegmentCacheIndex} for a given * {@link RolapStar}. */ public SegmentCacheIndex getIndex(RolapStar star) { if (!indexes.containsKey(star)) { indexes.put(star, new SegmentCacheIndexImpl(thread)); } return indexes.get(star); } /** * Returns the {@link SegmentCacheIndex} for a given * {@link SegmentHeader}. */ private SegmentCacheIndex getIndex( SegmentHeader header) { // First we check the indexes that already exist. // This is fast. for (Entry entry : indexes.entrySet()) { final String factTableName = entry.getKey().getFactTable().getTableName(); final ByteString schemaChecksum = entry.getKey().getSchema().getChecksum(); if (!factTableName.equals(header.rolapStarFactTableName)) { continue; } if (!schemaChecksum.equals(header.schemaChecksum)) { continue; } return entry.getValue(); } //The index doesn't exist. Let's create it. for (RolapSchema schema : RolapSchema.getRolapSchemas()) { if (!schema.getChecksum().equals(header.schemaChecksum)) { continue; } // We have a schema match. RolapStar star = schema.getStar(header.rolapStarFactTableName); if (star != null) { // Found it. indexes.put(star, new SegmentCacheIndexImpl(thread)); } return indexes.get(star); } return null; } } } // End SegmentCacheManager.java mondrian-3.4.1/src/main/mondrian/rolap/agg/SegmentCacheWorker.java0000644000175000017500000002023211735330606025005 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.olap.MondrianProperties; import mondrian.resource.MondrianResource; import mondrian.spi.*; import mondrian.util.ServiceDiscovery; import org.apache.log4j.Logger; import java.util.*; /** * Utility class to interact with the {@link SegmentCache}. * * @author LBoudreau * @see SegmentCache */ public final class SegmentCacheWorker { private static final Logger LOGGER = Logger.getLogger(SegmentCacheWorker.class); private final SegmentCache cache; private final Thread cacheMgrThread; private final boolean supportsRichIndex; /** * Creates a worker. * * @param cache Cache managed by this worker * @param cacheMgrThread Thread that the cache manager actor is running on, * and which therefore should not be used for * potentially long-running calls this this cache. * Pass null if methods can be called from any thread. */ public SegmentCacheWorker(SegmentCache cache, Thread cacheMgrThread) { this.cache = cache; this.cacheMgrThread = cacheMgrThread; // no need to call checkThread(): supportsRichIndex is a fast call this.supportsRichIndex = cache.supportsRichIndex(); LOGGER.debug( "Segment cache initialized: " + cache.getClass().getName()); } /** * Instantiates a cache. Returns null if there is no external cache defined. * * @return Cache */ public static List initCache() { final List caches = new ArrayList(); // First try to get the segmentcache impl class from // mondrian properties. final String cacheName = MondrianProperties.instance().SegmentCache.get(); if (cacheName != null) { caches.add(instantiateCache(cacheName)); } // There was no property set. Let's look for Java services. final List> implementors = ServiceDiscovery.forClass(SegmentCache.class).getImplementor(); if (implementors.size() > 0) { // The contract is to use the first implementation found. SegmentCache cache = instantiateCache(implementors.get(0).getName()); if (cache != null) { caches.add(cache); } } // Check the SegmentCacheInjector // People might have sent instances into this thing. caches.addAll(SegmentCache.SegmentCacheInjector.getCaches()); // Done. return caches; } /** * Instantiates a cache, given the name of the cache class. * * @param cacheName Name of class that implements the * {@link mondrian.spi.SegmentCache} SPI * * @return Cache instance, or null on error */ private static SegmentCache instantiateCache(String cacheName) { try { LOGGER.debug("Starting cache instance: " + cacheName); Class clazz = Class.forName(cacheName); Object scObject = clazz.newInstance(); if (!(scObject instanceof SegmentCache)) { throw MondrianResource.instance() .SegmentCacheIsNotImplementingInterface.ex(); } return (SegmentCache) scObject; } catch (Exception e) { LOGGER.error( MondrianResource.instance() .SegmentCacheFailedToInstanciate.baseMessage, e); throw MondrianResource.instance() .SegmentCacheFailedToInstanciate.ex(e); } } /** * Returns a segment body corresponding to a header. * *

If no cache is configured or there is an error while * querying the cache, null is returned none the less. * * @param header Header to search. * @return Either a segment body object or null if there * was no cache configured or no segment could be found * for the passed header. */ public SegmentBody get(SegmentHeader header) { checkThread(); try { return cache.get(header); } catch (Throwable t) { LOGGER.error( MondrianResource.instance() .SegmentCacheFailedToLoadSegment .baseMessage, t); throw MondrianResource.instance() .SegmentCacheFailedToLoadSegment.ex(t); } } /** * Returns whether there is a cached segment body available * for a given segment header. * *

If no cache is configured or there is an error while * querying the cache, returns false nonetheless. * * @param header A header to search for in the segment cache. * @return True or false, whether there is a segment body * available in a segment cache. */ public boolean contains(SegmentHeader header) { checkThread(); try { return cache.contains(header); } catch (Throwable t) { LOGGER.error( MondrianResource.instance() .SegmentCacheFailedToLookupSegment.baseMessage, t); throw MondrianResource.instance() .SegmentCacheFailedToLookupSegment.ex(t); } } /** * Places a segment in the cache. Returns true or false * if the operation succeeds. * * @param header A header to search for in the segment cache. * @param body The segment body to cache. */ public void put(SegmentHeader header, SegmentBody body) { checkThread(); try { final boolean result = cache.put(header, body); if (!result) { LOGGER.error( MondrianResource.instance() .SegmentCacheFailedToSaveSegment .baseMessage); throw MondrianResource.instance() .SegmentCacheFailedToSaveSegment.ex(); } } catch (Throwable t) { LOGGER.error( MondrianResource.instance() .SegmentCacheFailedToSaveSegment .baseMessage, t); throw MondrianResource.instance() .SegmentCacheFailedToSaveSegment.ex(t); } } /** * Removes a segment from the cache. * * @param header A header to remove in the segment cache. * @return Whether a segment was removed */ public boolean remove(SegmentHeader header) { checkThread(); try { return cache.remove(header); } catch (Throwable t) { LOGGER.error( MondrianResource.instance() .SegmentCacheFailedToDeleteSegment .baseMessage, t); throw MondrianResource.instance() .SegmentCacheFailedToDeleteSegment.ex(t); } } /** * Returns a list of segments present in the cache. * * @return List of headers in the cache */ public List getSegmentHeaders() { checkThread(); try { return cache.getSegmentHeaders(); } catch (Throwable t) { LOGGER.error("Failed to get a list of segment headers.", t); throw MondrianResource.instance() .SegmentCacheFailedToScanSegments.ex(t); } } public boolean supportsRichIndex() { return supportsRichIndex; } public void shutdown() { checkThread(); cache.tearDown(); } private void checkThread() { assert cacheMgrThread != Thread.currentThread() : "this method is potentially slow; you should not call it from " + "the cache manager thread, " + cacheMgrThread; } } // End SegmentCacheWorker.java mondrian-3.4.1/src/main/mondrian/rolap/agg/Aggregation.java0000644000175000017500000005572411735330606023532 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. // // jhyde, 28 August, 2001 */ package mondrian.rolap.agg; import mondrian.olap.*; import mondrian.rolap.*; import java.util.*; import java.util.concurrent.Future; /** * A Aggregation is a pre-computed aggregation over a set of * columns. * *

Rollup operations:

    *
  • drop an unrestricted column (e.g. state=*)
  • *
  • tighten any restriction (e.g. year={1997,1998} becomes * year={1997})
  • *
  • restrict an unrestricted column (e.g. year=* becomes * year={1997})
  • *
* *

Representation of aggregations. Sparse and dense representations are * necessary for different data sets. Should adapt automatically. Use an * interface to hold the data set, so the segment doesn't care.

* * Suppose we have a segment {year=1997, quarter={1,2,3}, * state={CA,WA}}. We want to roll up to a segment for {year=1997, * state={CA,WA}}. We need to know that we have all quarters. We don't. * Because year and quarter are independent, we know that we have all of * the ...

* *

Suppose we have a segment specified by {region=West, state=*, * year=*}, which materializes to ({West}, {CA,WA,OR}, {1997,1998}). * Because state=*, we can rollup to {region=West, year=*} or {region=West, * year=1997}.

* *

The space required for a segment depends upon the dimensionality (d), * cell count (c) and the value count (v). We don't count the space * required for the actual values, which is the same in any scheme.

* * @author jhyde * @since 28 August, 2001 */ public class Aggregation { private final List compoundPredicateList; private final RolapStar star; private final BitKey constrainedColumnsBitKey; /** * Setting for optimizing SQL predicates. */ private final int maxConstraints; /** * Timestamp of when the aggregation was created. (We use * {@link java.util.Date} rather than {@link java.sql.Timestamp} because it * has less baggage.) */ private final Date creationTimestamp; /** * Creates an Aggregation. * * @param aggregationKey the key specifying the axes, the context and * the RolapStar for this Aggregation */ public Aggregation( AggregationKey aggregationKey) { this.compoundPredicateList = aggregationKey.getCompoundPredicateList(); this.star = aggregationKey.getStar(); this.constrainedColumnsBitKey = aggregationKey.getConstrainedColumnsBitKey(); this.maxConstraints = MondrianProperties.instance().MaxConstraints.get(); this.creationTimestamp = new Date(); } /** * @return Returns the timestamp when the aggregation was created */ public Date getCreationTimestamp() { return creationTimestamp; } /** * Loads a set of segments into this aggregation, one per measure, * each constrained by the same set of column values, and each pinned * once. * *

A Column and its constraints are accessed at the same level in their * respective arrays. * *

For example, *

     * measures = {unit_sales, store_sales},
     * state = {CA, OR},
     * gender = unconstrained
* * @param segmentFutures List of futures wherein each statement will place * a list of the segments it has loaded, when it * completes */ public void load( SegmentCacheManager cacheMgr, int cellRequestCount, RolapStar.Column[] columns, List measures, StarColumnPredicate[] predicates, GroupingSetsCollector groupingSetsCollector, List>> segmentFutures) { BitKey measureBitKey = getConstrainedColumnsBitKey().emptyCopy(); int axisCount = columns.length; Util.assertTrue(predicates.length == axisCount); List segments = createSegments( columns, measures, measureBitKey, predicates); // The constrained columns are simply the level and foreign columns BitKey levelBitKey = getConstrainedColumnsBitKey(); GroupingSet groupingSet = new GroupingSet( segments, levelBitKey, measureBitKey, predicates, columns); if (groupingSetsCollector.useGroupingSets()) { groupingSetsCollector.add(groupingSet); } else { final SegmentLoader segmentLoader = new SegmentLoader(cacheMgr); segmentLoader.load( cellRequestCount, new ArrayList( Collections.singletonList(groupingSet)), compoundPredicateList, segmentFutures); } } private List createSegments( RolapStar.Column[] columns, List measures, BitKey measureBitKey, StarColumnPredicate[] predicates) { List segments = new ArrayList(measures.size()); for (RolapStar.Measure measure : measures) { measureBitKey.set(measure.getBitPosition()); Segment segment = new Segment( star, constrainedColumnsBitKey, columns, measure, predicates, Collections.emptyList(), compoundPredicateList); segments.add(segment); } // It is important to sort the segments per measure bitkey. // The order in which the measures come in is not deterministic. // It actually depends on the order of the CellRequests. // See: mondrian.rolap.BatchLoader.Batch.add(CellRequest request). // Failure to sort them will give out wrong results (uses the wrong // column) if we have more than one column in the grouping set. Collections.sort( segments, new Comparator() { public int compare(Segment o1, Segment o2) { return Integer.valueOf( o1.measure.getBitPosition()) .compareTo(o2.measure.getBitPosition()); } }); return segments; } /** * Drops predicates, where the list of values is close to the values which * would be returned anyway. */ public StarColumnPredicate[] optimizePredicates( RolapStar.Column[] columns, StarColumnPredicate[] predicates) { RolapStar star = getStar(); Util.assertTrue(predicates.length == columns.length); StarColumnPredicate[] newPredicates = predicates.clone(); double[] bloats = new double[columns.length]; // We want to handle the special case "drilldown" which occurs pretty // often. Here, the parent is here as a constraint with a single member // and the list of children as well. List potentialParents = new ArrayList(); for (final StarColumnPredicate predicate : predicates) { Member m; if (predicate instanceof MemberColumnPredicate) { m = ((MemberColumnPredicate) predicate).getMember(); potentialParents.add(m); } } for (int i = 0; i < newPredicates.length; i++) { // A set of constraints with only one entry will not be optimized // away if (!(newPredicates[i] instanceof ListColumnPredicate)) { bloats[i] = 0.0; continue; } final ListColumnPredicate newPredicate = (ListColumnPredicate) newPredicates[i]; final List predicateList = newPredicate.getPredicates(); final int valueCount = predicateList.size(); if (valueCount < 2) { bloats[i] = 0.0; continue; } if (valueCount > maxConstraints) { // Some databases can handle only a limited number of elements // in 'WHERE IN (...)'. This set is greater than this database // can handle, so we drop this constraint. Hopefully there are // other constraints that will limit the result. bloats[i] = 1.0; // will be optimized away continue; } // more than one - check for children of same parent double constraintLength = (double) valueCount; Member parent = null; Level level = null; for (int j = 0; j < valueCount; j++) { Object value = predicateList.get(j); if (value instanceof MemberColumnPredicate) { MemberColumnPredicate memberColumnPredicate = (MemberColumnPredicate) value; Member m = memberColumnPredicate.getMember(); if (j == 0) { parent = m.getParentMember(); level = m.getLevel(); } else { if (parent != null && !parent.equals(m.getParentMember())) { parent = null; // no common parent } if (level != null && !level.equals(m.getLevel())) { // should never occur, constraints are of same level level = null; } } } else { // Value constraint with no associated member. // Compute bloat by #constraints / column cardinality. parent = null; level = null; bloats[i] = constraintLength / columns[i].getCardinality(); break; } } boolean done = false; if (parent != null) { // common parent exists if (parent.isAll() || potentialParents.contains(parent)) { // common parent is there as constraint // if the children are complete, this constraint set is // unneccessary try to get the children directly from // cache for the drilldown case, the children will be // in the cache // - if not, forget this optimization. SchemaReader scr = star.getSchema().getSchemaReader(); int childCount = scr.getChildrenCountFromCache(parent); if (childCount == -1) { // nothing gotten from cache if (!parent.isAll()) { // parent is in constraints // no information about children cardinality // constraints must not be optimized away bloats[i] = 0.0; done = true; } } else { bloats[i] = constraintLength / childCount; done = true; } } } if (!done && level != null) { // if the level members are cached, we do not need "count *" SchemaReader scr = star.getSchema().getSchemaReader(); int memberCount = scr.getLevelCardinality(level, true, false); if (memberCount > 0) { bloats[i] = constraintLength / memberCount; done = true; } } if (!done) { bloats[i] = constraintLength / columns[i].getCardinality(); } } // build a list of constraints sorted by 'bloat factor' ConstraintComparator comparator = new ConstraintComparator(bloats); Integer[] indexes = new Integer[columns.length]; for (int i = 0; i < columns.length; i++) { indexes[i] = i; } // sort indexes by bloat descending Arrays.sort(indexes, comparator); // Eliminate constraints one by one, until the constrained cell count // became half of the unconstrained cell count. We can not have an // absolute value here, because its // very different if we fetch data for 2 years or 10 years (5 times // more means 5 times slower). So a relative comparison is ok here // but not an absolute one. double abloat = 1.0; final double aBloatLimit = .5; for (Integer j : indexes) { abloat = abloat * bloats[j]; if (abloat <= aBloatLimit) { break; } // eliminate this constraint if (MondrianProperties.instance().OptimizePredicates.get() || bloats[j] == 1) { newPredicates[j] = new LiteralStarPredicate(columns[j], true); } } // Now do simple structural optimizations, e.g. convert a list predicate // with one element to a value predicate. for (int i = 0; i < newPredicates.length; i++) { newPredicates[i] = StarPredicates.optimize(newPredicates[i]); } return newPredicates; } /** * This is called during SQL generation. */ public RolapStar getStar() { return star; } /** * Returns the BitKey for ALL columns (Measures and Levels) involved in the * query. */ public BitKey getConstrainedColumnsBitKey() { return constrainedColumnsBitKey; } // -- classes ------------------------------------------------------------- /** * Helper class to figure out which axis values evaluate to true at least * once by a given predicate. * *

Consider, for example, the flush predicate

* * member between [Time].[1997].[Q3] and [Time].[1999].[Q1] * *
applied to the segment
* * year in (1996, 1997, 1998, 1999)
* quarter in (Q1, Q2, Q3, Q4) * *
The predicate evaluates to true for the pairs *
* * {(1997, Q3), (1997, Q4), * (1998, Q1), (1998, Q2), (1998, Q3), (1998, Q4), (1999, Q1)} * *
and therefore we wish to eliminate these pairs from * the segment. But we can eliminate a value only if all of its * values are eliminated. * *

In this case, year=1998 is the only value which can be eliminated from * the segment. */ private static class ValuePruner { /** * Multi-column predicate. If the predicate evaluates to true, a cell * will be removed from the segment. But we can only eliminate a value * if all of its cells are eliminated. */ private final StarPredicate flushPredicate; /** * Number of columns predicate depends on. */ private final int arity; /** * For each column, the segment axis which the column corresponds to, or * null. */ private final SegmentAxis[] axes; /** * For each column, a bitmap of values for which the predicate is * sometimes false. These values cannot be eliminated from the axis. */ private final BitSet[] keepBitSets; /** * For each segment axis, the predicate column which depends on the * axis, or -1. */ private final int[] axisInverseOrdinals; /** * Workspace which contains the current key value for each column. */ private final Object[] values; /** * View onto {@link #values} as a list. */ private final List valueList; /** * Workspace which contains the ordinal of the current value of each * column on its axis. */ private final int[] ordinals; private final SegmentDataset data; private final CellKey cellKey; /** * Creates a ValuePruner. * * @param flushPredicate Multi-column predicate to test * @param segmentAxes Axes of the segment. (The columns that the * predicate may not be present, or may * be in a different order.) * @param data Segment dataset, which allows pruner * to determine whether a particular * cell is currently empty */ ValuePruner( StarPredicate flushPredicate, SegmentAxis[] segmentAxes, SegmentDataset data) { this.flushPredicate = flushPredicate; this.arity = flushPredicate.getConstrainedColumnList().size(); this.axes = new SegmentAxis[arity]; this.keepBitSets = new BitSet[arity]; this.axisInverseOrdinals = new int[segmentAxes.length]; Arrays.fill(axisInverseOrdinals, -1); this.values = new Object[arity]; this.valueList = Arrays.asList(values); this.ordinals = new int[arity]; assert data != null; this.data = data; this.cellKey = CellKey.Generator.newCellKey(segmentAxes.length); // Pair up constraint columns with axes. If one of the constraint's // columns is not in this segment, it gets the null axis. The // constraint will have to evaluate to true for all possible values // of that column. for (int i = 0; i < arity; i++) { RolapStar.Column column = flushPredicate.getConstrainedColumnList().get(i); int axisOrdinal = findAxis(segmentAxes, column.getBitPosition()); if (axisOrdinal < 0) { this.axes[i] = null; values[i] = StarPredicate.WILDCARD; keepBitSets[i] = new BitSet(1); // dummy } else { axes[i] = segmentAxes[axisOrdinal]; axisInverseOrdinals[axisOrdinal] = i; final int keyCount = axes[i].getKeys().length; keepBitSets[i] = new BitSet(keyCount); } } } private int findAxis(SegmentAxis[] axes, int bitPosition) { for (int i = 0; i < axes.length; i++) { SegmentAxis axis = axes[i]; if (axis.getPredicate().getConstrainedColumn().getBitPosition() == bitPosition) { return i; } } return -1; } /** * Applies this ValuePruner's predicate and sets bits in axisBitSets * to indicate extra values which can be removed. * * @param axisKeepBitSets Array containing, for each axis, a bitset * of values to keep (not flush) */ void go(BitSet[] axisKeepBitSets) { evaluatePredicate(0); // Clear bits in the axis bit sets (indicating that a value is never // used) if this predicate evaluates to true for every combination // of values which this axis value appears in. for (int i = 0; i < axisKeepBitSets.length; i++) { if (axisInverseOrdinals[i] < 0) { continue; } BitSet axisKeepBitSet = axisKeepBitSets[axisInverseOrdinals[i]]; final BitSet keepBitSet = keepBitSets[i]; axisKeepBitSet.and(keepBitSet); } } /** * Evaluates the predicate for axes i and higher, and marks * {@link #keepBitSets} if the predicate ever evaluates to false. * The result is that discardBitSets[i] will be false for column #i if * the predicate evaluates to true for all cells in the segment which * have that column value. * * @param axisOrdinal Axis ordinal */ private void evaluatePredicate(int axisOrdinal) { if (axisOrdinal == arity) { // If the flush predicate evaluates to false for this cell, // and this cell currently has some data (*), // then none of the values which are the coordinates of this // cell can be discarded. // // * Important when there is sparsity. Consider the cell // {year=1997, quarter=Q1, month=12}. This cell would never have // data, so there's no point keeping it. if (!flushPredicate.evaluate(valueList)) { // REVIEW: getObject forces an int or double dataset to // create a boxed object; use exists() instead? if (data.getObject(cellKey) != null) { for (int k = 0; k < arity; k++) { keepBitSets[k].set(ordinals[k]); } } } } else { final SegmentAxis axis = axes[axisOrdinal]; if (axis == null) { evaluatePredicate(axisOrdinal + 1); } else { final Comparable[] keys = axis.getKeys(); for (int keyOrdinal = 0; keyOrdinal < keys.length; keyOrdinal++) { Object key = keys[keyOrdinal]; values[axisOrdinal] = key; ordinals[axisOrdinal] = keyOrdinal; cellKey.setAxis( axisInverseOrdinals[axisOrdinal], keyOrdinal); evaluatePredicate(axisOrdinal + 1); } } } } } private static class ConstraintComparator implements Comparator { private final double[] bloats; ConstraintComparator(double[] bloats) { this.bloats = bloats; } // implement Comparator // order by bloat descending public int compare(Integer o0, Integer o1) { double bloat0 = bloats[o0]; double bloat1 = bloats[o1]; return (bloat0 == bloat1) ? 0 : (bloat0 < bloat1) ? 1 : -1; } } } // End Aggregation.java mondrian-3.4.1/src/main/mondrian/rolap/agg/DenseDoubleSegmentBody.java0000644000175000017500000000526411735330606025627 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.util.Pair; import java.util.*; /** * Implementation of a segment body which stores the data inside * a dense primitive array of double precision numbers. * * @author LBoudreau */ class DenseDoubleSegmentBody extends AbstractSegmentBody { private static final long serialVersionUID = 5775717165497921144L; private final double[] values; private final BitSet nullIndicators; /** * Creates a DenseDoubleSegmentBody. * *

Stores the given array of cell values and null indicators; caller must * not modify them afterwards.

* * @param nullIndicators Null indicators * @param values Cell values * @param axes Axes */ DenseDoubleSegmentBody( BitSet nullIndicators, double[] values, List, Boolean>> axes) { super(axes); this.values = values; this.nullIndicators = nullIndicators; } @Override public Object getValueArray() { return values; } @Override public BitSet getIndicators() { return nullIndicators; } @Override protected int getSize() { return values.length - nullIndicators.cardinality(); } @Override protected Object getObject(int i) { double value = values[i]; if (value == 0d && nullIndicators.get(i)) { return null; } return value; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("DenseDoubleSegmentBody(size="); sb.append(values.length); sb.append(", data="); sb.append(Arrays.toString(values)); sb.append(", nullIndicators=").append(nullIndicators); sb.append(", axisValueSets="); sb.append(Arrays.toString(getAxisValueSets())); sb.append(", nullAxisFlags="); sb.append(Arrays.toString(getNullAxisFlags())); if (getAxisValueSets().length > 0) { if (getAxisValueSets()[0].iterator().hasNext()) { sb.append(", aVS[0]="); sb.append(getAxisValueSets()[0].getClass()); sb.append(", aVS[0][0]="); sb.append(getAxisValueSets()[0].iterator().next().getClass()); } } sb.append(")"); return sb.toString(); } } // End DenseDoubleSegmentBody.java mondrian-3.4.1/src/main/mondrian/rolap/agg/AggregationManager.java0000644000175000017500000004461611735330606025023 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. // // jhyde, 30 August, 2001 */ package mondrian.rolap.agg; import mondrian.olap.CacheControl; import mondrian.olap.Exp; import mondrian.olap.MondrianProperties; import mondrian.olap.MondrianServer; import mondrian.olap.Util; import mondrian.rolap.*; import mondrian.rolap.SqlStatement.Type; import mondrian.rolap.aggmatcher.AggStar; import mondrian.server.Locus; import mondrian.util.Pair; import org.apache.log4j.Logger; import java.io.PrintWriter; import java.util.*; import java.util.concurrent.*; /** * RolapAggregationManager manages all {@link Aggregation}s * in the system. It is a singleton class. * * @author jhyde * @since 30 August, 2001 */ public class AggregationManager extends RolapAggregationManager { private static final MondrianProperties properties = MondrianProperties.instance(); private static final Logger LOGGER = Logger.getLogger(AggregationManager.class); public final SegmentCacheManager cacheMgr; /** * Creates the AggregationManager. */ public AggregationManager(MondrianServer server) { if (properties.EnableCacheHitCounters.get()) { LOGGER.error( "Property " + properties.EnableCacheHitCounters.getPath() + " is obsolete; ignored."); } this.cacheMgr = new SegmentCacheManager(server); } /** * Returns the log4j logger. * * @return Logger */ public final Logger getLogger() { return LOGGER; } /** * Returns or creates the singleton. * * @deprecated No longer a singleton, and will be removed in mondrian-4. * Use {@link mondrian.olap.MondrianServer#getAggregationManager()}. * To get a server, call * {@link mondrian.olap.MondrianServer#forConnection(mondrian.olap.Connection)}, * passing in a null connection if you absolutely must. */ public static synchronized AggregationManager instance() { return MondrianServer.forId(null).getAggregationManager(); } /** * Called by FastBatchingCellReader.load where the * RolapStar creates an Aggregation if needed. * * @param cacheMgr Cache manager * @param cellRequestCount Number of missed cells that led to this request * @param measures Measures to load * @param columns this is the CellRequest's constrained columns * @param aggregationKey this is the CellRequest's constraint key * @param predicates Array of constraints on each column * @param groupingSetsCollector grouping sets collector * @param segmentFutures List of futures into which each statement will * place a list of the segments it has loaded, when it completes */ public static void loadAggregation( SegmentCacheManager cacheMgr, int cellRequestCount, List measures, RolapStar.Column[] columns, AggregationKey aggregationKey, StarColumnPredicate[] predicates, GroupingSetsCollector groupingSetsCollector, List>> segmentFutures) { RolapStar star = measures.get(0).getStar(); Aggregation aggregation = star.lookupOrCreateAggregation(aggregationKey); // try to eliminate unnecessary constraints // for Oracle: prevent an IN-clause with more than 1000 elements predicates = aggregation.optimizePredicates(columns, predicates); aggregation.load( cacheMgr, cellRequestCount, columns, measures, predicates, groupingSetsCollector, segmentFutures); } /** * Returns an API with which to explicitly manage the contents of the cache. * * @param connection Server whose cache to control * @param pw Print writer, for tracing * @return CacheControl API */ public CacheControl getCacheControl( RolapConnection connection, final PrintWriter pw) { return new CacheControlImpl(connection) { protected void flushNonUnion(final CellRegion region) { final SegmentCacheManager.FlushResult result = cacheMgr.execute( new SegmentCacheManager.FlushCommand( Locus.peek(), cacheMgr, region, this)); final List> futures = new ArrayList>(); for (Callable task : result.tasks) { futures.add(cacheMgr.cacheExecutor.submit(task)); } for (Future future : futures) { Util.discard(Util.safeGet(future, "Flush cache")); } } public void flush(final CellRegion region) { if (pw != null) { pw.println("Cache state before flush:"); printCacheState(pw, region); pw.println(); } super.flush(region); if (pw != null) { pw.println("Cache state after flush:"); printCacheState(pw, region); pw.println(); } } public void trace(final String message) { if (pw != null) { pw.println(message); } } }; } public Object getCellFromCache(CellRequest request) { return getCellFromCache(request, null); } public Object getCellFromCache(CellRequest request, PinSet pinSet) { // NOTE: This method used to check both local (thread/statement) cache // and global cache (segments in JVM, shared between statements). Now it // only looks in local cache. This can be done without acquiring any // locks, because the local cache is thread-local. If a segment that // matches this cell-request in global cache, a call to // SegmentCacheManager will copy it into local cache. final RolapStar.Measure measure = request.getMeasure(); return measure.getStar().getCellFromCache(request, pinSet); } public Object getCellFromAllCaches(CellRequest request) { final RolapStar.Measure measure = request.getMeasure(); return measure.getStar().getCellFromAllCaches(request); } public String getDrillThroughSql( final DrillThroughCellRequest request, final StarPredicate starPredicateSlicer, List fields, final boolean countOnly) { DrillThroughQuerySpec spec = new DrillThroughQuerySpec( request, starPredicateSlicer, countOnly); Pair> pair = spec.generateSqlQuery(); if (getLogger().isDebugEnabled()) { getLogger().debug( "DrillThroughSQL: " + pair.left + Util.nl); } return pair.left; } /** * Generates the query to retrieve the cells for a list of segments. * Called by Segment.load. * * @return A pair consisting of a SQL statement and a list of suggested * types of columns */ public static Pair> generateSql( GroupingSetsList groupingSetsList, List compoundPredicateList) { final RolapStar star = groupingSetsList.getStar(); BitKey levelBitKey = groupingSetsList.getDefaultLevelBitKey(); BitKey measureBitKey = groupingSetsList.getDefaultMeasureBitKey(); // Check if using aggregates is enabled. boolean hasCompoundPredicates = false; if (compoundPredicateList != null && compoundPredicateList.size() > 0) { // Do not use Aggregate tables if compound predicates are present. hasCompoundPredicates = true; } if (MondrianProperties.instance().UseAggregates.get() && !hasCompoundPredicates) { final boolean[] rollup = {false}; AggStar aggStar = findAgg(star, levelBitKey, measureBitKey, rollup); if (aggStar != null) { // Got a match, hot damn if (LOGGER.isDebugEnabled()) { StringBuilder buf = new StringBuilder(256); buf.append("MATCH: "); buf.append(star.getFactTable().getAlias()); buf.append(Util.nl); buf.append(" foreign="); buf.append(levelBitKey); buf.append(Util.nl); buf.append(" measure="); buf.append(measureBitKey); buf.append(Util.nl); buf.append(" aggstar="); buf.append(aggStar.getBitKey()); buf.append(Util.nl); buf.append("AggStar="); buf.append(aggStar.getFactTable().getName()); buf.append(Util.nl); for (AggStar.Table.Column column : aggStar.getFactTable().getColumns()) { buf.append(" "); buf.append(column); buf.append(Util.nl); } LOGGER.debug(buf.toString()); } AggQuerySpec aggQuerySpec = new AggQuerySpec( aggStar, rollup[0], groupingSetsList); Pair> sql = aggQuerySpec.generateSqlQuery(); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "generateSqlQuery: sql=" + sql.left); } return sql; } // No match, fall through and use fact table. } if (LOGGER.isDebugEnabled()) { StringBuilder sb = new StringBuilder(); sb.append("NO MATCH : "); sb.append(star.getFactTable().getAlias()); sb.append(Util.nl); sb.append("Foreign columns bit key="); sb.append(levelBitKey); sb.append(Util.nl); sb.append("Measure bit key= "); sb.append(measureBitKey); sb.append(Util.nl); sb.append("Agg Stars=["); sb.append(Util.nl); for (AggStar aggStar : star.getAggStars()) { sb.append(aggStar.toString()); } sb.append(Util.nl); sb.append("]"); LOGGER.debug(sb.toString()); } // Fact table query SegmentArrayQuerySpec spec = new SegmentArrayQuerySpec(groupingSetsList, compoundPredicateList); Pair> pair = spec.generateSqlQuery(); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "generateSqlQuery: sql=" + pair.left); } return pair; } /** * Finds an aggregate table in the given star which has the desired levels * and measures. Returns null if no aggregate table is suitable. * *

If there no aggregate is an exact match, returns a more * granular aggregate which can be rolled up, and sets rollup to true. * If one or more of the measures are distinct-count measures * rollup is possible only in limited circumstances. * * @param star Star * @param levelBitKey Set of levels * @param measureBitKey Set of measures * @param rollup Out parameter, is set to true if the aggregate is not * an exact match * @return An aggregate, or null if none is suitable. */ public static AggStar findAgg( RolapStar star, final BitKey levelBitKey, final BitKey measureBitKey, boolean[] rollup) { // If there is no distinct count measure, isDistinct == false, // then all we want is an AggStar whose BitKey is a superset // of the combined measure BitKey and foreign-key/level BitKey. // // On the other hand, if there is at least one distinct count // measure, isDistinct == true, then what is wanted is an AggStar // whose measure BitKey is a superset of the measure BitKey, // whose level BitKey is an exact match and the aggregate table // can NOT have any foreign keys. assert rollup != null; BitKey fullBitKey = levelBitKey.or(measureBitKey); // The AggStars are already ordered from smallest to largest so // we need only find the first one and return it. for (AggStar aggStar : star.getAggStars()) { // superset match if (!aggStar.superSetMatch(fullBitKey)) { continue; } boolean isDistinct = measureBitKey.intersects( aggStar.getDistinctMeasureBitKey()); // The AggStar has no "distinct count" measures so // we can use it without looking any further. if (!isDistinct) { rollup[0] = !aggStar.getLevelBitKey().equals(levelBitKey); return aggStar; } // If there are distinct measures, we can only rollup in limited // circumstances. // No foreign keys (except when its used as a distinct count // measure). // Level key exact match. // Measure superset match. // Compute the core levels -- those which can be safely // rolled up to. For example, // if the measure is 'distinct customer count', // and the agg table has levels customer_id, // then gender is a core level. final BitKey distinctMeasuresBitKey = measureBitKey.and(aggStar.getDistinctMeasureBitKey()); final BitSet distinctMeasures = distinctMeasuresBitKey.toBitSet(); BitKey combinedLevelBitKey = null; for (int k = distinctMeasures.nextSetBit(0); k >= 0; k = distinctMeasures.nextSetBit(k + 1)) { final AggStar.FactTable.Measure distinctMeasure = aggStar.lookupMeasure(k); BitKey rollableLevelBitKey = distinctMeasure.getRollableLevelBitKey(); if (combinedLevelBitKey == null) { combinedLevelBitKey = rollableLevelBitKey; } else { // TODO use '&=' to remove unnecessary copy combinedLevelBitKey = combinedLevelBitKey.and(rollableLevelBitKey); } } if (aggStar.hasForeignKeys()) { /* StringBuilder buf = new StringBuilder(256); buf.append(""); buf.append(star.getFactTable().getAlias()); buf.append(Util.nl); buf.append("foreign ="); buf.append(levelBitKey); buf.append(Util.nl); buf.append("measure ="); buf.append(measureBitKey); buf.append(Util.nl); buf.append("aggstar ="); buf.append(aggStar.getBitKey()); buf.append(Util.nl); buf.append("distinct="); buf.append(aggStar.getDistinctMeasureBitKey()); buf.append(Util.nl); buf.append("AggStar="); buf.append(aggStar.getFactTable().getName()); buf.append(Util.nl); for (Iterator columnIter = aggStar.getFactTable().getColumns().iterator(); columnIter.hasNext();) { AggStar.Table.Column column = (AggStar.Table.Column) columnIter.next(); buf.append(" "); buf.append(column); buf.append(Util.nl); } System.out.println(buf.toString()); */ // This is a little pessimistic. If the measure is // 'count(distinct customer_id)' and one of the foreign keys is // 'customer_id' then it is OK to roll up. // Some of the measures in this query are distinct count. // Get all of the foreign key columns. // For each such measure, is it based upon a foreign key. // Are there any foreign keys left over. No, can use AggStar. BitKey fkBitKey = aggStar.getForeignKeyBitKey().copy(); for (AggStar.FactTable.Measure measure : aggStar.getFactTable().getMeasures()) { if (measure.isDistinct()) { if (measureBitKey.get(measure.getBitPosition())) { fkBitKey.clear(measure.getBitPosition()); } } } if (!fkBitKey.isEmpty()) { // there are foreign keys left so we can not use this // AggStar. continue; } } if (!aggStar.select( levelBitKey, combinedLevelBitKey, measureBitKey)) { continue; } rollup[0] = !aggStar.getLevelBitKey().equals(levelBitKey); return aggStar; } return null; } public PinSet createPinSet() { return new PinSetImpl(); } public void shutdown() { // Send a shutdown command and wait for it to return. cacheMgr.shutdown(); // Now we can cleanup. for (SegmentCacheWorker worker : cacheMgr.segmentCacheWorkers) { worker.shutdown(); } } /** * Implementation of {@link mondrian.rolap.RolapAggregationManager.PinSet} * using a {@link HashSet}. */ public static class PinSetImpl extends HashSet implements RolapAggregationManager.PinSet { } } // End AggregationManager.java mondrian-3.4.1/src/main/mondrian/rolap/agg/ListColumnPredicate.java0000644000175000017500000003164311735330606025207 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.olap.Util; import mondrian.rolap.*; import mondrian.rolap.sql.SqlQuery; import java.util.*; /** * Predicate which is the union of a list of predicates, each of which applies * to the same, single column. It evaluates to * true if any of the predicates evaluates to true. * * @see mondrian.rolap.agg.ListColumnPredicate * * @author jhyde * @since Nov 2, 2006 */ public class ListColumnPredicate extends AbstractColumnPredicate { /** * List of column predicates. */ private final List children; /** * Hash map of children predicates, keyed off of the hash code of each * child. Each entry in the map is a list of predicates matching that * hash code. */ private HashMap> childrenHashMap; /** * Set of child values, if all child predicates are value predicates; null * otherwise. */ private final Set values; /** * Pre-computed hash code for this list column predicate */ private int hashValue; /** * Creates a ListColumnPredicate * * @param column Column being constrained * @param list List of child predicates */ public ListColumnPredicate( RolapStar.Column column, List list) { super(column); this.children = list; childrenHashMap = null; hashValue = 0; values = createValues(list); } private static Set createValues(List list) { final HashSet set = new HashSet(); for (StarColumnPredicate predicate : list) { if (predicate instanceof ValueColumnPredicate) { set.add(((ValueColumnPredicate) predicate).getValue()); } else { // One of the children is not a value predicate. We will have to // evaluate the predicate long-hand. return null; } } return set; } /** * Returns the list of child predicates. * * @return list of child predicates */ public List getPredicates() { return children; } public int hashCode() { // Don't use the default list hashcode because we want a hash code // that's not order dependent if (hashValue == 0) { hashValue = 37; for (StarColumnPredicate child : children) { int childHashCode = child.hashCode(); if (childHashCode != 0) { hashValue *= childHashCode; } } hashValue ^= children.size(); } return hashValue; } public boolean equals(Object obj) { if (obj instanceof ListColumnPredicate) { ListColumnPredicate that = (ListColumnPredicate) obj; return this.children.equals(that.children); } else { return false; } } public void values(Collection collection) { if (values != null) { collection.addAll(values); } else { for (StarColumnPredicate child : children) { child.values(collection); } } } public boolean evaluate(Object value) { for (StarColumnPredicate childPredicate : children) { if (childPredicate.evaluate(value)) { return true; } } return false; } public boolean equalConstraint(StarPredicate that) { boolean isEqual = that instanceof ListColumnPredicate && getConstrainedColumnBitKey().equals( that.getConstrainedColumnBitKey()); if (isEqual) { ListColumnPredicate thatPred = (ListColumnPredicate) that; if (getPredicates().size() != thatPred.getPredicates().size()) { isEqual = false; } else { // Create a hash map of the children predicates, if not // already done if (childrenHashMap == null) { childrenHashMap = new HashMap>(); for (StarColumnPredicate thisChild : getPredicates()) { Integer key = thisChild.hashCode(); List predList = childrenHashMap.get(key); if (predList == null) { predList = new ArrayList(); childrenHashMap.put(key, predList); } predList.add(thisChild); } } // Loop through thatPred's children predicates. There needs // to be a matching entry in the hash map for each child // predicate. for (StarColumnPredicate thatChild : thatPred.getPredicates()) { List predList = childrenHashMap.get(thatChild.hashCode()); if (predList == null) { isEqual = false; break; } boolean foundMatch = false; for (StarColumnPredicate pred : predList) { if (thatChild.equalConstraint(pred)) { foundMatch = true; break; } } if (!foundMatch) { isEqual = false; break; } } } } return isEqual; } public void describe(StringBuilder buf) { buf.append("={"); for (int j = 0; j < children.size(); j++) { if (j > 0) { buf.append(", "); } buf.append(children.get(j)); } buf.append('}'); } public Overlap intersect(StarColumnPredicate predicate) { int matchCount = 0; for (StarColumnPredicate flushPredicate : children) { final Overlap r2 = flushPredicate.intersect(predicate); if (r2.matched) { // A hit! if (r2.remaining == null) { // Total match. return r2; } else { // Partial match. predicate = r2.remaining; ++matchCount; } } } if (matchCount == 0) { return new Overlap(false, null, 0f); } else { float selectivity = (float) matchCount / (float) children.size(); return new Overlap(true, predicate, selectivity); } } public boolean mightIntersect(StarPredicate other) { if (other instanceof LiteralStarPredicate) { return ((LiteralStarPredicate) other).getValue(); } if (other instanceof ValueColumnPredicate) { ValueColumnPredicate valueColumnPredicate = (ValueColumnPredicate) other; return evaluate(valueColumnPredicate.getValue()); } if (other instanceof ListColumnPredicate) { final List thatSet = new ArrayList(); ((ListColumnPredicate) other).values(thatSet); for (Object o : thatSet) { if (evaluate(o)) { return true; } } return false; } throw Util.newInternal("unknown constraint type " + other); } public StarColumnPredicate minus(StarPredicate predicate) { assert predicate != null; if (predicate instanceof LiteralStarPredicate) { LiteralStarPredicate literalStarPredicate = (LiteralStarPredicate) predicate; if (literalStarPredicate.getValue()) { // X minus TRUE --> FALSE return LiteralStarPredicate.FALSE; } else { // X minus FALSE --> X return this; } } StarColumnPredicate columnPredicate = (StarColumnPredicate) predicate; List newChildren = new ArrayList(children); int changeCount = 0; final Iterator iterator = newChildren.iterator(); while (iterator.hasNext()) { ValueColumnPredicate child = (ValueColumnPredicate) iterator.next(); if (columnPredicate.evaluate(child.getValue())) { ++changeCount; iterator.remove(); } } if (changeCount > 0) { return new ListColumnPredicate(getConstrainedColumn(), newChildren); } else { return this; } } public StarColumnPredicate orColumn(StarColumnPredicate predicate) { assert predicate.getConstrainedColumn() == getConstrainedColumn(); if (predicate instanceof ListColumnPredicate) { ListColumnPredicate that = (ListColumnPredicate) predicate; final List list = new ArrayList(children); list.addAll(that.children); return new ListColumnPredicate( getConstrainedColumn(), list); } else { final List list = new ArrayList(children); list.add(predicate); return new ListColumnPredicate( getConstrainedColumn(), list); } } public StarColumnPredicate cloneWithColumn(RolapStar.Column column) { return new ListColumnPredicate( column, cloneListWithColumn(column, children)); } public void toSql(SqlQuery sqlQuery, StringBuilder buf) { List predicates = getPredicates(); if (predicates.size() == 1) { predicates.get(0).toSql(sqlQuery, buf); return; } int notNullCount = 0; final RolapStar.Column column = getConstrainedColumn(); final String expr = column.generateExprString(sqlQuery); final int marker = buf.length(); // to allow backtrack later buf.append(expr); ValueColumnPredicate firstNotNull = null; buf.append(" in ("); for (StarColumnPredicate predicate1 : predicates) { final ValueColumnPredicate predicate2 = (ValueColumnPredicate) predicate1; Object key = predicate2.getValue(); if (key == RolapUtil.sqlNullValue) { continue; } if (notNullCount > 0) { buf.append(", "); } else { firstNotNull = predicate2; } ++notNullCount; sqlQuery.getDialect().quote(buf, key, column.getDatatype()); } buf.append(')'); // If all of the predicates were non-null, return what we've got, for // example, "x in (1, 2, 3)". if (notNullCount >= predicates.size()) { return; } // There was at least one null. Reset the buffer to how we // originally found it, and generate a more concise expression. switch (notNullCount) { case 0: // Special case -- there were no values besides null. // Return, for example, "x is null". buf.setLength(marker); buf.append(expr); buf.append(" is null"); break; case 1: // Special case -- one not-null value, and null, for // example "(x = 1 or x is null)". assert firstNotNull != null; buf.setLength(marker); buf.append('('); buf.append(expr); buf.append(" = "); sqlQuery.getDialect().quote( buf, firstNotNull.getValue(), column.getDatatype()); buf.append(" or "); buf.append(expr); buf.append(" is null)"); break; default: // Nulls and values, for example, // "(x in (1, 2) or x IS NULL)". String save = buf.substring(marker); buf.setLength(marker); // backtrack buf.append('('); buf.append(save); buf.append(" or "); buf.append(expr); buf.append(" is null)"); break; } } } // End ListColumnPredicate.java mondrian-3.4.1/src/main/mondrian/rolap/agg/LiteralStarPredicate.java0000644000175000017500000000632211735330606025340 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.*; import mondrian.rolap.sql.SqlQuery; import java.util.Collection; import java.util.List; /** * A constraint which always returns true or false. * * @author jhyde * @since Nov 2, 2006 */ public class LiteralStarPredicate extends AbstractColumnPredicate { private final boolean value; public static final LiteralStarPredicate TRUE = new LiteralStarPredicate(null, true); public static final LiteralStarPredicate FALSE = new LiteralStarPredicate(null, false); /** * Creates a LiteralStarPredicate. * * @param column Constrained column * @param value Truth value */ public LiteralStarPredicate(RolapStar.Column column, boolean value) { super(column); this.value = value; } public int hashCode() { return value ? 2 : 1; } public boolean equals(Object obj) { if (obj instanceof LiteralStarPredicate) { LiteralStarPredicate that = (LiteralStarPredicate) obj; return this.value == that.value; } else { return false; } } public boolean evaluate(List valueList) { assert valueList.isEmpty(); return value; } public boolean equalConstraint(StarPredicate that) { throw new UnsupportedOperationException(); } public String toString() { return Boolean.toString(value); } public void values(Collection collection) { collection.add(value); } public boolean evaluate(Object value) { return this.value; } public void describe(StringBuilder buf) { buf.append("=any"); } public Overlap intersect( StarColumnPredicate predicate) { return new Overlap(value, null, 0f); } public boolean mightIntersect(StarPredicate other) { // FALSE intersects nothing // TRUE intersects everything except FALSE if (!value) { return false; } else if (other instanceof LiteralStarPredicate) { return ((LiteralStarPredicate) other).value; } else { return true; } } public StarColumnPredicate minus(StarPredicate predicate) { assert predicate != null; if (value) { // We have no 'not' operator, so there's no shorter way to represent // "true - constraint". return new MinusStarPredicate( this, (StarColumnPredicate) predicate); } else { // "false - constraint" is "false" return this; } } public StarColumnPredicate cloneWithColumn(RolapStar.Column column) { return this; } public boolean getValue() { return value; } public void toSql(SqlQuery sqlQuery, StringBuilder buf) { // e.g. "true" buf.append(value); } } // End LiteralStarPredicate.java mondrian-3.4.1/src/main/mondrian/rolap/agg/StarPredicates.java0000644000175000017500000000252311735330606024205 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.StarColumnPredicate; /** * Utilities for {@link mondrian.rolap.StarPredicate}s and * {@link mondrian.rolap.StarColumnPredicate}s. * * @author jhyde */ public class StarPredicates { /** * Optimizes a column predicate. * * @param predicate Column predicate * @return Optimized predicate */ public static StarColumnPredicate optimize(StarColumnPredicate predicate) { if (predicate instanceof ListColumnPredicate && false) { ListColumnPredicate listColumnPredicate = (ListColumnPredicate) predicate; switch (listColumnPredicate.getPredicates().size()) { case 0: return new LiteralStarPredicate( predicate.getConstrainedColumn(), false); case 1: return listColumnPredicate.getPredicates().get(0); default: return listColumnPredicate; } } return predicate; } } // End StarPredicates.java mondrian-3.4.1/src/main/mondrian/rolap/agg/QuerySpec.java0000644000175000017500000000236111735330606023210 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. // // jhyde, 30 August, 2001 */ package mondrian.rolap.agg; import mondrian.rolap.*; import mondrian.util.Pair; import java.util.List; /** * Contains the information necessary to generate a SQL statement to * retrieve a set of cells. * * @author jhyde * @author Richard M. Emberson */ public interface QuerySpec { RolapStar getStar(); int getMeasureCount(); RolapStar.Measure getMeasure(int i); String getMeasureAlias(int i); RolapStar.Column[] getColumns(); String getColumnAlias(int i); /** * Returns the predicate on the ith column. * *

If the column is unconstrained, returns * {@link LiteralStarPredicate}(true). * * @param i Column ordinal * @return Constraint on column */ StarColumnPredicate getColumnPredicate(int i); Pair> generateSqlQuery(); } // End QuerySpec.java mondrian-3.4.1/src/main/mondrian/rolap/agg/SparseSegmentBody.java0000644000175000017500000000330211735330606024662 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.CellKey; import mondrian.util.Pair; import java.util.*; /** * Implementation of a segment body which stores the data of a * sparse segment data set into a dense array of java objects. * * @author LBoudreau */ class SparseSegmentBody extends AbstractSegmentBody { private static final long serialVersionUID = -6684830985364895836L; final CellKey[] keys; final Object[] data; SparseSegmentBody( Map dataToSave, List, Boolean>> axes) { super(axes); this.keys = new CellKey[dataToSave.size()]; this.data = new Object[dataToSave.size()]; int i = 0; for (Map.Entry entry : dataToSave.entrySet()) { keys[i] = entry.getKey(); data[i] = entry.getValue(); ++i; } } @Override protected int getSize() { return keys.length; } @Override protected Object getObject(int i) { throw new UnsupportedOperationException(); } @Override public Map getValueMap() { final Map map = new HashMap(keys.length * 3 / 2); for (int i = 0; i < keys.length; i++) { map.put(keys[i], data[i]); } return map; } } // End SparseSegmentBody.java mondrian-3.4.1/src/main/mondrian/rolap/agg/Segment.java0000644000175000017500000002524511735330606022700 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. // // jhyde, 21 March, 2002 */ package mondrian.rolap.agg; import mondrian.olap.Util; import mondrian.rolap.*; import mondrian.spi.SegmentHeader; import org.apache.log4j.Logger; import java.io.PrintWriter; import java.util.*; /** * A Segment is a collection of cell values parameterized by * a measure, and a set of (column, value) pairs. An example of a segment is

* *
*

(Unit sales, Gender = 'F', State in {'CA','OR'}, Marital Status = * anything)

*
* *

All segments over the same set of columns belong to an Aggregation, in * this case:

* *
*

('Sales' Star, Gender, State, Marital Status)

*
* *

Note that different measures (in the same Star) occupy the same * Aggregation. Aggregations belong to the AggregationManager, a singleton.

* *

Segments are pinned during the evaluation of a single MDX query. The query * evaluates the expressions twice. The first pass, it finds which cell values * it needs, pins the segments containing the ones which are already present * (one pin-count for each cell value used), and builds a {@link CellRequest * cell request} for those which are not present. It executes the cell request * to bring the required cell values into the cache, again, pinned. Then it * evalutes the query a second time, knowing that all cell values are * available. Finally, it releases the pins.

* *

A Segment may have a list of {@link ExcludedRegion} objects. These are * caused by cache flushing. Usually a segment is a hypercube: it is defined by * a set of values on each of its axes. But after a cache flush request, a * segment may have a rectangular 'hole', and therefore not be a hypercube * anymore. * *

For example, the segment defined by {CA, OR, WA} * {F, M} is a * 2-dimensional hyper-rectangle with 6 cells. After flushing {CA, OR, TX} * * {F}, the result is 4 cells: * *

 *     F     M
 * CA  out   in
 * OR  out   in
 * WA  in    in
 * 
* * defined by the original segment minus the region ({CA, OR} * {F}). * * @author jhyde * @since 21 March, 2002 */ public class Segment { private static int nextId = 0; // generator for "id" final int id; // for debug private String desc; /** * This is set in the load method and is used during * the processing of a particular aggregate load. */ protected final RolapStar.Column[] columns; public final RolapStar.Measure measure; /** * An array of axes, one for each constraining column, containing the values * returned for that constraining column. */ public final StarColumnPredicate[] predicates; protected final RolapStar star; protected final BitKey constrainedColumnsBitKey; /** * List of regions to ignore when reading this segment. This list is * populated when a region is flushed. The cells for these regions may be * physically in the segment, because trimming segments can be expensive, * but should still be ignored. */ protected final List excludedRegions; private final int aggregationKeyHashCode; protected final List compoundPredicateList; private final SegmentHeader segmentHeader; private static final Logger LOGGER = Logger.getLogger(Segment.class); /** * Creates a Segment; it's not loaded yet. * * @param star Star that this Segment belongs to * @param measure Measure whose values this Segment contains * @param predicates List of predicates constraining each axis * @param excludedRegions List of regions which are not in this segment. */ public Segment( RolapStar star, BitKey constrainedColumnsBitKey, RolapStar.Column[] columns, RolapStar.Measure measure, StarColumnPredicate[] predicates, List excludedRegions, final List compoundPredicateList) { this.id = nextId++; this.star = star; this.constrainedColumnsBitKey = constrainedColumnsBitKey; this.columns = columns; this.measure = measure; this.predicates = predicates; this.excludedRegions = excludedRegions; this.compoundPredicateList = compoundPredicateList; final List compoundPredicateBitKeys = compoundPredicateList == null ? null : new AbstractList() { public BitKey get(int index) { return compoundPredicateList.get(index) .getConstrainedColumnBitKey(); } public int size() { return compoundPredicateList.size(); } }; this.aggregationKeyHashCode = AggregationKey.computeHashCode( constrainedColumnsBitKey, star, compoundPredicateBitKeys); this.segmentHeader = SegmentBuilder.toHeader(this); } /** * Returns the constrained columns. */ public RolapStar.Column[] getColumns() { return columns; } /** * Returns the star. */ public RolapStar getStar() { return star; } /** * Returns the list of compound predicates. */ public List getCompoundPredicateList() { return compoundPredicateList; } /** * Returns the BitKey for ALL columns (Measures and Levels) involved in the * query. */ public BitKey getConstrainedColumnsBitKey() { return constrainedColumnsBitKey; } private void describe(StringBuilder buf, boolean values) { final String sep = Util.nl + " "; buf.append(printSegmentHeaderInfo(sep)); for (int i = 0; i < columns.length; i++) { buf.append(sep); buf.append(columns[i].getExpression().getGenericExpression()); describeAxes(buf, i, values); } if (!excludedRegions.isEmpty()) { buf.append(sep); buf.append("excluded={"); int k = 0; for (ExcludedRegion excludedRegion : excludedRegions) { if (k++ > 0) { buf.append(", "); } excludedRegion.describe(buf); } buf.append('}'); } buf.append('}'); } protected void describeAxes(StringBuilder buf, int i, boolean values) { predicates[i].describe(buf); } private String printSegmentHeaderInfo(String sep) { StringBuilder buf = new StringBuilder(); buf.append("Segment #"); buf.append(id); buf.append(" {"); buf.append(sep); buf.append("measure="); buf.append( measure.getExpression() == null ? measure.getAggregator().getExpression("*") : measure.getAggregator().getExpression( measure.getExpression().getGenericExpression())); return buf.toString(); } public String toString() { if (this.desc == null) { StringBuilder buf = new StringBuilder(64); describe(buf, false); this.desc = buf.toString(); } return this.desc; } /** * Returns whether a cell value is excluded from this segment. */ protected final boolean isExcluded(Object[] keys) { // Performance critical: cannot use foreach final int n = excludedRegions.size(); //noinspection ForLoopReplaceableByForEach for (int i = 0; i < n; i++) { ExcludedRegion excludedRegion = excludedRegions.get(i); if (excludedRegion.wouldContain(keys)) { return true; } } return false; } /** * Prints the state of this Segment, including constraints * and values. Blocks the current thread until the segment is loaded. * * @param pw Writer */ public void print(PrintWriter pw) { final StringBuilder buf = new StringBuilder(); describe(buf, true); pw.print(buf.toString()); pw.println(); } public List getExcludedRegions() { return excludedRegions; } SegmentDataset createDataset( SegmentAxis[] axes, boolean sparse, SqlStatement.Type type, int size) { if (sparse) { return new SparseSegmentDataset(); } else { switch (type) { case OBJECT: case STRING: return new DenseObjectSegmentDataset(axes, size); case INT: return new DenseIntSegmentDataset(axes, size); case DOUBLE: return new DenseDoubleSegmentDataset(axes, size); default: throw Util.unexpected(type); } } } public boolean matches( AggregationKey aggregationKey, RolapStar.Measure measure) { // Perform high-selectivity comparisons first. return aggregationKeyHashCode == aggregationKey.hashCode() && this.measure == measure && matchesInternal(aggregationKey); } private boolean matchesInternal(AggregationKey aggKey) { return constrainedColumnsBitKey.equals( aggKey.getConstrainedColumnsBitKey()) && star.equals(aggKey.getStar()) && AggregationKey.equal( compoundPredicateList, aggKey.compoundPredicateList); } /** * Definition of a region of values which are not in a segment. */ public static interface ExcludedRegion { /** * Tells whether this exclusion region would contain * the cell corresponding to the keys. */ public boolean wouldContain(Object[] keys); /** * Returns the arity of this region. */ public int getArity(); /** * Describes this exclusion region in a human readable way. */ public void describe(StringBuilder buf); /** * Returns an approximation of the number of cells exceluded * in this region. */ public int getCellCount(); } public SegmentHeader getHeader() { return this.segmentHeader; } } // End Segment.java mondrian-3.4.1/src/main/mondrian/rolap/agg/AbstractQuerySpec.java0000644000175000017500000003034711735330606024701 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.olap.Exp; import mondrian.olap.Util; import mondrian.rolap.*; import mondrian.rolap.sql.SqlQuery; import mondrian.spi.Dialect; import mondrian.util.Pair; import java.util.*; /** * Base class for {@link QuerySpec} implementations. * * @author jhyde * @author Richard M. Emberson */ public abstract class AbstractQuerySpec implements QuerySpec { private final RolapStar star; protected final boolean countOnly; /** * Creates an AbstractQuerySpec. * * @param star Star which defines columns of interest and their * relationships * * @param countOnly If true, generate no GROUP BY clause, so the query * returns a single row containing a grand total */ protected AbstractQuerySpec(final RolapStar star, boolean countOnly) { this.star = star; this.countOnly = countOnly; } /** * Creates a query object. * * @return a new query object */ protected SqlQuery newSqlQuery() { return getStar().getSqlQuery(); } public RolapStar getStar() { return star; } /** * Adds a measure to a query. * * @param i Ordinal of measure * @param sqlQuery Query object */ protected void addMeasure(final int i, final SqlQuery sqlQuery) { RolapStar.Measure measure = getMeasure(i); if (!isPartOfSelect(measure)) { return; } Util.assertTrue(measure.getTable() == getStar().getFactTable()); measure.getTable().addToFrom(sqlQuery, false, true); String exprInner = measure.getExpression() == null ? "*" : measure.generateExprString(sqlQuery); String exprOuter = measure.getAggregator().getExpression(exprInner); sqlQuery.addSelect( exprOuter, measure.getInternalType(), getMeasureAlias(i)); } protected abstract boolean isAggregate(); protected Map nonDistinctGenerateSql(SqlQuery sqlQuery) { // add constraining dimensions RolapStar.Column[] columns = getColumns(); int arity = columns.length; if (countOnly) { sqlQuery.addSelect("count(*)", SqlStatement.Type.INT); } for (int i = 0; i < arity; i++) { RolapStar.Column column = columns[i]; RolapStar.Table table = column.getTable(); if (table.isFunky()) { // this is a funky dimension -- ignore for now continue; } table.addToFrom(sqlQuery, false, true); String expr = column.generateExprString(sqlQuery); StarColumnPredicate predicate = getColumnPredicate(i); final String where = RolapStar.Column.createInExpr( expr, predicate, column.getDatatype(), sqlQuery); if (!where.equals("true")) { sqlQuery.addWhere(where); } if (countOnly) { continue; } if (!isPartOfSelect(column)) { continue; } // some DB2 (AS400) versions throw an error, if a column alias is // there and *not* used in a subsequent order by/group by final Dialect dialect = sqlQuery.getDialect(); final String alias; final Dialect.DatabaseProduct databaseProduct = dialect.getDatabaseProduct(); if (databaseProduct == Dialect.DatabaseProduct.DB2_AS400) { alias = sqlQuery.addSelect(expr, column.getInternalType(), null); } else { alias = sqlQuery.addSelect( expr, column.getInternalType(), getColumnAlias(i)); } if (isAggregate()) { sqlQuery.addGroupBy(expr, alias); } // Add ORDER BY clause to make the results deterministic. // Derby has a bug with ORDER BY, so ignore it. if (isOrdered()) { sqlQuery.addOrderBy(expr, true, false, false); } } // Add compound member predicates extraPredicates(sqlQuery); // add measures for (int i = 0, count = getMeasureCount(); i < count; i++) { addMeasure(i, sqlQuery); } return Collections.emptyMap(); } /** * Allows subclasses to specify if a given column must * be returned as part of the result set, in the select clause. */ protected boolean isPartOfSelect(RolapStar.Column col) { return true; } /** * Allows subclasses to specify if a given column must * be returned as part of the result set, in the select clause. */ protected boolean isPartOfSelect(RolapStar.Measure measure) { return true; } /** * Whether to add an ORDER BY clause to make results deterministic. * Necessary if query returns more than one row and results are for * human consumption. * * @return whether to sort query */ protected boolean isOrdered() { return false; } public Pair> generateSqlQuery() { SqlQuery sqlQuery = newSqlQuery(); int k = getDistinctMeasureCount(); final Dialect dialect = sqlQuery.getDialect(); final Map groupingSetsAliases; if (!dialect.allowsCountDistinct() && k > 0 || !dialect.allowsMultipleCountDistinct() && k > 1) { groupingSetsAliases = distinctGenerateSql(sqlQuery, countOnly); } else { groupingSetsAliases = nonDistinctGenerateSql(sqlQuery); } if (!countOnly) { addGroupingFunction(sqlQuery); addGroupingSets(sqlQuery, groupingSetsAliases); } return sqlQuery.toSqlAndTypes(); } protected void addGroupingFunction(SqlQuery sqlQuery) { throw new UnsupportedOperationException(); } protected void addGroupingSets( SqlQuery sqlQuery, Map groupingSetsAliases) { throw new UnsupportedOperationException(); } /** * Returns the number of measures whose aggregation function is * distinct-count. * * @return Number of distinct-count measures */ protected int getDistinctMeasureCount() { int k = 0; for (int i = 0, count = getMeasureCount(); i < count; i++) { RolapStar.Measure measure = getMeasure(i); if (measure.getAggregator().isDistinct()) { ++k; } } return k; } /** * Generates a SQL query to retrieve the values in this segment using * an algorithm which converts distinct-aggregates to non-distinct * aggregates over subqueries. * * @param outerSqlQuery Query to modify * @param countOnly If true, only generate a single row: no need to * generate a GROUP BY clause or put any constraining columns in the * SELECT clause * @return A map of aliases used in the inner query if grouping sets * were enabled. */ protected Map distinctGenerateSql( final SqlQuery outerSqlQuery, boolean countOnly) { final Dialect dialect = outerSqlQuery.getDialect(); final Dialect.DatabaseProduct databaseProduct = dialect.getDatabaseProduct(); final Map groupingSetsAliases = new HashMap(); // Generate something like // // select d0, d1, count(m0) // from ( // select distinct dim1.x as d0, dim2.y as d1, f.z as m0 // from f, dim1, dim2 // where dim1.k = f.k1 // and dim2.k = f.k2) as dummyname // group by d0, d1 // // or, if countOnly=true // // select count(m0) // from ( // select distinct f.z as m0 // from f, dim1, dim2 // where dim1.k = f.k1 // and dim2.k = f.k2) as dummyname final SqlQuery innerSqlQuery = newSqlQuery(); if (databaseProduct == Dialect.DatabaseProduct.GREENPLUM) { innerSqlQuery.setDistinct(false); } else { innerSqlQuery.setDistinct(true); } // add constraining dimensions RolapStar.Column[] columns = getColumns(); int arity = columns.length; for (int i = 0; i < arity; i++) { RolapStar.Column column = columns[i]; RolapStar.Table table = column.getTable(); if (table.isFunky()) { // this is a funky dimension -- ignore for now continue; } table.addToFrom(innerSqlQuery, false, true); String expr = column.generateExprString(innerSqlQuery); StarColumnPredicate predicate = getColumnPredicate(i); final String where = RolapStar.Column.createInExpr( expr, predicate, column.getDatatype(), innerSqlQuery); if (!where.equals("true")) { innerSqlQuery.addWhere(where); } if (countOnly) { continue; } String alias = "d" + i; alias = innerSqlQuery.addSelect(expr, null, alias); if (databaseProduct == Dialect.DatabaseProduct.GREENPLUM) { innerSqlQuery.addGroupBy(expr, alias); } final String quotedAlias = dialect.quoteIdentifier(alias); outerSqlQuery.addSelectGroupBy(quotedAlias, null); // Add this alias to the map of grouping sets aliases groupingSetsAliases.put( expr, dialect.quoteIdentifier( "dummyname." + alias)); } // add predicates not associated with columns extraPredicates(innerSqlQuery); // add measures for (int i = 0, count = getMeasureCount(); i < count; i++) { RolapStar.Measure measure = getMeasure(i); Util.assertTrue(measure.getTable() == getStar().getFactTable()); measure.getTable().addToFrom(innerSqlQuery, false, true); String alias = getMeasureAlias(i); String expr = measure.generateExprString(outerSqlQuery); innerSqlQuery.addSelect(expr, null, alias); if (databaseProduct == Dialect.DatabaseProduct.GREENPLUM) { innerSqlQuery.addGroupBy(expr, alias); } outerSqlQuery.addSelect( measure.getAggregator().getNonDistinctAggregator() .getExpression(dialect.quoteIdentifier(alias)), null); } outerSqlQuery.addFrom(innerSqlQuery, "dummyname", true); return groupingSetsAliases; } /** * Adds predicates not associated with columns. * * @param sqlQuery Query */ protected void extraPredicates(SqlQuery sqlQuery) { List predicateList = getPredicateList(); for (StarPredicate predicate : predicateList) { for (RolapStar.Column column : predicate.getConstrainedColumnList()) { final RolapStar.Table table = column.getTable(); table.addToFrom(sqlQuery, false, true); } StringBuilder buf = new StringBuilder(); predicate.toSql(sqlQuery, buf); final String where = buf.toString(); if (!where.equals("true")) { sqlQuery.addWhere(where); } } } /** * Returns a list of predicates not associated with a particular column. * * @return list of non-column predicates */ protected List getPredicateList() { return Collections.emptyList(); } } // End AbstractQuerySpec.java mondrian-3.4.1/src/main/mondrian/rolap/agg/SegmentBuilder.java0000644000175000017500000006612611744246504024215 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.olap.Aggregator; import mondrian.olap.Util; import mondrian.rolap.*; import mondrian.rolap.agg.Segment.ExcludedRegion; import mondrian.rolap.sql.SqlQuery; import mondrian.spi.*; import mondrian.util.ArraySortedSet; import mondrian.util.Pair; import java.util.*; import java.util.Map.Entry; /** * Helper class that contains methods to convert between * {@link Segment} and {@link SegmentHeader}, and also * {@link SegmentWithData} and {@link SegmentBody}. * * @author LBoudreau */ public class SegmentBuilder { /** * Converts a segment plus a {@link SegmentBody} into a * {@link mondrian.rolap.agg.SegmentWithData}. * * @param segment Segment * @param sb Segment body * @return SegmentWithData */ public static SegmentWithData addData(Segment segment, SegmentBody sb) { // Load the axis keys for this segment SegmentAxis[] axes = new SegmentAxis[segment.predicates.length]; for (int i = 0; i < segment.predicates.length; i++) { StarColumnPredicate predicate = segment.predicates[i]; axes[i] = new SegmentAxis( predicate, sb.getAxisValueSets()[i], sb.getNullAxisFlags()[i]); } final SegmentDataset dataSet = createDataset(sb, axes); return new SegmentWithData(segment, dataSet, axes); } /** * Creates a SegmentDataset that contains the cached * data and is initialized to be used with the supplied segment. * * @param body Segment with which the returned dataset will be associated * @param axes Segment axes, containing actual column values * @return A SegmentDataset object that contains cached data. */ private static SegmentDataset createDataset( SegmentBody body, SegmentAxis[] axes) { final SegmentDataset dataSet; if (body instanceof DenseDoubleSegmentBody) { dataSet = new DenseDoubleSegmentDataset( axes, (double[]) body.getValueArray(), body.getIndicators()); } else if (body instanceof DenseIntSegmentBody) { dataSet = new DenseIntSegmentDataset( axes, (int[]) body.getValueArray(), body.getIndicators()); } else if (body instanceof DenseObjectSegmentBody) { dataSet = new DenseObjectSegmentDataset( axes, (Object[]) body.getValueArray()); } else if (body instanceof SparseSegmentBody) { dataSet = new SparseSegmentDataset(body.getValueMap()); } else { throw Util.newInternal( "Unknown segment body type: " + body.getClass() + ": " + body); } return dataSet; } /** * Creates a segment from a SegmentHeader. The star, * constrainedColsBitKey, constrainedColumns and measure arguments are a * helping hand, because we know what we were looking for. * * @param header The header to convert. * @param star Star * @param constrainedColumnsBitKey Constrained columns * @param constrainedColumns Constrained columns * @param measure Measure * @return Segment */ public static Segment toSegment( SegmentHeader header, RolapStar star, BitKey constrainedColumnsBitKey, RolapStar.Column[] constrainedColumns, RolapStar.Measure measure, List compoundPredicates) { final List predicateList = new ArrayList(); for (int i = 0; i < constrainedColumns.length; i++) { RolapStar.Column constrainedColumn = constrainedColumns[i]; final SortedSet values = header.getConstrainedColumns().get(i).values; StarColumnPredicate predicate; if (values == null) { predicate = new LiteralStarPredicate( constrainedColumn, true); } else if (values.size() == 1) { predicate = new ValueColumnPredicate( constrainedColumn, values.first()); } else { final List valuePredicateList = new ArrayList(); for (Object value : values) { valuePredicateList.add( new ValueColumnPredicate( constrainedColumn, value)); } predicate = new ListColumnPredicate( constrainedColumn, valuePredicateList); } predicateList.add(predicate); } return new Segment( star, constrainedColumnsBitKey, constrainedColumns, measure, predicateList.toArray( new StarColumnPredicate[predicateList.size()]), new ExcludedRegionList(header), compoundPredicates); } /** * Given a collection of segments, all of the same dimensionality, rolls up * to create a segment with reduced dimensionality. * * @param map Source segment headers and bodies * @param keepColumns A list of column names to keep as part of * the rolled up segment. * @param targetBitkey The column bit key to match with the * resulting segment. * @param rollupAggregator The aggregator to use to rollup. * @return Segment header and body of requested dimensionality */ public static Pair rollup( Map map, Set keepColumns, BitKey targetBitkey, Aggregator rollupAggregator) { class AxisInfo { SegmentColumn column; SortedSet requestedValues; SortedSet valueSet; Comparable[] values; boolean hasNull; int src; boolean lostPredicate; } final SegmentHeader firstHeader = map.keySet().iterator().next(); final AxisInfo[] axes = new AxisInfo[keepColumns.size()]; int z = 0, j = 0; for (SegmentColumn column : firstHeader.getConstrainedColumns()) { if (keepColumns.contains(column.columnExpression)) { final AxisInfo axisInfo = axes[z++] = new AxisInfo(); axisInfo.src = j; axisInfo.column = column; axisInfo.requestedValues = column.values; } j++; } // Compute the sets of values in each axis of the target segment. These // are the intersection of the input axes. for (Map.Entry entry : map.entrySet()) { final SegmentHeader header = entry.getKey(); for (AxisInfo axis : axes) { final SortedSet values = entry.getValue().getAxisValueSets()[axis.src]; final SegmentColumn headerColumn = header.getConstrainedColumn(axis.column.columnExpression); final boolean hasNull = entry.getValue().getNullAxisFlags()[axis.src]; final SortedSet requestedValues = headerColumn.getValues(); if (axis.valueSet == null) { axis.valueSet = new TreeSet(values); axis.hasNull = hasNull; axis.requestedValues = requestedValues; } else { final SortedSet filteredValues; final boolean filteredHasNull; if (axis.requestedValues == null) { filteredValues = values; filteredHasNull = hasNull; } else { filteredValues = Util.intersect( values, axis.requestedValues); // SegmentColumn predicates cannot ask for the null // value (at present). filteredHasNull = false; } axis.valueSet.addAll(filteredValues); axis.hasNull = axis.hasNull || filteredHasNull; if (!Util.equals(axis.requestedValues, requestedValues)) { if (axis.requestedValues == null) { // Downgrade from wildcard to a specific list. axis.requestedValues = requestedValues; } else { // Segment requests have incompatible predicates. // Best we can say is "we must have asked for the // values that came back". axis.lostPredicate = true; } } } } } for (AxisInfo axis : axes) { axis.values = axis.valueSet.toArray(new Comparable[axis.valueSet.size()]); } // Populate cells. // // (This is a rough implementation, very inefficient. It makes all // segment types pretend to be sparse, for purposes of reading. It // maps all axis ordinals to a value, then back to an axis ordinal, // even if this translation were not necessary, say if the source and // target axes had the same set of values. And it always creates a // sparse segment. // // We should do really efficient rollup if the source is an array: we // should box values (e.g double to Double and back), and we should read // a stripe of values from the and add them up into a single cell. final Map> cellValues = new HashMap>(); for (Map.Entry entry : map.entrySet()) { final int[] pos = new int[axes.length]; final Comparable[][] valueArrays = new Comparable[firstHeader.getConstrainedColumns().size()][]; final SegmentBody body = entry.getValue(); // Copy source value sets into arrays. For axes that are being // projected away, store null. z = 0; for (SortedSet set : body.getAxisValueSets()) { valueArrays[z] = keepColumns.contains( firstHeader.getConstrainedColumns().get(z).columnExpression) ? set.toArray(new Comparable[set.size()]) : null; ++z; } Map v = body.getValueMap(); for (Map.Entry vEntry : v.entrySet()) { z = 0; for (int i = 0; i < vEntry.getKey().size(); i++) { final Comparable[] valueArray = valueArrays[i]; if (valueArray == null) { continue; } final int ordinal = vEntry.getKey().getOrdinals()[i]; final int targetOrdinal; if (axes[z].hasNull && ordinal == valueArray.length) { targetOrdinal = axes[z].valueSet.size(); } else { final Comparable value = valueArray[ordinal]; if (value == null) { targetOrdinal = axes[z].valueSet.size(); } else { targetOrdinal = Util.binarySearch( axes[z].values, 0, axes[z].values.length, value); } } pos[z++] = targetOrdinal; } final CellKey ck = CellKey.Generator.newCellKey(pos); if (!cellValues.containsKey(ck)) { cellValues.put(ck, new ArrayList()); } cellValues.get(ck).add(vEntry.getValue()); } } // Build the axis list. final List, Boolean>> axisList = new ArrayList, Boolean>>(); final BitSet nullIndicators = new BitSet(axes.length); int nbValues = 1; for (int i = 0; i < axes.length; i++) { axisList.add( new Pair, Boolean>( axes[i].valueSet, axes[i].hasNull)); nullIndicators.set(i, axes[i].hasNull); nbValues *= axes[i].hasNull ? axes[i].values.length + 1 : axes[i].values.length; } final int[] axisMultipliers = computeAxisMultipliers(axisList); final SegmentBody body; // Peak at the values and determine the best way to store them // (whether to use a dense native dataset or a sparse one. if (cellValues.size() == 0) { // Just store the data into an empty dense object dataset. body = new DenseObjectSegmentBody( new Object[0], axisList); } else if (SegmentLoader.useSparse( cellValues.size(), cellValues.size() - nullIndicators.cardinality())) { // The rule says we must use a sparse dataset. // First, aggregate the values of each key. final Map data = new HashMap(); for (Entry> entry : cellValues.entrySet()) { data.put( CellKey.Generator.newCellKey(entry.getKey().getOrdinals()), rollupAggregator.aggregate(entry.getValue())); } body = new SparseSegmentBody( data, axisList); } else { // Peek at the value class. We will use a native dataset // if possible. final Object peek = cellValues.entrySet().iterator().next().getValue().get(0); if (peek instanceof Double) { final double[] data = new double[nbValues]; for (Entry> entry : cellValues.entrySet()) { final int offset = CellKey.Generator.getOffset( entry.getKey().getOrdinals(), axisMultipliers); data[offset] = (Double)rollupAggregator.aggregate(entry.getValue()); } body = new DenseDoubleSegmentBody( nullIndicators, data, axisList); } else if (peek instanceof Integer) { final int[] data = new int[nbValues]; for (Entry> entry : cellValues.entrySet()) { final int offset = CellKey.Generator.getOffset( entry.getKey().getOrdinals(), axisMultipliers); data[offset] = (Integer)rollupAggregator.aggregate(entry.getValue()); } body = new DenseIntSegmentBody( nullIndicators, data, axisList); } else { final Object[] data = new Object[nbValues]; for (Entry> entry : cellValues.entrySet()) { final int offset = CellKey.Generator.getOffset( entry.getKey().getOrdinals(), axisMultipliers); data[offset] = (Object)rollupAggregator.aggregate(entry.getValue()); } body = new DenseObjectSegmentBody( data, axisList); } } // Create header. final List constrainedColumns = new ArrayList(); for (int i = 0; i < axes.length; i++) { AxisInfo axisInfo = axes[i]; constrainedColumns.add( new SegmentColumn( axisInfo.column.getColumnExpression(), axisInfo.column.getValueCount(), axisInfo.lostPredicate ? axisList.get(i).left : axisInfo.column.values)); } final SegmentHeader header = new SegmentHeader( firstHeader.schemaName, firstHeader.schemaChecksum, firstHeader.cubeName, firstHeader.measureName, constrainedColumns, firstHeader.compoundPredicates, firstHeader.rolapStarFactTableName, targetBitkey, Collections.emptyList()); return Pair.of(header, body); } private static int[] computeAxisMultipliers( List, Boolean>> axes) { final int[] axisMultipliers = new int[axes.size()]; int multiplier = 1; for (int i = axes.size() - 1; i >= 0; --i) { axisMultipliers[i] = multiplier; multiplier *= axes.get(i).left.size(); } return axisMultipliers; } private static class ExcludedRegionList extends AbstractList implements Segment.ExcludedRegion { private final int cellCount; private final SegmentHeader header; public ExcludedRegionList(SegmentHeader header) { this.header = header; int cellCount = 1; for (SegmentColumn cc : header.getExcludedRegions()) { // TODO find a way to approximate the cardinality // of wildcard columns. if (cc.values != null) { cellCount *= cc.values.size(); } } this.cellCount = cellCount; } public void describe(StringBuilder buf) { // TODO } public int getArity() { return header.getConstrainedColumns().size(); } public int getCellCount() { return cellCount; } public boolean wouldContain(Object[] keys) { assert keys.length == header.getConstrainedColumns().size(); for (int i = 0; i < keys.length; i++) { final SegmentColumn excl = header.getExcludedRegion( header.getConstrainedColumns().get(i).columnExpression); if (excl == null) { continue; } if (excl.values.contains(keys[i])) { return true; } } return false; } public ExcludedRegion get(int index) { return this; } public int size() { return 1; } } /** * Tells if the passed segment is a subset of this segment * and could be used for a rollup in cache operation. * @param segment A segment which might be a subset of the * current segment. * @return True or false. */ public static boolean isSubset( SegmentHeader header, Segment segment) { if (!segment.getStar().getSchema().getName() .equals(header.schemaName)) { return false; } if (!segment.getStar().getFactTable().getAlias() .equals(header.rolapStarFactTableName)) { return false; } if (!segment.measure.getName().equals(header.measureName)) { return false; } if (!segment.measure.getCubeName().equals(header.cubeName)) { return false; } if (segment.getConstrainedColumnsBitKey() .equals(header.constrainedColsBitKey)) { return true; } return false; } public static List toConstrainedColumns( StarColumnPredicate[] predicates) { return toConstrainedColumns( Arrays.asList(predicates)); } public static List toConstrainedColumns( Collection predicates) { List ccs = new ArrayList(); for (StarColumnPredicate predicate : predicates) { final List values = new ArrayList(); predicate.values(Util.cast(values)); final Comparable[] valuesArray = values.toArray(new Comparable[values.size()]); if (valuesArray.length == 1 && valuesArray[0].equals(true)) { ccs.add( new SegmentColumn( predicate.getConstrainedColumn() .getExpression().getGenericExpression(), predicate.getConstrainedColumn().getCardinality(), null)); } else { Arrays.sort( valuesArray, Util.SqlNullSafeComparator.instance); ccs.add( new SegmentColumn( predicate.getConstrainedColumn() .getExpression().getGenericExpression(), predicate.getConstrainedColumn().getCardinality(), new ArraySortedSet(valuesArray))); } } return ccs; } /** * Creates a SegmentHeader object describing the supplied * Segment object. * * @param segment A segment object for which we want to generate * a SegmentHeader. * @return A SegmentHeader describing the supplied Segment object. */ public static SegmentHeader toHeader(Segment segment) { final List cc = SegmentBuilder.toConstrainedColumns(segment.predicates); final List cp = new ArrayList(); StringBuilder buf = new StringBuilder(); for (StarPredicate compoundPredicate : segment.compoundPredicateList) { buf.setLength(0); SqlQuery query = new SqlQuery( segment.star.getSqlQueryDialect()); compoundPredicate.toSql(query, buf); cp.add(buf.toString()); } final RolapSchema schema = segment.star.getSchema(); return new SegmentHeader( schema.getName(), schema.getChecksum(), segment.measure.getCubeName(), segment.measure.getName(), cc, cp, segment.star.getFactTable().getAlias(), segment.constrainedColumnsBitKey, Collections.emptyList()); } private static RolapStar.Column[] getConstrainedColumns( RolapStar star, BitKey bitKey) { final List list = new ArrayList(); for (int bit : bitKey) { list.add(star.getColumn(bit)); } return list.toArray(new RolapStar.Column[list.size()]); } /** * Functor to convert a segment header and body into a * {@link mondrian.rolap.agg.SegmentWithData}. */ public static interface SegmentConverter { SegmentWithData convert( SegmentHeader header, SegmentBody body); } /** * Implementation of {@link SegmentConverter} that uses an * {@link mondrian.rolap.agg.AggregationKey} * and {@link mondrian.rolap.agg.CellRequest} as context to * convert a {@link mondrian.spi.SegmentHeader}. * *

This is nasty. A converter might be used for several headers, not * necessarily with the context as the cell request and aggregation key. * Converters only exist for fact tables and compound predicate combinations * for which we have already done a load request.

* *

It would be much better if there was a way to convert compound * predicates from strings to predicates. Then we could obsolete the * messy context inside converters, and maybe obsolete converters * altogether.

*/ public static class SegmentConverterImpl implements SegmentConverter { private final AggregationKey key; private final CellRequest request; public SegmentConverterImpl(AggregationKey key, CellRequest request) { this.key = key; this.request = request; } public SegmentWithData convert( SegmentHeader header, SegmentBody body) { final Segment segment = toSegment( header, key.getStar(), header.getConstrainedColumnsBitKey(), getConstrainedColumns( key.getStar(), header.getConstrainedColumnsBitKey()), request.getMeasure(), key.getCompoundPredicateList()); return addData(segment, body); } } /** * Implementation of {@link SegmentConverter} that uses a star measure * and a list of {@link StarPredicate}. */ public static class StarSegmentConverter implements SegmentConverter { private final RolapStar.Measure measure; private final List compoundPredicateList; public StarSegmentConverter( RolapStar.Measure measure, List compoundPredicateList) { this.measure = measure; this.compoundPredicateList = compoundPredicateList; } public SegmentWithData convert( SegmentHeader header, SegmentBody body) { final Segment segment = toSegment( header, measure.getStar(), header.getConstrainedColumnsBitKey(), getConstrainedColumns( measure.getStar(), header.getConstrainedColumnsBitKey()), measure, compoundPredicateList); return addData(segment, body); } } } // End SegmentBuilder.java mondrian-3.4.1/src/main/mondrian/rolap/agg/ListPredicate.java0000644000175000017500000001433111735330606024024 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.olap.Util; import mondrian.rolap.*; import mondrian.rolap.sql.SqlQuery; import java.util.*; /** * Base class for {@link AndPredicate} and {@link OrPredicate}. * * @see mondrian.rolap.agg.ListColumnPredicate * * @author jhyde */ public abstract class ListPredicate implements StarPredicate { protected final List children = new ArrayList(); /** * Hash map of children predicates, keyed off of the hash code of each * child. Each entry in the map is a list of predicates matching that * hash code. */ private HashMap> childrenHashMap; /** * Pre-computed hash code for this list column predicate */ private int hashValue; protected final List columns; private BitKey columnBitKey = null; protected ListPredicate(List predicateList) { childrenHashMap = null; hashValue = 0; // Ensure that columns are sorted by bit-key, for determinacy. final SortedSet columnSet = new TreeSet(RolapStar.Column.COMPARATOR); for (StarPredicate predicate : predicateList) { children.add(predicate); columnSet.addAll(predicate.getConstrainedColumnList()); } columns = new ArrayList(columnSet); } public List getConstrainedColumnList() { return columns; } public BitKey getConstrainedColumnBitKey() { if (columnBitKey == null) { for (StarPredicate predicate : children) { if (columnBitKey == null) { columnBitKey = predicate.getConstrainedColumnBitKey().copy(); } else { columnBitKey = columnBitKey.or(predicate.getConstrainedColumnBitKey()); } } } return columnBitKey; } public List getChildren() { return children; } public int hashCode() { // Don't use the default list hashcode because we want a hash code // that's not order dependent if (hashValue == 0) { hashValue = 37; for (StarPredicate child : children) { int childHashCode = child.hashCode(); if (childHashCode != 0) { hashValue *= childHashCode; } } hashValue ^= children.size(); } return hashValue; } public boolean equalConstraint(StarPredicate that) { boolean isEqual = that instanceof ListPredicate && getConstrainedColumnBitKey().equals( that.getConstrainedColumnBitKey()); if (isEqual) { ListPredicate thatPred = (ListPredicate) that; if (getOp() != thatPred.getOp() || getChildren().size() != thatPred.getChildren().size()) { isEqual = false; } if (isEqual) { // Create a hash map of the children predicates, if not // already done if (childrenHashMap == null) { childrenHashMap = new HashMap>(); for (StarPredicate thisChild : getChildren()) { Integer key = new Integer(thisChild.hashCode()); List predList = childrenHashMap.get(key); if (predList == null) { predList = new ArrayList(); } predList.add(thisChild); childrenHashMap.put(key, predList); } } // Loop through thatPred's children predicates. There needs // to be a matching entry in the hash map for each child // predicate. for (StarPredicate thatChild : thatPred.getChildren()) { List predList = childrenHashMap.get(thatChild.hashCode()); if (predList == null) { isEqual = false; break; } boolean foundMatch = false; for (StarPredicate pred : predList) { if (thatChild.equalConstraint(pred)) { foundMatch = true; break; } } if (!foundMatch) { isEqual = false; break; } } } } return isEqual; } public StarPredicate minus(StarPredicate predicate) { throw Util.needToImplement(this); } public void toSql(SqlQuery sqlQuery, StringBuilder buf) { if (children.size() == 1) { children.get(0).toSql(sqlQuery, buf); } else { int k = 0; buf.append("("); for (StarPredicate child : children) { if (k++ > 0) { buf.append(" ").append(getOp()).append(" "); } child.toSql(sqlQuery, buf); } buf.append(")"); } } protected abstract String getOp(); public void describe(StringBuilder buf) { buf.append(getOp()).append("("); int k = 0; for (StarPredicate child : children) { if (k++ > 0) { buf.append(", "); } buf.append(child); } buf.append(')'); } public String toString() { final StringBuilder buf = new StringBuilder(); describe(buf); return buf.toString(); } } // End ListPredicate.java mondrian-3.4.1/src/main/mondrian/rolap/agg/DenseIntSegmentDataset.java0000644000175000017500000000654511735330606025642 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.CellKey; import mondrian.rolap.SqlStatement; import mondrian.spi.SegmentBody; import mondrian.util.Pair; import java.util.*; /** * Implementation of {@link DenseSegmentDataset} that stores * values of type {@link Object}. * *

The storage requirements are as follows. Table requires 1 word per * cell.

* * @author jhyde * @since 21 March, 2002 */ class DenseIntSegmentDataset extends DenseNativeSegmentDataset { final int[] values; // length == m[0] * ... * m[axes.length-1] /** * Creates a DenseIntSegmentDataset. * * @param axes Segment axes, containing actual column values * @param size Number of coordinates */ DenseIntSegmentDataset(SegmentAxis[] axes, int size) { this(axes, new int[size], new BitSet(size)); } /** * Creates a populated DenseIntSegmentDataset. * * @param axes Segment axes, containing actual column values * @param values Cell values; not copied * @param nullIndicators Null indicators */ DenseIntSegmentDataset( SegmentAxis[] axes, int[] values, BitSet nullIndicators) { super(axes, nullIndicators); this.values = values; } public int getInt(CellKey key) { int offset = key.getOffset(axisMultipliers); return values[offset]; } public Object getObject(CellKey pos) { int offset = pos.getOffset(axisMultipliers); return getObject(offset); } protected Integer getObject(int offset) { final int value = values[offset]; if (value == 0 && isNull(offset)) { return null; } return value; } public boolean exists(CellKey pos) { return true; } public void populateFrom(int[] pos, SegmentDataset data, CellKey key) { final int offset = getOffset(pos); final int value = values[offset] = data.getInt(key); if (value == 0) { nullIndicators.set(offset, !data.isNull(key)); } } public void populateFrom( int[] pos, SegmentLoader.RowList rowList, int column) { int offset = getOffset(pos); final int value = values[offset] = rowList.getInt(column); if (value == 0) { nullIndicators.set(offset, !rowList.isNull(column)); } } public SqlStatement.Type getType() { return SqlStatement.Type.INT; } public void put(CellKey key, int value) { int offset = key.getOffset(axisMultipliers); values[offset] = value; } public void put(int[] ordinals, int value) { int offset = getOffset(ordinals); values[offset] = value; } void set(int k, int o) { values[k] = o; } protected int getSize() { return values.length; } public SegmentBody createSegmentBody( List, Boolean>> axes) { return new DenseIntSegmentBody( nullIndicators, values, axes); } } // End DenseIntSegmentDataset.java mondrian-3.4.1/src/main/mondrian/rolap/agg/SegmentArrayQuerySpec.java0000644000175000017500000001137711735330606025541 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.*; import mondrian.rolap.sql.SqlQuery; import java.util.*; /** * Provides the information necessary to generate a SQL statement to * retrieve a list of segments. * * @author jhyde * @author Richard M. Emberson */ class SegmentArrayQuerySpec extends AbstractQuerySpec { private final List segments; private final Segment segment0; private final GroupingSetsList groupingSetsList; /* * Compound member predicates. * Each list constrains one dimension. */ private final List compoundPredicateList; /** * Creates a SegmentArrayQuerySpec. * * @param groupingSetsList Collection of grouping sets * @param compoundPredicateList list of predicates representing the * compound member constraints */ SegmentArrayQuerySpec( GroupingSetsList groupingSetsList, List compoundPredicateList) { super(groupingSetsList.getStar(), false); this.segments = groupingSetsList.getDefaultSegments(); this.segment0 = segments.get(0); this.groupingSetsList = groupingSetsList; this.compoundPredicateList = compoundPredicateList; assert isValid(true); } /** * Returns whether this query specification is valid, or throws if invalid * and fail is true. * * @param fail Whether to throw if invalid * @return Whether this query specification is valid */ private boolean isValid(boolean fail) { assert segments.size() > 0; for (Segment segment : segments) { int n = segment.predicates.length; if (n != segment0.predicates.length) { assert !fail; return false; } for (int j = 0; j < segment.predicates.length; j++) { // We only require that the two arrays have the same // contents, we but happen to know they are the same array, // because we constructed them at the same time. if (segment.predicates[j] != segment0.predicates[j]) { assert !fail; return false; } } } return true; } public int getMeasureCount() { return segments.size(); } public RolapStar.Measure getMeasure(final int i) { return segments.get(i).measure; } public String getMeasureAlias(final int i) { return "m" + Integer.toString(i); } public RolapStar.Column[] getColumns() { return segment0.getColumns(); } /** * SqlQuery relies on "c" and index. All this should go into SqlQuery! * * @see mondrian.rolap.sql.SqlQuery#addOrderBy */ public String getColumnAlias(final int i) { return "c" + Integer.toString(i); } public StarColumnPredicate getColumnPredicate(final int i) { return segment0.predicates[i]; } protected List getPredicateList() { if (compoundPredicateList == null) { return super.getPredicateList(); } else { return compoundPredicateList; } } protected void addGroupingFunction(SqlQuery sqlQuery) { List list = groupingSetsList.getRollupColumns(); for (RolapStar.Column column : list) { sqlQuery.addGroupingFunction(column.generateExprString(sqlQuery)); } } protected void addGroupingSets( SqlQuery sqlQuery, Map groupingSetsAliases) { List groupingSetsColumns = groupingSetsList.getGroupingSetsColumns(); for (RolapStar.Column[] groupingSetsColumn : groupingSetsColumns) { ArrayList groupingColumnsExpr = new ArrayList(); for (RolapStar.Column aColumn : groupingSetsColumn) { final String columnExpr = aColumn.generateExprString(sqlQuery); if (groupingSetsAliases.containsKey(columnExpr)) { groupingColumnsExpr.add( groupingSetsAliases.get(columnExpr)); } else { groupingColumnsExpr.add(columnExpr); } } sqlQuery.addGroupingSet(groupingColumnsExpr); } } protected boolean isAggregate() { return true; } } // End SegmentArrayQuerySpec.java mondrian-3.4.1/src/main/mondrian/rolap/agg/SegmentLoader.java0000644000175000017500000013362411735330606024030 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.olap.MondrianException; import mondrian.olap.MondrianProperties; import mondrian.olap.Util; import mondrian.rolap.*; import mondrian.rolap.cache.SegmentCacheIndex; import mondrian.server.Locus; import mondrian.server.monitor.SqlStatementEvent; import mondrian.spi.*; import mondrian.util.*; import org.apache.log4j.Logger; import java.io.Serializable; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import java.util.concurrent.*; /** *

The SegmentLoader queries database and loads the data into * the given set of segments.

* *

It reads a segment of measure, where columns * are constrained to values. Each entry in values * can be null, meaning don't constrain, or can have several values. For * example, getSegment({Unit_sales}, {Region, State, Year}, {"West"}, * {"CA", "OR", "WA"}, null}) returns sales in states CA, OR and WA * in the Western region, for all years.

* *

It will also look at the {@link MondrianProperties#SegmentCache} property * and make usage of the SegmentCache provided as an SPI. * * @author Thiyagu, LBoudreau * @since 24 May 2007 */ public class SegmentLoader { private static final Logger LOGGER = Logger.getLogger(SegmentLoader.class); private final SegmentCacheManager cacheMgr; /** * Creates a SegmentLoader. * * @param cacheMgr Cache manager */ public SegmentLoader(SegmentCacheManager cacheMgr) { this.cacheMgr = cacheMgr; } /** * Loads data for all the segments of the GroupingSets. If the grouping sets * list contains more than one Grouping Set then data is loaded using the * GROUP BY GROUPING SETS sql. Else if only one grouping set is passed in * the list data is loaded without using GROUP BY GROUPING SETS sql. If the * database does not support grouping sets * {@link mondrian.spi.Dialect#supportsGroupingSets()} then * grouping sets list should always have only one element in it. * *

For example, if list has 2 grouping sets with columns A, B, C and B, C * respectively, then the SQL will be * "GROUP BY GROUPING SETS ((A, B, C), (B, C))". * *

Else if the list has only one grouping set then sql would be without * grouping sets. * *

The groupingSets list should be topological order, with * more detailed higher-level grouping sets occurring first. In other words, * the first element of the list should always be the detailed grouping * set (default grouping set), followed by grouping sets which can be * rolled-up on this detailed grouping set. * In the example (A, B, C) is the detailed grouping set and (B, C) is * rolled-up using the detailed. * *

Grouping sets are removed from the {@code groupingSets} list as they * are loaded.

* * @param cellRequestCount Number of missed cells that led to this request * @param groupingSets List of grouping sets whose segments are loaded * @param compoundPredicateList Compound predicates * @param segmentFutures List of futures wherein each statement will place * a list of the segments it has loaded, when it * completes */ public void load( int cellRequestCount, List groupingSets, List compoundPredicateList, List>> segmentFutures) { for (GroupingSet groupingSet : groupingSets) { for (Segment segment : groupingSet.getSegments()) { final SegmentCacheIndex index = cacheMgr.getIndexRegistry().getIndex(segment.star); index.add( segment.getHeader(), true, new SegmentBuilder.StarSegmentConverter( segment.measure, compoundPredicateList)); } } try { segmentFutures.add( cacheMgr.sqlExecutor.submit( new SegmentLoadCommand( Locus.peek(), this, cellRequestCount, groupingSets, compoundPredicateList))); } catch (Exception e) { throw new MondrianException(e); } } private static class SegmentLoadCommand implements Callable> { private final Locus locus; private final SegmentLoader segmentLoader; private final int cellRequestCount; private final List groupingSets; private final List compoundPredicateList; public SegmentLoadCommand( Locus locus, SegmentLoader segmentLoader, int cellRequestCount, List groupingSets, List compoundPredicateList) { this.locus = locus; this.segmentLoader = segmentLoader; this.cellRequestCount = cellRequestCount; this.groupingSets = groupingSets; this.compoundPredicateList = compoundPredicateList; } public Map call() throws Exception { Locus.push(locus); try { return segmentLoader.loadImpl( cellRequestCount, groupingSets, compoundPredicateList); } finally { Locus.pop(locus); } } } private Map loadImpl( int cellRequestCount, List groupingSets, List compoundPredicateList) { // Simple assertion. Is this Execution instance still valid, // or should we get outa here. Locus.peek().execution.checkCancelOrTimeout(); SqlStatement stmt = null; GroupingSetsList groupingSetsList = new GroupingSetsList(groupingSets); RolapStar.Column[] defaultColumns = groupingSetsList.getDefaultColumns(); final Map segmentMap = new HashMap(); Throwable throwable = null; try { int arity = defaultColumns.length; SortedSet[] axisValueSets = getDistinctValueWorkspace(arity); stmt = createExecuteSql( cellRequestCount, groupingSetsList, compoundPredicateList); boolean[] axisContainsNull = new boolean[arity]; RowList rows = processData( stmt, axisContainsNull, axisValueSets, groupingSetsList); boolean sparse = setAxisDataAndDecideSparseUse( axisValueSets, axisContainsNull, groupingSetsList, rows); final Map groupingDataSetsMap = createDataSetsForGroupingSets( groupingSetsList, sparse, rows.getTypes().subList( arity, rows.getTypes().size())); loadDataToDataSets( groupingSetsList, rows, groupingDataSetsMap); setDataToSegments( groupingSetsList, groupingDataSetsMap, segmentMap); return segmentMap; } catch (RuntimeException e) { throwable = e; throw e; } catch (Error e) { throwable = e; throw e; } catch (Throwable e) { throwable = e; if (stmt == null) { throw new MondrianException(e); } throw stmt.handle(e); } finally { if (stmt != null) { stmt.close(); } setFailOnStillLoadingSegments( segmentMap, groupingSetsList, throwable); } } /** * Called when a segment has been loaded from SQL, to put into the segment * index and the external cache. * * @param header Segment header * @param body Segment body */ private void cacheSegment( RolapStar star, SegmentHeader header, SegmentBody body) { cacheMgr.loadSucceeded(star, header, body); // Write the segment into external cache. // // It would be a mistake to do this from the cacheMgr -- because the // calls may take time. The cacheMgr's actions must all be quick. We // are a worker, so we have plenty of time. // // Also note that we push the segments to external cache after we have // called cacheMgr.loadSucceeded. That call will allow the current // query to proceed. cacheMgr.compositeCache.put(header, body); } private boolean setFailOnStillLoadingSegments( Map segmentMap, GroupingSetsList groupingSetsList, Throwable throwable) { int n = 0; for (GroupingSet groupingSet : groupingSetsList.getGroupingSets()) { for (Segment segment : groupingSet.getSegments()) { if (!segmentMap.containsKey(segment)) { if (throwable == null) { throwable = new RuntimeException("Segment failed to load"); } final SegmentHeader header = segment.getHeader(); cacheMgr.loadFailed( segment.star, header, throwable); ++n; } } } return n > 0; } /** * Loads data to the datasets. If the grouping sets is used, * dataset is fetched from groupingDataSetMap using grouping bit keys of * the row data. If grouping sets is not used, data is loaded on to * nonGroupingDataSets. */ private void loadDataToDataSets( GroupingSetsList groupingSetsList, RowList rows, Map groupingDataSetMap) { int arity = groupingSetsList.getDefaultColumns().length; SegmentAxis[] axes = groupingSetsList.getDefaultAxes(); int segmentLength = groupingSetsList.getDefaultSegments().size(); final List types = rows.getTypes(); final boolean useGroupingSet = groupingSetsList.useGroupingSets(); for (rows.first(); rows.next();) { final BitKey groupingBitKey; final GroupingSetsList.Cohort cohort; if (useGroupingSet) { groupingBitKey = (BitKey) rows.getObject( groupingSetsList.getGroupingBitKeyIndex()); cohort = groupingDataSetMap.get(groupingBitKey); } else { groupingBitKey = null; cohort = groupingDataSetMap.get(BitKey.EMPTY); } final int[] pos = cohort.pos; for (int j = 0, k = 0; j < arity; j++) { final SqlStatement.Type type = types.get(j); switch (type) { // TODO: different treatment for INT, LONG, DOUBLE case OBJECT: case STRING: case INT: case LONG: case DOUBLE: Object o = rows.getObject(j); if (useGroupingSet && (o == null || o == RolapUtil.sqlNullValue) && groupingBitKey.get( groupingSetsList.findGroupingFunctionIndex(j))) { continue; } SegmentAxis axis = axes[j]; if (o == null) { o = RolapUtil.sqlNullValue; } // Note: We believe that all value types are Comparable. // In JDK 1.4, Boolean did not implement Comparable, but // that's too minor/long ago to worry about. int offset = axis.getOffset((Comparable) o); pos[k++] = offset; break; default: throw Util.unexpected(type); } } for (int j = 0; j < segmentLength; j++) { cohort.segmentDatasetList.get(j).populateFrom( pos, rows, arity + j); } } } private boolean setAxisDataAndDecideSparseUse( SortedSet[] axisValueSets, boolean[] axisContainsNull, GroupingSetsList groupingSetsList, RowList rows) { SegmentAxis[] axes = groupingSetsList.getDefaultAxes(); RolapStar.Column[] allColumns = groupingSetsList.getDefaultColumns(); // Figure out size of dense array, and allocate it, or use a sparse // array if appropriate. boolean sparse = false; int n = 1; for (int i = 0; i < axes.length; i++) { SortedSet valueSet = axisValueSets[i]; axes[i] = new SegmentAxis( groupingSetsList.getDefaultPredicates()[i], valueSet, axisContainsNull[i]); int size = axes[i].getKeys().length; setAxisDataToGroupableList( groupingSetsList, valueSet, axisContainsNull[i], allColumns[i]); int previous = n; n *= size; if ((n < previous) || (n < size)) { // Overflow has occurred. n = Integer.MAX_VALUE; sparse = true; } } return useSparse(sparse, n, rows); } boolean useSparse(boolean sparse, int n, RowList rows) { sparse = sparse || useSparse((double) n, (double) rows.size()); return sparse; } private void setDataToSegments( GroupingSetsList groupingSetsList, Map datasetsMap, Map segmentSlotMap) { List groupingSets = groupingSetsList.getGroupingSets(); for (int i = 0; i < groupingSets.size(); i++) { List segments = groupingSets.get(i).getSegments(); GroupingSetsList.Cohort cohort = datasetsMap.get( groupingSetsList.getRollupColumnsBitKeyList().get(i)); for (int j = 0; j < segments.size(); j++) { Segment segment = segments.get(j); final SegmentDataset segmentDataset = cohort.segmentDatasetList.get(j); final SegmentWithData segmentWithData = new SegmentWithData( segment, segmentDataset, cohort.axes); segmentSlotMap.put(segment, segmentWithData); final SegmentHeader header = segmentWithData.getHeader(); final SegmentBody body = segmentWithData.getData().createSegmentBody( new AbstractList< Pair, Boolean>>() { public Pair, Boolean> get( int index) { return segmentWithData.axes[index] .getValuesAndIndicator(); } public int size() { return segmentWithData.axes.length; } }); // Send a message to the agg manager. It will place the segment // in the index. cacheSegment(segment.star, header, body); } } } private Map createDataSetsForGroupingSets( GroupingSetsList groupingSetsList, boolean sparse, List types) { if (!groupingSetsList.useGroupingSets()) { final GroupingSetsList.Cohort datasets = createDataSets( sparse, groupingSetsList.getDefaultSegments(), groupingSetsList.getDefaultAxes(), types); return Collections.singletonMap(BitKey.EMPTY, datasets); } Map datasetsMap = new HashMap(); List groupingSets = groupingSetsList.getGroupingSets(); List groupingColumnsBitKeyList = groupingSetsList.getRollupColumnsBitKeyList(); for (int i = 0; i < groupingSets.size(); i++) { GroupingSet groupingSet = groupingSets.get(i); GroupingSetsList.Cohort cohort = createDataSets( sparse, groupingSet.getSegments(), groupingSet.getAxes(), types); datasetsMap.put(groupingColumnsBitKeyList.get(i), cohort); } return datasetsMap; } private int calculateMaxDataSize(SegmentAxis[] axes) { int n = 1; for (SegmentAxis axis : axes) { n *= axis.getKeys().length; } return n; } private GroupingSetsList.Cohort createDataSets( boolean sparse, List segments, SegmentAxis[] axes, List types) { final List datasets = new ArrayList(segments.size()); final int n; if (sparse) { n = 0; } else { n = calculateMaxDataSize(axes); } for (int i = 0; i < segments.size(); i++) { final Segment segment = segments.get(i); datasets.add(segment.createDataset(axes, sparse, types.get(i), n)); } return new GroupingSetsList.Cohort(datasets, axes); } private void setAxisDataToGroupableList( GroupingSetsList groupingSetsList, SortedSet valueSet, boolean axisContainsNull, RolapStar.Column column) { for (GroupingSet groupingSet : groupingSetsList.getRollupGroupingSets()) { RolapStar.Column[] columns = groupingSet.getColumns(); for (int i = 0; i < columns.length; i++) { if (columns[i].equals(column)) { groupingSet.getAxes()[i] = new SegmentAxis( groupingSet.getPredicates()[i], valueSet, axisContainsNull); } } } } /** * Creates and executes a SQL statement to retrieve the set of cells * specified by a GroupingSetsList. * *

This method may be overridden in tests. * * @param cellRequestCount Number of missed cells that led to this request * @param groupingSetsList Grouping * @param compoundPredicateList Compound predicate list * @return An executed SQL statement, or null */ SqlStatement createExecuteSql( int cellRequestCount, GroupingSetsList groupingSetsList, List compoundPredicateList) { RolapStar star = groupingSetsList.getStar(); Pair> pair = AggregationManager.generateSql( groupingSetsList, compoundPredicateList); return RolapUtil.executeQuery( star.getDataSource(), pair.left, pair.right, 0, 0, new SqlStatement.StatementLocus( Locus.peek().execution, "Segment.load", "Error while loading segment", SqlStatementEvent.Purpose.CELL_SEGMENT, cellRequestCount), -1, -1); } RowList processData( SqlStatement stmt, final boolean[] axisContainsNull, final SortedSet[] axisValueSets, final GroupingSetsList groupingSetsList) throws SQLException { List segments = groupingSetsList.getDefaultSegments(); int measureCount = segments.size(); ResultSet rawRows = loadData(stmt, groupingSetsList); assert stmt != null; final List types = stmt.guessTypes(); int arity = axisValueSets.length; final int groupingColumnStartIndex = arity + measureCount; // If we're using grouping sets, the SQL query will have a number of // indicator columns, and we roll these into a single BitSet column in // the processed data set. final List processedTypes; if (groupingSetsList.useGroupingSets()) { processedTypes = new ArrayList( types.subList(0, groupingColumnStartIndex)); processedTypes.add(SqlStatement.Type.OBJECT); } else { processedTypes = types; } final RowList processedRows = new RowList(processedTypes, 100); while (rawRows.next()) { ++stmt.rowCount; processedRows.createRow(); // get the columns int columnIndex = 0; for (int axisIndex = 0; axisIndex < arity; axisIndex++, columnIndex++) { final SqlStatement.Type type = types.get(columnIndex); switch (type) { case OBJECT: case STRING: Object o = rawRows.getObject(columnIndex + 1); if (o == null) { o = RolapUtil.sqlNullValue; if (!groupingSetsList.useGroupingSets() || !isAggregateNull( rawRows, groupingColumnStartIndex, groupingSetsList, axisIndex)) { axisContainsNull[axisIndex] = true; } } else { // We assume that all values are Comparable. Boolean // wasn't Comparable until JDK 1.5, but we can live with // that bug because JDK 1.4 is no longer important. axisValueSets[axisIndex].add((Comparable) o); } processedRows.setObject(columnIndex, o); break; case INT: final int intValue = rawRows.getInt(columnIndex + 1); if (intValue == 0 && rawRows.wasNull()) { if (!groupingSetsList.useGroupingSets() || !isAggregateNull( rawRows, groupingColumnStartIndex, groupingSetsList, axisIndex)) { axisContainsNull[axisIndex] = true; } processedRows.setNull(columnIndex, true); } else { axisValueSets[axisIndex].add(intValue); processedRows.setInt(columnIndex, intValue); } break; case LONG: final long longValue = rawRows.getLong(columnIndex + 1); if (longValue == 0 && rawRows.wasNull()) { if (!groupingSetsList.useGroupingSets() || !isAggregateNull( rawRows, groupingColumnStartIndex, groupingSetsList, axisIndex)) { axisContainsNull[axisIndex] = true; } processedRows.setNull(columnIndex, true); } else { axisValueSets[axisIndex].add(longValue); processedRows.setLong(columnIndex, longValue); } break; case DOUBLE: final double doubleValue = rawRows.getDouble(columnIndex + 1); if (doubleValue == 0 && rawRows.wasNull()) { if (!groupingSetsList.useGroupingSets() || !isAggregateNull( rawRows, groupingColumnStartIndex, groupingSetsList, axisIndex)) { axisContainsNull[axisIndex] = true; } } axisValueSets[axisIndex].add(doubleValue); processedRows.setDouble(columnIndex, doubleValue); break; default: throw Util.unexpected(type); } } // pre-compute which measures are numeric final boolean[] numeric = new boolean[measureCount]; int k = 0; for (Segment segment : segments) { numeric[k++] = segment.measure.getDatatype().isNumeric(); } // get the measure for (int i = 0; i < measureCount; i++, columnIndex++) { final SqlStatement.Type type = types.get(columnIndex); switch (type) { case OBJECT: case STRING: Object o = rawRows.getObject(columnIndex + 1); if (o == null) { o = Util.nullValue; // convert to placeholder } else if (numeric[i]) { if (o instanceof Double) { // nothing to do } else if (o instanceof Number) { o = ((Number) o).doubleValue(); } else if (o instanceof byte[]) { // On MySQL 5.0 in German locale, values can come // out as byte arrays. Don't know why. Bug 1594119. o = Double.parseDouble(new String((byte[]) o)); } else { o = Double.parseDouble(o.toString()); } } processedRows.setObject(columnIndex, o); break; case INT: final int intValue = rawRows.getInt(columnIndex + 1); processedRows.setInt(columnIndex, intValue); if (intValue == 0 && rawRows.wasNull()) { processedRows.setNull(columnIndex, true); } break; case LONG: final long longValue = rawRows.getLong(columnIndex + 1); processedRows.setLong(columnIndex, longValue); if (longValue == 0 && rawRows.wasNull()) { processedRows.setNull(columnIndex, true); } break; case DOUBLE: final double doubleValue = rawRows.getDouble(columnIndex + 1); processedRows.setDouble(columnIndex, doubleValue); if (doubleValue == 0 && rawRows.wasNull()) { processedRows.setNull(columnIndex, true); } break; default: throw Util.unexpected(type); } } if (groupingSetsList.useGroupingSets()) { processedRows.setObject( columnIndex, getRollupBitKey( groupingSetsList.getRollupColumns().size(), rawRows, columnIndex)); } } return processedRows; } /** * Generates bit key representing roll up columns */ BitKey getRollupBitKey(int arity, ResultSet rowList, int k) throws SQLException { BitKey groupingBitKey = BitKey.Factory.makeBitKey(arity); for (int i = 0; i < arity; i++) { int o = rowList.getInt(k + i + 1); if (o == 1) { groupingBitKey.set(i); } } return groupingBitKey; } private boolean isAggregateNull( ResultSet rowList, int groupingColumnStartIndex, GroupingSetsList groupingSetsList, int axisIndex) throws SQLException { int groupingFunctionIndex = groupingSetsList.findGroupingFunctionIndex(axisIndex); if (groupingFunctionIndex == -1) { // Not a rollup column return false; } return rowList.getInt( groupingColumnStartIndex + groupingFunctionIndex + 1) == 1; } ResultSet loadData( SqlStatement stmt, GroupingSetsList groupingSetsList) throws SQLException { int arity = groupingSetsList.getDefaultColumns().length; int measureCount = groupingSetsList.getDefaultSegments().size(); int groupingFunctionsCount = groupingSetsList.getRollupColumns().size(); List types = stmt.guessTypes(); assert arity + measureCount + groupingFunctionsCount == types.size(); return stmt.getResultSet(); } SortedSet[] getDistinctValueWorkspace(int arity) { // Workspace to build up lists of distinct values for each axis. SortedSet[] axisValueSets = new SortedSet[arity]; for (int i = 0; i < axisValueSets.length; i++) { axisValueSets[i] = Util.PreJdk15 ? new TreeSet(BooleanComparator.INSTANCE) : new TreeSet(); } return axisValueSets; } /** * Decides whether to use a sparse representation for this segment, using * the formula described * {@link mondrian.olap.MondrianProperties#SparseSegmentCountThreshold * here}. * * @param possibleCount Number of values in the space. * @param actualCount Actual number of values. * @return Whether to use a sparse representation. */ static boolean useSparse( final double possibleCount, final double actualCount) { final MondrianProperties properties = MondrianProperties.instance(); double densityThreshold = properties.SparseSegmentDensityThreshold.get(); if (densityThreshold < 0) { densityThreshold = 0; } if (densityThreshold > 1) { densityThreshold = 1; } int countThreshold = properties.SparseSegmentCountThreshold.get(); if (countThreshold < 0) { countThreshold = 0; } boolean sparse = (possibleCount - countThreshold) * densityThreshold > actualCount; if (possibleCount < countThreshold) { assert !sparse : "Should never use sparse if count is less " + "than threshold, possibleCount=" + possibleCount + ", actualCount=" + actualCount + ", countThreshold=" + countThreshold + ", densityThreshold=" + densityThreshold; } if (possibleCount == actualCount) { assert !sparse : "Should never use sparse if result is 100% dense: " + "possibleCount=" + possibleCount + ", actualCount=" + actualCount + ", countThreshold=" + countThreshold + ", densityThreshold=" + densityThreshold; } return sparse; } /** * This is a private abstraction wrapper to perform * rollups. It allows us to rollup from a mix of segments * coming from either the local cache or the external one. */ abstract class SegmentRollupWrapper { abstract BitKey getConstrainedColumnsBitKey(); abstract SegmentColumn[] getConstrainedColumns(); abstract SegmentDataset getDataset(); abstract Object[] getValuesForColumn(SegmentColumn cc); abstract mondrian.spi.SegmentColumn getHeader(); public int hashCode() { return getHeader().hashCode(); } public boolean equals(Object obj) { return getHeader().equals(obj); } } /** * Collection of rows, each with a set of columns of type Object, double, or * int. Native types are not boxed. */ protected static class RowList { private final Column[] columns; private int rowCount = 0; private int capacity = 0; private int currentRow = -1; /** * Creates a RowList. * * @param types Column types */ RowList(List types) { this(types, 100); } /** * Creates a RowList with a specified initial capacity. * * @param types Column types * @param capacity Initial capacity */ RowList(List types, int capacity) { this.columns = new Column[types.size()]; this.capacity = capacity; for (int i = 0; i < columns.length; i++) { columns[i] = Column.forType(i, types.get(i), capacity); } } void createRow() { currentRow = rowCount++; if (rowCount > capacity) { capacity *= 3; for (Column column : columns) { column.resize(capacity); } } } void setObject(int column, Object value) { columns[column].setObject(currentRow, value); } void setDouble(int column, double value) { columns[column].setDouble(currentRow, value); } void setInt(int column, int value) { columns[column].setInt(currentRow, value); } void setLong(int column, long value) { columns[column].setLong(currentRow, value); } public int size() { return rowCount; } public void createRow(ResultSet resultSet) throws SQLException { createRow(); for (Column column : columns) { column.populateFrom(currentRow, resultSet); } } public List getTypes() { return new AbstractList() { public SqlStatement.Type get(int index) { return columns[index].type; } public int size() { return columns.length; } }; } /** * Moves to before the first row. */ public void first() { currentRow = -1; } /** * Moves to after the last row. */ public void last() { currentRow = rowCount; } /** * Moves forward one row, or returns false if at the last row. * * @return whether moved forward */ public boolean next() { if (currentRow < rowCount - 1) { ++currentRow; return true; } return false; } /** * Moves backward one row, or returns false if at the first row. * * @return whether moved backward */ public boolean previous() { if (currentRow > 0) { --currentRow; return true; } return false; } /** * Returns the object in the given column of the current row. * * @param columnIndex Column index * @return Value of the column */ public Object getObject(int columnIndex) { return columns[columnIndex].getObject(currentRow); } public int getInt(int columnIndex) { return columns[columnIndex].getInt(currentRow); } public double getDouble(int columnIndex) { return columns[columnIndex].getDouble(currentRow); } public boolean isNull(int columnIndex) { return columns[columnIndex].isNull(currentRow); } public void setNull(int columnIndex, boolean b) { columns[columnIndex].setNull(currentRow, b); } static abstract class Column { final int ordinal; final SqlStatement.Type type; protected Column(int ordinal, SqlStatement.Type type) { this.ordinal = ordinal; this.type = type; } static Column forType( int ordinal, SqlStatement.Type type, int capacity) { switch (type) { case OBJECT: case STRING: return new ObjectColumn(ordinal, type, capacity); case INT: return new IntColumn(ordinal, type, capacity); case LONG: return new LongColumn(ordinal, type, capacity); case DOUBLE: return new DoubleColumn(ordinal, type, capacity); default: throw Util.unexpected(type); } } public abstract void resize(int newSize); public void setObject(int row, Object value) { throw new UnsupportedOperationException(); } public void setDouble(int row, double value) { throw new UnsupportedOperationException(); } public void setInt(int row, int value) { throw new UnsupportedOperationException(); } public void setLong(int row, long value) { throw new UnsupportedOperationException(); } public void setNull(int row, boolean b) { throw new UnsupportedOperationException(); } public abstract void populateFrom(int row, ResultSet resultSet) throws SQLException; public Object getObject(int row) { throw new UnsupportedOperationException(); } public int getInt(int row) { throw new UnsupportedOperationException(); } public double getDouble(int row) { throw new UnsupportedOperationException(); } protected abstract int getCapacity(); public abstract boolean isNull(int row); } static class ObjectColumn extends Column { private Object[] objects; ObjectColumn(int ordinal, SqlStatement.Type type, int size) { super(ordinal, type); objects = new Object[size]; } protected int getCapacity() { return objects.length; } public boolean isNull(int row) { return objects[row] == null; } public void resize(int newSize) { objects = Util.copyOf(objects, newSize); } public void populateFrom(int row, ResultSet resultSet) throws SQLException { objects[row] = resultSet.getObject(ordinal + 1); } public void setObject(int row, Object value) { objects[row] = value; } public Object getObject(int row) { return objects[row]; } } static abstract class NativeColumn extends Column { protected BitSet nullIndicators; NativeColumn(int ordinal, SqlStatement.Type type) { super(ordinal, type); } public void setNull(int row, boolean b) { getNullIndicators().set(row, b); } protected BitSet getNullIndicators() { if (nullIndicators == null) { nullIndicators = new BitSet(getCapacity()); } return nullIndicators; } } static class IntColumn extends NativeColumn { private int[] ints; IntColumn(int ordinal, SqlStatement.Type type, int size) { super(ordinal, type); ints = new int[size]; } public void resize(int newSize) { ints = Util.copyOf(ints, newSize); } public void populateFrom(int row, ResultSet resultSet) throws SQLException { int i = ints[row] = resultSet.getInt(ordinal + 1); if (i == 0) { getNullIndicators().set(row, resultSet.wasNull()); } } public void setInt(int row, int value) { ints[row] = value; } public int getInt(int row) { return ints[row]; } public boolean isNull(int row) { return ints[row] == 0 && nullIndicators != null && nullIndicators.get(row); } protected int getCapacity() { return ints.length; } public Integer getObject(int row) { return isNull(row) ? null : ints[row]; } } static class LongColumn extends NativeColumn { private long[] longs; LongColumn(int ordinal, SqlStatement.Type type, int size) { super(ordinal, type); longs = new long[size]; } public void resize(int newSize) { longs = Util.copyOf(longs, newSize); } public void populateFrom(int row, ResultSet resultSet) throws SQLException { long i = longs[row] = resultSet.getLong(ordinal + 1); if (i == 0) { getNullIndicators().set(row, resultSet.wasNull()); } } public void setLong(int row, long value) { longs[row] = value; } public long getLong(int row) { return longs[row]; } public boolean isNull(int row) { return longs[row] == 0 && nullIndicators != null && nullIndicators.get(row); } protected int getCapacity() { return longs.length; } public Long getObject(int row) { return isNull(row) ? null : longs[row]; } } static class DoubleColumn extends NativeColumn { private double[] doubles; DoubleColumn(int ordinal, SqlStatement.Type type, int size) { super(ordinal, type); doubles = new double[size]; } public void resize(int newSize) { doubles = Util.copyOf(doubles, newSize); } public void populateFrom(int row, ResultSet resultSet) throws SQLException { double d = doubles[row] = resultSet.getDouble(ordinal + 1); if (d == 0d) { getNullIndicators().set(row, resultSet.wasNull()); } } public void setDouble(int row, double value) { doubles[row] = value; } public double getDouble(int row) { return doubles[row]; } protected int getCapacity() { return doubles.length; } public boolean isNull(int row) { return doubles[row] == 0d && nullIndicators != null && nullIndicators.get(row); } public Double getObject(int row) { return isNull(row) ? null : doubles[row]; } } public interface Handler { } } private static class BooleanComparator implements Comparator, Serializable { public static final BooleanComparator INSTANCE = new BooleanComparator(); private BooleanComparator() { if (Util.PreJdk15) { // This class exists to work around the fact that Boolean is not // Comparable until JDK 1.5. assert !(Comparable.class.isAssignableFrom(Boolean.class)); } else { assert Comparable.class.isAssignableFrom(Boolean.class); } } public int compare(Object o1, Object o2) { if (o1 instanceof Boolean) { boolean b1 = (Boolean) o1; if (o2 instanceof Boolean) { boolean b2 = (Boolean) o2; return b1 == b2 ? 0 : (b1 ? 1 : -1); } else { return -1; } } else { return ((Comparable) o1).compareTo(o2); } } } } // End SegmentLoader.java mondrian-3.4.1/src/main/mondrian/rolap/agg/AbstractColumnPredicate.java0000644000175000017500000001477311735330606026044 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2010 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.olap.Util; import mondrian.rolap.*; import mondrian.rolap.sql.SqlQuery; import java.util.*; /** * A AbstractColumnPredicate is an abstract implementation for * {@link mondrian.rolap.StarColumnPredicate}. */ public abstract class AbstractColumnPredicate implements StarColumnPredicate { protected final RolapStar.Column constrainedColumn; private BitKey constrainedColumnBitKey; /** * Creates an AbstractColumnPredicate. * * @param constrainedColumn Constrained column */ protected AbstractColumnPredicate(RolapStar.Column constrainedColumn) { this.constrainedColumn = constrainedColumn; } public String toString() { final StringBuilder buf = new StringBuilder(); buf.append(constrainedColumn.getExpression().getGenericExpression()); describe(buf); return buf.toString(); } public RolapStar.Column getConstrainedColumn() { return constrainedColumn; } public List getConstrainedColumnList() { return Collections.singletonList(constrainedColumn); } public BitKey getConstrainedColumnBitKey() { // Check whether constrainedColumn are null. // Example: FastBatchingCellReaderTest.testAggregateDistinctCount5(). if (constrainedColumnBitKey == null && constrainedColumn != null && constrainedColumn.getTable() != null) { constrainedColumnBitKey = BitKey.Factory.makeBitKey( constrainedColumn.getStar().getColumnCount()); constrainedColumnBitKey.set(constrainedColumn.getBitPosition()); } return constrainedColumnBitKey; } public boolean evaluate(List valueList) { assert valueList.size() == 1; return evaluate(valueList.get(0)); } public boolean equalConstraint(StarPredicate that) { return false; } public StarPredicate or(StarPredicate predicate) { if (predicate instanceof StarColumnPredicate) { StarColumnPredicate starColumnPredicate = (StarColumnPredicate) predicate; if (starColumnPredicate.getConstrainedColumn() == getConstrainedColumn()) { return orColumn(starColumnPredicate); } } final List list = new ArrayList(2); list.add(this); list.add(predicate); return new OrPredicate(list); } public StarColumnPredicate orColumn(StarColumnPredicate predicate) { assert predicate.getConstrainedColumn() == getConstrainedColumn(); if (predicate instanceof ListColumnPredicate) { ListColumnPredicate that = (ListColumnPredicate) predicate; final List list = new ArrayList(); list.add(this); list.addAll(that.getPredicates()); return new ListColumnPredicate( getConstrainedColumn(), list); } else { final List list = new ArrayList(2); list.add(this); list.add(predicate); return new ListColumnPredicate( getConstrainedColumn(), list); } } public StarPredicate and(StarPredicate predicate) { final List list = new ArrayList(2); list.add(this); list.add(predicate); return new AndPredicate(list); } public void toSql(SqlQuery sqlQuery, StringBuilder buf) { throw Util.needToImplement(this); } protected static List cloneListWithColumn( RolapStar.Column column, List list) { List newList = new ArrayList(list.size()); for (StarColumnPredicate predicate : list) { newList.add(predicate.cloneWithColumn(column)); } return newList; } /** * Factory for {@link mondrian.rolap.StarPredicate}s and * {@link mondrian.rolap.StarColumnPredicate}s. */ public static class Factory { /** * Returns a predicate which tests whether the column's * value is equal to a given constant. * * @param column Constrained column * @param value Value * @return Predicate which tests whether the column's value is equal * to a column constraint's value */ public static StarColumnPredicate equal( RolapStar.Column column, Object value) { return new ValueColumnPredicate(column, value); } /** * Returns predicate which is the OR of a list of predicates. * * @param column Column being constrained * @param list List of predicates * @return Predicate which is an OR of the list of predicates */ public static StarColumnPredicate or( RolapStar.Column column, List list) { return new ListColumnPredicate(column, list); } /** * Returns a predicate which always evaluates to TRUE or FALSE. * @param b Truth value * @return Predicate which always evaluates to truth value */ public static LiteralStarPredicate bool(boolean b) { return b ? LiteralStarPredicate.TRUE : LiteralStarPredicate.FALSE; } /** * Returns a predicate which tests whether the column's * value is equal to column predicate's value. * * @param predicate Column predicate * @return Predicate which tests whether the column's value is equal * to a column predicate's value */ public static StarColumnPredicate equal( ValueColumnPredicate predicate) { return equal( predicate.getConstrainedColumn(), predicate.getValue()); } } } // End AbstractColumnPredicate.java mondrian-3.4.1/src/main/mondrian/rolap/agg/AggQuerySpec.java0000644000175000017500000001474711735330606023642 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. // // jhyde, 21 March, 2002 */ package mondrian.rolap.agg; import mondrian.rolap.RolapStar; import mondrian.rolap.SqlStatement.Type; import mondrian.rolap.StarColumnPredicate; import mondrian.rolap.aggmatcher.AggStar; import mondrian.rolap.sql.SqlQuery; import mondrian.util.Pair; import org.apache.log4j.Logger; import java.util.ArrayList; import java.util.List; /** * An AggStar's version of the {@link QuerySpec}.

* * When/if the {@link AggStar} code is merged into {@link RolapStar} * (or RolapStar is merged into AggStar}, then this, indeed, can implement the * {@link QuerySpec} interface. * * @author Richard M. Emberson */ class AggQuerySpec { private static final Logger LOGGER = Logger.getLogger(AggQuerySpec.class); private final AggStar aggStar; private final List segments; private final Segment segment0; private final boolean rollup; private final GroupingSetsList groupingSetsList; AggQuerySpec( final AggStar aggStar, final boolean rollup, GroupingSetsList groupingSetsList) { this.aggStar = aggStar; this.segments = groupingSetsList.getDefaultSegments(); this.segment0 = segments.get(0); this.rollup = rollup; this.groupingSetsList = groupingSetsList; } protected SqlQuery newSqlQuery() { return getStar().getSqlQuery(); } public RolapStar getStar() { return aggStar.getStar(); } public int getMeasureCount() { return segments.size(); } public AggStar.FactTable.Column getMeasureAsColumn(final int i) { int bitPos = segments.get(i).measure.getBitPosition(); return aggStar.lookupColumn(bitPos); } public String getMeasureAlias(final int i) { return "m" + Integer.toString(i); } public int getColumnCount() { return segment0.getColumns().length; } public AggStar.Table.Column getColumn(final int i) { RolapStar.Column[] columns = segment0.getColumns(); int bitPos = columns[i].getBitPosition(); AggStar.Table.Column column = aggStar.lookupColumn(bitPos); // this should never happen if (column == null) { LOGGER.error("column null for bitPos=" + bitPos); } return column; } public String getColumnAlias(final int i) { return "c" + Integer.toString(i); } /** * Returns the predicate on the ith column. * *

If the column is unconstrained, returns * {@link LiteralStarPredicate}(true). * * @param i Column ordinal * @return Constraint on column */ public StarColumnPredicate getPredicate(int i) { return segment0.predicates[i]; } public Pair> generateSqlQuery() { SqlQuery sqlQuery = newSqlQuery(); generateSql(sqlQuery); return sqlQuery.toSqlAndTypes(); } private void addGroupingSets(SqlQuery sqlQuery) { List groupingSetsColumns = groupingSetsList.getGroupingSetsColumns(); for (RolapStar.Column[] groupingSetColumns : groupingSetsColumns) { ArrayList groupingColumnsExpr = new ArrayList(); for (RolapStar.Column aColumnArr : groupingSetColumns) { groupingColumnsExpr.add(findColumnExpr(aColumnArr, sqlQuery)); } sqlQuery.addGroupingSet(groupingColumnsExpr); } } private String findColumnExpr(RolapStar.Column columnj, SqlQuery sqlQuery) { AggStar.Table.Column column = aggStar.lookupColumn(columnj.getBitPosition()); return column.generateExprString(sqlQuery); } protected void addMeasure(final int i, final SqlQuery query) { AggStar.FactTable.Measure column = (AggStar.FactTable.Measure) getMeasureAsColumn(i); column.getTable().addToFrom(query, false, true); String alias = getMeasureAlias(i); String expr; if (rollup) { expr = column.generateRollupString(query); } else { expr = column.generateExprString(query); } query.addSelect(expr, null, alias); } protected void generateSql(final SqlQuery sqlQuery) { // add constraining dimensions int columnCnt = getColumnCount(); for (int i = 0; i < columnCnt; i++) { AggStar.Table.Column column = getColumn(i); AggStar.Table table = column.getTable(); table.addToFrom(sqlQuery, false, true); String expr = column.generateExprString(sqlQuery); StarColumnPredicate predicate = getPredicate(i); final String where = RolapStar.Column.createInExpr( expr, predicate, column.getDatatype(), sqlQuery); if (!where.equals("true")) { sqlQuery.addWhere(where); } // some DB2 (AS400) versions throw an error, if a column alias is // there and *not* used in a subsequent order by/group by final String alias0; switch (sqlQuery.getDialect().getDatabaseProduct()) { case DB2_AS400: case DB2_OLD_AS400: alias0 = null; break; default: alias0 = getColumnAlias(i); break; } final String alias = sqlQuery.addSelect(expr, column.getInternalType(), alias0); if (rollup) { sqlQuery.addGroupBy(expr, alias); } } // Add measures. // This can also add non-shared local dimension columns, which are // not measures. for (int i = 0, count = getMeasureCount(); i < count; i++) { addMeasure(i, sqlQuery); } addGroupingSets(sqlQuery); addGroupingFunction(sqlQuery); } private void addGroupingFunction(SqlQuery sqlQuery) { List list = groupingSetsList.getRollupColumns(); for (RolapStar.Column column : list) { sqlQuery.addGroupingFunction(findColumnExpr(column, sqlQuery)); } } } // End AggQuerySpec.java mondrian-3.4.1/src/main/mondrian/rolap/agg/SegmentAxis.java0000644000175000017500000001562011735330606023521 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.olap.Util; import mondrian.rolap.RolapUtil; import mondrian.rolap.StarColumnPredicate; import mondrian.util.ArraySortedSet; import mondrian.util.Pair; import java.util.*; /** * Collection of values of one of the columns that parameterizes a * {@link Segment}. */ public class SegmentAxis { /** * Constraint on the keys in this Axis. Never null. */ final StarColumnPredicate predicate; /** * Whether predicate is always true. */ private final boolean predicateAlwaysTrue; private final Set predicateValues; /** * Map holding the position of each key value. * *

TODO: Hold keys in a sorted array, then deduce ordinal by doing * binary search. */ private final Map mapKeyToOffset; /** * Actual key values retrieved. */ private final Comparable[] keys; private static final Integer ZERO = Integer.valueOf(0); private static final Integer ONE = Integer.valueOf(1); private static final Comparable[] NO_COMPARABLES = new Comparable[0]; /** * Internal constructor. */ private SegmentAxis( StarColumnPredicate predicate, Comparable[] keys, boolean safe) { this.predicate = predicate; this.predicateAlwaysTrue = predicate instanceof LiteralStarPredicate && ((LiteralStarPredicate) predicate).getValue(); this.predicateValues = predicateValueSet(predicate); if (keys.length == 0) { // Optimize the case where axis is empty. Not that infrequent: // it records that mondrian has looked in the database and found // nothing. this.keys = NO_COMPARABLES; this.mapKeyToOffset = Collections.emptyMap(); } else { this.keys = keys; mapKeyToOffset = new HashMap(keys.length * 3 / 2); for (int i = 0; i < keys.length; i++) { mapKeyToOffset.put(keys[i], i); } } assert predicate != null; assert safe || Util.isSorted(Arrays.asList(keys)); } private static Set predicateValueSet( StarColumnPredicate predicate) { if (!(predicate instanceof ListColumnPredicate)) { return null; } ListColumnPredicate listColumnPredicate = (ListColumnPredicate) predicate; final List predicates = listColumnPredicate.getPredicates(); if (predicates.size() < 10) { return null; } final HashSet set = new HashSet(); for (StarColumnPredicate subPredicate : predicates) { if (subPredicate instanceof ValueColumnPredicate) { ValueColumnPredicate valueColumnPredicate = (ValueColumnPredicate) subPredicate; valueColumnPredicate.values(set); } else { return null; } } return set; } /** * Creates a SegmentAxis populated with an array of key values. The key * values must be sorted. * * @param predicate Predicate defining which keys should appear on * axis. (If a key passes the predicate but * is not in the list, every cell with that * key is assumed to have a null value.) * @param keys Keys */ SegmentAxis(StarColumnPredicate predicate, Comparable[] keys) { this(predicate, keys, false); } /** * Creates a SegmentAxis populated with a set of key values. * * @param predicate Predicate defining which keys should appear on * axis. (If a key passes the predicate but * is not in the list, every cell with that * key is assumed to have a null value.) * @param keySet Set of distinct key values, sorted * @param hasNull Whether the axis contains the null value, in addition * to the values in valueSet */ public SegmentAxis( StarColumnPredicate predicate, SortedSet keySet, boolean hasNull) { this(predicate, toArray(keySet, hasNull), true); } private static Comparable[] toArray( SortedSet keySet, boolean hasNull) { int size = keySet.size(); if (hasNull) { size++; } Comparable[] keys = keySet.toArray(new Comparable[size]); if (hasNull) { keys[size - 1] = RolapUtil.sqlNullValue; } return keys; } final StarColumnPredicate getPredicate() { return predicate; } final Comparable[] getKeys() { return keys; } final int getOffset(Comparable key) { if (keys.length == 1) { return keys[0].equals(key) ? 0 : -1; } Integer ordinal = mapKeyToOffset.get(key); if (ordinal == null) { return -1; } return ordinal; } /** * Returns whether this axis contains a given key, or would contain it * if it existed. * *

For example, if this axis is unconstrained, then this method * returns true for any value. * * @param key Key * @return Whether this axis would contain key */ public final boolean wouldContain(Object key) { return predicateAlwaysTrue || (predicateValues != null ? predicateValues.contains(key) : predicate.evaluate(key)); } /** * Returns how many of this SegmentAxis's keys match a given constraint. * * @param predicate Predicate * @return How many keys match constraint */ public int getMatchCount(StarColumnPredicate predicate) { int matchCount = 0; for (Object key : keys) { if (predicate.evaluate(key)) { ++matchCount; } } return matchCount; } @SuppressWarnings({"unchecked"}) public Pair, Boolean> getValuesAndIndicator() { if (keys.length > 0 && keys[keys.length - 1] == RolapUtil.sqlNullValue) { return (Pair) Pair.of( new ArraySortedSet(keys, 0, keys.length - 1), Boolean.TRUE); } else { return (Pair) Pair.of( new ArraySortedSet(keys), Boolean.FALSE); } } } // End SegmentAxis.java mondrian-3.4.1/src/main/mondrian/rolap/agg/SparseSegmentDataset.java0000644000175000017500000000610011735330606025351 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.CellKey; import mondrian.rolap.SqlStatement; import mondrian.spi.SegmentBody; import mondrian.util.Pair; import java.util.*; /** * A SparseSegmentDataset is a means of storing segment values * which is suitable when few of the combinations of keys have a value present. * *

The storage requirements are as follows. Key is 1 word for each * dimension. Hashtable entry is 3 words. Value is 1 word. Total space is (4 + * d) * v. (May also need hash table to ensure that values are only stored * once.)

* *

NOTE: This class is not synchronized.

* * @author jhyde * @since 21 March, 2002 */ class SparseSegmentDataset implements SegmentDataset { private final Map values; /** * Creates an empty SparseSegmentDataset. */ SparseSegmentDataset() { this(new HashMap()); } /** * Creates a SparseSegmentDataset with a given value map. The map is not * copied; a reference to the map is retained inside the dataset, and * therefore the contents of the dataset will change if the map is modified. * * @param values Value map */ SparseSegmentDataset(Map values) { this.values = values; } public Object getObject(CellKey pos) { return values.get(pos); } public boolean isNull(CellKey pos) { // cf exists -- calls values.containsKey return values.get(pos) == null; } public int getInt(CellKey pos) { throw new UnsupportedOperationException(); } public double getDouble(CellKey pos) { throw new UnsupportedOperationException(); } public boolean exists(CellKey pos) { return values.containsKey(pos); } public void put(CellKey key, Object value) { values.put(key, value); } public Iterator> iterator() { return values.entrySet().iterator(); } public double getBytes() { // assume a slot, key, and value are each 4 bytes return values.size() * 12; } public void populateFrom(int[] pos, SegmentDataset data, CellKey key) { values.put(CellKey.Generator.newCellKey(pos), data.getObject(key)); } public void populateFrom( int[] pos, SegmentLoader.RowList rowList, int column) { final Object o = rowList.getObject(column); put(CellKey.Generator.newCellKey(pos), o); } public SqlStatement.Type getType() { return SqlStatement.Type.OBJECT; } public SegmentBody createSegmentBody( List, Boolean>> axes) { return new SparseSegmentBody( values, axes); } } // End SparseSegmentDataset.java mondrian-3.4.1/src/main/mondrian/rolap/agg/SegmentWithData.java0000644000175000017500000003041311735330606024317 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.olap.Util; import mondrian.rolap.*; import java.util.*; /** * Extension to {@link Segment} with a data set. * * @author jhyde */ public class SegmentWithData extends Segment { /** * An array of axes, one for each constraining column, containing the values * returned for that constraining column. */ final SegmentAxis[] axes; /** *

data holds a reference to the SegmentDataset * that contains the underlying cell values.

* *

Since the SegmentDataset is loaded and assigned after * Segment is constructed, threadsafe access to it is only * guaranteed if the access is guarded.

* *

Access which does not depend on data already having been * loaded should be guarded by obtaining either a read or write lock on * stateLock, as appropriate.

* *

Access that should not proceed until the data reference * has been loaded should be guarded using the dataGate latch. * This is typically accomplished by calling waitUntilLoaded(), * which will block until the latch is released and throw an error if * data failed to load.

* *

Once set, the value of data is presumed to be invariant * and should never be reset, nor should the contents be modified. Thus, * for a given thread, any read access to data which comes after * dataGate.await() (or, by extension, * waitUntilLoaded will be threadsafe.

*/ private final SegmentDataset data; /** * Creates a SegmentWithData from an existing Segment. * * @param segment Segment (without data) * @param data Data set */ public SegmentWithData( Segment segment, SegmentDataset data, SegmentAxis[] axes) { this( segment.getStar(), segment.getConstrainedColumnsBitKey(), segment.getColumns(), segment.measure, segment.predicates, segment.getExcludedRegions(), segment.compoundPredicateList, data, axes); if (segment instanceof SegmentWithData) { throw new AssertionError(); } } /** * Creates a SegmentWithData. * * @param star Star that this Segment belongs to * @param measure Measure whose values this Segment contains * @param predicates List of axes; each is a constraint plus a list of * values. * @param excludedRegions List of regions which are not in this segment. */ private SegmentWithData( RolapStar star, BitKey constrainedColumnsBitKey, RolapStar.Column[] columns, RolapStar.Measure measure, StarColumnPredicate[] predicates, List excludedRegions, final List compoundPredicateList, SegmentDataset data, SegmentAxis[] axes) { super( star, constrainedColumnsBitKey, columns, measure, predicates, excludedRegions, compoundPredicateList); this.axes = axes; this.data = data; } @Override protected void describeAxes(StringBuilder buf, int i, boolean values) { super.describeAxes(buf, i, values); if (!values) { return; } Object[] keys = axes[i].getKeys(); buf.append(", values={"); for (int j = 0; j < keys.length; j++) { if (j > 0) { buf.append(", "); } Object key = keys[j]; buf.append(key); } buf.append("}"); } /** * Retrieves the value at the location identified by * keys. * *

Returns

    * *
  • {@link mondrian.olap.Util#nullValue} if the cell value * is null (because no fact table rows met those criteria);
  • * *
  • null if the value is not supposed to be in this segment * (because one or more of the keys do not pass the axis criteria);
  • * *
  • the data value otherwise
  • * *

* * @see mondrian.olap.Util#deprecated(Object) make package-private? */ public Object getCellValue(Object[] keys) { assert keys.length == axes.length; int missed = 0; CellKey cellKey = CellKey.Generator.newCellKey(axes.length); for (int i = 0; i < keys.length; i++) { Comparable key = (Comparable) keys[i]; int offset = axes[i].getOffset(key); if (offset < 0) { if (axes[i].wouldContain(key)) { // see whether this segment should contain this value missed++; continue; } else { // this value should not appear in this segment; we // should be looking in a different segment return null; } } cellKey.setAxis(i, offset); } if (isExcluded(keys)) { // this value should not appear in this segment; we // should be looking in a different segment return null; } if (missed > 0) { // the value should be in this segment, but isn't, because one // or more of its keys does have any values return Util.nullValue; } else { Object o = data.getObject(cellKey); if (o == null) { o = Util.nullValue; } return o; } } /** * Returns whether the given set of key values will be in this segment * when it finishes loading. */ boolean wouldContain(Object[] keys) { Util.assertTrue(keys.length == axes.length); for (int i = 0; i < keys.length; i++) { Object key = keys[i]; if (!axes[i].wouldContain(key)) { return false; } } return !isExcluded(keys); } /** * Returns the number of cells in this Segment, deducting cells in * excluded regions. * *

This method may return a value which is slightly too low, or * occasionally even negative. This occurs when a Segment has more than one * excluded region, and those regions overlap. Cells which are in both * regions will be counted twice. * * @return Number of cells in this Segment */ public int getCellCount() { int cellCount = 1; for (SegmentAxis axis : axes) { cellCount *= axis.getKeys().length; } for (ExcludedRegion excludedRegion : excludedRegions) { cellCount -= excludedRegion.getCellCount(); } return cellCount; } /** * Creates a Segment which has the same dimensionality as this Segment and a * subset of the values. * *

If bestColumn is not -1, the bestColumnth * column's predicate should be replaced by bestPredicate. * * @param axisKeepBitSets For each axis, a bitmap of the axis values to * keep; each axis must have at least one bit set * @param bestColumn The column that retains most of its values * @param bestPredicate * @param excludedRegions List of regions to exclude from segment * @return Segment containing a subset of the values */ SegmentWithData createSubSegment( BitSet[] axisKeepBitSets, int bestColumn, StarColumnPredicate bestPredicate, List excludedRegions) { assert axisKeepBitSets.length == axes.length; // Create a new segment with a subset of the values. If only one // of the axes is restricted, restrict just that axis. If more than // one of the axis is restricted, add a negation to the segment. final SegmentAxis[] newAxes = axes.clone(); final StarColumnPredicate[] newPredicates = predicates.clone(); // For each axis, map from old position to new position. final Map[] axisPosMaps = new Map[axes.length]; int valueCount = 1; for (int j = 0; j < axes.length; j++) { SegmentAxis axis = axes[j]; StarColumnPredicate newPredicate = axis.getPredicate(); if (j == bestColumn) { newPredicate = bestPredicate; } final Comparable[] axisKeys = axis.getKeys(); BitSet keepBitSet = axisKeepBitSets[j]; int firstClearBit = keepBitSet.nextClearBit(0); Comparable[] newAxisKeys; if (firstClearBit >= axisKeys.length) { // Keep everything newAxisKeys = axisKeys; axisPosMaps[j] = null; // identity map } else { List newAxisKeyList = new ArrayList(); Map map = axisPosMaps[j] = new HashMap(); for (int bit = keepBitSet.nextSetBit(0); bit >= 0; bit = keepBitSet.nextSetBit(bit + 1)) { map.put(bit, newAxisKeyList.size()); newAxisKeyList.add(axisKeys[bit]); } newAxisKeys = newAxisKeyList.toArray( new Comparable[newAxisKeyList.size()]); assert newAxisKeys.length > 0; } final SegmentAxis newAxis = new SegmentAxis(newPredicate, newAxisKeys); newAxes[j] = newAxis; newPredicates[j] = newPredicate; valueCount *= newAxisKeys.length; } // Create a dataset containing a subset of the current dataset. // Keep the same representation as the current dataset. // (We could be smarter - sometimes a subset of a sparse dataset will // be dense and VERY occasionally a subset of a relatively dense dataset // will be sparse.) SegmentDataset newData = createDataset( axes, data instanceof SparseSegmentDataset, data.getType(), valueCount); // If the source is sparse, it is more efficient to iterate over the // values we need. If it's dense, it doesn't matter too much. int[] pos = new int[axes.length]; data: for (Map.Entry entry : data) { CellKey key = entry.getKey(); // Map each of the source coordinates to the target coordinate. // If any of the coordinates maps to null, it means that the // cell falls outside the subset. for (int i = 0; i < pos.length; i++) { int ordinal = key.getAxis(i); Map axisPosMap = axisPosMaps[i]; if (axisPosMap == null) { pos[i] = ordinal; } else { Integer integer = axisPosMap.get(ordinal); if (integer == null) { continue data; } pos[i] = integer; } } newData.populateFrom(pos, data, key); } // Create a segment with the new data set. return new SegmentWithData( star, constrainedColumnsBitKey, columns, measure, newPredicates, excludedRegions, compoundPredicateList, newData, newAxes); } /** *

Returns the data set.

* *

WARNING: the returned SegmentDataset reference should not be modified; * it is assumed to be invariant.

* * @return The data reference */ public final SegmentDataset getData() { return data; } } // End SegmentWithData.java mondrian-3.4.1/src/main/mondrian/rolap/agg/CellRequest.java0000644000175000017500000003175511735330606023531 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. // // jhyde, 21 March, 2002 */ package mondrian.rolap.agg; import mondrian.rolap.*; import java.util.*; /** * A CellRequest contains the context necessary to get a cell * value from a star. * * @author jhyde * @since 21 March, 2002 */ public class CellRequest { private final RolapStar.Measure measure; public final boolean extendedContext; public final boolean drillThrough; /* * Sparsely populated array of column predicates. Each predicate will * be located according to the bitPosition of the column to which it * corresponds. This costs a little memory in terms of unused array * slots, but avoids the need to explicitly sort the column predicates * into a canonical order. There aren't usually a lot of predicates to * sort, but that time adds up quickly. */ private StarColumnPredicate[] sparseColumnPredicateList; /** * An array that contains the bit positions of each constrained * column. * *

Used to allow us to convert from a numeric index (i.e., give me the * third constrained column) to the actual column as referenced * by bit position. * * For example, if the three constrained columns have bitPositions 3, 16, * and 21, the contents of this array should be [3,16,21]. */ private int[] columnBitPositions; /** * Tracks the number of column constraints actually associated with this * CellRequest. We could figure this out by iterating over * sparseColumnPredicateList, but it's quicker to just track them * as they are added. */ private int numColumns; /** * Reference back to the CellRequest's star. All CellRequests in a * given query are associated with a single star. Keeping this * reference allows us to maintain a list of columns by bit position * in just one place (the star) rather than duplicate that * information in each CellRequest. */ private RolapStar star = null; /* * Array of column values; * Not used to represent the compound members along one or more dimensions. */ private Object[] singleValues; /** * After all of the columns are loaded, the columnsCache is created * the first time the getColumns method (or any method that itself * calls the check method) is called. * *

It is assumed that the call to all additional columns, * {@link #addConstrainedColumn}, will not be called after the first call * to the {@link #getConstrainedColumns()} method. * *

TODO: Since the expectation is that the columns do not change once * set, it may be worth either caching these structures so that only one * exists for every unique combination, or eliminating the cache and * creating a wrapper that makes the bit key + star's column list look * like a column array. */ private RolapStar.Column[] columnsCache = null; /** * A bit is set for each column in the column list. Allows us to rapidly * figure out whether two requests are for the same column set. * These are all of the columns that are involved with a query, that is, all * required to be present in an aggregate table for the table be used to * fulfill the query. */ private final BitKey constrainedColumnsBitKey; /** * Map from BitKey (representing a group of columns that forms a * compound key) to StarPredicate (representing the predicate * defining the compound member). * *

We use LinkedHashMap so that the entries occur in deterministic * order; otherwise, successive runs generate different SQL queries. * Another solution worth considering would be to use the inherent ordering * of BitKeys and create a sorted map. * *

Creating CellRequests is one of the top hotspots in Mondrian. * Therefore we initialize the map to null, and don't create a map until * we add the first entry. * *

The map (when not null) is sorted by key, to allow more rapid * comparison with maps of other requests and with existing segments.

*/ private SortedMap compoundPredicateMap = null; /** * Whether the request is impossible to satisfy. This is set to 'true' if * contradictory constraints are applied to the same column. For example, * the levels [Customer].[City] and [Cities].[City] map to the same column * via the same join-path, and one constraint sets city = 'Burbank' and * another sets city = 'Los Angeles'. */ private boolean unsatisfiable; /** * The columnPredicateList and columnsCache must be set after all * constraints have been added. This is used by access methods to determine * if both columnPredicateList and columnsCache need to be generated. */ private boolean isDirty = true; /** * Creates a {@link CellRequest}. * * @param measure Measure the request is for * @param extendedContext If a drill-through request, whether to join in * unconstrained levels so as to display extra columns * @param drillThrough Whether this is a request for a drill-through set */ public CellRequest( RolapStar.Measure measure, boolean extendedContext, boolean drillThrough) { this.measure = measure; this.extendedContext = extendedContext; this.drillThrough = drillThrough; this.constrainedColumnsBitKey = BitKey.Factory.makeBitKey(measure.getStar().getColumnCount()); this.sparseColumnPredicateList = new StarColumnPredicate[measure.getStar().getColumnCount()]; } /** * Adds a constraint to this request. * * @param column Column to constraint * @param predicate Constraint to apply, or null to add column to the * output without applying constraint */ public final void addConstrainedColumn( RolapStar.Column column, StarColumnPredicate predicate) { assert columnsCache == null; // Sanity check; we should never be adding column constraints // from more than one star if (star == null) { star = column.getStar(); } else { assert (star == column.getStar()); } final int bitPosition = column.getBitPosition(); if (this.constrainedColumnsBitKey.get(bitPosition)) { // This column is already constrained. Unless the value is the // same, or this value or the previous value is null (meaning // unconstrained) the request will never return any results. final StarColumnPredicate prevValue = sparseColumnPredicateList[bitPosition]; if (prevValue == null) { // Previous column was unconstrained. Constrain on new // value. } else if (predicate == null) { // Previous column was constrained. Nothing to do. return; } else if (predicate.equalConstraint(prevValue)) { // Same constraint again. Nothing to do. return; } else { // Different constraint. Request is impossible to satisfy. predicate = null; unsatisfiable = true; } } else { this.constrainedColumnsBitKey.set(bitPosition); numColumns++; } // Note: it is possible and valid for predicate to be null here this.sparseColumnPredicateList[bitPosition] = predicate; } /** * Add compound member (formed via aggregate function) constraint to the * Cell. * * @param compoundBitKey Compound bit key * @param compoundPredicate Compound predicate */ public void addAggregateList( BitKey compoundBitKey, StarPredicate compoundPredicate) { if (compoundPredicateMap == null) { compoundPredicateMap = new TreeMap(); } compoundPredicateMap.put(compoundBitKey, compoundPredicate); } /** * Returns the measure of this cell request. * * @return Measure */ public RolapStar.Measure getMeasure() { return measure; } public RolapStar.Column[] getConstrainedColumns() { if (this.columnsCache == null) { // This is called more than once so caching the value makes sense. check(); } return this.columnsCache; } /** * Returns the BitKey for the list of columns. * * @return BitKey for the list of columns */ public BitKey getConstrainedColumnsBitKey() { return constrainedColumnsBitKey; } /** * Returns the map of compound predicates, or null if empty. * *

NOTE: It is not generally considered good API design to return null * to represent empty collections, but this collection is very often empty * and the the implementation of Collections.emptyMap().keySet().iterator() * is slow, so we optimize for the common case. * * @return predicate map, or null if empty */ SortedMap getCompoundPredicateMap() { return compoundPredicateMap; } /** * Builds the {@link #columnsCache} and {@link #columnBitPositions} * based upon bit key position of the columns. */ private void check() { if (isDirty) { columnsCache = new RolapStar.Column[numColumns]; columnBitPositions = new int[numColumns]; int i = 0; for (int bitPos = constrainedColumnsBitKey.nextSetBit(0); bitPos >= 0; bitPos = constrainedColumnsBitKey.nextSetBit(bitPos + 1)) { columnBitPositions[i] = bitPos; columnsCache[i] = this.star.getColumn(bitPos); i++; } isDirty = false; } } /** * Return the predicate value associated with the given index. Note that * index is different than bit position; if there are three constraints then * the indices are 0, 1, and 2, while the bitPositions could span a larger * range. * *

It is valid for the predicate at a given index to be null (there * should always be a column at that index, but it may not have an * associated predicate). * * @param index Index of the constraint we're looking up * @return predicate value associated with the given index */ public StarColumnPredicate getValueAt(int index) { check(); return sparseColumnPredicateList[columnBitPositions[index]]; } /** * Return the number of column constraints associated with this CellRequest. * * @return number of columns in the CellRequest */ public int getNumValues() { check(); return numColumns; } /** * Returns an array of the values for each column. * *

The caller must check whether this request is satisfiable before * calling this method. May throw {@link NullPointerException} if request * is not satisfiable. * * @pre !isUnsatisfiable() * @return Array of values for each column */ public Object[] getSingleValues() { assert !unsatisfiable; if (singleValues == null) { check(); singleValues = new Object[numColumns]; int i = 0; for (int bitPos : columnBitPositions) { ValueColumnPredicate predicate = (ValueColumnPredicate) sparseColumnPredicateList[bitPos]; singleValues[i++] = predicate.getValue(); } } return singleValues; } /** * Builds a map of column names to values, as specified * by this cell request object. */ public Map getMappedCellValues() { final Map map = new HashMap(); final RolapStar.Column[] columns = this.getConstrainedColumns(); final Object[] values = this.getSingleValues(); for (int i = 0; i < columns.length; i++) { RolapStar.Column column = columns[i]; final Object o = values[i]; map.put( column.getExpression().getGenericExpression(), (Comparable) o); } return map; } /** * Returns whether this cell request is impossible to satisfy. * This occurs when the same column has two or more inconsistent * constraints. * * @return whether this cell request is impossible to satisfy */ public boolean isUnsatisfiable() { return unsatisfiable; } } // End CellRequest.java mondrian-3.4.1/src/main/mondrian/rolap/agg/ValueColumnPredicate.java0000644000175000017500000001264111735330606025345 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.*; import mondrian.rolap.sql.SqlQuery; import java.util.Collection; /** * A constraint which requires a column to have a particular value. * * @author jhyde * @since Nov 2, 2006 */ public class ValueColumnPredicate extends AbstractColumnPredicate implements Comparable { private final Object value; /** * Creates a column constraint. * * @param value Value to constraint the column to. (We require that it is * {@link Comparable} because we will sort the values in order to * generate deterministic SQL.) */ public ValueColumnPredicate( RolapStar.Column constrainedColumn, Object value) { super(constrainedColumn); // assert constrainedColumn != null; assert value != null; assert ! (value instanceof StarColumnPredicate); this.value = value; } /** * Returns the value which the column is compared to. */ public Object getValue() { return value; } public String toString() { return String.valueOf(value); } public boolean equalConstraint(StarPredicate that) { return that instanceof ValueColumnPredicate && getConstrainedColumnBitKey().equals( that.getConstrainedColumnBitKey()) && this.value.equals(((ValueColumnPredicate) that).value); } public int compareTo(Object o) { ValueColumnPredicate that = (ValueColumnPredicate) o; int columnBitKeyComp = getConstrainedColumnBitKey().compareTo( that.getConstrainedColumnBitKey()); // First compare the column bitkeys. if (columnBitKeyComp != 0) { return columnBitKeyComp; } if (this.value instanceof Comparable && that.value instanceof Comparable && this.value.getClass() == that.value.getClass()) { return ((Comparable) this.value).compareTo(that.value); } else { String thisComp = String.valueOf(this.value); String thatComp = String.valueOf(that.value); return thisComp.compareTo(thatComp); } } public boolean equals(Object other) { if (!(other instanceof ValueColumnPredicate)) { return false; } final ValueColumnPredicate that = (ValueColumnPredicate) other; // First compare the column bitkeys. if (!getConstrainedColumnBitKey().equals( that.getConstrainedColumnBitKey())) { return false; } if (value != null) { return value.equals(that.getValue()); } else { return null == that.getValue(); } } public int hashCode() { int hashCode = getConstrainedColumnBitKey().hashCode(); if (value != null) { hashCode = hashCode ^ value.hashCode(); } return hashCode; } public void values(Collection collection) { collection.add(value); } public boolean evaluate(Object value) { return this.value.equals(value); } public void describe(StringBuilder buf) { buf.append(value); } public Overlap intersect(StarColumnPredicate predicate) { throw new UnsupportedOperationException(); } public boolean mightIntersect(StarPredicate other) { return ((StarColumnPredicate) other).evaluate(value); } public StarColumnPredicate minus(StarPredicate predicate) { assert predicate != null; if (((StarColumnPredicate) predicate).evaluate(value)) { return LiteralStarPredicate.FALSE; } else { return this; } } public StarColumnPredicate cloneWithColumn(RolapStar.Column column) { return new ValueColumnPredicate(column, value); } public void toSql(SqlQuery sqlQuery, StringBuilder buf) { final RolapStar.Column column = getConstrainedColumn(); String expr = column.generateExprString(sqlQuery); buf.append(expr); Object key = getValue(); if (key == RolapUtil.sqlNullValue) { buf.append(" is null"); } else { buf.append(" = "); sqlQuery.getDialect().quote(buf, key, column.getDatatype()); } } public BitKey checkInList(BitKey inListLHSBitKey) { // ValueColumn predicate by itself is not using IN list; when it is // one of the children to an OR predicate, then using IN list // is helpful. The later is checked by passing in a bitmap that // represent the LHS or the IN list, i.e. the column that is // constrained by the OR. BitKey inListRHSBitKey = inListLHSBitKey.copy(); if (!getConstrainedColumnBitKey().equals(inListLHSBitKey) || value == RolapUtil.sqlNullValue) { inListRHSBitKey.clear(); } return inListRHSBitKey; } public void toInListSql(SqlQuery sqlQuery, StringBuilder buf) { sqlQuery.getDialect().quote( buf, value, getConstrainedColumn().getDatatype()); } } // End ValueColumnPredicate.java mondrian-3.4.1/src/main/mondrian/rolap/agg/GroupingSetsList.java0000644000175000017500000001661711735330606024566 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.*; import java.util.*; /** * Class for using GROUP BY GROUPING SETS sql query. * *

For example, suppose we have the 3 grouping sets (a, b, c), (a, b) and * (b, c).

    *
  • detailed grouping set -> (a, b, c) *
  • rolled-up grouping sets -> (a, b), (b, c) *
  • rollup columns -> c, a (c for (a, b) and a for (b, c)) *
  • rollup columns bitkey ->
    * (a, b, c) grouping set represented as 0, 0, 0
    * (a, b) grouping set represented as 0, 0, 1
    * (b, c) grouping set represented as 1, 0, 0 *
* * @author Thiyagu * @since 24 May 2007 */ final class GroupingSetsList { private final List rollupColumns; private final List groupingSetsColumns; private final boolean useGroupingSet; private final List rollupColumnsBitKeyList; /** * Maps column index to grouping function index. */ private final int[] columnIndexToGroupingIndexMap; private final List groupingSets; private final int groupingBitKeyIndex; /** * Creates a GroupingSetsList. * *

First element of the groupingSets list should be the detailed * grouping set (default grouping set), followed by grouping sets which can * be rolled-up. * * @param groupingSets List of groups of columns */ public GroupingSetsList(List groupingSets) { this.groupingSets = groupingSets; this.useGroupingSet = groupingSets.size() > 1; if (useGroupingSet) { this.groupingSetsColumns = getGroupingColumnsList(groupingSets); this.rollupColumns = findRollupColumns(); int arity = getDefaultColumns().length; int segmentLength = getDefaultSegments().size(); this.groupingBitKeyIndex = arity + segmentLength; } else { this.groupingSetsColumns = Collections.emptyList(); this.rollupColumns = Collections.emptyList(); this.groupingBitKeyIndex = -1; } this.columnIndexToGroupingIndexMap = loadRollupIndex(); this.rollupColumnsBitKeyList = loadGroupingColumnBitKeys(); } List getGroupingColumnsList( List groupingSets) { List groupingColumns = new ArrayList(); for (GroupingSet aggBatchDetail : groupingSets) { groupingColumns.add( aggBatchDetail.segment0.getColumns()); } return groupingColumns; } public int getGroupingBitKeyIndex() { return groupingBitKeyIndex; } public List getRollupColumns() { return rollupColumns; } public List getGroupingSetsColumns() { return groupingSetsColumns; } public List getRollupColumnsBitKeyList() { return rollupColumnsBitKeyList; } private List loadGroupingColumnBitKeys() { if (!useGroupingSet) { return Collections.singletonList(BitKey.EMPTY); } final List rollupColumnsBitKeyList = new ArrayList(); final int bitKeyLength = getDefaultColumns().length; for (RolapStar.Column[] groupingSetColumns : groupingSetsColumns) { BitKey groupingColumnsBitKey = BitKey.Factory.makeBitKey(bitKeyLength); Set columns = new HashSet( Arrays.asList(groupingSetColumns)); int bitPosition = 0; for (RolapStar.Column rollupColumn : rollupColumns) { if (!columns.contains(rollupColumn)) { groupingColumnsBitKey.set(bitPosition); } bitPosition++; } rollupColumnsBitKeyList.add(groupingColumnsBitKey); } return rollupColumnsBitKeyList; } private int[] loadRollupIndex() { if (!useGroupingSet) { return new int[0]; } RolapStar.Column[] detailedColumns = getDefaultColumns(); int[] columnIndexToGroupingIndexMap = new int[detailedColumns.length]; for (int columnIndex = 0; columnIndex < detailedColumns.length; columnIndex++) { int rollupIndex = rollupColumns.indexOf(detailedColumns[columnIndex]); columnIndexToGroupingIndexMap[columnIndex] = rollupIndex; } return columnIndexToGroupingIndexMap; } private List findRollupColumns() { Set rollupSet = new TreeSet( RolapStar.ColumnComparator.instance); for (RolapStar.Column[] groupingSetColumn : groupingSetsColumns) { Set summaryColumns = new HashSet( Arrays.asList(groupingSetColumn)); for (RolapStar.Column column : getDefaultColumns()) { if (!summaryColumns.contains(column)) { rollupSet.add(column); } } } return new ArrayList(rollupSet); } public boolean useGroupingSets() { return useGroupingSet; } public int findGroupingFunctionIndex(int columnIndex) { return columnIndexToGroupingIndexMap[columnIndex]; } public SegmentAxis[] getDefaultAxes() { return getDefaultGroupingSet().getAxes(); } public StarColumnPredicate[] getDefaultPredicates() { return getDefaultGroupingSet().getPredicates(); } protected GroupingSet getDefaultGroupingSet() { return groupingSets.get(0); } public RolapStar.Column[] getDefaultColumns() { return getDefaultGroupingSet().segment0.getColumns(); } public List getDefaultSegments() { return getDefaultGroupingSet().getSegments(); } public BitKey getDefaultLevelBitKey() { return getDefaultGroupingSet().getLevelBitKey(); } public BitKey getDefaultMeasureBitKey() { return getDefaultGroupingSet().getMeasureBitKey(); } public RolapStar getStar() { return getDefaultGroupingSet().segment0.getStar(); } public List getGroupingSets() { return groupingSets; } public List getRollupGroupingSets() { return groupingSets.subList(1, groupingSets.size()); } /** * Collection of {@link mondrian.rolap.agg.SegmentDataset} that have the * same dimensionality and identical axis values. A cohort contains * corresponding cell values for set of measures. */ static class Cohort { final List segmentDatasetList; final SegmentAxis[] axes; // workspace final int[] pos; Cohort( List segmentDatasetList, SegmentAxis[] axes) { this.segmentDatasetList = segmentDatasetList; this.axes = axes; this.pos = new int[axes.length]; } } } // End GroupingSetsList.java mondrian-3.4.1/src/main/mondrian/rolap/agg/AggregationKey.java0000644000175000017500000001512311735330606024170 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.olap.Util; import mondrian.rolap.*; import mondrian.rolap.sql.SqlQuery; import java.util.*; /** * Column context that an Aggregation is computed for. * *

Column context has two components:

*
    *
  • The column constraints which define the dimentionality of an * Aggregation
  • *
  • An orthogonal context for which the measures are defined. This context * is sometimes referred to as the compound member predicates, and usually of * the shape: *
    OR(AND(column predicates))
  • *
* *

Any column is only used in either column context or compound context, not * both.

* * @author Rushan Chen */ public class AggregationKey { /** * This is needed because for a Virtual Cube: two CellRequests * could have the same BitKey but have different underlying * base cubes. Without this, one get the result in the * SegmentArrayQuerySpec addMeasure Util.assertTrue being * triggered (which is what happened). */ private final RolapStar star; private final BitKey constrainedColumnsBitKey; /** * List of StarPredicate (representing the predicate * defining the compound member). * *

In sorted order of BitKey. This ensures that the map is deternimistic * (otherwise different runs generate SQL statements in different orders), * and speeds up comparison. */ final List compoundPredicateList; private int hashCode; /** * Creates an AggregationKey. * * @param request Cell request */ public AggregationKey(CellRequest request) { this.constrainedColumnsBitKey = request.getConstrainedColumnsBitKey(); this.star = request.getMeasure().getStar(); Map compoundPredicateMap = request.getCompoundPredicateMap(); this.compoundPredicateList = compoundPredicateMap == null ? Collections.emptyList() : new ArrayList(compoundPredicateMap.values()); } public final int computeHashCode() { return computeHashCode( constrainedColumnsBitKey, star, compoundPredicateList == null ? null : new AbstractList() { public BitKey get(int index) { return compoundPredicateList.get(index) .getConstrainedColumnBitKey(); } public int size() { return compoundPredicateList.size(); } }); } public static int computeHashCode( BitKey constrainedColumnsBitKey, RolapStar star, Collection compoundPredicateBitKeys) { int retCode = constrainedColumnsBitKey.hashCode(); retCode = Util.hash(retCode, star); return Util.hash(retCode, compoundPredicateBitKeys); } public int hashCode() { if (hashCode == 0) { // Compute hash code on first use. It is expensive to compute, and // not always required. hashCode = computeHashCode(); } return hashCode; } public boolean equals(Object other) { if (!(other instanceof AggregationKey)) { return false; } final AggregationKey that = (AggregationKey) other; return constrainedColumnsBitKey.equals(that.constrainedColumnsBitKey) && star.equals(that.star) && equal(compoundPredicateList, that.compoundPredicateList); } /** * Returns whether two lists of compound predicates are equal. * * @param list1 First compound predicate map * @param list2 Second compound predicate map * @return Whether compound predicate maps are equal */ static boolean equal( final List list1, final List list2) { if (list1 == null) { return list2 == null; } if (list2 == null) { return false; } final int size = list1.size(); if (size != list2.size()) { return false; } for (int i = 0; i < size; i++) { StarPredicate pred1 = list1.get(i); StarPredicate pred2 = list2.get(i); if (!pred1.equalConstraint(pred2)) { return false; } } return true; } public String toString() { return star.getFactTable().getTableName() + " " + constrainedColumnsBitKey.toString() + "\n" + (compoundPredicateList == null ? "{}" : compoundPredicateList.toString()); } /** * Returns the bitkey of columns that constrain this aggregation. * * @return Bitkey of contraining columns */ public final BitKey getConstrainedColumnsBitKey() { return constrainedColumnsBitKey; } /** * Returns the star. * * @return Star */ public final RolapStar getStar() { return star; } /** * Returns the list of compound predicates. * * @return list of predicates */ public List getCompoundPredicateList() { return compoundPredicateList; } /** * Returns a list of compound predicates, expressed as SQL strings. * * @param star Star * @param compoundPredicateList Predicate list * @return list of predicate strings */ public static List getCompoundPredicateStringList( RolapStar star, List compoundPredicateList) { if (compoundPredicateList.isEmpty()) { return Collections.emptyList(); } final List cp = new ArrayList(); final StringBuilder buf = new StringBuilder(); for (StarPredicate compoundPredicate : compoundPredicateList) { buf.setLength(0); SqlQuery query = new SqlQuery( star.getSqlQueryDialect()); compoundPredicate.toSql(query, buf); cp.add(buf.toString()); } return cp; } } // End AggregationKey.java mondrian-3.4.1/src/main/mondrian/rolap/agg/MemberColumnPredicate.java0000644000175000017500000000357411735330606025505 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2007 Pentaho // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.*; import java.util.List; /** * Column constraint defined by a member. * * @author jhyde * @since Mar 16, 2006 */ public class MemberColumnPredicate extends ValueColumnPredicate { private final RolapMember member; /** * Creates a MemberColumnPredicate * * @param column Constrained column * @param member Member to constrain column to; must not be null */ public MemberColumnPredicate(RolapStar.Column column, RolapMember member) { super(column, member.getKey()); this.member = member; } // for debug public String toString() { return member.getUniqueName(); } public List getConstrainedColumnList() { return super.getConstrainedColumnList(); } /** * Returns the Member. * * @return Returns the Member, not null. */ public RolapMember getMember() { return member; } public boolean equals(Object other) { if (!(other instanceof MemberColumnPredicate)) { return false; } final MemberColumnPredicate that = (MemberColumnPredicate) other; return member.equals(that.getMember()); } public int hashCode() { return member.hashCode(); } public void describe(StringBuilder buf) { buf.append(member.getUniqueName()); } public StarColumnPredicate cloneWithColumn(RolapStar.Column column) { return new MemberColumnPredicate(column, member); } } // End MemberColumnPredicate.java mondrian-3.4.1/src/main/mondrian/rolap/agg/MemberTuplePredicate.java0000644000175000017500000002205211735330606025331 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.olap.Util; import mondrian.rolap.*; import mondrian.rolap.sql.SqlQuery; import java.util.*; /** * Predicate which constrains a column to a particular member, or a range * above or below a member, or a range between two members. * * @author jhyde */ public class MemberTuplePredicate implements StarPredicate { private final Bound[] bounds; private final List columnList; private BitKey columnBitKey; /** * Creates a MemberTuplePredicate which evaluates to true for a given * range of members. * *

The range can be open above or below, but at least one bound is * required. * * @param baseCube base cube for virtual members * @param lower Member which forms the lower bound, or null if range is * open below * @param lowerStrict Whether lower bound of range is strict * @param upper Member which forms the upper bound, or null if range is * open above * @param upperStrict Whether upper bound of range is strict */ public MemberTuplePredicate( RolapCube baseCube, RolapMember lower, boolean lowerStrict, RolapMember upper, boolean upperStrict) { columnBitKey = null; this.columnList = computeColumnList(lower != null ? lower : upper, baseCube); if (lower == null) { assert upper != null; bounds = new Bound[] { new Bound(upper, upperStrict ? RelOp.LT : RelOp.LE) }; } else if (upper == null) { bounds = new Bound[] { new Bound(lower, lowerStrict ? RelOp.GT : RelOp.GE) }; } else { bounds = new Bound[] { new Bound(lower, lowerStrict ? RelOp.GT : RelOp.GE), new Bound(upper, upperStrict ? RelOp.LT : RelOp.LE) }; } } /** * Creates a MemberTuplePredicate which evaluates to true for a given * member. * * @param baseCube base cube for virtual members * @param member Member */ public MemberTuplePredicate(RolapCube baseCube, RolapCubeMember member) { this.columnList = computeColumnList(member, baseCube); this.bounds = new Bound[] { new Bound(member, RelOp.EQ) }; } public int hashCode() { return this.columnList.hashCode() * 31 + Arrays.hashCode(this.bounds) * 31; } public boolean equals(Object obj) { if (obj instanceof MemberTuplePredicate) { MemberTuplePredicate that = (MemberTuplePredicate) obj; return this.columnList.equals(that.columnList) && Arrays.equals(this.bounds, that.bounds); } else { return false; } } private List computeColumnList( RolapMember member, RolapCube baseCube) { List columnList = new ArrayList(); while (true) { RolapLevel level = member.getLevel(); RolapStar.Column column = null; if (level instanceof RolapCubeLevel) { column = ((RolapCubeLevel)level) .getBaseStarKeyColumn(baseCube); } else { (new Exception()).printStackTrace(); } if (columnBitKey == null) { columnBitKey = BitKey.Factory.makeBitKey( column.getStar().getColumnCount()); columnBitKey.clear(); } columnBitKey.set(column.getBitPosition()); columnList.add(0, column); if (level.isUnique()) { return columnList; } member = member.getParentMember(); } } /** * Returns a list of constrained columns. * * @return List of constrained columns */ public List getConstrainedColumnList() { return columnList; } public BitKey getConstrainedColumnBitKey() { return columnBitKey; } public boolean equalConstraint(StarPredicate that) { throw new UnsupportedOperationException(); } public StarPredicate minus(StarPredicate predicate) { throw new UnsupportedOperationException(); } public StarPredicate or(StarPredicate predicate) { throw new UnsupportedOperationException(); } public StarPredicate and(StarPredicate predicate) { throw new UnsupportedOperationException(); } /** * Evaluates a constraint against a list of values. * * @param valueList List of values, one for each constrained column * @return Whether constraint holds for given set of values */ public boolean evaluate(List valueList) { for (Bound bound : bounds) { for (int k = 0; k < bound.values.length; ++k) { Object value = valueList.get(k); if (value == WILDCARD) { return false; } Object boundValue = bound.values[k]; RelOp relOp = bound.relOps[k]; int c = Util.compareKey(value, boundValue); switch (relOp) { case GT: if (c > 0) { break; } else { return false; } case GE: if (c > 0) { return true; } else if (c == 0) { break; } else { return false; } case LT: if (c < 0) { break; } else { return false; } case LE: if (c < 0) { return true; } else if (c == 0) { break; } else { return false; } } } } return true; } public void describe(StringBuilder buf) { int k = 0; for (Bound bound : bounds) { if (k++ > 0) { buf.append(" AND "); } buf.append(bound.relOps[bound.relOps.length - 1].getOp()); buf.append(' '); buf.append(bound.member); } } private enum RelOp { LT("<"), LE("<="), GT(">"), GE(">="), EQ("="); private final String op; RelOp(String op) { this.op = op; } String getOp() { return op; } /** * If this is a strict operator (LT, GT) returns the non-strict * equivalent (LE, GE); otherwise returns this operator. * * @return less strict version of this operator */ public RelOp desctrict() { switch (this) { case GT: return RelOp.GE; case LT: return RelOp.LE; default: return this; } } } private static class Bound { private final RolapMember member; private final Object[] values; private final RelOp[] relOps; Bound(RolapMember member, RelOp relOp) { this.member = member; List valueList = new ArrayList(); List relOpList = new ArrayList(); while (true) { valueList.add(0, member.getKey()); relOpList.add(0, relOp); if (member.getLevel().isUnique()) { break; } member = member.getParentMember(); relOp = relOp.desctrict(); } this.values = valueList.toArray(new Object[valueList.size()]); this.relOps = relOpList.toArray(new RelOp[relOpList.size()]); } public int hashCode() { int h = member.hashCode(); h = h * 31 + Arrays.hashCode(values); h = h * 31 + Arrays.hashCode(relOps); return h; } public boolean equals(Object obj) { if (obj instanceof Bound) { Bound that = (Bound) obj; return this.member.equals(that.member) && Arrays.equals(this.values, that.values) && Arrays.equals(this.relOps, that.relOps); } else { return false; } } } public void toSql(SqlQuery sqlQuery, StringBuilder buf) { throw Util.needToImplement(this); } } // End MemberTuplePredicate.java mondrian-3.4.1/src/main/mondrian/rolap/agg/AndPredicate.java0000644000175000017500000001566711735330606023630 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.*; import mondrian.rolap.sql.SqlQuery; import java.util.*; /** * Predicate which is the intersection of a list of predicates. It evaluates to * true if all of the predicates evaluate to true. * * @see OrPredicate * * @author jhyde */ public class AndPredicate extends ListPredicate { public AndPredicate(List predicateList) { super(predicateList); } public boolean evaluate(List valueList) { // NOTE: If we know that every predicate in the list is a // ValueColumnPredicate, we could optimize the evaluate method by // building a value list at construction time. But it's a tradeoff, // considering the extra time and space required. for (StarPredicate childPredicate : children) { if (childPredicate.evaluate(valueList)) { return true; } } return false; } public StarPredicate and(StarPredicate predicate) { if (predicate instanceof AndPredicate) { ListPredicate that = (ListPredicate) predicate; final List list = new ArrayList(children); list.addAll(that.children); return new AndPredicate(list); } else { final List list = new ArrayList(children); list.add(predicate); return new AndPredicate(list); } } public StarPredicate or(StarPredicate predicate) { List list = new ArrayList(); list.add(this); list.add(predicate); return new OrPredicate(list); } public BitKey checkInList(SqlQuery sqlQuery, BitKey inListLHSBitKey) { // AND predicate by itself is not using IN list; when it is // one of the children to an OR predicate, then using IN list // is helpful. The later is checked by passing in a bitmap that // represent the LHS or the IN list, i.e. the columns that are // constrained by the OR. // If the child predicates contains null values, those predicates cannot // be translated as IN list; however, the rest of the child predicates // might still be translated to IN. For example, neither of the two AND // conditions below(part of an OR list) can be translated using IN list, // covering all the levels // // (null, null, San Francisco) // (null, null, New York) // // However, after extracting the null part, they can be translated to: // // (country is null AND state is null AND city IN ("San Fancisco", "New // York")) // // which is still more compact than the default AND/OR translation: // // (country is null AND state is null AND city = "San Francisco") OR // (country is null AND state is null AND city = "New York") // // This method will mark all the columns that can be translated as part // of IN list, so that similar predicates can be grouped together to // form partial IN list sql. By default, all columns constrained by this // predicates can be part of an IN list. // // This is very similar to the logic in // SqlConstraintUtil.generateMultiValueInExpr(). The only difference // being that the predicates here are all "flattened" so the hierarchy // information is no longer available to guide the grouping of // predicates with common parents. So some optimization possible in // generateMultiValueInExpr() is not tried here, as they require // implementing "longest common prefix" algorithm which is an overkill. BitKey inListRHSBitKey = inListLHSBitKey.copy(); if (!getConstrainedColumnBitKey().equals(inListLHSBitKey) || (children.size() > 1 && !sqlQuery.getDialect().supportsMultiValueInExpr())) { inListRHSBitKey.clear(); } else { for (StarPredicate predicate : children) { // If any predicate requires comparison to null value, cannot // use IN list for this predicate. if (predicate instanceof ValueColumnPredicate) { ValueColumnPredicate columnPred = ((ValueColumnPredicate) predicate); if (columnPred.getValue() == RolapUtil.sqlNullValue) { // This column predicate cannot be translated to IN inListRHSBitKey.clear( columnPred.getConstrainedColumn().getBitPosition()); } // else do nothing because this column predicate can be // translated to IN } else { inListRHSBitKey.clear(); break; } } } return inListRHSBitKey; } /* * Generate value list for this predicate to be used in an IN-list * sql predicate. * * The values in a multi-column IN list predicates are generated in the * same order, based on the bit position from the columnBitKey. * */ public void toInListSql( SqlQuery sqlQuery, StringBuilder buf, BitKey inListRHSBitKey) { boolean firstValue = true; buf.append("("); /* * Arranging children according to the bit position. This is required * as RHS of IN list needs to list the column values in the same order. */ Set sortedPredicates = new TreeSet(); for (StarPredicate predicate : children) { // inListPossible() checks gaurantees that predicate is of type // ValueColumnPredicate assert predicate instanceof ValueColumnPredicate; if (inListRHSBitKey.get( ((ValueColumnPredicate) predicate).getConstrainedColumn() .getBitPosition())) { sortedPredicates.add((ValueColumnPredicate)predicate); } } for (ValueColumnPredicate predicate : sortedPredicates) { if (firstValue) { firstValue = false; } else { buf.append(", "); } sqlQuery.getDialect().quote( buf, predicate.getValue(), predicate.getConstrainedColumn().getDatatype()); } buf.append(")"); } protected String getOp() { return "and"; } } // End AndPredicate.java mondrian-3.4.1/src/main/mondrian/rolap/agg/DrillThroughCellRequest.java0000644000175000017500000000371411735330606026053 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2012-2012 Pentaho // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.rolap.RolapStar; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Subclass of {@link CellRequest} that allows to specify * which columns and measures to return as part of the ResultSet * which we return to the client. */ public class DrillThroughCellRequest extends CellRequest { private final List drillThroughColumns = new ArrayList(); private final List drillThroughMeasures = new ArrayList(); public DrillThroughCellRequest( RolapStar.Measure measure, boolean extendedContext) { super(measure, extendedContext, true); } public void addDrillThroughColumn(RolapStar.Column column) { this.drillThroughColumns.add(column); } public boolean includeInSelect(RolapStar.Column column) { if (drillThroughColumns.size() == 0 && drillThroughMeasures.size() == 0) { return true; } return drillThroughColumns.contains(column); } public void addDrillThroughMeasure(RolapStar.Measure measure) { this.drillThroughMeasures.add(measure); } public boolean includeInSelect(RolapStar.Measure measure) { if (drillThroughColumns.size() == 0 && drillThroughMeasures.size() == 0) { return true; } return drillThroughMeasures.contains(measure); } public List getDrillThroughMeasures() { return Collections.unmodifiableList(drillThroughMeasures); } } // End DrillThroughCellRequest.java mondrian-3.4.1/src/main/mondrian/rolap/agg/DenseObjectSegmentBody.java0000644000175000017500000000260511735330606025617 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.agg; import mondrian.util.Pair; import java.util.*; /** * Implementation of a segment body which stores the data inside * a dense array of Java objects. * * @author LBoudreau */ class DenseObjectSegmentBody extends AbstractSegmentBody { private static final long serialVersionUID = -3558427982849392173L; private final Object[] values; /** * Creates a DenseObjectSegmentBody. * *

Stores the given array of cell values; caller must not modify it * afterwards.

* * @param values Cell values * @param axes Axes */ DenseObjectSegmentBody( Object[] values, List, Boolean>> axes) { super(axes); this.values = values; } @Override public Object getValueArray() { return values; } @Override protected Object getObject(int i) { return values[i]; } @Override protected int getSize() { return values.length; // TODO: subtract number of nulls? } } // End DenseObjectSegmentBody.java mondrian-3.4.1/src/main/mondrian/rolap/agg/DenseSegmentDataset.java0000644000175000017500000001102011735330606025147 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. // // jhyde, 21 March, 2002 */ package mondrian.rolap.agg; import mondrian.rolap.CellKey; import java.util.Iterator; import java.util.Map; /** * A DenseSegmentDataset is a means of storing segment values * which is suitable when most of the combinations of keys have a value * present. * *

The storage requirements are as follows. Table requires 1 word per * cell.

* * @author jhyde * @since 21 March, 2002 */ abstract class DenseSegmentDataset implements SegmentDataset { private final SegmentAxis[] axes; protected final int[] axisMultipliers; /** * Creates a DenseSegmentDataset. * * @param axes Segment axes, containing actual column values */ DenseSegmentDataset(SegmentAxis[] axes) { this.axes = axes; this.axisMultipliers = computeAxisMultipliers(); } private int[] computeAxisMultipliers() { final int[] axisMultipliers = new int[axes.length]; int multiplier = 1; for (int i = axes.length - 1; i >= 0; --i) { final SegmentAxis axis = axes[i]; axisMultipliers[i] = multiplier; multiplier *= axis.getKeys().length; } return axisMultipliers; } public final double getBytes() { // assume a slot, key, and value are each 4 bytes return getSize() * 12; } public Iterator> iterator() { return new DenseSegmentDatasetIterator(); } protected abstract Object getObject(int i); protected final int getOffset(int[] keys) { return CellKey.Generator.getOffset(keys, axisMultipliers); } protected final int getOffset(Object[] keys) { int offset = 0; outer: for (int i = 0; i < keys.length; i++) { SegmentAxis axis = axes[i]; Object[] ks = axis.getKeys(); final int axisLength = ks.length; offset *= axisLength; Object value = keys[i]; for (int j = 0; j < axisLength; j++) { if (ks[j].equals(value)) { offset += j; continue outer; } } return -1; // not found } return offset; } public Object getObject(CellKey pos) { throw new UnsupportedOperationException(); } public int getInt(CellKey pos) { throw new UnsupportedOperationException(); } public double getDouble(CellKey pos) { throw new UnsupportedOperationException(); } protected abstract int getSize(); /** * Iterator over a DenseSegmentDataset. * *

This is a 'cheap' implementation * which doesn't allocate a new Entry every step: it just returns itself. * The Entry must therefore be used immediately, before calling * {@link #next()} again. */ private class DenseSegmentDatasetIterator implements Iterator>, Map.Entry { private int i = -1; private final int[] ordinals; DenseSegmentDatasetIterator() { ordinals = new int[axes.length]; ordinals[ordinals.length - 1] = -1; } public boolean hasNext() { return i < getSize() - 1; } public Map.Entry next() { ++i; int k = ordinals.length - 1; while (k >= 0) { if (ordinals[k] < axes[k].getKeys().length - 1) { ++ordinals[k]; break; } else { ordinals[k] = 0; --k; } } return this; } // implement Iterator public void remove() { throw new UnsupportedOperationException(); } // implement Entry public CellKey getKey() { return CellKey.Generator.newCellKey(ordinals); } // implement Entry public Object getValue() { return getObject(i); } // implement Entry public Object setValue(Object value) { throw new UnsupportedOperationException(); } } } // End DenseSegmentDataset.java mondrian-3.4.1/src/main/mondrian/rolap/RolapHierarchy.java0000644000175000017500000015275011735330606023456 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.mdx.*; import mondrian.olap.*; import mondrian.olap.DimensionType; import mondrian.olap.LevelType; import mondrian.olap.Role.HierarchyAccess; import mondrian.olap.fun.*; import mondrian.olap.type.*; import mondrian.resource.MondrianResource; import mondrian.rolap.RestrictedMemberReader.MultiCardinalityDefaultMember; import mondrian.rolap.sql.SqlQuery; import mondrian.spi.CellFormatter; import mondrian.spi.impl.Scripts; import mondrian.util.UnionIterator; import org.apache.log4j.Logger; import java.io.PrintWriter; import java.util.*; /** * RolapHierarchy implements {@link Hierarchy} for a ROLAP database. * *

The ordinal of a hierarchy within a particular cube is found by * calling {@link #getOrdinalInCube()}. Ordinals are contiguous and zero-based. * Zero is always the [Measures] dimension. * *

NOTE: It is only valid to call that method on the measures hierarchy, and * on members of the {@link RolapCubeHierarchy} subclass. When the measures * hierarchy is of that class, we will move the method down.) * * @author jhyde * @since 10 August, 2001 */ public class RolapHierarchy extends HierarchyBase { private static final Logger LOGGER = Logger.getLogger(RolapHierarchy.class); /** * The raw member reader. For a member reader which incorporates access * control and deals with hidden members (if the hierarchy is ragged), use * {@link #createMemberReader(Role)}. */ private MemberReader memberReader; protected MondrianDef.Hierarchy xmlHierarchy; private String memberReaderClass; protected MondrianDef.RelationOrJoin relation; private Member defaultMember; private String defaultMemberName; private RolapNullMember nullMember; private String sharedHierarchyName; private String uniqueKeyLevelName; private Exp aggregateChildrenExpression; /** * The level that the null member belongs too. */ protected final RolapLevel nullLevel; /** * The 'all' member of this hierarchy. This exists even if the hierarchy * does not officially have an 'all' member. */ private RolapMemberBase allMember; private static final String ALL_LEVEL_CARDINALITY = "1"; private final Map annotationMap; final RolapHierarchy closureFor; /** * Creates a hierarchy. * * @param dimension Dimension * @param subName Name of this hierarchy * @param hasAll Whether hierarchy has an 'all' member * @param closureFor Hierarchy for which the new hierarchy is a closure; * null for regular hierarchies */ RolapHierarchy( RolapDimension dimension, String subName, String caption, boolean visible, String description, boolean hasAll, RolapHierarchy closureFor, Map annotationMap) { super(dimension, subName, caption, visible, description, hasAll); this.annotationMap = annotationMap; this.allLevelName = "(All)"; this.allMemberName = subName != null && (MondrianProperties.instance().SsasCompatibleNaming.get() || name.equals(subName + "." + subName)) ? "All " + subName + "s" : "All " + name + "s"; this.closureFor = closureFor; if (hasAll) { this.levels = new RolapLevel[1]; this.levels[0] = new RolapLevel( this, this.allLevelName, null, true, null, 0, null, null, null, null, null, null, null, RolapProperty.emptyArray, RolapLevel.FLAG_ALL | RolapLevel.FLAG_UNIQUE, null, null, RolapLevel.HideMemberCondition.Never, LevelType.Regular, "", Collections.emptyMap()); } else { this.levels = new RolapLevel[0]; } // The null member belongs to a level with very similar properties to // the 'all' level. this.nullLevel = new RolapLevel( this, this.allLevelName, null, true, null, 0, null, null, null, null, null, null, null, RolapProperty.emptyArray, RolapLevel.FLAG_ALL | RolapLevel.FLAG_UNIQUE, null, null, RolapLevel.HideMemberCondition.Never, LevelType.Null, "", Collections.emptyMap()); } /** * Creates a RolapHierarchy. * * @param dimension the dimension this hierarchy belongs to * @param xmlHierarchy the xml object defining this hierarchy * @param xmlCubeDimension the xml object defining the cube * dimension for this object */ RolapHierarchy( RolapDimension dimension, MondrianDef.Hierarchy xmlHierarchy, MondrianDef.CubeDimension xmlCubeDimension) { this( dimension, xmlHierarchy.name, xmlHierarchy.caption, xmlHierarchy.visible, xmlHierarchy.description, xmlHierarchy.hasAll, null, createAnnotationMap(xmlHierarchy.annotations)); assert !(this instanceof RolapCubeHierarchy); this.xmlHierarchy = xmlHierarchy; this.relation = xmlHierarchy.relation; if (xmlHierarchy.relation instanceof MondrianDef.InlineTable) { this.relation = RolapUtil.convertInlineTableToRelation( (MondrianDef.InlineTable) xmlHierarchy.relation, getRolapSchema().getDialect()); } this.memberReaderClass = xmlHierarchy.memberReaderClass; this.uniqueKeyLevelName = xmlHierarchy.uniqueKeyLevelName; // Create an 'all' level even if the hierarchy does not officially // have one. if (xmlHierarchy.allMemberName != null) { this.allMemberName = xmlHierarchy.allMemberName; } if (xmlHierarchy.allLevelName != null) { this.allLevelName = xmlHierarchy.allLevelName; } RolapLevel allLevel = new RolapLevel( this, this.allLevelName, null, true, null, 0, null, null, null, null, null, null, null, RolapProperty.emptyArray, RolapLevel.FLAG_ALL | RolapLevel.FLAG_UNIQUE, null, null, RolapLevel.HideMemberCondition.Never, LevelType.Regular, ALL_LEVEL_CARDINALITY, Collections.emptyMap()); allLevel.init(xmlCubeDimension); this.allMember = new RolapMemberBase( null, allLevel, null, allMemberName, Member.MemberType.ALL); // assign "all member" caption if (xmlHierarchy.allMemberCaption != null && xmlHierarchy.allMemberCaption.length() > 0) { this.allMember.setCaption(xmlHierarchy.allMemberCaption); } this.allMember.setOrdinal(0); if (xmlHierarchy.levels.length == 0) { throw MondrianResource.instance().HierarchyHasNoLevels.ex( getUniqueName()); } Set levelNameSet = new HashSet(); for (MondrianDef.Level level : xmlHierarchy.levels) { if (!levelNameSet.add(level.name)) { throw MondrianResource.instance().HierarchyLevelNamesNotUnique .ex( getUniqueName(), level.name); } } // If the hierarchy has an 'all' member, the 'all' level is level 0. if (hasAll) { this.levels = new RolapLevel[xmlHierarchy.levels.length + 1]; this.levels[0] = allLevel; for (int i = 0; i < xmlHierarchy.levels.length; i++) { final MondrianDef.Level xmlLevel = xmlHierarchy.levels[i]; if (xmlLevel.getKeyExp() == null && xmlHierarchy.memberReaderClass == null) { throw MondrianResource.instance() .LevelMustHaveNameExpression.ex(xmlLevel.name); } levels[i + 1] = new RolapLevel(this, i + 1, xmlLevel); } } else { this.levels = new RolapLevel[xmlHierarchy.levels.length]; for (int i = 0; i < xmlHierarchy.levels.length; i++) { levels[i] = new RolapLevel(this, i, xmlHierarchy.levels[i]); } } if (xmlCubeDimension instanceof MondrianDef.DimensionUsage) { String sharedDimensionName = ((MondrianDef.DimensionUsage) xmlCubeDimension).source; this.sharedHierarchyName = sharedDimensionName; if (subName != null) { this.sharedHierarchyName += "." + subName; // e.g. "Time.Weekly" } } else { this.sharedHierarchyName = null; } if (xmlHierarchy.relation != null && xmlHierarchy.memberReaderClass != null) { throw MondrianResource.instance() .HierarchyMustNotHaveMoreThanOneSource.ex(getUniqueName()); } if (!Util.isEmpty(xmlHierarchy.caption)) { setCaption(xmlHierarchy.caption); } defaultMemberName = xmlHierarchy.defaultMember; } public static Map createAnnotationMap( MondrianDef.Annotations annotations) { if (annotations == null || annotations.array == null || annotations.array.length == 0) { return Collections.emptyMap(); } // Use linked hash map because it retains order. final Map map = new LinkedHashMap(); for (MondrianDef.Annotation annotation : annotations.array) { final String name = annotation.name; final String value = annotation.cdata; map.put( annotation.name, new Annotation() { public String getName() { return name; } public Object getValue() { return value; } }); } return map; } protected Logger getLogger() { return LOGGER; } public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof RolapHierarchy)) { return false; } RolapHierarchy that = (RolapHierarchy)o; if (sharedHierarchyName == null || that.sharedHierarchyName == null) { return false; } else { return sharedHierarchyName.equals(that.sharedHierarchyName) && getUniqueName().equals(that.getUniqueName()); } } protected int computeHashCode() { return super.computeHashCode() ^ (sharedHierarchyName == null ? 0 : sharedHierarchyName.hashCode()); } /** * Initializes a hierarchy within the context of a cube. */ void init(MondrianDef.CubeDimension xmlDimension) { // first create memberReader if (this.memberReader == null) { this.memberReader = getRolapSchema().createMemberReader( sharedHierarchyName, this, memberReaderClass); } for (Level level : levels) { ((RolapLevel) level).init(xmlDimension); } if (defaultMemberName != null) { List uniqueNameParts; if (defaultMemberName.contains("[")) { uniqueNameParts = Util.parseIdentifier(defaultMemberName); } else { uniqueNameParts = Collections.singletonList( new Id.Segment(defaultMemberName, Id.Quoting.UNQUOTED)); } // First look up from within this hierarchy. Works for unqualified // names, e.g. [USA].[CA]. defaultMember = (Member) Util.lookupCompound( getRolapSchema().getSchemaReader(), this, uniqueNameParts, false, Category.Member, MatchType.EXACT); // Next look up within global context. Works for qualified names, // e.g. [Store].[USA].[CA] or [Time].[Weekly].[1997].[Q2]. if (defaultMember == null) { defaultMember = (Member) Util.lookupCompound( getRolapSchema().getSchemaReader(), new DummyElement(), uniqueNameParts, false, Category.Member, MatchType.EXACT); } if (defaultMember == null) { throw Util.newInternal( "Can not find Default Member with name \"" + defaultMemberName + "\" in Hierarchy \"" + getName() + "\""); } } } void setMemberReader(MemberReader memberReader) { this.memberReader = memberReader; } MemberReader getMemberReader() { return memberReader; } public Map getAnnotationMap() { return annotationMap; } RolapLevel newMeasuresLevel() { RolapLevel level = new RolapLevel( this, "MeasuresLevel", null, true, null, this.levels.length, null, null, null, null, null, null, null, RolapProperty.emptyArray, 0, null, null, RolapLevel.HideMemberCondition.Never, LevelType.Regular, "", Collections.emptyMap()); this.levels = Util.append(this.levels, level); return level; } /** * If this hierarchy has precisely one table, returns that table; * if this hierarchy has no table, return the cube's fact-table; * otherwise, returns null. */ MondrianDef.Relation getUniqueTable() { if (relation instanceof MondrianDef.Relation) { return (MondrianDef.Relation) relation; } else if (relation instanceof MondrianDef.Join) { return null; } else { throw Util.newInternal( "hierarchy's relation is a " + relation.getClass()); } } boolean tableExists(String tableName) { return (relation != null) && getTable(tableName, relation) != null; } MondrianDef.Relation getTable(String tableName) { return relation == null ? null : getTable(tableName, relation); } private static MondrianDef.Relation getTable( String tableName, MondrianDef.RelationOrJoin relationOrJoin) { if (relationOrJoin instanceof MondrianDef.Relation) { MondrianDef.Relation relation = (MondrianDef.Relation) relationOrJoin; if (relation.getAlias().equals(tableName)) { return relation; } else { return null; } } else { MondrianDef.Join join = (MondrianDef.Join) relationOrJoin; MondrianDef.Relation rel = getTable(tableName, join.left); if (rel != null) { return rel; } return getTable(tableName, join.right); } } public RolapSchema getRolapSchema() { return (RolapSchema) dimension.getSchema(); } public MondrianDef.RelationOrJoin getRelation() { return relation; } public MondrianDef.Hierarchy getXmlHierarchy() { return xmlHierarchy; } public Member getDefaultMember() { // use lazy initialization to get around bootstrap issues if (defaultMember == null) { List rootMembers = memberReader.getRootMembers(); final SchemaReader schemaReader = getRolapSchema().getSchemaReader(); List calcMemberList = Util.cast(schemaReader.getCalculatedMembers(getLevels()[0])); for (RolapMember rootMember : UnionIterator.over(rootMembers, calcMemberList)) { if (rootMember.isHidden()) { continue; } // Note: We require that the root member is not a hidden member // of a ragged hierarchy, but we do not require that it is // visible. In particular, if a cube contains no explicit // measures, the default measure will be the implicitly defined // [Fact Count] measure, which happens to be non-visible. defaultMember = rootMember; break; } if (defaultMember == null) { throw MondrianResource.instance().InvalidHierarchyCondition.ex( this.getUniqueName()); } } return defaultMember; } public Member getNullMember() { // use lazy initialization to get around bootstrap issues if (nullMember == null) { nullMember = new RolapNullMember(nullLevel); } return nullMember; } /** * Returns the 'all' member. */ public RolapMember getAllMember() { return allMember; } public Member createMember( Member parent, Level level, String name, Formula formula) { if (formula == null) { return new RolapMemberBase( (RolapMember) parent, (RolapLevel) level, name); } else if (level.getDimension().isMeasures()) { return new RolapCalculatedMeasure( (RolapMember) parent, (RolapLevel) level, name, formula); } else { return new RolapCalculatedMember( (RolapMember) parent, (RolapLevel) level, name, formula); } } String getAlias() { return getName(); } /** * Returns the name of the source hierarchy, if this hierarchy is shared, * otherwise null. * *

If this hierarchy is a public -- that is, it belongs to a dimension * which is a usage of a shared dimension -- then * sharedHierarchyName holds the unique name of the shared * hierarchy; otherwise it is null. * *

Suppose this hierarchy is "Weekly" in the dimension "Order Date" of * cube "Sales", and that "Order Date" is a usage of the "Time" * dimension. Then sharedHierarchyName will be * "[Time].[Weekly]". */ public String getSharedHierarchyName() { return sharedHierarchyName; } /** * Adds to the FROM clause of the query the tables necessary to access the * members of this hierarchy in an inverse join order, used with agg tables. * If expression is not null, adds the tables necessary to * compute that expression. * *

This method is idempotent: if you call it more than once, it only * adds the table(s) to the FROM clause once. * * @param query Query to add the hierarchy to * @param expression Level to qualify up to; if null, qualifies up to the * topmost ('all') expression, which may require more columns and more * joins */ void addToFromInverse(SqlQuery query, MondrianDef.Expression expression) { if (relation == null) { throw Util.newError( "cannot add hierarchy " + getUniqueName() + " to query: it does not have a , or "); } final boolean failIfExists = false; MondrianDef.RelationOrJoin subRelation = relation; if (relation instanceof MondrianDef.Join) { if (expression != null) { subRelation = relationSubsetInverse(relation, expression.getTableAlias()); } } query.addFrom(subRelation, null, failIfExists); } /** * Adds to the FROM clause of the query the tables necessary to access the * members of this hierarchy. If expression is not null, adds * the tables necessary to compute that expression. * *

This method is idempotent: if you call it more than once, it only * adds the table(s) to the FROM clause once. * * @param query Query to add the hierarchy to * @param expression Level to qualify up to; if null, qualifies up to the * topmost ('all') expression, which may require more columns and more * joins */ void addToFrom(SqlQuery query, MondrianDef.Expression expression) { if (relation == null) { throw Util.newError( "cannot add hierarchy " + getUniqueName() + " to query: it does not have a

, or "); } query.registerRootRelation(relation); final boolean failIfExists = false; MondrianDef.RelationOrJoin subRelation = relation; if (relation instanceof MondrianDef.Join) { if (expression != null) { // Suppose relation is // (((A join B) join C) join D) // and the fact table is // F // and our expression uses C. We want to make the expression // F left join ((A join B) join C). // Search for the smallest subset of the relation which // uses C. subRelation = relationSubset(relation, expression.getTableAlias()); } } query.addFrom(subRelation, null, failIfExists); } /** * Adds a table to the FROM clause of the query. * If table is not null, adds the table. Otherwise, add the * relation on which this hierarchy is based on. * *

This method is idempotent: if you call it more than once, it only * adds the table(s) to the FROM clause once. * * @param query Query to add the hierarchy to * @param table table to add to the query */ void addToFrom(SqlQuery query, RolapStar.Table table) { if (getRelation() == null) { throw Util.newError( "cannot add hierarchy " + getUniqueName() + " to query: it does not have a

, or "); } final boolean failIfExists = false; MondrianDef.RelationOrJoin subRelation = null; if (table != null) { // Suppose relation is // (((A join B) join C) join D) // and the fact table is // F // and the table to add is C. We want to make the expression // F left join ((A join B) join C). // Search for the smallest subset of the relation which // joins with C. subRelation = lookupRelationSubset(getRelation(), table); } if (subRelation == null) { // If no table is found or specified, add the entire base relation. subRelation = getRelation(); } boolean tableAdded = query.addFrom( subRelation, table != null ? table.getAlias() : null, failIfExists); if (tableAdded && table != null) { RolapStar.Condition joinCondition = table.getJoinCondition(); if (joinCondition != null) { query.addWhere(joinCondition); } } } /** * Returns the smallest subset of relation which contains * the relation alias, or null if these is no relation with * such an alias, in inverse join order, used for agg tables. * * @param relation the relation in which to look for table by its alias * @param alias table alias to search for * @return the smallest containing relation or null if no matching table * is found in relation */ private static MondrianDef.RelationOrJoin relationSubsetInverse( MondrianDef.RelationOrJoin relation, String alias) { if (relation instanceof MondrianDef.Relation) { MondrianDef.Relation table = (MondrianDef.Relation) relation; return table.getAlias().equals(alias) ? relation : null; } else if (relation instanceof MondrianDef.Join) { MondrianDef.Join join = (MondrianDef.Join) relation; MondrianDef.RelationOrJoin leftRelation = relationSubsetInverse(join.left, alias); return (leftRelation == null) ? relationSubsetInverse(join.right, alias) : join; } else { throw Util.newInternal("bad relation type " + relation); } } /** * Returns the smallest subset of relation which contains * the relation alias, or null if these is no relation with * such an alias. * @param relation the relation in which to look for table by its alias * @param alias table alias to search for * @return the smallest containing relation or null if no matching table * is found in relation */ private static MondrianDef.RelationOrJoin relationSubset( MondrianDef.RelationOrJoin relation, String alias) { if (relation instanceof MondrianDef.Relation) { MondrianDef.Relation table = (MondrianDef.Relation) relation; return table.getAlias().equals(alias) ? relation : null; } else if (relation instanceof MondrianDef.Join) { MondrianDef.Join join = (MondrianDef.Join) relation; MondrianDef.RelationOrJoin rightRelation = relationSubset(join.right, alias); return (rightRelation == null) ? relationSubset(join.left, alias) : MondrianProperties.instance() .FilterChildlessSnowflakeMembers.get() ? join : rightRelation; } else { throw Util.newInternal("bad relation type " + relation); } } /** * Returns the smallest subset of relation which contains * the table targetTable, or null if the targetTable is not * one of the joining table in relation. * * @param relation the relation in which to look for targetTable * @param targetTable table to add to the query * @return the smallest containing relation or null if no matching table * is found in relation */ private static MondrianDef.RelationOrJoin lookupRelationSubset( MondrianDef.RelationOrJoin relation, RolapStar.Table targetTable) { if (relation instanceof MondrianDef.Table) { MondrianDef.Table table = (MondrianDef.Table) relation; if (table.name.equals(targetTable.getTableName())) { return relation; } else { // Not the same table if table names are different return null; } } else if (relation instanceof MondrianDef.Join) { // Search inside relation, starting from the rightmost table, // and move left along the join chain. MondrianDef.Join join = (MondrianDef.Join) relation; MondrianDef.RelationOrJoin rightRelation = lookupRelationSubset(join.right, targetTable); if (rightRelation == null) { // Keep searching left. return lookupRelationSubset( join.left, targetTable); } else { // Found a match. return join; } } return null; } /** * Creates a member reader which enforces the access-control profile of * role. * *

This method may not be efficient, so the caller should take care * not to call it too often. A cache is a good idea. * * @param role Role * @return Member reader that implements access control * * @pre role != null * @post return != null */ MemberReader createMemberReader(Role role) { final Access access = role.getAccess(this); switch (access) { case NONE: role.getAccess(this); // todo: remove throw Util.newInternal( "Illegal access to members of hierarchy " + this); case ALL: return (isRagged()) ? new RestrictedMemberReader(getMemberReader(), role) : getMemberReader(); case CUSTOM: final Role.HierarchyAccess hierarchyAccess = role.getAccessDetails(this); final Role.RollupPolicy rollupPolicy = hierarchyAccess.getRollupPolicy(); final NumericType returnType = new NumericType(); switch (rollupPolicy) { case FULL: return new RestrictedMemberReader(getMemberReader(), role); case PARTIAL: Type memberType1 = new mondrian.olap.type.MemberType( getDimension(), this, null, null); SetType setType = new SetType(memberType1); ListCalc listCalc = new AbstractListCalc( new DummyExp(setType), new Calc[0]) { public TupleList evaluateList( Evaluator evaluator) { return new UnaryTupleList( getLowestMembersForAccess( evaluator, hierarchyAccess, null)); } public boolean dependsOn(Hierarchy hierarchy) { return true; } }; final Calc partialCalc = new LimitedRollupAggregateCalc(returnType, listCalc); final Exp partialExp = new ResolvedFunCall( new FunDefBase("$x", "x", "In") { public Calc compileCall( ResolvedFunCall call, ExpCompiler compiler) { return partialCalc; } public void unparse(Exp[] args, PrintWriter pw) { pw.print("$RollupAccessibleChildren()"); } }, new Exp[0], returnType); return new LimitedRollupSubstitutingMemberReader( getMemberReader(), role, hierarchyAccess, partialExp); case HIDDEN: Exp hiddenExp = new ResolvedFunCall( new FunDefBase("$x", "x", "In") { public Calc compileCall( ResolvedFunCall call, ExpCompiler compiler) { return new ConstantCalc(returnType, null); } public void unparse(Exp[] args, PrintWriter pw) { pw.print("$RollupAccessibleChildren()"); } }, new Exp[0], returnType); return new LimitedRollupSubstitutingMemberReader( getMemberReader(), role, hierarchyAccess, hiddenExp); default: throw Util.unexpected(rollupPolicy); } default: throw Util.badValue(access); } } /** * Goes recursively down a hierarchy and builds a list of the * members that should be constrained on because of access controls. * It isn't sufficient to constrain on the current level in the * evaluator because the actual constraint could be even more limited *

Example. If we only give access to Seattle but the query is on * the country level, we have to constrain at the city level, not state, * or else all the values of all cities in the state will be returned. */ private List getLowestMembersForAccess( Evaluator evaluator, HierarchyAccess hAccess, List currentList) { if (currentList == null) { currentList = FunUtil.getNonEmptyMemberChildren( evaluator, ((RolapEvaluator) evaluator) .getExpanding()); } boolean goesLower = false; for (Member member : currentList) { if (hAccess.getAccess(member) != Access.ALL) { goesLower = true; break; } } if (goesLower) { // We still have to go one more level down. List newList = new ArrayList(); for (Member member : currentList) { int savepoint = evaluator.savepoint(); try { evaluator.setContext(member); newList.addAll( FunUtil.getNonEmptyMemberChildren( evaluator, member)); } finally { evaluator.restore(savepoint); } // Now pass it recursively to this method. return getLowestMembersForAccess(evaluator, hAccess, newList); } } return currentList; } /** * A hierarchy is ragged if it contains one or more levels with hidden * members. */ public boolean isRagged() { for (Level level : levels) { if (((RolapLevel) level).getHideMemberCondition() != RolapLevel.HideMemberCondition.Never) { return true; } } return false; } /** * Returns an expression which will compute a member's value by aggregating * its children. * *

It is efficient to share one expression between all calculated members * in a parent-child hierarchy, so we only need need to validate the * expression once. */ synchronized Exp getAggregateChildrenExpression() { if (aggregateChildrenExpression == null) { UnresolvedFunCall fc = new UnresolvedFunCall( "$AggregateChildren", Syntax.Internal, new Exp[] {new HierarchyExpr(this)}); Validator validator = Util.createSimpleValidator(BuiltinFunTable.instance()); aggregateChildrenExpression = fc.accept(validator); } return aggregateChildrenExpression; } /** * Builds a dimension which maps onto a table holding the transitive * closure of the relationship for this parent-child level. * *

This method is triggered by the * {@link mondrian.olap.MondrianDef.Closure} element * in a schema, and is only meaningful for a parent-child hierarchy. * *

When a Schema contains a parent-child Hierarchy that has an * associated closure table, Mondrian creates a parallel internal * Hierarchy, called a "closed peer", that refers to the closure table. * This is indicated in the schema at the level of a Level, by including a * Closure element. The closure table represents * the transitive closure of the parent-child relationship. * *

The peer dimension, with its single hierarchy, and 3 levels (all, * closure, item) really 'belong to' the parent-child level. If a single * hierarchy had two parent-child levels (however unlikely this might be) * then each level would have its own auxiliary dimension. * *

For example, in the demo schema the [HR].[Employee] dimension * contains a parent-child hierarchy: * *

     * <Dimension name="Employees" foreignKey="employee_id">
     *   <Hierarchy hasAll="true" allMemberName="All Employees"
     *         primaryKey="employee_id">
     *     <Table name="employee"/>
     *     <Level name="Employee Id" type="Numeric" uniqueMembers="true"
     *            column="employee_id" parentColumn="supervisor_id"
     *            nameColumn="full_name" nullParentValue="0">
     *       <Closure parentColumn="supervisor_id"
     *                   childColumn="employee_id">
     *          <Table name="employee_closure"/>
     *       </Closure>
     *       ...
     * 
* The internal closed peer Hierarchy has this structure: *
     * <Dimension name="Employees" foreignKey="employee_id">
     *     ...
     *     <Hierarchy name="Employees$Closure"
     *         hasAll="true" allMemberName="All Employees"
     *         primaryKey="employee_id" primaryKeyTable="employee_closure">
     *       <Join leftKey="supervisor_id" rightKey="employee_id">
     *         <Table name="employee_closure"/>
     *         <Table name="employee"/>
     *       </Join>
     *       <Level name="Closure"  type="Numeric" uniqueMembers="false"
     *           table="employee_closure" column="supervisor_id"/>
     *       <Level name="Employee" type="Numeric" uniqueMembers="true"
     *           table="employee_closure" column="employee_id"/>
     *     </Hierarchy>
     * 
* *

Note that the original Level with the Closure produces two Levels in * the closed peer Hierarchy: a simple peer (Employee) and a closed peer * (Closure). * * @param src a parent-child Level that has a Closure clause * @param clos a Closure clause * @return the closed peer Level in the closed peer Hierarchy */ RolapDimension createClosedPeerDimension( RolapLevel src, MondrianDef.Closure clos, MondrianDef.CubeDimension xmlDimension) { // REVIEW (mb): What about attribute primaryKeyTable? // Create a peer dimension. RolapDimension peerDimension = new RolapDimension( dimension.getSchema(), dimension.getName() + "$Closure", null, true, "Closure dimension for parent-child hierarchy " + getName(), DimensionType.StandardDimension, dimension.isHighCardinality(), Collections.emptyMap()); // Create a peer hierarchy. RolapHierarchy peerHier = peerDimension.newHierarchy(null, true, this); peerHier.allMemberName = getAllMemberName(); peerHier.allMember = (RolapMemberBase) getAllMember(); peerHier.allLevelName = getAllLevelName(); peerHier.sharedHierarchyName = getSharedHierarchyName(); MondrianDef.Join join = new MondrianDef.Join(); peerHier.relation = join; join.left = clos.table; // the closure table join.leftKey = clos.parentColumn; join.right = relation; // the unclosed base table join.rightKey = clos.childColumn; // Create the upper level. // This represents all groups of descendants. For example, in the // Employee closure hierarchy, this level has a row for every employee. int index = peerHier.levels.length; int flags = src.getFlags() &~ RolapLevel.FLAG_UNIQUE; MondrianDef.Expression keyExp = new MondrianDef.Column(clos.table.name, clos.parentColumn); RolapLevel level = new RolapLevel( peerHier, "Closure", caption, true, description, index++, keyExp, null, null, null, null, null, // no longer a parent-child hierarchy null, RolapProperty.emptyArray, flags | RolapLevel.FLAG_UNIQUE, src.getDatatype(), null, src.getHideMemberCondition(), src.getLevelType(), "", Collections.emptyMap()); peerHier.levels = Util.append(peerHier.levels, level); // Create lower level. // This represents individual items. For example, in the Employee // closure hierarchy, this level has a row for every direct and // indirect report of every employee (which is more than the number // of employees). flags = src.getFlags() | RolapLevel.FLAG_UNIQUE; keyExp = new MondrianDef.Column(clos.table.name, clos.childColumn); RolapLevel sublevel = new RolapLevel( peerHier, "Item", null, true, null, index++, keyExp, null, null, null, null, null, // no longer a parent-child hierarchy null, RolapProperty.emptyArray, flags, src.getDatatype(), src.getInternalType(), src.getHideMemberCondition(), src.getLevelType(), "", Collections.emptyMap()); peerHier.levels = Util.append(peerHier.levels, sublevel); return peerDimension; } /** * Sets default member of this Hierarchy. * * @param defaultMember Default member */ public void setDefaultMember(Member defaultMember) { if (defaultMember != null) { this.defaultMember = defaultMember; } } /** *

Gets "unique key level name" attribute of this Hierarchy, if set. * If set, this property indicates that all level properties are * functionally dependent (invariant) on their associated levels, * and that the set of levels from the root to the named level (inclusive) * effectively defines an alternate key.

* *

This allows the GROUP BY to be eliminated from associated queries.

* * @return the name of the "unique key" level, or null if not specified */ public String getUniqueKeyLevelName() { return uniqueKeyLevelName; } /** * Returns the ordinal of this hierarchy in its cube. * *

Temporarily defined against RolapHierarchy; will be moved to * RolapCubeHierarchy as soon as the measures hierarchy is a * RolapCubeHierarchy. * * @return Ordinal of this hierarchy in its cube */ public int getOrdinalInCube() { // This is temporary to verify that all calls to this method are for // the measures hierarchy. For all other hierarchies, the context will // be a RolapCubeHierarchy. // // In particular, if this method is called from // RolapEvaluator.setContext, the caller of that method should have // passed in a RolapCubeMember, not a RolapMember. assert dimension.isMeasures(); return 0; } /** * A RolapNullMember is the null member of its hierarchy. * Every hierarchy has precisely one. They are yielded by operations such as * [Gender].[All].ParentMember. Null members are usually * omitted from sets (in particular, in the set constructor operator "{ ... * }". */ static class RolapNullMember extends RolapMemberBase { RolapNullMember(final RolapLevel level) { super( null, level, null, RolapUtil.mdxNullLiteral(), MemberType.NULL); assert level != null; } } /** * Calculated member which is also a measure (that is, a member of the * [Measures] dimension). */ protected static class RolapCalculatedMeasure extends RolapCalculatedMember implements RolapMeasure { private RolapResult.ValueFormatter cellFormatter; public RolapCalculatedMeasure( RolapMember parent, RolapLevel level, String name, Formula formula) { super(parent, level, name, formula); } public synchronized void setProperty(String name, Object value) { if (name.equals(Property.CELL_FORMATTER.getName())) { String cellFormatterClass = (String) value; try { CellFormatter formatter = RolapSchema.getCellFormatter( cellFormatterClass, null); this.cellFormatter = new RolapResult.CellFormatterValueFormatter(formatter); } catch (Exception e) { throw MondrianResource.instance().CellFormatterLoadFailed .ex( cellFormatterClass, getUniqueName(), e); } } if (name.equals(Property.CELL_FORMATTER_SCRIPT.name)) { String language = (String) getPropertyValue( Property.CELL_FORMATTER_SCRIPT_LANGUAGE.name); String scriptText = (String) value; try { final Scripts.ScriptDefinition script = new Scripts.ScriptDefinition( scriptText, Scripts.ScriptLanguage.lookup(language)); CellFormatter formatter = RolapSchema.getCellFormatter( null, script); this.cellFormatter = new RolapResult.CellFormatterValueFormatter(formatter); } catch (Exception e) { throw MondrianResource.instance().CellFormatterLoadFailed .ex( scriptText, getUniqueName(), e); } } super.setProperty(name, value); } public RolapResult.ValueFormatter getFormatter() { return cellFormatter; } } /** * Substitute for a member in a hierarchy whose rollup policy is 'partial' * or 'hidden'. The member is calculated using an expression which * aggregates only visible descendants. * *

Note that this class extends RolapCubeMember only because other code * expects that all members in a RolapCubeHierarchy are RolapCubeMembers. * As part of {@link mondrian.util.Bug#BugSegregateRolapCubeMemberFixed}, * maybe make {@link mondrian.rolap.RolapCubeMember} an interface. * * @see mondrian.olap.Role.RollupPolicy */ public static class LimitedRollupMember extends RolapCubeMember { public final RolapMember member; private final Exp exp; LimitedRollupMember( RolapCubeMember member, Exp exp) { super( member.getParentMember(), member.getRolapMember(), member.getLevel()); assert !(member instanceof LimitedRollupMember); this.member = member; this.exp = exp; } public boolean equals(Object o) { return o instanceof LimitedRollupMember && ((LimitedRollupMember) o).member.equals(member); } public Exp getExpression() { return exp; } protected boolean computeCalculated(final MemberType memberType) { return true; } public boolean isCalculated() { return false; } public boolean isEvaluated() { return true; } } /** * Member reader which wraps a hierarchy's member reader, and if the * role has limited access to the hierarchy, replaces members with * dummy members which evaluate to the sum of only the accessible children. */ private static class LimitedRollupSubstitutingMemberReader extends SubstitutingMemberReader { private final Role.HierarchyAccess hierarchyAccess; private final Exp exp; /** * Creates a LimitedRollupSubstitutingMemberReader. * * @param memberReader Underlying member reader * @param role Role to enforce * @param hierarchyAccess Access this role has to the hierarchy * @param exp Expression for hidden member */ public LimitedRollupSubstitutingMemberReader( MemberReader memberReader, Role role, Role.HierarchyAccess hierarchyAccess, Exp exp) { super( new RestrictedMemberReader( memberReader, role)); this.hierarchyAccess = hierarchyAccess; this.exp = exp; } @Override public RolapMember substitute(final RolapMember member) { if (member != null && member instanceof MultiCardinalityDefaultMember && hierarchyAccess.hasInaccessibleDescendants( member.getParentMember())) { return new LimitedRollupMember( (RolapCubeMember) ((MultiCardinalityDefaultMember)member) .member.getParentMember(), exp); } if (member != null && (hierarchyAccess.getAccess(member) == Access.CUSTOM || hierarchyAccess.hasInaccessibleDescendants(member))) { // Member is visible, but at least one of its // descendants is not. return new LimitedRollupMember((RolapCubeMember)member, exp); } else { // No need to substitute. Member and all of its // descendants are accessible. Total for member // is same as for FULL policy. return member; } } @Override public RolapMember desubstitute(RolapMember member) { if (member instanceof LimitedRollupMember) { return ((LimitedRollupMember) member).member; } else { return member; } } } /** * Compiled expression that computes rollup over a set of visible children. * The {@code listCalc} expression determines that list of children. */ private static class LimitedRollupAggregateCalc extends AggregateFunDef.AggregateCalc { public LimitedRollupAggregateCalc( Type returnType, ListCalc listCalc) { super( new DummyExp(returnType), listCalc, new ValueCalc(new DummyExp(returnType))); } } /** * Dummy element that acts as a namespace for resolving member names within * shared hierarchies. Acts like a cube that has a single child, the * hierarchy in question. */ private class DummyElement implements OlapElement { public String getUniqueName() { throw new UnsupportedOperationException(); } public String getName() { return "$"; } public String getDescription() { throw new UnsupportedOperationException(); } public OlapElement lookupChild( SchemaReader schemaReader, Id.Segment s, MatchType matchType) { if (Util.equalName(s.name, dimension.getName())) { return dimension; } // Archaic form ., e.g. [Time.Weekly].[1997] if (Util.equalName(s.name, dimension.getName() + "." + subName)) { return RolapHierarchy.this; } return null; } public String getQualifiedName() { throw new UnsupportedOperationException(); } public String getCaption() { throw new UnsupportedOperationException(); } public Hierarchy getHierarchy() { throw new UnsupportedOperationException(); } public Dimension getDimension() { throw new UnsupportedOperationException(); } public boolean isVisible() { throw new UnsupportedOperationException(); } public String getLocalized(LocalizedProperty prop, Locale locale) { throw new UnsupportedOperationException(); } } } // End RolapHierarchy.java mondrian-3.4.1/src/main/mondrian/rolap/MemberNoCacheHelper.java0000644000175000017500000000525411735330606024326 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2008-2008 TASecurity Group Spain // Copyright (C) 2008-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.rolap.sql.MemberChildrenConstraint; import mondrian.rolap.sql.TupleConstraint; import mondrian.spi.DataSourceChangeListener; import java.util.List; /** * Encapsulation of member caching for no caching. * * @author Luis F. Canals (lcanals@tasecurity.net) * @version 1.0 */ public class MemberNoCacheHelper extends MemberCacheHelper { DataSourceChangeListener changeListener; public MemberNoCacheHelper() { super(null); } // implement MemberCache public RolapMember getMember( Object key, boolean mustCheckCacheStatus) { return null; } // implement MemberCache public Object putMember(Object key, RolapMember value) { return value; } // implement MemberCache public Object makeKey(RolapMember parent, Object key) { return new MemberKey(parent, key); } // implement MemberCache // synchronization: Must synchronize, because modifies mapKeyToMember public synchronized RolapMember getMember(Object key) { return getMember(key, true); } public void checkCacheStatus() { } /** * ??? * * @param level * @param constraint * @param members */ public void putLevelMembersInCache( RolapLevel level, TupleConstraint constraint, List members) { } public List getChildrenFromCache( RolapMember member, MemberChildrenConstraint constraint) { return null; } public void putChildren( RolapMember member, MemberChildrenConstraint constraint, List children) { } public List getLevelMembersFromCache( RolapLevel level, TupleConstraint constraint) { return null; } public DataSourceChangeListener getChangeListener() { return changeListener; } public void setChangeListener(DataSourceChangeListener listener) { changeListener = listener; } public boolean isMutable() { return true; } public synchronized RolapMember removeMember(Object key) { return null; } public synchronized RolapMember removeMemberAndDescendants(Object key) { return null; } } // End MemberNoCacheHelper.java mondrian-3.4.1/src/main/mondrian/rolap/BitKey.java0000644000175000017500000016100711735330606021724 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. // // jhyde, 30 August, 2001 */ package mondrian.rolap; import java.io.Serializable; import java.util.BitSet; import java.util.Iterator; /** * Represents a set of bits. * *

Unlike {@link java.util.BitSet}, the number of bits cannot be changed * after the BitKey is created. This allows us to optimize. * *

If you have a collection of immutable objects, each of which has a unique * positive number and you wish to do comparisons between subsets of those * objects testing for equality, then encoding the subsets as BitKeys is very * efficient. * *

There are two implementations that target groups of objects with maximum * number less than 64 and less than 128; and there is one implements that is * general for any positive number. * *

One caution: if the maximum number assigned to one of the * objects is large, then this representation might be sparse and therefore * not efficient. * * @author Richard M. Emberson */ public interface BitKey extends Serializable, Comparable, Iterable { /** * The BitKey with no bits set. */ BitKey EMPTY = Factory.makeBitKey(0); /** * Sets the bit at the specified index to the specified value. */ void set(int bitIndex, boolean value); /** * Sets the bit at the specified index to true. */ void set(int bitIndex); /** * Returns the value of the bit with the specified index. The value * is true if the bit with the index bitIndex * is currently set in this BitKey; otherwise, the result * is false. */ boolean get(int bitIndex); /** * Sets the bit specified by the index to false. */ void clear(int bitIndex); /** * Sets all of the bits in this BitKey to false. */ void clear(); /** * Is every bit set in the parameter bitKey also set in * this. * If one switches this with the parameter bitKey * one gets the equivalent of isSubSetOf. * * @param bitKey Bit key */ boolean isSuperSetOf(BitKey bitKey); /** * Or the parameter BitKey with this. * * @param bitKey Bit key */ BitKey or(BitKey bitKey); /** * XOr the parameter BitKey with this. * * @param bitKey Bit key */ BitKey orNot(BitKey bitKey); /** * Returns the boolean AND of this bitkey and the given bitkey. * * @param bitKey Bit key */ BitKey and(BitKey bitKey); /** * Returns a BitKey containing all of the bits in this * BitSet whose corresponding * bit is NOT set in the specified BitSet. */ BitKey andNot(BitKey bitKey); /** * Returns a copy of this BitKey. * * @return copy of BitKey */ BitKey copy(); /** * Returns an empty BitKey of the same type. This is the same * as calling {@link #copy} followed by {@link #clear()}. * * @return BitKey of same type */ BitKey emptyCopy(); /** * Returns true if this BitKey contains no bits that are set * to true. */ boolean isEmpty(); /** * Returns whether this BitKey has any bits in common with a given BitKey. */ boolean intersects(BitKey bitKey); /** * Returns a {@link BitSet} with the same contents as this BitKey. */ BitSet toBitSet(); /** * An Iterator over the bit positions. * For example, if the BitKey had positions 3 and 4 set, then * the Iterator would return the values 3 and then 4. The bit * positions returned by the iterator are in the order, from * smallest to largest, as they are set in the BitKey. */ Iterator iterator(); /** * Returns the index of the first bit that is set to true * that occurs on or after the specified starting index. If no such * bit exists then -1 is returned. * * To iterate over the true bits in a BitKey, * use the following loop: * *

     * for (int i = bk.nextSetBit(0); i >= 0; i = bk.nextSetBit(i + 1)) {
     *     // operate on index i here
     * }
* * @param fromIndex the index to start checking from (inclusive) * @return the index of the next set bit * @throws IndexOutOfBoundsException if the specified index is negative */ int nextSetBit(int fromIndex); /** * Returns the number of bits set. * * @return Number of bits set */ int cardinality(); public abstract class Factory { /** * Creates a {@link BitKey} with a capacity for a given number of bits. * @param size Number of bits in key */ public static BitKey makeBitKey(int size) { return makeBitKey(size, false); } /** * Creates a {@link BitKey} with a capacity for a given number of bits. * @param size Number of bits in key * @param init The default value of all bits. */ public static BitKey makeBitKey(int size, boolean init) { if (size < 0) { String msg = "Negative size \"" + size + "\" not allowed"; throw new IllegalArgumentException(msg); } final BitKey bk; if (size < 64) { bk = new BitKey.Small(); } else if (size < 128) { bk = new BitKey.Mid128(); } else { bk = new BitKey.Big(size); } if (init) { for (int i = 0; i < size; i++) { bk.set(i, init); } } return bk; } /** * Creates a {@link BitKey} with the same contents as a {@link BitSet}. */ public static BitKey makeBitKey(BitSet bitSet) { BitKey bitKey = makeBitKey(bitSet.length()); for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) { bitKey.set(i); } return bitKey; } } /** * Abstract implementation of {@link BitKey}. */ abstract class AbstractBitKey implements BitKey { private static final long serialVersionUID = -2942302671676103450L; // chunk is a long, which has 64 bits protected static final int ChunkBitCount = 6; protected static final int Mask = 63; protected static final long WORD_MASK = 0xffffffffffffffffL; /** * Creates a chunk containing a single bit. */ protected static long bit(int pos) { return (1L << (pos & Mask)); } /** * Returns which chunk a given bit falls into. * Bits 0 to 63 fall in chunk 0, bits 64 to 127 fall into chunk 1. */ protected static int chunkPos(int size) { return (size >> ChunkBitCount); } /** * Returns the number of chunks required for a given number of bits. * *

0 bits requires 0 chunks; 1 - 64 bits requires 1 chunk; etc. */ protected static int chunkCount(int size) { return (size + 63) >> ChunkBitCount; } /** * Returns the number of one-bits in the two's complement binary * representation of the specified long value. This function * is sometimes referred to as the population count. * *

(Copied from {@link java.lang.Long#bitCount(long)}, which was * introduced in JDK 1.5, but we need the functionality in JDK 1.4.) * * @return the number of one-bits in the two's complement binary * representation of the specified long value. * @since 1.5 */ protected static int bitCount(long i) { i = i - ((i >>> 1) & 0x5555555555555555L); i = (i & 0x3333333333333333L) + ((i >>> 2) & 0x3333333333333333L); i = (i + (i >>> 4)) & 0x0f0f0f0f0f0f0f0fL; i = i + (i >>> 8); i = i + (i >>> 16); i = i + (i >>> 32); return (int)i & 0x7f; } public final void set(int pos, boolean value) { if (value) { set(pos); } else { clear(pos); } } /** * Copies a byte into a bit set at a particular position. * * @param bitSet Bit set * @param pos Position * @param x Byte */ protected static void copyFromByte(BitSet bitSet, int pos, byte x) { if (x == 0) { return; } if ((x & 0x01) != 0) { bitSet.set(pos, true); } ++pos; if ((x & 0x02) != 0) { bitSet.set(pos, true); } ++pos; if ((x & 0x04) != 0) { bitSet.set(pos, true); } ++pos; if ((x & 0x08) != 0) { bitSet.set(pos, true); } ++pos; if ((x & 0x10) != 0) { bitSet.set(pos, true); } ++pos; if ((x & 0x20) != 0) { bitSet.set(pos, true); } ++pos; if ((x & 0x40) != 0) { bitSet.set(pos, true); } ++pos; if ((x & 0x80) != 0) { bitSet.set(pos, true); } } /** * Copies a {@code long} value (interpreted as 64 bits) into a bit set. * * @param bitSet Bit set * @param pos Position * @param x Byte */ protected static void copyFromLong( final BitSet bitSet, int pos, long x) { while (x != 0) { copyFromByte(bitSet, pos, (byte) (x & 0xff)); x >>>= 8; pos += 8; } } protected IllegalArgumentException createException(BitKey bitKey) { final String msg = (bitKey == null) ? "Null BitKey" : "Bad BitKey type: " + bitKey.getClass().getName(); return new IllegalArgumentException(msg); } /** * Compares a pair of {@code long} arrays, using unsigned comparison * semantics and padding to the left with 0s. * *

Values are treated as unsigned for the purposes of comparison. * *

If the arrays have different lengths, the shorter is padded with * 0s. * * @param a1 First array * @param a2 Second array * @return -1 if a1 compares less to a2, * 0 if a1 is equal to a2, * 1 if a1 is greater than a2 */ static int compareUnsignedArrays(long[] a1, long[] a2) { int i1 = a1.length - 1; int i2 = a2.length - 1; if (i1 > i2) { do { if (a1[i1] != 0) { return 1; } --i1; } while (i1 > i2); } else if (i2 > i1) { do { if (a2[i2] != 0) { return -1; } --i2; } while (i2 > i1); } assert i1 == i2; for (; i1 >= 0; --i1) { int c = compareUnsigned(a1[i1], a2[i1]); if (c != 0) { return c; } } return 0; } /** * Performs unsigned comparison on two {@code long} values. * * @param i1 First value * @param i2 Second value * @return -1 if i1 is less than i2, * 1 if i1 is greater than i2, * 0 if i1 equals i2 */ static int compareUnsigned(long i1, long i2) { // We want to do unsigned comparison. // Signed comparison returns the correct result except // if i1<0 & i2>=0 // or i1>=0 & i2<0 if (i1 == i2) { return 0; } else if ((i1 < 0) == (i2 < 0)) { // Same signs, signed comparison gives the right result return i1 < i2 ? -1 : 1; } else { // Different signs, use signed comparison and invert the result return i1 < i2 ? 1 : -1; } } } /** * Implementation of {@link BitKey} for bit counts less than 64. */ public class Small extends AbstractBitKey { private static final long serialVersionUID = -7891880560056571197L; private long bits; /** * Creates a Small with no bits set. */ private Small() { } /** * Creates a Small and initializes it to the 64 bit value. * * @param bits 64 bit value */ private Small(long bits) { this.bits = bits; } public void set(int pos) { if (pos < 64) { bits |= bit(pos); } else { throw new IllegalArgumentException( "pos " + pos + " exceeds capacity 64"); } } public boolean get(int pos) { return pos < 64 && ((bits & bit(pos)) != 0); } public void clear(int pos) { bits &= ~bit(pos); } public void clear() { bits = 0; } public int cardinality() { return bitCount(bits); } private void or(long bits) { this.bits |= bits; } private void orNot(long bits) { this.bits ^= bits; } private void and(long bits) { this.bits &= bits; } public BitKey or(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { final BitKey.Small other = (BitKey.Small) bitKey; final BitKey.Small bk = (BitKey.Small) copy(); bk.or(other.bits); return bk; } else if (bitKey instanceof BitKey.Mid128) { final BitKey.Mid128 other = (BitKey.Mid128) bitKey; final BitKey.Mid128 bk = (BitKey.Mid128) other.copy(); bk.or(this.bits, 0); return bk; } else if (bitKey instanceof BitKey.Big) { final BitKey.Big other = (BitKey.Big) bitKey; final BitKey.Big bk = (BitKey.Big) other.copy(); bk.or(this.bits); return bk; } throw createException(bitKey); } public BitKey orNot(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { final BitKey.Small other = (BitKey.Small) bitKey; final BitKey.Small bk = (BitKey.Small) copy(); bk.orNot(other.bits); return bk; } else if (bitKey instanceof BitKey.Mid128) { final BitKey.Mid128 other = (BitKey.Mid128) bitKey; final BitKey.Mid128 bk = (BitKey.Mid128) other.copy(); bk.orNot(this.bits, 0); return bk; } else if (bitKey instanceof BitKey.Big) { final BitKey.Big other = (BitKey.Big) bitKey; final BitKey.Big bk = (BitKey.Big) other.copy(); bk.orNot(this.bits); return bk; } throw createException(bitKey); } public BitKey and(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { final BitKey.Small other = (BitKey.Small) bitKey; final BitKey.Small bk = (BitKey.Small) copy(); bk.and(other.bits); return bk; } else if (bitKey instanceof BitKey.Mid128) { final BitKey.Mid128 other = (BitKey.Mid128) bitKey; final BitKey.Small bk = (BitKey.Small) copy(); bk.and(other.bits0); return bk; } else if (bitKey instanceof BitKey.Big) { final BitKey.Big other = (BitKey.Big) bitKey; final BitKey.Small bk = (BitKey.Small) copy(); bk.and(other.bits[0]); return bk; } throw createException(bitKey); } public BitKey andNot(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { final BitKey.Small other = (BitKey.Small) bitKey; final BitKey.Small bk = (BitKey.Small) copy(); bk.andNot(other.bits); return bk; } else if (bitKey instanceof BitKey.Mid128) { final BitKey.Mid128 other = (BitKey.Mid128) bitKey; final BitKey.Small bk = (BitKey.Small) copy(); bk.andNot(other.bits0); return bk; } else if (bitKey instanceof BitKey.Big) { final BitKey.Big other = (BitKey.Big) bitKey; final BitKey.Small bk = (BitKey.Small) copy(); bk.andNot(other.bits[0]); return bk; } throw createException(bitKey); } private void andNot(long bits) { this.bits &= ~bits; } public boolean isSuperSetOf(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { BitKey.Small other = (BitKey.Small) bitKey; return ((this.bits | other.bits) == this.bits); } else if (bitKey instanceof BitKey.Mid128) { BitKey.Mid128 other = (BitKey.Mid128) bitKey; return ((this.bits | other.bits0) == this.bits) && (other.bits1 == 0); } else if (bitKey instanceof BitKey.Big) { BitKey.Big other = (BitKey.Big) bitKey; if ((this.bits | other.bits[0]) != this.bits) { return false; } else { for (int i = 1; i < other.bits.length; i++) { if (other.bits[i] != 0) { return false; } } return true; } } return false; } public boolean intersects(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { BitKey.Small other = (BitKey.Small) bitKey; return (this.bits & other.bits) != 0; } else if (bitKey instanceof BitKey.Mid128) { BitKey.Mid128 other = (BitKey.Mid128) bitKey; return (this.bits & other.bits0) != 0; } else if (bitKey instanceof BitKey.Big) { BitKey.Big other = (BitKey.Big) bitKey; return (this.bits & other.bits[0]) != 0; } return false; } public BitSet toBitSet() { final BitSet bitSet = new BitSet(64); long x = bits; int pos = 0; while (x != 0) { copyFromByte(bitSet, pos, (byte) (x & 0xff)); x >>>= 8; pos += 8; } return bitSet; } /** * To say that I am happy about this algorithm (or the variations * of the algorithm used for the Mid128 and Big BitKey implementations) * would be a stretch. It works but there has to be a more * elegant and faster one but this is the best I could come up * with in a couple of hours. * */ public Iterator iterator() { return new Iterator() { int pos = -1; long bits = Small.this.bits; public boolean hasNext() { if (bits == 0) { return false; } // This is a special case // Long.MIN_VALUE == -9223372036854775808 if (bits == Long.MIN_VALUE) { pos = 63; bits = 0; return true; } long b = (bits & -bits); if (b == 0) { // should never happen return false; } int delta = 0; while (b >= 256) { b = (b >> 8); delta += 8; } int p = bitPositionTable[(int) b]; if (p >= 0) { p += delta; } else { p = delta; } if (pos < 0) { // first time pos = p; } else if (p == 0) { pos++; } else { pos += (p + 1); } bits = bits >>> (p + 1); return true; } public Integer next() { return Integer.valueOf(pos); } public void remove() { throw new UnsupportedOperationException("remove"); } }; } public int nextSetBit(int fromIndex) { if (fromIndex < 0) { throw new IndexOutOfBoundsException( "fromIndex < 0: " + fromIndex); } if (fromIndex < 64) { long word = bits & (WORD_MASK << fromIndex); if (word != 0) { return Long.numberOfTrailingZeros(word); } } return -1; } public boolean equals(Object o) { if (this == o) { return true; } if (o instanceof BitKey.Small) { BitKey.Small other = (BitKey.Small) o; return (this.bits == other.bits); } else if (o instanceof BitKey.Mid128) { BitKey.Mid128 other = (BitKey.Mid128) o; return (this.bits == other.bits0) && (other.bits1 == 0); } else if (o instanceof BitKey.Big) { BitKey.Big other = (BitKey.Big) o; if (this.bits != other.bits[0]) { return false; } else { for (int i = 1; i < other.bits.length; i++) { if (other.bits[i] != 0) { return false; } } return true; } } return false; } public int hashCode() { return (int)(1234L ^ bits ^ (bits >>> 32)); } public int compareTo(BitKey bitKey) { if (bitKey instanceof Small) { Small that = (Small) bitKey; return this.bits == that.bits ? 0 : this.bits < that.bits ? -1 : 1; } else if (bitKey instanceof Mid128) { Mid128 that = (Mid128) bitKey; if (that.bits1 != 0) { return -1; } return compareUnsigned(this.bits, that.bits0); } else { return compareToBig((Big) bitKey); } } protected int compareToBig(Big that) { int thatBitsLength = that.effectiveSize(); switch (thatBitsLength) { case 0: return this.bits == 0 ? 0 : 1; case 1: return compareUnsigned(this.bits, that.bits[0]); default: return -1; } } public String toString() { StringBuilder buf = new StringBuilder(64); buf.append("0x"); for (int i = 63; i >= 0; i--) { buf.append((get(i)) ? '1' : '0'); } return buf.toString(); } public BitKey copy() { return new Small(this.bits); } public BitKey emptyCopy() { return new Small(); } public boolean isEmpty() { return bits == 0; } } /** * Implementation of {@link BitKey} good for sizes less than 128. */ public class Mid128 extends AbstractBitKey { private static final long serialVersionUID = -8409143207943258659L; private long bits0; private long bits1; private Mid128() { } private Mid128(Mid128 mid) { this.bits0 = mid.bits0; this.bits1 = mid.bits1; } public void set(int pos) { if (pos < 64) { bits0 |= bit(pos); } else if (pos < 128) { bits1 |= bit(pos); } else { throw new IllegalArgumentException( "pos " + pos + " exceeds capacity 128"); } } public boolean get(int pos) { if (pos < 64) { return (bits0 & bit(pos)) != 0; } else if (pos < 128) { return (bits1 & bit(pos)) != 0; } else { return false; } } public void clear(int pos) { if (pos < 64) { bits0 &= ~bit(pos); } else if (pos < 128) { bits1 &= ~bit(pos); } else { throw new IndexOutOfBoundsException( "pos " + pos + " exceeds size " + 128); } } public void clear() { bits0 = 0; bits1 = 0; } public int cardinality() { return bitCount(bits0) + bitCount(bits1); } private void or(long bits0, long bits1) { this.bits0 |= bits0; this.bits1 |= bits1; } private void orNot(long bits0, long bits1) { this.bits0 ^= bits0; this.bits1 ^= bits1; } private void and(long bits0, long bits1) { this.bits0 &= bits0; this.bits1 &= bits1; } public BitKey or(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { final BitKey.Small other = (BitKey.Small) bitKey; final BitKey.Mid128 bk = (BitKey.Mid128) copy(); bk.or(other.bits, 0); return bk; } else if (bitKey instanceof BitKey.Mid128) { final BitKey.Mid128 other = (BitKey.Mid128) bitKey; final BitKey.Mid128 bk = (BitKey.Mid128) copy(); bk.or(other.bits0, other.bits1); return bk; } else if (bitKey instanceof BitKey.Big) { final BitKey.Big other = (BitKey.Big) bitKey; final BitKey.Big bk = (BitKey.Big) other.copy(); bk.or(this.bits0, this.bits1); return bk; } throw createException(bitKey); } public BitKey orNot(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { final BitKey.Small other = (BitKey.Small) bitKey; final BitKey.Mid128 bk = (BitKey.Mid128) copy(); bk.orNot(other.bits, 0); return bk; } else if (bitKey instanceof BitKey.Mid128) { final BitKey.Mid128 other = (BitKey.Mid128) bitKey; final BitKey.Mid128 bk = (BitKey.Mid128) copy(); bk.orNot(other.bits0, other.bits1); return bk; } else if (bitKey instanceof BitKey.Big) { final BitKey.Big other = (BitKey.Big) bitKey; final BitKey.Big bk = (BitKey.Big) other.copy(); bk.orNot(this.bits0, this.bits1); return bk; } throw createException(bitKey); } public BitKey and(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { final BitKey.Small other = (BitKey.Small) bitKey; final BitKey.Mid128 bk = (BitKey.Mid128) copy(); bk.and(other.bits, 0); return bk; } else if (bitKey instanceof BitKey.Mid128) { final BitKey.Mid128 other = (BitKey.Mid128) bitKey; final BitKey.Mid128 bk = (BitKey.Mid128) copy(); bk.and(other.bits0, other.bits1); return bk; } else if (bitKey instanceof BitKey.Big) { final BitKey.Big other = (BitKey.Big) bitKey; final BitKey.Mid128 bk = (BitKey.Mid128) copy(); bk.and(other.bits[0], other.bits[1]); return bk; } throw createException(bitKey); } public BitKey andNot(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { final BitKey.Small other = (BitKey.Small) bitKey; final BitKey.Mid128 bk = (BitKey.Mid128) copy(); bk.andNot(other.bits, 0); return bk; } else if (bitKey instanceof BitKey.Mid128) { final BitKey.Mid128 other = (BitKey.Mid128) bitKey; final BitKey.Mid128 bk = (BitKey.Mid128) copy(); bk.andNot(other.bits0, other.bits1); return bk; } else if (bitKey instanceof BitKey.Big) { final BitKey.Big other = (BitKey.Big) bitKey; final BitKey.Mid128 bk = (BitKey.Mid128) copy(); bk.andNot(other.bits[0], other.bits[1]); return bk; } throw createException(bitKey); } private void andNot(long bits0, long bits1) { this.bits0 &= ~bits0; this.bits1 &= ~bits1; } public boolean isSuperSetOf(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { BitKey.Small other = (BitKey.Small) bitKey; return ((this.bits0 | other.bits) == this.bits0); } else if (bitKey instanceof BitKey.Mid128) { BitKey.Mid128 other = (BitKey.Mid128) bitKey; return ((this.bits0 | other.bits0) == this.bits0) && ((this.bits1 | other.bits1) == this.bits1); } else if (bitKey instanceof BitKey.Big) { BitKey.Big other = (BitKey.Big) bitKey; if ((this.bits0 | other.bits[0]) != this.bits0) { return false; } else if ((this.bits1 | other.bits[1]) != this.bits1) { return false; } else { for (int i = 2; i < other.bits.length; i++) { if (other.bits[i] != 0) { return false; } } return true; } } return false; } public boolean intersects(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { BitKey.Small other = (BitKey.Small) bitKey; return (this.bits0 & other.bits) != 0; } else if (bitKey instanceof BitKey.Mid128) { BitKey.Mid128 other = (BitKey.Mid128) bitKey; return (this.bits0 & other.bits0) != 0 || (this.bits1 & other.bits1) != 0; } else if (bitKey instanceof BitKey.Big) { BitKey.Big other = (BitKey.Big) bitKey; if ((this.bits0 & other.bits[0]) != 0) { return true; } else if ((this.bits1 & other.bits[1]) != 0) { return true; } else { return false; } } return false; } public BitSet toBitSet() { final BitSet bitSet = new BitSet(128); copyFromLong(bitSet, 0, bits0); copyFromLong(bitSet, 64, bits1); return bitSet; } public Iterator iterator() { return new Iterator() { long bits0 = Mid128.this.bits0; long bits1 = Mid128.this.bits1; int pos = -1; public boolean hasNext() { if (bits0 != 0) { if (bits0 == Long.MIN_VALUE) { pos = 63; bits0 = 0; return true; } long b = (bits0&-bits0); int delta = 0; while (b >= 256) { b = (b >> 8); delta += 8; } int p = bitPositionTable[(int) b]; if (p >= 0) { p += delta; } else { p = delta; } if (pos < 0) { pos = p; } else if (p == 0) { pos++; } else { pos += (p + 1); } bits0 = bits0 >>> (p + 1); return true; } else { if (pos < 63) { pos = 63; } if (bits1 == Long.MIN_VALUE) { pos = 127; bits1 = 0; return true; } long b = (bits1&-bits1); if (b == 0) { return false; } int delta = 0; while (b >= 256) { b = (b >> 8); delta += 8; } int p = bitPositionTable[(int) b]; if (p >= 0) { p += delta; } else { p = delta; } if (pos < 0) { pos = p; } else if (p == 63) { pos++; } else { pos += (p + 1); } bits1 = bits1 >>> (p + 1); return true; } } public Integer next() { return Integer.valueOf(pos); } public void remove() { throw new UnsupportedOperationException("remove"); } }; } public int nextSetBit(int fromIndex) { if (fromIndex < 0) { throw new IndexOutOfBoundsException( "fromIndex < 0: " + fromIndex); } int u = fromIndex >> 6; long word; switch (u) { case 0: word = bits0 & (WORD_MASK << fromIndex); if (word != 0) { return Long.numberOfTrailingZeros(word); } word = bits1; if (word != 0) { return 64 + Long.numberOfTrailingZeros(word); } return -1; case 1: word = bits1 & (WORD_MASK << fromIndex); if (word != 0) { return 64 + Long.numberOfTrailingZeros(word); } return -1; default: return -1; } } public boolean equals(Object o) { if (this == o) { return true; } if (o instanceof BitKey.Small) { BitKey.Small other = (BitKey.Small) o; return (this.bits0 == other.bits) && (this.bits1 == 0); } else if (o instanceof BitKey.Mid128) { BitKey.Mid128 other = (BitKey.Mid128) o; return (this.bits0 == other.bits0) && (this.bits1 == other.bits1); } else if (o instanceof BitKey.Big) { BitKey.Big other = (BitKey.Big) o; if (this.bits0 != other.bits[0]) { return false; } else if (this.bits1 != other.bits[1]) { return false; } else { for (int i = 2; i < other.bits.length; i++) { if (other.bits[i] != 0) { return false; } } return true; } } return false; } public int hashCode() { long h = 1234; h ^= bits0; h ^= bits1 * 2; return (int)((h >> 32) ^ h); } public String toString() { StringBuilder buf = new StringBuilder(64); buf.append("0x"); for (int i = 127; i >= 0; i--) { buf.append((get(i)) ? '1' : '0'); } return buf.toString(); } public BitKey copy() { return new Mid128(this); } public BitKey emptyCopy() { return new Mid128(); } public boolean isEmpty() { return bits0 == 0 && bits1 == 0; } // implement Comparable (in lazy, expensive fashion) public int compareTo(BitKey bitKey) { if (bitKey instanceof Mid128) { Mid128 that = (Mid128) bitKey; if (this.bits1 != that.bits1) { return compareUnsigned(this.bits1, that.bits1); } return compareUnsigned(this.bits0, that.bits0); } else if (bitKey instanceof Small) { Small that = (Small) bitKey; if (this.bits1 != 0) { return 1; } return compareUnsigned(this.bits0, that.bits); } else { return compareToBig((Big) bitKey); } } int compareToBig(Big that) { int thatBitsLength = that.effectiveSize(); switch (thatBitsLength) { case 0: return this.bits1 == 0 && this.bits0 == 0 ? 0 : 1; case 1: if (this.bits1 != 0) { return 1; } return compareUnsigned(this.bits0, that.bits[0]); case 2: if (this.bits1 != that.bits[1]) { return compareUnsigned(this.bits1, that.bits[1]); } return compareUnsigned(this.bits0, that.bits[0]); default: return -1; } } } /** * Implementation of {@link BitKey} with more than 64 bits. Similar to * {@link java.util.BitSet}, but does not require dynamic resizing. */ public class Big extends AbstractBitKey { private static final long serialVersionUID = -3715282769845236295L; private long[] bits; private Big(int size) { bits = new long[chunkCount(size + 1)]; } private Big(Big big) { bits = big.bits.clone(); } private int size() { return bits.length; } /** * Returns the number of chunks, ignoring any chunks on the leading * edge that are all zero. * * @return number of chunks that are not on the leading edge */ private int effectiveSize() { int n = bits.length; while (n > 0 && bits[n - 1] == 0) { --n; } return n; } public void set(int pos) { bits[chunkPos(pos)] |= bit(pos); } public boolean get(int pos) { return (bits[chunkPos(pos)] & bit(pos)) != 0; } public void clear(int pos) { bits[chunkPos(pos)] &= ~bit(pos); } public void clear() { for (int i = 0; i < bits.length; i++) { bits[i] = 0; } } public int cardinality() { int n = 0; for (int i = 0; i < bits.length; i++) { n += bitCount(bits[i]); } return n; } private void or(long bits0) { this.bits[0] |= bits0; } private void or(long bits0, long bits1) { this.bits[0] |= bits0; this.bits[1] |= bits1; } private void or(long[] bits) { for (int i = 0; i < bits.length; i++) { this.bits[i] |= bits[i]; } } private void orNot(long bits0) { this.bits[0] ^= bits0; } private void orNot(long bits0, long bits1) { this.bits[0] ^= bits0; this.bits[1] ^= bits1; } private void orNot(long[] bits) { for (int i = 0; i < bits.length; i++) { this.bits[i] ^= bits[i]; } } private void and(long[] bits) { int length = Math.min(bits.length, this.bits.length); for (int i = 0; i < length; i++) { this.bits[i] &= bits[i]; } for (int i = bits.length; i < this.bits.length; i++) { this.bits[i] = 0; } } public BitKey or(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { final BitKey.Small other = (BitKey.Small) bitKey; final BitKey.Big bk = (BitKey.Big) copy(); bk.or(other.bits); return bk; } else if (bitKey instanceof BitKey.Mid128) { final BitKey.Mid128 other = (BitKey.Mid128) bitKey; final BitKey.Big bk = (BitKey.Big) copy(); bk.or(other.bits0, other.bits1); return bk; } else if (bitKey instanceof BitKey.Big) { final BitKey.Big other = (BitKey.Big) bitKey; if (other.size() > size()) { final BitKey.Big bk = (BitKey.Big) other.copy(); bk.or(bits); return bk; } else { final BitKey.Big bk = (BitKey.Big) copy(); bk.or(other.bits); return bk; } } throw createException(bitKey); } public BitKey orNot(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { final BitKey.Small other = (BitKey.Small) bitKey; final BitKey.Big bk = (BitKey.Big) copy(); bk.orNot(other.bits); return bk; } else if (bitKey instanceof BitKey.Mid128) { final BitKey.Mid128 other = (BitKey.Mid128) bitKey; final BitKey.Big bk = (BitKey.Big) copy(); bk.orNot(other.bits0, other.bits1); return bk; } else if (bitKey instanceof BitKey.Big) { final BitKey.Big other = (BitKey.Big) bitKey; if (other.size() > size()) { final BitKey.Big bk = (BitKey.Big) other.copy(); bk.orNot(bits); return bk; } else { final BitKey.Big bk = (BitKey.Big) copy(); bk.orNot(other.bits); return bk; } } throw createException(bitKey); } public BitKey and(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { final BitKey.Small bk = (BitKey.Small) bitKey.copy(); bk.and(bits[0]); return bk; } else if (bitKey instanceof BitKey.Mid128) { final BitKey.Mid128 bk = (BitKey.Mid128) bitKey.copy(); bk.and(bits[0], bits[1]); return bk; } else if (bitKey instanceof BitKey.Big) { final BitKey.Big other = (BitKey.Big) bitKey; if (other.size() < size()) { final BitKey.Big bk = (BitKey.Big) other.copy(); bk.and(bits); return bk; } else { final BitKey.Big bk = (BitKey.Big) copy(); bk.and(other.bits); return bk; } } throw createException(bitKey); } public BitKey andNot(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { final BitKey.Small other = (BitKey.Small) bitKey; final BitKey.Big bk = (BitKey.Big) copy(); bk.andNot(other.bits); return bk; } else if (bitKey instanceof BitKey.Mid128) { final BitKey.Mid128 other = (Mid128) bitKey; final BitKey.Big bk = (BitKey.Big) copy(); bk.andNot(other.bits0, other.bits1); return bk; } else if (bitKey instanceof BitKey.Big) { final BitKey.Big other = (BitKey.Big) bitKey; final BitKey.Big bk = (BitKey.Big) copy(); bk.andNot(other.bits); return bk; } throw createException(bitKey); } private void andNot(long[] bits) { for (int i = 0; i < bits.length; i++) { this.bits[i] &= ~bits[i]; } } private void andNot(long bits0, long bits1) { this.bits[0] &= ~bits0; this.bits[1] &= ~bits1; } private void andNot(long bits) { this.bits[0] &= ~bits; } public boolean isSuperSetOf(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { BitKey.Small other = (BitKey.Small) bitKey; return ((this.bits[0] | other.bits) == this.bits[0]); } else if (bitKey instanceof BitKey.Mid128) { BitKey.Mid128 other = (BitKey.Mid128) bitKey; return ((this.bits[0] | other.bits0) == this.bits[0]) && ((this.bits[1] | other.bits1) == this.bits[1]); } else if (bitKey instanceof BitKey.Big) { BitKey.Big other = (BitKey.Big) bitKey; int len = Math.min(bits.length, other.bits.length); for (int i = 0; i < len; i++) { if ((this.bits[i] | other.bits[i]) != this.bits[i]) { return false; } } if (other.bits.length > this.bits.length) { for (int i = len; i < other.bits.length; i++) { if (other.bits[i] != 0) { return false; } } } return true; } return false; } public boolean intersects(BitKey bitKey) { if (bitKey instanceof BitKey.Small) { BitKey.Small other = (BitKey.Small) bitKey; return (this.bits[0] & other.bits) != 0; } else if (bitKey instanceof BitKey.Mid128) { BitKey.Mid128 other = (BitKey.Mid128) bitKey; return (this.bits[0] & other.bits0) != 0 || (this.bits[1] & other.bits1) != 0; } else if (bitKey instanceof BitKey.Big) { BitKey.Big other = (BitKey.Big) bitKey; int len = Math.min(bits.length, other.bits.length); for (int i = 0; i < len; i++) { if ((this.bits[i] & other.bits[i]) != 0) { return true; } } return false; } return false; } public BitSet toBitSet() { final BitSet bitSet = new BitSet(64); int pos = 0; for (int i = 0; i < bits.length; i++) { copyFromLong(bitSet, pos, bits[i]); pos += 64; } return bitSet; } public Iterator iterator() { return new Iterator() { long[] bits = Big.this.bits.clone(); int pos = -1; int index = 0; public boolean hasNext() { if (index >= bits.length) { return false; } if (pos < 0) { while (bits[index] == 0) { index++; if (index >= bits.length) { return false; } } pos = (64 * index) - 1; } long bs = bits[index]; if (bs == 0) { while (bits[index] == 0) { index++; if (index >= bits.length) { return false; } } pos = (64 * index) - 1; bs = bits[index]; } if (bs != 0) { if (bs == Long.MIN_VALUE) { pos = (64 * index) + 63; bits[index] = 0; return true; } long b = (bs&-bs); int delta = 0; while (b >= 256) { b = (b >> 8); delta += 8; } int p = bitPositionTable[(int) b]; if (p >= 0) { p += delta; } else { p = delta; } if (pos < 0) { pos = p; } else if (p == 0) { pos++; } else { pos += (p + 1); } bits[index] = bits[index] >>> (p + 1); return true; } return false; } public Integer next() { return Integer.valueOf(pos); } public void remove() { throw new UnsupportedOperationException("remove"); } }; } public int nextSetBit(int fromIndex) { if (fromIndex < 0) { throw new IndexOutOfBoundsException( "fromIndex < 0: " + fromIndex); } int u = chunkPos(fromIndex); if (u >= bits.length) { return -1; } long word = bits[u] & (WORD_MASK << fromIndex); while (true) { if (word != 0) { return (u * 64) + Long.numberOfTrailingZeros(word); } if (++u == bits.length) { return -1; } word = bits[u]; } } public boolean equals(Object o) { if (this == o) { return true; } if (o instanceof BitKey.Small) { BitKey.Small other = (BitKey.Small) o; if (this.bits[0] != other.bits) { return false; } else { for (int i = 1; i < this.bits.length; i++) { if (this.bits[i] != 0) { return false; } } return true; } } else if (o instanceof BitKey.Mid128) { BitKey.Mid128 other = (BitKey.Mid128) o; if (this.bits[0] != other.bits0) { return false; } else if (this.bits[1] != other.bits1) { return false; } else { for (int i = 2; i < this.bits.length; i++) { if (this.bits[i] != 0) { return false; } } return true; } } else if (o instanceof BitKey.Big) { BitKey.Big other = (BitKey.Big) o; int len = Math.min(bits.length, other.bits.length); for (int i = 0; i < len; i++) { if (this.bits[i] != other.bits[i]) { return false; } } if (this.bits.length > other.bits.length) { for (int i = len; i < this.bits.length; i++) { if (this.bits[i] != 0) { return false; } } } else if (other.bits.length > this.bits.length) { for (int i = len; i < other.bits.length; i++) { if (other.bits[i] != 0) { return false; } } } return true; } return false; } public int hashCode() { // It is important that leading 0s, and bits.length do not affect // the hash code. For instance, we want {1} to be equal to // {1, 0, 0}. This algorithm in fact ignores all 0s. // // It is also important that the hash code is the same as produced // by Small and Mid128. long h = 1234; for (int i = bits.length; --i >= 0;) { h ^= bits[i] * (i + 1); } return (int)((h >> 32) ^ h); } public String toString() { StringBuilder buf = new StringBuilder(64); buf.append("0x"); int start = bits.length * 64 - 1; for (int i = start; i >= 0; i--) { buf.append((get(i)) ? '1' : '0'); } return buf.toString(); } public BitKey copy() { return new Big(this); } public BitKey emptyCopy() { return new Big(bits.length << ChunkBitCount); } public boolean isEmpty() { for (long bit : bits) { if (bit != 0) { return false; } } return true; } public int compareTo(BitKey bitKey) { if (bitKey instanceof Big) { return compareUnsignedArrays(this.bits, ((Big) bitKey).bits); } else if (bitKey instanceof Mid128) { Mid128 that = (Mid128) bitKey; return -that.compareToBig(this); } else { Small that = (Small) bitKey; return -that.compareToBig(this); } } } static final byte bitPositionTable[] = { -1, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0 }; } // End BitKey.java mondrian-3.4.1/src/main/mondrian/rolap/RolapEvaluatorRoot.java0000644000175000017500000002123611735330606024340 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2008-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.*; import mondrian.olap.*; import mondrian.server.Execution; import mondrian.server.Statement; import mondrian.spi.Dialect; import mondrian.spi.DialectManager; import java.util.*; /** * Context at the root of a tree of evaluators. * *

Contains the context that does not change as evaluation context is * pushed/popped. * * @author jhyde * @since Nov 11, 2008 */ class RolapEvaluatorRoot { final Map expResultCache = new HashMap(); final Map tmpExpResultCache = new HashMap(); final RolapCube cube; final RolapConnection connection; final SchemaReader schemaReader; final Map compiledExps = new HashMap(); final Statement statement; final Query query; private final Date queryStartTime; final Dialect currentDialect; /** * Default members of each hierarchy, from the schema reader's * perspective. Finding the default member is moderately expensive, but * happens very often. */ final RolapMember[] defaultMembers; final int[] nonAllPositions; int nonAllPositionCount; final SolveOrderMode solveOrderMode = Util.lookup( SolveOrderMode.class, MondrianProperties.instance().SolveOrderMode.get().toUpperCase(), SolveOrderMode.ABSOLUTE); final Set activeNativeExpansions = new HashSet(); /** * The size of the command stack at which we will next check for recursion. */ int recursionCheckCommandCount; public final Execution execution; /** * Creates a RolapEvaluatorRoot. * * @param statement statement * @deprecated */ public RolapEvaluatorRoot(Statement statement) { this(statement, null); } public RolapEvaluatorRoot(Execution execution) { this(execution.getMondrianStatement(), execution); } private RolapEvaluatorRoot(Statement statement, Execution execution) { this.execution = execution; this.statement = statement; this.query = statement.getQuery(); this.cube = (RolapCube) query.getCube(); this.connection = statement.getMondrianConnection(); this.schemaReader = query.getSchemaReader(true); this.queryStartTime = new Date(); List list = new ArrayList(); nonAllPositions = new int[cube.getHierarchies().size()]; nonAllPositionCount = 0; for (RolapHierarchy hierarchy : cube.getHierarchies()) { RolapMember defaultMember = (RolapMember) schemaReader.getHierarchyDefaultMember(hierarchy); assert defaultMember != null; if (ScenarioImpl.isScenario(hierarchy) && connection.getScenario() != null) { defaultMember = ((ScenarioImpl) connection.getScenario()).getMember(); } // This fragment is a concurrency bottleneck, so use a cache of // hierarchy usages. final HierarchyUsage hierarchyUsage = cube.getFirstUsage(hierarchy); if (hierarchyUsage != null) { if (defaultMember instanceof RolapMemberBase) { ((RolapMemberBase) defaultMember).makeUniqueName( hierarchyUsage); } } list.add(defaultMember); if (!defaultMember.isAll()) { nonAllPositions[nonAllPositionCount] = hierarchy.getOrdinalInCube(); nonAllPositionCount++; } } this.defaultMembers = list.toArray(new RolapMember[list.size()]); this.currentDialect = DialectManager.createDialect(schemaReader.getDataSource(), null); this.recursionCheckCommandCount = (defaultMembers.length << 4); } /** * Implements a cheap-and-cheerful mapping from expressions to compiled * expressions. * *

TODO: Save compiled expressions somewhere better. * * @param exp Expression * @param scalar Whether expression is scalar * @param resultStyle Preferred result style; if null, use query's default * result style; ignored if expression is scalar * @return compiled expression */ final Calc getCompiled( Exp exp, boolean scalar, ResultStyle resultStyle) { CompiledExpKey key = new CompiledExpKey(exp, scalar, resultStyle); Calc calc = compiledExps.get(key); if (calc == null) { calc = statement.getQuery().compileExpression( exp, scalar, resultStyle); compiledExps.put(key, calc); } return calc; } /** * Just a simple key of Exp/scalar/resultStyle, used for keeping * compiled expressions. Previous to the introduction of this * class, the key was a list constructed as Arrays.asList(exp, scalar, * resultStyle) and having poorer performance on equals, hashCode, * and construction. */ private static class CompiledExpKey { private final Exp exp; private final boolean scalar; private final ResultStyle resultStyle; private int hashCode = Integer.MIN_VALUE; private CompiledExpKey( Exp exp, boolean scalar, ResultStyle resultStyle) { this.exp = exp; this.scalar = scalar; this.resultStyle = resultStyle; } public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof CompiledExpKey)) { return false; } CompiledExpKey otherKey = (CompiledExpKey)other; return this.scalar == otherKey.scalar && this.resultStyle == otherKey.resultStyle && this.exp.equals(otherKey.exp); } public int hashCode() { if (hashCode != Integer.MIN_VALUE) { return hashCode; } else { int hash = 0; hash = Util.hash(hash, scalar); hash = Util.hash(hash, resultStyle); this.hashCode = Util.hash(hash, exp); } return this.hashCode; } } /** * Evaluates a named set. * *

The default implementation throws * {@link UnsupportedOperationException}. * * @param namedSet Named set * @param create Whether to create named set evaluator if not found */ protected Evaluator.NamedSetEvaluator evaluateNamedSet( NamedSet namedSet, boolean create) { throw new UnsupportedOperationException(); } /** * Returns the value of a parameter, evaluating its default expression * if necessary. * *

The default implementation throws * {@link UnsupportedOperationException}. */ public Object getParameterValue(ParameterSlot slot) { throw new UnsupportedOperationException(); } /** * Puts result in cache. * * @param key key * @param result value to be cached * @param isValidResult indicate if this result is valid */ public final void putCacheResult( Object key, Object result, boolean isValidResult) { if (isValidResult) { expResultCache.put(key, result); } else { tmpExpResultCache.put(key, result); } } /** * Gets result from cache. * * @param key cache key * @return cached expression */ public final Object getCacheResult(Object key) { Object result = expResultCache.get(key); if (result == null) { result = tmpExpResultCache.get(key); } return result; } /** * Clears the expression result cache. * * @param clearValidResult whether to clear valid expression results */ public final void clearResultCache(boolean clearValidResult) { if (clearValidResult) { expResultCache.clear(); } tmpExpResultCache.clear(); } /** * Get query start time. * * @return the query start time */ public Date getQueryStartTime() { return queryStartTime; } } // End RolapEvaluatorRoot.java mondrian-3.4.1/src/main/mondrian/rolap/RolapCubeHierarchy.java0000644000175000017500000012457411735330606024260 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.olap.fun.VisualTotalsFunDef; import mondrian.rolap.TupleReader.MemberBuilder; import mondrian.rolap.sql.MemberChildrenConstraint; import mondrian.rolap.sql.TupleConstraint; import mondrian.util.UnsupportedList; import java.sql.SQLException; import java.util.*; /** * Hierarchy that is associated with a specific Cube. * * @author Will Gorman, 19 October 2007 */ public class RolapCubeHierarchy extends RolapHierarchy { private final boolean cachingEnabled = MondrianProperties.instance().EnableRolapCubeMemberCache.get(); private final RolapCubeDimension cubeDimension; private final RolapHierarchy rolapHierarchy; private final RolapCubeLevel currentNullLevel; private RolapCubeMember currentNullMember; private RolapCubeMember currentAllMember; private final MondrianDef.RelationOrJoin currentRelation; private final RolapCubeHierarchyMemberReader reader; private HierarchyUsage usage; private final Map aliases = new HashMap(); private RolapCubeMember currentDefaultMember; private final int ordinal; /** * True if the hierarchy is degenerate - has no dimension table of its own, * just drives from the cube's fact table. */ protected final boolean usingCubeFact; /** * Length of prefix to be removed when translating member unique names, or * 0 if no translation is necessary. */ private final int removePrefixLength; // redundant copy of {@link #levels} with tigher type private final RolapCubeLevel[] cubeLevels; /** * Creates a RolapCubeHierarchy. * * @param cubeDimension Dimension * @param cubeDim XML dimension element * @param rolapHierarchy Wrapped hierarchy * @param subName Name of hierarchy within dimension * @param ordinal Ordinal of hierarchy within cube */ public RolapCubeHierarchy( RolapCubeDimension cubeDimension, MondrianDef.CubeDimension cubeDim, RolapHierarchy rolapHierarchy, String subName, int ordinal) { super( cubeDimension, subName, applyPrefix(cubeDim, rolapHierarchy.getCaption()), rolapHierarchy.isVisible(), applyPrefix(cubeDim, rolapHierarchy.getDescription()), rolapHierarchy.hasAll(), null, rolapHierarchy.getAnnotationMap()); this.ordinal = ordinal; if (!cubeDimension.getCube().isVirtual()) { this.usage = new HierarchyUsage( cubeDimension.getCube(), rolapHierarchy, cubeDim); } this.rolapHierarchy = rolapHierarchy; this.cubeDimension = cubeDimension; this.xmlHierarchy = rolapHierarchy.getXmlHierarchy(); // this relation should equal the name of the new dimension table // The null member belongs to a level with very similar properties to // the 'all' level. this.currentNullLevel = new RolapCubeLevel(nullLevel, this); usingCubeFact = (cubeDimension.getCube().getFact() == null || cubeDimension.getCube().getFact().equals( rolapHierarchy.getRelation())); // re-alias names if necessary if (!usingCubeFact) { // join expressions are columns only assert (usage.getJoinExp() instanceof MondrianDef.Column); currentRelation = this.cubeDimension.getCube().getStar().getUniqueRelation( rolapHierarchy.getRelation(), usage.getForeignKey(), ((MondrianDef.Column)usage.getJoinExp()).getColumnName(), usage.getJoinTable().getAlias()); } else { currentRelation = rolapHierarchy.getRelation(); } extractNewAliases(rolapHierarchy.getRelation(), currentRelation); this.relation = currentRelation; this.levels = this.cubeLevels = new RolapCubeLevel[rolapHierarchy.getLevels().length]; for (int i = 0; i < rolapHierarchy.getLevels().length; i++) { this.cubeLevels[i] = new RolapCubeLevel( (RolapLevel) rolapHierarchy.getLevels()[i], this); if (i == 0) { if (rolapHierarchy.getAllMember() != null) { RolapCubeLevel allLevel; if (hasAll()) { allLevel = this.cubeLevels[0]; } else { // create an all level if one doesn't normally // exist in the hierarchy allLevel = new RolapCubeLevel( rolapHierarchy.getAllMember().getLevel(), this); allLevel.init(cubeDimension.xmlDimension); } this.currentAllMember = new RolapAllCubeMember( rolapHierarchy.getAllMember(), allLevel); } } } // Compute whether the unique names of members of this hierarchy are // different from members of the underlying hierarchy. If so, compute // the length of the prefix to be removed before this hierarchy's unique // name is added. For example, if this.uniqueName is "[Ship Time]" and // rolapHierarchy.uniqueName is "[Time]", remove prefixLength will be // length("[Ship Time]") = 11. if (uniqueName.equals(rolapHierarchy.getUniqueName())) { this.removePrefixLength = 0; } else { this.removePrefixLength = rolapHierarchy.getUniqueName().length(); } if (cubeDimension.isHighCardinality() || !cachingEnabled) { this.reader = new NoCacheRolapCubeHierarchyMemberReader(); } else { this.reader = new CacheRolapCubeHierarchyMemberReader(); } } /** * Applies a prefix to a caption or description of a hierarchy in a shared * dimension. Ensures that if a dimension is used more than once in the same * cube then the hierarchies are distinguishable. * *

For example, if the [Time] dimension is imported as [Order Time] and * [Ship Time], then the [Time].[Weekly] hierarchy would have caption * "Order Time.Weekly caption" and description "Order Time.Weekly * description". * *

If the dimension usage has a caption, it overrides. * *

If the dimension usage has a null name, or the name is the same * as the dimension, and no caption, then no prefix is applied. * * @param cubeDim Cube dimension (maybe a usage of a shared dimension) * @param caption Caption or description * @return Caption or description, possibly prefixed by dimension role name */ private static String applyPrefix( MondrianDef.CubeDimension cubeDim, String caption) { if (caption == null) { return null; } if (cubeDim instanceof MondrianDef.DimensionUsage) { final MondrianDef.DimensionUsage dimensionUsage = (MondrianDef.DimensionUsage) cubeDim; if (dimensionUsage.name != null && !dimensionUsage.name.equals(dimensionUsage.source)) { if (dimensionUsage.caption != null) { return dimensionUsage.caption + "." + caption; } else { return dimensionUsage.name + "." + caption; } } } return caption; } @Override public RolapCubeLevel[] getLevels() { return cubeLevels; } public String getAllMemberName() { return rolapHierarchy.getAllMemberName(); } public String getSharedHierarchyName() { return rolapHierarchy.getSharedHierarchyName(); } public String getAllLevelName() { return rolapHierarchy.getAllLevelName(); } public boolean isUsingCubeFact() { return usingCubeFact; } public String lookupAlias(String origTable) { return aliases.get(origTable); } public final RolapHierarchy getRolapHierarchy() { return rolapHierarchy; } public final int getOrdinalInCube() { return ordinal; } /** * Populates the alias map for the old and new relations. * *

This method may be simplified when we obsolete * {@link mondrian.rolap.HierarchyUsage}. * * @param oldrel Original relation, as defined in the schema * @param newrel New star relation, generated by RolapStar, canonical, and * shared between all cubes with similar structure */ protected void extractNewAliases( MondrianDef.RelationOrJoin oldrel, MondrianDef.RelationOrJoin newrel) { if (oldrel == null && newrel == null) { return; } else if (oldrel instanceof MondrianDef.Relation && newrel instanceof MondrianDef.Relation) { aliases.put( ((MondrianDef.Relation) oldrel).getAlias(), ((MondrianDef.Relation) newrel).getAlias()); } else if (oldrel instanceof MondrianDef.Join && newrel instanceof MondrianDef.Join) { MondrianDef.Join oldjoin = (MondrianDef.Join)oldrel; MondrianDef.Join newjoin = (MondrianDef.Join)newrel; extractNewAliases(oldjoin.left, newjoin.left); extractNewAliases(oldjoin.right, newjoin.right); } else { throw new UnsupportedOperationException(); } } public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof RolapCubeHierarchy)) { return false; } RolapCubeHierarchy that = (RolapCubeHierarchy)o; return cubeDimension.equals(that.cubeDimension) && getUniqueName().equals(that.getUniqueName()); } protected int computeHashCode() { return Util.hash(super.computeHashCode(), this.cubeDimension.cube); } public Member createMember( Member parent, Level level, String name, Formula formula) { RolapLevel rolapLevel = ((RolapCubeLevel)level).getRolapLevel(); if (formula == null) { RolapMember rolapParent = null; if (parent != null) { rolapParent = ((RolapCubeMember)parent).getRolapMember(); } RolapMember member = new RolapMemberBase(rolapParent, rolapLevel, name); return new RolapCubeMember( (RolapCubeMember) parent, member, (RolapCubeLevel) level); } else if (level.getDimension().isMeasures()) { RolapCalculatedMeasure member = new RolapCalculatedMeasure( (RolapMember) parent, rolapLevel, name, formula); return new RolapCubeMember( (RolapCubeMember) parent, member, (RolapCubeLevel) level); } else { RolapCalculatedMember member = new RolapCalculatedMember( (RolapMember) parent, rolapLevel, name, formula); return new RolapCubeMember( (RolapCubeMember) parent, member, (RolapCubeLevel) level); } } boolean tableExists(String tableName) { return rolapHierarchy.tableExists(tableName); } /** * The currentRelation object is derived from the shared relation object * it is generated via the RolapStar object, and contains unique aliases * for it's particular join path * * @return rolap cube hierarchy relation */ public MondrianDef.RelationOrJoin getRelation() { return currentRelation; } // override with stricter return type; make final, important for performance public final RolapCubeMember getDefaultMember() { if (currentDefaultMember == null) { reader.getRootMembers(); currentDefaultMember = bootstrapLookup( (RolapMember) rolapHierarchy.getDefaultMember()); } return currentDefaultMember; } /** * Looks up a {@link RolapCubeMember} corresponding to a {@link RolapMember} * of the underlying hierarchy. Safe to be called while the hierarchy is * initializing. * * @param rolapMember Member of underlying hierarchy * @return Member of this hierarchy */ private RolapCubeMember bootstrapLookup(RolapMember rolapMember) { RolapCubeMember parent = rolapMember.getParentMember() == null ? null : rolapMember.getParentMember().isAll() ? currentAllMember : bootstrapLookup(rolapMember.getParentMember()); RolapCubeLevel level = cubeLevels[rolapMember.getLevel().getDepth()]; return reader.lookupCubeMember(parent, rolapMember, level); } public Member getNullMember() { // use lazy initialization to get around bootstrap issues if (currentNullMember == null) { currentNullMember = new RolapCubeMember( null, (RolapMember) rolapHierarchy.getNullMember(), currentNullLevel); } return currentNullMember; } /** * Returns the 'all' member. */ public RolapCubeMember getAllMember() { return currentAllMember; } void setMemberReader(MemberReader memberReader) { rolapHierarchy.setMemberReader(memberReader); } MemberReader getMemberReader() { return reader; } public void setDefaultMember(Member defaultMeasure) { // refactor this! rolapHierarchy.setDefaultMember(defaultMeasure); RolapCubeLevel level = new RolapCubeLevel( (RolapLevel)rolapHierarchy.getDefaultMember().getLevel(), this); currentDefaultMember = new RolapCubeMember( null, (RolapMember) rolapHierarchy.getDefaultMember(), level); } void init(MondrianDef.CubeDimension xmlDimension) { // first init shared hierarchy rolapHierarchy.init(xmlDimension); // second init cube hierarchy super.init(xmlDimension); } /** * Converts the unique name of a member of the underlying hierarchy to * the appropriate name for this hierarchy. * *

For example, if the shared hierarchy is [Time].[Quarterly] and the * hierarchy usage is [Ship Time].[Quarterly], then [Time].[1997].[Q1] would * be translated to [Ship Time].[Quarerly].[1997].[Q1]. * * @param memberUniqueName Unique name of member from underlying hierarchy * @return Translated unique name */ final String convertMemberName(String memberUniqueName) { if (removePrefixLength > 0) { return uniqueName + memberUniqueName.substring(removePrefixLength); } return memberUniqueName; } public final RolapCube getCube() { return cubeDimension.cube; } /** * TODO: Since this is part of a caching strategy, should be implemented * as a Strategy Pattern, avoiding hirarchy. */ public static interface RolapCubeHierarchyMemberReader extends MemberReader { public RolapCubeMember lookupCubeMember( final RolapCubeMember parent, final RolapMember member, final RolapCubeLevel level); public MemberCacheHelper getRolapCubeMemberCacheHelper(); } /****** RolapCubeMember Caching Approach: - RolapHierarchy.SmartMemberReader.SmartCacheHelper -> This is the shared cache across shared hierarchies. This member cache only contains members loaded by non-cube specific member lookups. This cache should only contain RolapMembers, not RolapCubeMembers - RolapCubeHierarchy.RolapCubeHierarchyMemberReader.rolapCubeCacheHelper -> This cache contains the RolapCubeMember objects, which are cube specific wrappers of shared members. - RolapCubeHierarchy.RolapCubeHierarchyMemberReader.SmartCacheHelper -> This is the inherited shared cache from SmartMemberReader, and is used when a join with the fact table is necessary, aka a SqlContextConstraint is used. This cache may be redundant with rolapCubeCacheHelper. - A Special note regarding RolapCubeHierarchyMemberReader.cubeSource - This class was required for the special situation getMemberBuilder() method call from RolapNativeSet. This class utilizes both the rolapCubeCacheHelper class for storing RolapCubeMembers, and also the RolapCubeHierarchyMemberReader's inherited SmartCacheHelper. ******/ /** * member reader wrapper - uses existing member reader, * but wraps and caches all intermediate members. * *

Synchronization. Most synchronization takes place within * SmartMemberReader. All synchronization is done on the cacheHelper * object. */ public class CacheRolapCubeHierarchyMemberReader extends SmartMemberReader implements RolapCubeHierarchyMemberReader { /** * cubeSource is passed as our member builder */ protected final RolapCubeSqlMemberSource cubeSource; /** * this cache caches RolapCubeMembers that are light wrappers around * shared and non-shared Hierarchy RolapMembers. The inherited * cacheHelper object contains non-shared hierarchy RolapMembers. * non-shared hierarchy RolapMembers are created when a member lookup * involves the Cube's fact table. */ protected MemberCacheHelper rolapCubeCacheHelper; private final boolean enableCache = MondrianProperties.instance().EnableRolapCubeMemberCache.get(); public CacheRolapCubeHierarchyMemberReader() { super(new SqlMemberSource(RolapCubeHierarchy.this)); rolapCubeCacheHelper = new MemberCacheHelper(RolapCubeHierarchy.this); cubeSource = new RolapCubeSqlMemberSource( this, RolapCubeHierarchy.this, rolapCubeCacheHelper, cacheHelper); cubeSource.setCache(getMemberCache()); } public MemberBuilder getMemberBuilder() { return this.cubeSource; } public MemberCacheHelper getRolapCubeMemberCacheHelper() { return rolapCubeCacheHelper; } public List getRootMembers() { if (rootMembers == null) { rootMembers = getMembersInLevel(cubeLevels[0], 0, Integer.MAX_VALUE); } return rootMembers; } protected void readMemberChildren( List parentMembers, List children, MemberChildrenConstraint constraint) { List rolapChildren = new ArrayList(); List rolapParents = new ArrayList(); Map lookup = new HashMap(); // extract RolapMembers from their RolapCubeMember objects // populate lookup for reconnecting parents and children for (RolapMember member : parentMembers) { if (member instanceof VisualTotalsFunDef.VisualTotalMember) { continue; } final RolapCubeMember cubeMember = (RolapCubeMember) member; final RolapMember rolapMember = cubeMember.getRolapMember(); lookup.put(rolapMember.getUniqueName(), cubeMember); rolapParents.add(rolapMember); } // get member children from shared member reader if possible, // if not get them from our own source boolean joinReq = (constraint instanceof SqlContextConstraint); if (joinReq) { super.readMemberChildren( parentMembers, rolapChildren, constraint); } else { rolapHierarchy.getMemberReader().getMemberChildren( rolapParents, rolapChildren, constraint); } // now lookup or create RolapCubeMember for (RolapMember currMember : rolapChildren) { RolapCubeMember parent = lookup.get( currMember.getParentMember().getUniqueName()); RolapCubeLevel level = parent.getLevel().getChildLevel(); if (level == null) { // most likely a parent child hierarchy level = parent.getLevel(); } RolapCubeMember newmember = lookupCubeMember( parent, currMember, level); children.add(newmember); } // Put them in a temporary hash table first. Register them later, // when we know their size (hence their 'cost' to the cache pool). Map> tempMap = new HashMap>(); for (RolapMember member1 : parentMembers) { tempMap.put(member1, Collections.emptyList()); } // note that this stores RolapCubeMembers in our cache, // which also stores RolapMembers. for (RolapMember child : children) { // todo: We could optimize here. If members.length is small, it's // more efficient to drive from members, rather than hashing // children.length times. We could also exploit the fact that the // result is sorted by ordinal and therefore, unless the "members" // contains members from different levels, children of the same // member will be contiguous. assert child != null : "child"; final RolapMember parentMember = child.getParentMember(); List cacheList = tempMap.get(parentMember); if (cacheList == null) { // The list is null if, due to dropped constraints, we now // have a children list of a member we didn't explicitly // ask for it. Adding it to the cache would be viable, but // let's ignore it. continue; } else if (cacheList == Collections.EMPTY_LIST) { cacheList = new ArrayList(); tempMap.put(parentMember, cacheList); } cacheList.add(child); } synchronized (cacheHelper) { for (Map.Entry> entry : tempMap.entrySet()) { final RolapMember member = entry.getKey(); if (rolapCubeCacheHelper.getChildrenFromCache( member, constraint) == null) { final List cacheList = entry.getValue(); if (enableCache) { rolapCubeCacheHelper.putChildren( member, constraint, cacheList); } } } } } public void getMemberChildren( List parentMembers, List children, MemberChildrenConstraint constraint) { synchronized (cacheHelper) { checkCacheStatus(); List missed = new ArrayList(); for (RolapMember parentMember : parentMembers) { List list = rolapCubeCacheHelper.getChildrenFromCache( parentMember, constraint); if (list == null) { // the null member has no children if (!parentMember.isNull()) { missed.add(parentMember); } } else { children.addAll(list); } } if (missed.size() > 0) { readMemberChildren(missed, children, constraint); } } } public List getMembersInLevel( RolapLevel level, int startOrdinal, int endOrdinal, TupleConstraint constraint) { synchronized (cacheHelper) { checkCacheStatus(); List members = rolapCubeCacheHelper.getLevelMembersFromCache( level, constraint); if (members != null) { return members; } // if a join is required, we need to pass in the RolapCubeLevel // vs. the regular level boolean joinReq = (constraint instanceof SqlContextConstraint); List list; final RolapCubeLevel cubeLevel = (RolapCubeLevel) level; if (!joinReq) { list = rolapHierarchy.getMemberReader().getMembersInLevel( cubeLevel.getRolapLevel(), startOrdinal, endOrdinal, constraint); } else { list = super.getMembersInLevel( level, startOrdinal, endOrdinal, constraint); } List newlist = new ArrayList(); for (RolapMember member : list) { // note that there is a special case for the all member // REVIEW: disabled, to see what happens. if this code is // for performance, we should check level.isAll at the top // of the method; if it is for correctness, leave the code // in if (false && member == rolapHierarchy.getAllMember()) { newlist.add(getAllMember()); } else { RolapCubeMember cubeMember = lookupCubeMemberWithParent( member, cubeLevel); newlist.add(cubeMember); } } rolapCubeCacheHelper.putLevelMembersInCache( level, constraint, newlist); return newlist; } } private RolapCubeMember lookupCubeMemberWithParent( RolapMember member, RolapCubeLevel cubeLevel) { final RolapMember parentMember = member.getParentMember(); final RolapCubeMember parentCubeMember; if (parentMember == null) { parentCubeMember = null; } else { // In parent-child hierarchies, a member's parent may be in the // same level. final RolapCubeLevel parentLevel = parentMember.getLevel() == member.getLevel() ? cubeLevel : cubeLevel.getParentLevel(); parentCubeMember = lookupCubeMemberWithParent( parentMember, parentLevel); } return lookupCubeMember( parentCubeMember, member, cubeLevel); } public RolapCubeMember lookupCubeMember( RolapCubeMember parent, RolapMember member, RolapCubeLevel level) { synchronized (cacheHelper) { if (member.getKey() == null) { if (member.isAll()) { return getAllMember(); } throw new NullPointerException(); } RolapCubeMember cubeMember; if (enableCache) { Object key = rolapCubeCacheHelper.makeKey(parent, member.getKey()); cubeMember = (RolapCubeMember) rolapCubeCacheHelper.getMember(key, false); if (cubeMember == null) { cubeMember = new RolapCubeMember(parent, member, level); rolapCubeCacheHelper.putMember(key, cubeMember); } } else { cubeMember = new RolapCubeMember(parent, member, level); } return cubeMember; } } public int getMemberCount() { return rolapHierarchy.getMemberReader().getMemberCount(); } protected void checkCacheStatus() { synchronized (cacheHelper) { // if necessary, flush all caches: // - shared SmartMemberReader RolapMember cache // - local key to cube member RolapCubeMember cache // - cube source RolapCubeMember cache // - local regular RolapMember cache, used when cube // specific joins occur if (cacheHelper.getChangeListener() != null) { if (cacheHelper.getChangeListener().isHierarchyChanged( getHierarchy())) { cacheHelper.flushCache(); rolapCubeCacheHelper.flushCache(); if (rolapHierarchy.getMemberReader() instanceof SmartMemberReader) { SmartMemberReader smartMemberReader = (SmartMemberReader) rolapHierarchy.getMemberReader(); if (smartMemberReader.getMemberCache() instanceof MemberCacheHelper) { MemberCacheHelper helper = (MemberCacheHelper) smartMemberReader.getMemberCache(); helper.flushCache(); } } } } } } } /** * Same as {@link RolapCubeHierarchyMemberReader} but without caching * anything. */ public class NoCacheRolapCubeHierarchyMemberReader extends NoCacheMemberReader implements RolapCubeHierarchyMemberReader { /** * cubeSource is passed as our member builder */ protected final RolapCubeSqlMemberSource cubeSource; /** * this cache caches RolapCubeMembers that are light wrappers around * shared and non-shared Hierarchy RolapMembers. The inherited * cacheHelper object contains non-shared hierarchy RolapMembers. * non-shared hierarchy RolapMembers are created when a member lookup * involves the Cube's fact table. */ protected MemberCacheHelper rolapCubeCacheHelper; public NoCacheRolapCubeHierarchyMemberReader() { super(new SqlMemberSource(RolapCubeHierarchy.this)); rolapCubeCacheHelper = new MemberNoCacheHelper(); cubeSource = new RolapCubeSqlMemberSource( this, RolapCubeHierarchy.this, rolapCubeCacheHelper, new MemberNoCacheHelper()); cubeSource.setCache(rolapCubeCacheHelper); } public MemberBuilder getMemberBuilder() { return this.cubeSource; } public MemberCacheHelper getRolapCubeMemberCacheHelper() { return rolapCubeCacheHelper; } public List getRootMembers() { return getMembersInLevel(cubeLevels[0], 0, Integer.MAX_VALUE); } protected void readMemberChildren( List parentMembers, List children, MemberChildrenConstraint constraint) { List rolapChildren = new ArrayList(); List rolapParents = new ArrayList(); Map lookup = new HashMap(); // extract RolapMembers from their RolapCubeMember objects // populate lookup for reconnecting parents and children final List parentRolapCubeMemberList = Util.cast(parentMembers); for (RolapCubeMember member : parentRolapCubeMemberList) { final RolapMember rolapMember = member.getRolapMember(); lookup.put(rolapMember.getUniqueName(), member); rolapParents.add(rolapMember); } // get member children from shared member reader if possible, // if not get them from our own source boolean joinReq = (constraint instanceof SqlContextConstraint); if (joinReq) { super.readMemberChildren( parentMembers, rolapChildren, constraint); } else { rolapHierarchy.getMemberReader().getMemberChildren( rolapParents, rolapChildren, constraint); } // now lookup or create RolapCubeMember for (RolapMember currMember : rolapChildren) { RolapCubeMember parent = lookup.get( currMember.getParentMember().getUniqueName()); RolapCubeLevel level = parent.getLevel().getChildLevel(); if (level == null) { // most likely a parent child hierarchy level = parent.getLevel(); } RolapCubeMember newmember = lookupCubeMember( parent, currMember, level); children.add(newmember); } // Put them in a temporary hash table first. Register them later, // when we know their size (hence their 'cost' to the cache pool). Map> tempMap = new HashMap>(); for (RolapMember member1 : parentMembers) { tempMap.put(member1, Collections.emptyList()); } // note that this stores RolapCubeMembers in our cache, // which also stores RolapMembers. for (RolapMember child : children) { // todo: We could optimize here. If members.length is small, it's // more efficient to drive from members, rather than hashing // children.length times. We could also exploit the fact that the // result is sorted by ordinal and therefore, unless the "members" // contains members from different levels, children of the same // member will be contiguous. assert child != null : "child"; final RolapMember parentMember = child.getParentMember(); } } public void getMemberChildren( List parentMembers, List children, MemberChildrenConstraint constraint) { List missed = new ArrayList(); for (RolapMember parentMember : parentMembers) { // the null member has no children if (!parentMember.isNull()) { missed.add(parentMember); } } if (missed.size() > 0) { readMemberChildren(missed, children, constraint); } } public List getMembersInLevel( final RolapLevel level, int startOrdinal, int endOrdinal, TupleConstraint constraint) { List members = null; // if a join is required, we need to pass in the RolapCubeLevel // vs. the regular level boolean joinReq = (constraint instanceof SqlContextConstraint); final List list; if (!joinReq) { list = rolapHierarchy.getMemberReader().getMembersInLevel( ((RolapCubeLevel) level).getRolapLevel(), startOrdinal, endOrdinal, constraint); } else { list = super.getMembersInLevel( level, startOrdinal, endOrdinal, constraint); } return new UnsupportedList() { public RolapMember get(final int index) { return mutate(list.get(index)); } public int size() { return list.size(); } public Iterator iterator() { final Iterator it = list.iterator(); return new Iterator() { public boolean hasNext() { return it.hasNext(); } public RolapMember next() { return mutate(it.next()); } public void remove() { throw new UnsupportedOperationException(); } }; } private RolapMember mutate(final RolapMember member) { RolapCubeMember parent = null; if (member.getParentMember() != null) { parent = createAncestorMembers( (RolapCubeLevel) level.getParentLevel(), member.getParentMember()); } return lookupCubeMember( parent, member, (RolapCubeLevel) level); } }; } private RolapCubeMember createAncestorMembers( RolapCubeLevel level, RolapMember member) { RolapCubeMember parent = null; if (member.getParentMember() != null) { parent = createAncestorMembers( level.getParentLevel(), member.getParentMember()); } return lookupCubeMember(parent, member, level); } public RolapCubeMember lookupCubeMember( RolapCubeMember parent, RolapMember member, RolapCubeLevel level) { if (member.getKey() == null) { if (member.isAll()) { return getAllMember(); } throw new NullPointerException(); } return new RolapCubeMember(parent, member, level); } public int getMemberCount() { return rolapHierarchy.getMemberReader().getMemberCount(); } } public static class RolapCubeSqlMemberSource extends SqlMemberSource { private final RolapCubeHierarchyMemberReader memberReader; private final MemberCacheHelper memberSourceCacheHelper; private final Object memberCacheLock; public RolapCubeSqlMemberSource( RolapCubeHierarchyMemberReader memberReader, RolapCubeHierarchy hierarchy, MemberCacheHelper memberSourceCacheHelper, Object memberCacheLock) { super(hierarchy); this.memberReader = memberReader; this.memberSourceCacheHelper = memberSourceCacheHelper; this.memberCacheLock = memberCacheLock; } public RolapMember makeMember( RolapMember parentMember, RolapLevel childLevel, Object value, Object captionValue, boolean parentChild, SqlStatement stmt, Object key, int columnOffset) throws SQLException { final RolapCubeMember parentCubeMember = (RolapCubeMember) parentMember; final RolapCubeLevel childCubeLevel = (RolapCubeLevel) childLevel; final RolapMember parent; if (parentMember != null) { parent = parentCubeMember.getRolapMember(); } else { parent = null; } RolapMember member = super.makeMember( parent, childCubeLevel.getRolapLevel(), value, captionValue, parentChild, stmt, key, columnOffset); return memberReader.lookupCubeMember( parentCubeMember, member, childCubeLevel); } public MemberCache getMemberCache() { // this is a special cache used solely for rolapcubemembers return memberSourceCacheHelper; } /** * use the same lock in the RolapCubeMemberSource as the * RolapCubeHiearchyMemberReader to avoid deadlocks */ public Object getMemberCacheLock() { return memberCacheLock; } public RolapMember allMember() { return getHierarchy().getAllMember(); } } } // End RolapCubeHierarchy.java mondrian-3.4.1/src/main/mondrian/rolap/DelegatingMemberReader.java0000644000175000017500000001024211735330606025045 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho // All Rights Reserved. // // jhyde, Feb 26, 2003 */ package mondrian.rolap; import mondrian.olap.Id; import mondrian.rolap.TupleReader.MemberBuilder; import mondrian.rolap.sql.MemberChildrenConstraint; import mondrian.rolap.sql.TupleConstraint; import java.util.List; /** * A DelegatingMemberReader is a {@link MemberReader} which * redirects all method calls to an underlying {@link MemberReader}. * * @author jhyde * @since Feb 26, 2003 */ class DelegatingMemberReader implements MemberReader { protected final MemberReader memberReader; DelegatingMemberReader(MemberReader memberReader) { this.memberReader = memberReader; } public RolapMember substitute(RolapMember member) { return memberReader.substitute(member); } public RolapMember desubstitute(RolapMember member) { return memberReader.desubstitute(member); } public RolapMember getLeadMember(RolapMember member, int n) { return memberReader.getLeadMember(member, n); } public List getMembersInLevel( RolapLevel level, int startOrdinal, int endOrdinal) { return memberReader.getMembersInLevel(level, startOrdinal, endOrdinal); } public void getMemberRange( RolapLevel level, RolapMember startMember, RolapMember endMember, List list) { memberReader.getMemberRange(level, startMember, endMember, list); } public int compare( RolapMember m1, RolapMember m2, boolean siblingsAreEqual) { return memberReader.compare(m1, m2, siblingsAreEqual); } public RolapHierarchy getHierarchy() { return memberReader.getHierarchy(); } public boolean setCache(MemberCache cache) { return memberReader.setCache(cache); } public List getMembers() { return memberReader.getMembers(); } public List getRootMembers() { return memberReader.getRootMembers(); } public void getMemberChildren( RolapMember parentMember, List children) { memberReader.getMemberChildren(parentMember, children); } public void getMemberChildren( List parentMembers, List children) { memberReader.getMemberChildren(parentMembers, children); } public int getMemberCount() { return memberReader.getMemberCount(); } public RolapMember lookupMember( List uniqueNameParts, boolean failIfNotFound) { return memberReader.lookupMember(uniqueNameParts, failIfNotFound); } public void getMemberChildren( RolapMember member, List children, MemberChildrenConstraint constraint) { memberReader.getMemberChildren(member, children, constraint); } public void getMemberChildren( List parentMembers, List children, MemberChildrenConstraint constraint) { memberReader.getMemberChildren(parentMembers, children, constraint); } public List getMembersInLevel( RolapLevel level, int startOrdinal, int endOrdinal, TupleConstraint constraint) { return memberReader.getMembersInLevel( level, startOrdinal, endOrdinal, constraint); } public int getLevelMemberCount(RolapLevel level) { return memberReader.getLevelMemberCount(level); } public MemberBuilder getMemberBuilder() { return memberReader.getMemberBuilder(); } public RolapMember getDefaultMember() { return memberReader.getDefaultMember(); } public RolapMember getMemberParent(RolapMember member) { return memberReader.getMemberParent(member); } } // End DelegatingMemberReader.java mondrian-3.4.1/src/main/mondrian/rolap/RolapEvaluator.java0000644000175000017500000013471011735330606023476 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. // // jhyde, 10 August, 2001 */ package mondrian.rolap; import mondrian.calc.Calc; import mondrian.calc.ParameterSlot; import mondrian.olap.*; import mondrian.olap.fun.FunUtil; import mondrian.server.Statement; import mondrian.spi.Dialect; import mondrian.util.Format; import org.apache.log4j.Logger; import java.util.*; /** * RolapEvaluator evaluates expressions in a dimensional * environment. * *

The context contains a member (which may be the default member) * for every dimension in the current cube. Certain operations, such as * evaluating a calculated member or a tuple, change the current context. * *

There are two ways of preserving context. * *

First, the {@link #push} * method creates a verbatim copy of the evaluator. Use that copy for * computations, and any changes of state will be made only to the copy. * *

Second, the {@link #savepoint()} method tells the evaluator to create a * checkpoint of its state, and returns an {@code int} value that can later be * passed to {@link #restore(int)}. * *

The {@code savepoint} method is recommended for most purposes, because the * initial checkpoint is extremely cheap. Each call that modifies state (such as * {@link mondrian.olap.Evaluator#setContext(mondrian.olap.Member)}) creates, at * a modest cost, an entry on an internal command stack. * *

One occasion that you would use {@code push} is when creating an * iterator, and the iterator needs its own evaluator context, even if the * code that created the iterator later reverts the context. In this case, * the iterator's constructor should call {@code push}. * *

Developers note

* *

Many of the methods in this class are performance-critical. Where * possible they are declared 'final' so that the JVM can optimize calls to * these methods. If future functionality requires it, the 'final' modifier * can be removed and these methods can be overridden. * * @author jhyde * @since 10 August, 2001 */ public class RolapEvaluator implements Evaluator { private static final Logger LOGGER = Logger.getLogger(RolapEvaluator.class); /** * Dummy value to represent null results in the expression cache. */ private static final Object nullResult = new Object(); private final RolapMember[] currentMembers; private final RolapEvaluator parent; protected CellReader cellReader; private final int ancestorCommandCount; private Member expandingMember; private boolean firstExpanding; private boolean nonEmpty; protected final RolapEvaluatorRoot root; private int iterationLength; private boolean evalAxes; private final RolapCalculation[] calculations; private int calculationCount; /** * List of lists of tuples or members, rarely used, but overrides the * ordinary dimensional context if set when a cell value comes to be * evaluated. */ protected final List>> aggregationLists; private final List slicerMembers; private boolean nativeEnabled; private Member[] nonAllMembers; private int commandCount; private Object[] commands; /** * Set of expressions actively being expanded. Prevents infinite cycle of * expansions. * * @return Mutable set of expressions being expanded */ public Set getActiveNativeExpansions() { return root.activeNativeExpansions; } /** * States of the finite state machine for determining the max solve order * for the "scoped" behavior. */ private enum ScopedMaxSolveOrderFinderState { START, AGG_SCOPE, CUBE_SCOPE, QUERY_SCOPE } /** * Creates a non-root evaluator. * * @param root Root context for stack of evaluators (contains information * which does not change during the evaluation) * @param parent Parent evaluator, not null * @param aggregationList List of tuples to add to aggregation context, * or null */ protected RolapEvaluator( RolapEvaluatorRoot root, RolapEvaluator parent, List> aggregationList) { this.iterationLength = 1; this.root = root; assert parent != null; this.parent = parent; ancestorCommandCount = parent.ancestorCommandCount + parent.commandCount; nonEmpty = parent.nonEmpty; nativeEnabled = parent.nativeEnabled; evalAxes = parent.evalAxes; cellReader = parent.cellReader; currentMembers = parent.currentMembers.clone(); calculations = parent.calculations.clone(); calculationCount = parent.calculationCount; slicerMembers = new ArrayList(parent.slicerMembers); commands = new Object[10]; commands[0] = Command.SAVEPOINT; // sentinel commandCount = 1; // Build aggregationLists, combining parent's aggregationLists (if not // null) and the new aggregation list (if any). List>> aggregationLists = null; if (parent.aggregationLists != null) { aggregationLists = new ArrayList>>(parent.aggregationLists); } if (aggregationList != null) { if (aggregationLists == null) { aggregationLists = new ArrayList>>(); } aggregationLists.add(aggregationList); List tuple = aggregationList.get(0); for (Member member : tuple) { setContext(member.getHierarchy().getAllMember()); } } this.aggregationLists = aggregationLists; expandingMember = parent.expandingMember; } /** * Creates a root evaluator. * * @param root Shared context between this evaluator and its children */ public RolapEvaluator(RolapEvaluatorRoot root) { this.iterationLength = 1; this.root = root; this.parent = null; ancestorCommandCount = 0; nonEmpty = false; nativeEnabled = MondrianProperties.instance().EnableNativeNonEmpty.get() || MondrianProperties.instance().EnableNativeCrossJoin.get(); evalAxes = false; cellReader = null; currentMembers = root.defaultMembers.clone(); calculations = new RolapCalculation[currentMembers.length]; calculationCount = 0; slicerMembers = new ArrayList(); aggregationLists = null; commands = new Object[10]; commands[0] = Command.SAVEPOINT; // sentinel commandCount = 1; for (RolapMember member : currentMembers) { if (member.isEvaluated()) { addCalculation(member, true); } } // we expect client to set CellReader } /** * Creates an evaluator. */ public static Evaluator create(Statement statement) { final RolapEvaluatorRoot root = new RolapEvaluatorRoot(statement); return new RolapEvaluator(root); } public RolapCube getMeasureCube() { final RolapMember measure = currentMembers[0]; if (measure instanceof RolapStoredMeasure) { return ((RolapStoredMeasure) measure).getCube(); } return null; } public boolean mightReturnNullForUnrelatedDimension() { if (!MondrianProperties.instance() .IgnoreMeasureForNonJoiningDimension.get()) { return false; } RolapCube virtualCube = getCube(); return virtualCube.isVirtual(); } public boolean needToReturnNullForUnrelatedDimension(Member[] members) { assert mightReturnNullForUnrelatedDimension() : "Should not even call this method if nulls are impossible"; RolapCube baseCube = getMeasureCube(); if (baseCube == null) { return false; } RolapCube virtualCube = getCube(); if (virtualCube.shouldIgnoreUnrelatedDimensions(baseCube.getName())) { return false; } Set nonJoiningDimensions = baseCube.nonJoiningDimensions(members); return !nonJoiningDimensions.isEmpty(); } public boolean nativeEnabled() { return nativeEnabled; } public boolean currentIsEmpty() { // If a cell evaluates to null, it is always deemed empty. Object o = evaluateCurrent(); if (o == Util.nullValue || o == null) { return true; } final RolapCube measureCube = getMeasureCube(); if (measureCube == null) { return false; } // For other cell values (e.g. zero), the cell is deemed empty if the // number of fact table rows is zero. final int savepoint = savepoint(); setContext(measureCube.getFactCountMeasure()); o = evaluateCurrent(); restore(savepoint); return o == null || (o instanceof Number && ((Number) o).intValue() == 0); } public Member getPreviousContext(Hierarchy hierarchy) { for (RolapEvaluator e = this; e != null; e = e.parent) { for (int i = commandCount - 1; i > 0;) { Command command = (Command) commands[i]; if (command == Command.SET_CONTEXT) { return (Member) commands[i - 2]; } i -= command.width; } } return null; } public final QueryTiming getTiming() { return root.execution.getQueryTiming(); } public final int savepoint() { final int commandCount1 = commandCount; if (commands[commandCount - 1] == Command.SAVEPOINT) { // Already at a save point; no need to create another. return commandCount1; } // enough room for CHECKSUM command, if asserts happen to be enabled ensureCommandCapacity(commandCount + 3); commands[commandCount++] = Command.SAVEPOINT; //noinspection AssertWithSideEffects assert !Util.DEBUG || addChecksumStateCommand(); return commandCount1; } public final void setNativeEnabled(boolean nativeEnabled) { if (nativeEnabled != this.nativeEnabled) { ensureCommandCapacity(commandCount + 2); commands[commandCount++] = this.nativeEnabled; commands[commandCount++] = Command.SET_NATIVE_ENABLED; this.nativeEnabled = nativeEnabled; } } protected final Logger getLogger() { return LOGGER; } public final Member[] getMembers() { return currentMembers; } public final Member[] getNonAllMembers() { if (nonAllMembers == null) { nonAllMembers = new RolapMember[root.nonAllPositionCount]; for (int i = 0; i < root.nonAllPositionCount; i++) { int nonAllPosition = root.nonAllPositions[i]; nonAllMembers[i] = currentMembers[nonAllPosition]; } } return nonAllMembers; } public final List>> getAggregationLists() { return aggregationLists; } final void setCellReader(CellReader cellReader) { if (cellReader != this.cellReader) { ensureCommandCapacity(commandCount + 2); commands[commandCount++] = this.cellReader; commands[commandCount++] = Command.SET_CELL_READER; this.cellReader = cellReader; } } public final RolapCube getCube() { return root.cube; } public final Query getQuery() { return root.query; } public final int getDepth() { return 0; } public final RolapEvaluator getParent() { return parent; } public final SchemaReader getSchemaReader() { return root.schemaReader; } public Date getQueryStartTime() { return root.getQueryStartTime(); } public Dialect getDialect() { return root.currentDialect; } public final RolapEvaluator push(Member[] members) { final RolapEvaluator evaluator = _push(null); evaluator.setContext(members); return evaluator; } public final RolapEvaluator push(Member member) { final RolapEvaluator evaluator = _push(null); evaluator.setContext(member); return evaluator; } public final Evaluator push(boolean nonEmpty) { final RolapEvaluator evaluator = _push(null); evaluator.setNonEmpty(nonEmpty); return evaluator; } public final Evaluator push(boolean nonEmpty, boolean nativeEnabled) { final RolapEvaluator evaluator = _push(null); evaluator.setNonEmpty(nonEmpty); evaluator.setNativeEnabled(nativeEnabled); return evaluator; } public final RolapEvaluator push() { return _push(null); } private void ensureCommandCapacity(int minCapacity) { if (minCapacity > commands.length) { int newCapacity = commands.length * 2; if (newCapacity < minCapacity) { newCapacity = minCapacity; } commands = Util.copyOf(commands, newCapacity); } } /** * Adds a command to the stack that ensures that the state after restoring * is the same as the current state. * *

Returns true so that can conveniently be called from 'assert'. * * @return true */ private boolean addChecksumStateCommand() { // assume that caller has checked that command array is large enough commands[commandCount++] = checksumState(); commands[commandCount++] = Command.CHECKSUM; return true; } /** * Creates a clone of the current validator. * * @param aggregationList List of tuples to add to aggregation context, * or null */ protected RolapEvaluator _push(List> aggregationList) { root.execution.checkCancelOrTimeout(); return new RolapEvaluator(root, this, aggregationList); } public final void restore(int savepoint) { while (commandCount > savepoint) { ((Command) commands[--commandCount]).execute(this); } } public final Evaluator pushAggregation(List> list) { return _push(list); } /** * Returns true if the other object is a {@link RolapEvaluator} with * identical context. */ public final boolean equals(Object obj) { if (!(obj instanceof RolapEvaluator)) { return false; } RolapEvaluator that = (RolapEvaluator) obj; return Arrays.equals(this.currentMembers, that.currentMembers); } public final int hashCode() { return Util.hashArray(0, this.currentMembers); } /** * Adds a slicer member to the evaluator context, and remember it as part * of the slicer. The slicer members are passed onto derived evaluators * so that functions using those evaluators can choose to ignore the * slicer members. One such function is CrossJoin emptiness check. * * @param member a member in the slicer */ public final void setSlicerContext(Member member) { setContext(member); slicerMembers.add(member); } /** * Return the list of slicer members in the current evaluator context. * @return slicerMembers */ public final List getSlicerMembers() { return slicerMembers; } public final Member setContext(Member member) { // Note: the body of this function is identical to calling // 'setContext(member, true)'. We inline the logic for performance. final RolapMemberBase m = (RolapMemberBase) member; final int ordinal = m.getHierarchy().getOrdinalInCube(); final RolapMember previous = currentMembers[ordinal]; // If the context is unchanged, save ourselves some effort. It would be // a mistake to use equals here; we might treat the visual total member // 'Gender.All' the same as the true 'Gender.All' because they have the // same unique name, and that would be wrong. if (m == previous) { return previous; } // We call 'exists' before 'removeCalcMember' for efficiency. // 'exists' has a smaller stack to search before 'removeCalcMember' // adds an 'ADD_CALCULATION' command. if (!exists(ordinal)) { ensureCommandCapacity(commandCount + 3); commands[commandCount++] = previous; commands[commandCount++] = ordinal; commands[commandCount++] = Command.SET_CONTEXT; } if (previous.isEvaluated()) { removeCalculation(previous, false); } currentMembers[ordinal] = m; if (previous.isAll() && !m.isAll() && isNewPosition(ordinal)) { root.nonAllPositions[root.nonAllPositionCount] = ordinal; root.nonAllPositionCount++; } if (m.isEvaluated()) { addCalculation(m, false); } nonAllMembers = null; return previous; } public final void setContext(Member member, boolean safe) { final RolapMemberBase m = (RolapMemberBase) member; final int ordinal = m.getHierarchy().getOrdinalInCube(); final RolapMember previous = currentMembers[ordinal]; // If the context is unchanged, save ourselves some effort. It would be // a mistake to use equals here; we might treat the visual total member // 'Gender.All' the same as the true 'Gender.All' because they have the // same unique name, and that would be wrong. if (m == previous) { return; } if (safe) { // We call 'exists' before 'removeCalcMember' for efficiency. // 'exists' has a smaller stack to search before 'removeCalcMember' // adds an 'ADD_CALCULATION' command. if (!exists(ordinal)) { ensureCommandCapacity(commandCount + 3); commands[commandCount++] = previous; commands[commandCount++] = ordinal; commands[commandCount++] = Command.SET_CONTEXT; } } if (previous.isEvaluated()) { removeCalculation(previous, false); } currentMembers[ordinal] = m; if (previous.isAll() && !m.isAll() && isNewPosition(ordinal)) { root.nonAllPositions[root.nonAllPositionCount] = ordinal; root.nonAllPositionCount++; } if (m.isEvaluated()) { addCalculation(m, false); } nonAllMembers = null; } /** * Returns whether a member of the hierarchy with a given ordinal has been * preserved on the stack since the last savepoint. * * @param ordinal Hierarchy ordinal * @return Whether there is a member with the given hierarchy ordinal on * the stack */ private boolean exists(int ordinal) { for (int i = commandCount - 1;;) { final Command command = (Command) commands[i]; switch (command) { case SAVEPOINT: return false; case SET_CONTEXT: final Integer memberOrdinal = (Integer) commands[i - 1]; if (ordinal == memberOrdinal) { return true; } break; } i -= command.width; } } private boolean isNewPosition(int ordinal) { for (int nonAllPosition : root.nonAllPositions) { if (ordinal == nonAllPosition) { return false; } } return true; } public final void setContext(List memberList) { for (int i = 0, n = memberList.size(); i < n; i++) { Member member = memberList.get(i); assert member != null : "null member in " + memberList; setContext(member); } } public final void setContext(List memberList, boolean safe) { for (int i = 0, n = memberList.size(); i < n; i++) { Member member = memberList.get(i); assert member != null : "null member in " + memberList; setContext(member, safe); } } public final void setContext(Member[] members) { for (int i = 0, length = members.length; i < length; i++) { Member member = members[i]; assert member != null : "null member in " + Arrays.toString(members); setContext(member); } } public final void setContext(Member[] members, boolean safe) { for (int i = 0, length = members.length; i < length; i++) { Member member = members[i]; assert member != null : Arrays.asList(members); setContext(member, safe); } } public final RolapMember getContext(Hierarchy hierarchy) { return currentMembers[((RolapHierarchy) hierarchy).getOrdinalInCube()]; } /** * More specific version of {@link #getContext(mondrian.olap.Hierarchy)}, * for internal code. * * @param hierarchy Hierarchy * @return current member */ public final RolapMember getContext(RolapHierarchy hierarchy) { return currentMembers[hierarchy.getOrdinalInCube()]; } public final Object evaluateCurrent() { // Get the member in the current context which is (a) calculated, and // (b) has the highest solve order. If there are no calculated members, // go ahead and compute the cell. RolapCalculation maxSolveMember; switch (calculationCount) { case 0: final Object o = cellReader.get(this); if (o == Util.nullValue) { return null; } return o; case 1: maxSolveMember = calculations[0]; break; default: switch (root.solveOrderMode) { case ABSOLUTE: maxSolveMember = getAbsoluteMaxSolveOrder(); break; case SCOPED: maxSolveMember = getScopedMaxSolveOrder(); break; default: throw Util.unexpected(root.solveOrderMode); } } final int savepoint = savepoint(); maxSolveMember.setContextIn(this); final Calc calc = maxSolveMember.getCompiledExpression(root); final Object o; try { o = calc.evaluate(this); } finally { restore(savepoint); } if (o == Util.nullValue) { return null; } return o; } void setExpanding(Member member) { assert member != null; ensureCommandCapacity(commandCount + 3); commands[commandCount++] = this.expandingMember; commands[commandCount++] = this.firstExpanding; commands[commandCount++] = Command.SET_EXPANDING; expandingMember = member; firstExpanding = true; // REVIEW: is firstExpanding used? final int totalCommandCount = commandCount + ancestorCommandCount; if (totalCommandCount > root.recursionCheckCommandCount) { checkRecursion(this, commandCount - 4); // Set the threshold where we will next check for infinite // recursion. root.recursionCheckCommandCount = totalCommandCount + (root.defaultMembers.length << 4); } } /** * Returns the calculated member being currently expanded. * *

This can be useful if many calculated members are generated with * essentially the same expression. The compiled expression can call this * method to find which instance of the member is current, and therefore the * calculated members can share the same {@link Calc} object. * * @return Calculated member currently being expanded */ Member getExpanding() { return expandingMember; } /** * Makes sure that there is no evaluator with identical context on the * stack. * * @param eval Evaluator * @throws mondrian.olap.fun.MondrianEvaluationException if there is a loop */ private static void checkRecursion(RolapEvaluator eval, int c) { RolapMember[] members = eval.currentMembers.clone(); Member expanding = eval.expandingMember; // Find an ancestor evaluator that has identical context to this one: // same member context, and expanding the same calculation. while (true) { if (c < 0) { eval = eval.parent; if (eval == null) { return; } c = eval.commandCount - 1; } else { Command command = (Command) eval.commands[c]; switch (command) { case SET_CONTEXT: int memberOrdinal = (Integer) eval.commands[c - 1]; RolapMember member = (RolapMember) eval.commands[c - 2]; members[memberOrdinal] = member; break; case SET_EXPANDING: expanding = (RolapMember) eval.commands[c - 2]; if (Arrays.equals(members, eval.currentMembers) && expanding == eval.expandingMember) { throw FunUtil.newEvalException( null, "Infinite loop while evaluating calculated member '" + eval.expandingMember + "'; context stack is " + eval.getContextString()); } } c -= command.width; } } } private String getContextString() { RolapMember[] members = currentMembers.clone(); final boolean skipDefaultMembers = true; final StringBuilder buf = new StringBuilder("{"); int frameCount = 0; boolean changedSinceLastSavepoint = false; for (RolapEvaluator eval = this; eval != null; eval = eval.parent) { if (eval.expandingMember == null) { continue; } for (int c = eval.commandCount - 1; c > 0;) { Command command = (Command) eval.commands[c]; switch (command) { case SAVEPOINT: if (changedSinceLastSavepoint) { if (frameCount++ > 0) { buf.append(", "); } buf.append("("); int memberCount = 0; for (Member m : members) { if (skipDefaultMembers && m == m.getHierarchy().getDefaultMember()) { continue; } if (memberCount++ > 0) { buf.append(", "); } buf.append(m.getUniqueName()); } buf.append(")"); } changedSinceLastSavepoint = false; break; case SET_CONTEXT: changedSinceLastSavepoint = true; int memberOrdinal = (Integer) eval.commands[c - 1]; RolapMember member = (RolapMember) eval.commands[c - 2]; members[memberOrdinal] = member; break; } c -= command.width; } } buf.append("}"); return buf.toString(); } public final Object getProperty(String name, Object defaultValue) { Object o = defaultValue; int maxSolve = Integer.MIN_VALUE; int i = -1; for (Member member : getNonAllMembers()) { i++; // more than one usage if (member == null) { if (getLogger().isDebugEnabled()) { getLogger().debug( "RolapEvaluator.getProperty: member == null " + " , count=" + i); } continue; } // Don't call member.getPropertyValue unless this member's // solve order is greater than one we've already seen. // The getSolveOrder call is cheap call compared to the // getPropertyValue call, and when we're evaluating millions // of members, this has proven to make a significant performance // difference. final int solve = member.getSolveOrder(); if (solve > maxSolve) { final Object p = member.getPropertyValue(name); if (p != null) { o = p; maxSolve = solve; } } } return o; } /** * Returns the format string for this cell. This is computed by evaluating * the format expression in the current context, and therefore different * cells may have different format strings. * * @post return != null */ public final String getFormatString() { final Exp formatExp = (Exp) getProperty(Property.FORMAT_EXP_PARSED.name, null); if (formatExp == null) { return "Standard"; } final Calc formatCalc = root.getCompiled(formatExp, true, null); final Object o = formatCalc.evaluate(this); if (o == null) { return "Standard"; } return o.toString(); } private Format getFormat() { final String formatString = getFormatString(); return getFormat(formatString); } private Format getFormat(String formatString) { return Format.get(formatString, root.connection.getLocale()); } public final Locale getConnectionLocale() { return root.connection.getLocale(); } public final String format(Object o) { if (o == Util.nullValue) { o = null; } if (o instanceof Throwable) { return "#ERR: " + o.toString(); } Format format = getFormat(); return format.format(o); } public final String format(Object o, String formatString) { if (o == Util.nullValue) { o = null; } if (o instanceof Throwable) { return "#ERR: " + o.toString(); } Format format = getFormat(formatString); return format.format(o); } /** * Creates a key which uniquely identifes an expression and its * context. The context includes members of dimensions which the * expression is dependent upon. */ private Object getExpResultCacheKey(ExpCacheDescriptor descriptor) { // in NON EMPTY mode the result depends on everything, e.g. // "NON EMPTY [Customer].[Name].members" may return different results // for 1997-01 and 1997-02 final List key; if (nonEmpty) { key = new ArrayList(currentMembers.length + 1); key.add(descriptor.getExp()); //noinspection ManualArrayToCollectionCopy for (RolapMember currentMember : currentMembers) { key.add(currentMember); } } else { final int[] hierarchyOrdinals = descriptor.getDependentHierarchyOrdinals(); key = new ArrayList(hierarchyOrdinals.length + 1); key.add(descriptor.getExp()); for (final int hierarchyOrdinal : hierarchyOrdinals) { final Member member = currentMembers[hierarchyOrdinal]; assert member != null; key.add(member); } } return key; } public final Object getCachedResult(ExpCacheDescriptor cacheDescriptor) { // Look up a cached result, and if not present, compute one and add to // cache. Use a dummy value to represent nulls. final Object key = getExpResultCacheKey(cacheDescriptor); Object result = root.getCacheResult(key); if (result == null) { boolean aggCacheDirty = cellReader.isDirty(); int aggregateCacheMissCountBefore = cellReader.getMissCount(); result = cacheDescriptor.evaluate(this); int aggregateCacheMissCountAfter = cellReader.getMissCount(); boolean isValidResult; if (!aggCacheDirty && (aggregateCacheMissCountBefore == aggregateCacheMissCountAfter)) { // Cache the evaluation result as valid result if the // evaluation did not use any missing aggregates. Missing // aggregates could be used when aggregate cache is not fully // loaded, or if new missing aggregates are seen. isValidResult = true; } else { // Cache the evaluation result as invalid result if the // evaluation uses missing aggregates. isValidResult = false; } root.putCacheResult( key, result == null ? nullResult : result, isValidResult); } else if (result == nullResult) { result = null; } return result; } public final void clearExpResultCache(boolean clearValidResult) { root.clearResultCache(clearValidResult); } public final boolean isNonEmpty() { return nonEmpty; } public final void setNonEmpty(boolean nonEmpty) { if (nonEmpty != this.nonEmpty) { ensureCommandCapacity(commandCount + 2); commands[commandCount++] = this.nonEmpty; commands[commandCount++] = Command.SET_NON_EMPTY; this.nonEmpty = nonEmpty; } } public final RuntimeException newEvalException(Object context, String s) { return FunUtil.newEvalException((FunDef) context, s); } public final NamedSetEvaluator getNamedSetEvaluator( NamedSet namedSet, boolean create) { return root.evaluateNamedSet(namedSet, create); } public final int getMissCount() { return cellReader.getMissCount(); } public final Object getParameterValue(ParameterSlot slot) { return root.getParameterValue(slot); } final void addCalculation( RolapCalculation calculation, boolean reversible) { assert calculation != null; calculations[calculationCount++] = calculation; if (reversible && !(calculation instanceof RolapMember)) { // Add command to remove this calculation. ensureCommandCapacity(commandCount + 2); commands[commandCount++] = calculation; commands[commandCount++] = Command.REMOVE_CALCULATION; } } /** * Returns the member with the highest solve order according to AS2000 * rules. This was the behavior prior to solve order mode being * configurable. * *

The SOLVE_ORDER value is absolute regardless of where it is defined; * e.g. a query defined calculated member with a SOLVE_ORDER of 1 always * takes precedence over a cube defined value of 2. * *

No special consideration is given to the aggregate function. */ private RolapCalculation getAbsoluteMaxSolveOrder() { // Find member with the highest solve order. RolapCalculation maxSolveMember = calculations[0]; for (int i = 1; i < calculationCount; i++) { RolapCalculation member = calculations[i]; if (expandsBefore(member, maxSolveMember)) { maxSolveMember = member; } } return maxSolveMember; } /** * Returns the member with the highest solve order according to AS2005 * scoping rules. * *

By default, cube calculated members are resolved before any session * scope calculated members, and session scope members are resolved before * any query defined calculation. The SOLVE_ORDER value only applies within * the scope in which it was defined. * *

The aggregate function is always applied to base members; i.e. as if * SOLVE_ORDER was defined to be the lowest value in a given evaluation in a * SSAS2000 sense. */ private RolapCalculation getScopedMaxSolveOrder() { // Finite state machine that determines the member with the highest // solve order. RolapCalculation maxSolveMember = null; ScopedMaxSolveOrderFinderState state = ScopedMaxSolveOrderFinderState.START; for (int i = 0; i < calculationCount; i++) { RolapCalculation calculation = calculations[i]; switch (state) { case START: maxSolveMember = calculation; if (maxSolveMember.containsAggregateFunction()) { state = ScopedMaxSolveOrderFinderState.AGG_SCOPE; } else if (maxSolveMember.isCalculatedInQuery()) { state = ScopedMaxSolveOrderFinderState.QUERY_SCOPE; } else { state = ScopedMaxSolveOrderFinderState.CUBE_SCOPE; } break; case AGG_SCOPE: if (calculation.containsAggregateFunction()) { if (expandsBefore(calculation, maxSolveMember)) { maxSolveMember = calculation; } } else if (calculation.isCalculatedInQuery()) { maxSolveMember = calculation; state = ScopedMaxSolveOrderFinderState.QUERY_SCOPE; } else { maxSolveMember = calculation; state = ScopedMaxSolveOrderFinderState.CUBE_SCOPE; } break; case CUBE_SCOPE: if (calculation.containsAggregateFunction()) { continue; } if (calculation.isCalculatedInQuery()) { maxSolveMember = calculation; state = ScopedMaxSolveOrderFinderState.QUERY_SCOPE; } else if (expandsBefore(calculation, maxSolveMember)) { maxSolveMember = calculation; } break; case QUERY_SCOPE: if (calculation.containsAggregateFunction()) { continue; } if (calculation.isCalculatedInQuery()) { if (expandsBefore(calculation, maxSolveMember)) { maxSolveMember = calculation; } } break; } } return maxSolveMember; } /** * Returns whether a given calculation expands before another. * A calculation expands before another if its solve order is higher, * or if its solve order is the same and its dimension ordinal is lower. * * @param calc1 First calculated member or tuple * @param calc2 Second calculated member or tuple * @return Whether calc1 expands before calc2 */ private boolean expandsBefore( RolapCalculation calc1, RolapCalculation calc2) { final int solveOrder1 = calc1.getSolveOrder(); final int solveOrder2 = calc2.getSolveOrder(); if (solveOrder1 > solveOrder2) { return true; } else { return solveOrder1 == solveOrder2 && calc1.getHierarchyOrdinal() < calc2.getHierarchyOrdinal(); } } final void removeCalculation( RolapCalculation calculation, boolean reversible) { for (int i = 0; i < calculationCount; i++) { if (calculations[i] == calculation) { // overwrite this member with the end member --calculationCount; calculations[i] = calculations[calculationCount]; assert calculations[i] != null; calculations[calculationCount] = null; // to allow gc if (reversible && !(calculation instanceof RolapMember)) { // Add a command to re-add the calculation. ensureCommandCapacity(commandCount + 2); commands[commandCount++] = calculation; commands[commandCount++] = Command.ADD_CALCULATION; } return; } } throw new AssertionError( "calculation " + calculation + " not on stack"); } public final int getIterationLength() { return iterationLength; } public final void setIterationLength(int iterationLength) { ensureCommandCapacity(commandCount + 2); commands[commandCount++] = this.iterationLength; commands[commandCount++] = Command.SET_ITERATION_LENGTH; this.iterationLength = iterationLength; } public final boolean isEvalAxes() { return evalAxes; } public final void setEvalAxes(boolean evalAxes) { if (evalAxes != this.evalAxes) { ensureCommandCapacity(commandCount + 2); commands[commandCount++] = this.evalAxes; commands[commandCount++] = Command.SET_EVAL_AXES; this.evalAxes = evalAxes; } } private int checksumState() { int h = 0; h = h * 31 + Arrays.asList(currentMembers).hashCode(); h = h * 31 + new HashSet( Arrays.asList(calculations) .subList(0, calculationCount)).hashCode(); h = h * 31 + slicerMembers.hashCode(); h = h * 31 + (expandingMember == null ? 0 : expandingMember.hashCode()); h = h * 31 + (aggregationLists == null ? 0 : aggregationLists.hashCode()); h = h * 31 + (nonEmpty ? 0x1 : 0x2) + (nativeEnabled ? 0x4 : 0x8) + (firstExpanding ? 0x10 : 0x20) + (evalAxes ? 0x40 : 0x80); if (false) { // Enable this code block to debug checksum mismatches. System.err.println( "h=" + h + ": " + Arrays.asList( Arrays.asList(currentMembers), new HashSet( Arrays.asList(calculations).subList( 0, calculationCount)), expandingMember, aggregationLists, nonEmpty, nativeEnabled, firstExpanding, evalAxes)); } return h; } /** * Checks if unrelated dimensions to the measure in the current context * should be ignored. * @return boolean */ public boolean shouldIgnoreUnrelatedDimensions() { return getCube().shouldIgnoreUnrelatedDimensions( getMeasureCube().getName()); } private enum Command { SET_CONTEXT(2) { @Override void execute(RolapEvaluator evaluator) { final int memberOrdinal = (Integer) evaluator.commands[--evaluator.commandCount]; final RolapMember member = (RolapMember) evaluator.commands[--evaluator.commandCount]; evaluator.setContext(member, false); } }, SET_NATIVE_ENABLED(1) { @Override void execute(RolapEvaluator evaluator) { evaluator.nativeEnabled = (Boolean) evaluator.commands[--evaluator.commandCount]; } }, SET_NON_EMPTY(1) { @Override void execute(RolapEvaluator evaluator) { evaluator.nonEmpty = (Boolean) evaluator.commands[--evaluator.commandCount]; } }, SET_EVAL_AXES(1) { @Override void execute(RolapEvaluator evaluator) { evaluator.evalAxes = (Boolean) evaluator.commands[--evaluator.commandCount]; } }, SET_EXPANDING(2) { @Override void execute(RolapEvaluator evaluator) { evaluator.firstExpanding = (Boolean) evaluator.commands[--evaluator.commandCount]; evaluator.expandingMember = (Member) evaluator.commands[--evaluator.commandCount]; } }, SET_ITERATION_LENGTH(1) { @Override void execute(RolapEvaluator evaluator) { evaluator.iterationLength = (Integer) evaluator.commands[--evaluator.commandCount]; } }, SET_CELL_READER(1) { @Override void execute(RolapEvaluator evaluator) { evaluator.cellReader = (CellReader) evaluator.commands[--evaluator.commandCount]; } }, CHECKSUM(1) { @Override void execute(RolapEvaluator evaluator) { final int value = (Integer) evaluator.commands[--evaluator.commandCount]; final int currentState = evaluator.checksumState(); assert value == currentState : "Current checksum " + currentState + " != previous checksum " + value; } }, ADD_CALCULATION(1) { @Override void execute(RolapEvaluator evaluator) { final RolapCalculation calculation = (RolapCalculation) evaluator.commands[--evaluator.commandCount]; evaluator.calculations[evaluator.calculationCount++] = calculation; } }, REMOVE_CALCULATION(1) { @Override void execute(RolapEvaluator evaluator) { final RolapCalculation calculation = (RolapCalculation) evaluator.commands[--evaluator.commandCount]; evaluator.removeCalculation(calculation, false); } }, SAVEPOINT(0) { @Override void execute(RolapEvaluator evaluator) { // nothing to do; command is just a marker } }; public final int width; Command(int argCount) { this.width = argCount + 1; } abstract void execute(RolapEvaluator evaluator); } } // End RolapEvaluator.java mondrian-3.4.1/src/main/mondrian/rolap/MemberKey.java0000644000175000017500000000325011735330606022410 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. // // jhyde, 21 March, 2002 */ package mondrian.rolap; import mondrian.olap.Util; /** * MemberKey todo: * * @author jhyde * @since 21 March, 2002 */ class MemberKey { private final RolapMember parent; private final Object value; MemberKey(RolapMember parent, Object value) { this.parent = parent; this.value = value; } @Override public boolean equals(Object o) { if (!(o instanceof MemberKey)) { return false; } MemberKey other = (MemberKey) o; return Util.equals(this.parent, other.parent) && Util.equals(this.value, other.value); } @Override public int hashCode() { int h = 0; if (value != null) { h = value.hashCode(); } if (parent != null) { h = (h * 31) + parent.hashCode(); } return h; } /** * Returns the level of the member that this key represents. * * @return Member level, or null if is root member */ public RolapLevel getLevel() { if (parent == null) { return null; } final RolapLevel level = parent.getLevel(); if (level.isParentChild()) { return level; } return (RolapLevel) level.getChildLevel(); } } // End MemberKey.java mondrian-3.4.1/src/main/mondrian/rolap/RolapNativeCrossJoin.java0000644000175000017500000002544311735330606024616 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.olap.fun.NonEmptyCrossJoinFunDef; import mondrian.rolap.sql.*; import java.util.*; /** * Creates a {@link mondrian.olap.NativeEvaluator} that evaluates NON EMPTY * CrossJoin in SQL. The generated SQL will join the dimension tables with * the fact table and return all combinations that have a * corresponding row in the fact table. The current context (slicer) is * used for filtering (WHERE clause in SQL). This very effective computes * queries like * *

 *   SELECT ...
 *   NON EMTPY Crossjoin(
 *       [product].[name].members,
 *       [customer].[name].members) ON ROWS
 *   FROM [Sales]
 *   WHERE ([store].[store #14])
 * 
* * where both, customer.name and product.name have many members, but the * resulting crossjoin only has few. * *

The implementation currently can not handle sets containting * parent/child hierarchies, ragged hierarchies, calculated members and * the ALL member. Otherwise all * * @author av * @since Nov 21, 2005 */ public class RolapNativeCrossJoin extends RolapNativeSet { public RolapNativeCrossJoin() { super.setEnabled( MondrianProperties.instance().EnableNativeCrossJoin.get()); } /** * Constraint that restricts the result to the current context. * *

If the current context contains calculated members, silently ignores * them. This means means that too many members are returned, but this does * not matter, because the {@link RolapConnection.NonEmptyResult} will * filter out these later.

*/ static class NonEmptyCrossJoinConstraint extends SetConstraint { NonEmptyCrossJoinConstraint( CrossJoinArg[] args, RolapEvaluator evaluator) { // Cross join ignores calculated members, including the ones from // the slicer. super(args, evaluator, false); } public RolapMember findMember(Object key) { for (CrossJoinArg arg : args) { if (arg instanceof MemberListCrossJoinArg) { final MemberListCrossJoinArg crossJoinArg = (MemberListCrossJoinArg) arg; final List memberList = crossJoinArg.getMembers(); for (RolapMember rolapMember : memberList) { if (key.equals(rolapMember.getKey())) { return rolapMember; } } } } return null; } } protected boolean restrictMemberTypes() { return false; } NativeEvaluator createEvaluator( RolapEvaluator evaluator, FunDef fun, Exp[] args) { if (!isEnabled()) { // native crossjoins were explicitly disabled, so no need // to alert about not using them return null; } RolapCube cube = evaluator.getCube(); List allArgs = crossJoinArgFactory() .checkCrossJoin(evaluator, fun, args, false); // checkCrossJoinArg returns a list of CrossJoinArg arrays. The first // array is the CrossJoin dimensions. The second array, if any, // contains additional constraints on the dimensions. If either the list // or the first array is null, then native cross join is not feasible. if (allArgs == null || allArgs.isEmpty() || allArgs.get(0) == null) { // Something in the arguments to the crossjoin prevented // native evaluation; may need to alert alertCrossJoinNonNative( evaluator, fun, "arguments not supported"); return null; } CrossJoinArg[] cjArgs = allArgs.get(0); // check if all CrossJoinArgs are "All" members or Calc members // "All" members do not have relational expression, and Calc members // in the input could produce incorrect results. // // If NECJ only has AllMembers, or if there is at least one CalcMember, // then sql evaluation is not possible. int countNonNativeInputArg = 0; for (CrossJoinArg arg : cjArgs) { if (arg instanceof MemberListCrossJoinArg) { MemberListCrossJoinArg cjArg = (MemberListCrossJoinArg)arg; if (cjArg.hasAllMember() || cjArg.isEmptyCrossJoinArg()) { ++countNonNativeInputArg; } if (cjArg.hasCalcMembers()) { countNonNativeInputArg = cjArgs.length; break; } } } if (countNonNativeInputArg == cjArgs.length) { // If all inputs contain "All" members; or // if all inputs are MemberListCrossJoinArg with empty member list // content, then native evaluation is not feasible. alertCrossJoinNonNative( evaluator, fun, "either all arguments contain the ALL member, " + "or empty member lists, or one has a calculated member"); return null; } if (isPreferInterpreter(cjArgs, true)) { // Native evaluation wouldn't buy us anything, so no // need to alert return null; } // Verify that args are valid List levels = new ArrayList(); for (CrossJoinArg cjArg : cjArgs) { RolapLevel level = cjArg.getLevel(); if (level != null) { // Only add non null levels. These levels have real // constraints. levels.add(level); } } if (cube.isVirtual() && !evaluator.getQuery().nativeCrossJoinVirtualCube()) { // Something in the query at large (namely, some unsupported // function on the [Measures] dimension) prevented native // evaluation with virtual cubes; may need to alert alertCrossJoinNonNative( evaluator, fun, "not all functions on [Measures] dimension supported"); return null; } if (!NonEmptyCrossJoinConstraint.isValidContext( evaluator, false, levels.toArray(new RolapLevel[levels.size()]), restrictMemberTypes())) { // Missing join conditions due to non-conforming dimensions // meant native evaluation would have led to a true cross // product, which we want to defer instead of pushing it down; // so no need to alert return null; } // join with fact table will always filter out those members // that dont have a row in the fact table if (!evaluator.isNonEmpty()) { return null; } LOGGER.debug("using native crossjoin"); // Create a new evaluation context, eliminating any outer context for // the dimensions referenced by the inputs to the NECJ // (otherwise, that outer context would be incorrectly intersected // with the constraints from the inputs). final int savepoint = evaluator.savepoint(); Member[] evalMembers = evaluator.getMembers().clone(); for (RolapLevel level : levels) { RolapHierarchy hierarchy = level.getHierarchy(); for (int i = 0; i < evalMembers.length; ++i) { Dimension evalMemberDimension = evalMembers[i].getHierarchy().getDimension(); if (evalMemberDimension == hierarchy.getDimension()) { evalMembers[i] = hierarchy.getAllMember(); } } } evaluator.setContext(evalMembers); // Use the combined CrossJoinArg for the tuple constraint, which will be // translated to the SQL WHERE clause. CrossJoinArg[] cargs = combineArgs(allArgs); // Now construct the TupleConstraint that contains both the CJ // dimensions and the additional filter on them. It will make a copy // of the evaluator. TupleConstraint constraint = buildConstraint(evaluator, fun, cargs); // Use the just the CJ CrossJoiArg for the evaluator context, which will // be translated to select list in sql. final SchemaReader schemaReader = evaluator.getSchemaReader(); evaluator.restore(savepoint); return new SetEvaluator(cjArgs, schemaReader, constraint); } CrossJoinArg[] combineArgs( List allArgs) { CrossJoinArg[] cjArgs = allArgs.get(0); if (allArgs.size() == 2) { CrossJoinArg[] predicateArgs = allArgs.get(1); if (predicateArgs != null) { // Combine the CJ and the additional predicate args. return Util.appendArrays(cjArgs, predicateArgs); } } return cjArgs; } private TupleConstraint buildConstraint( final RolapEvaluator evaluator, final FunDef fun, final CrossJoinArg[] cargs) { CrossJoinArg[] myArgs; if (safeToConstrainByOtherAxes(fun)) { myArgs = buildArgs(evaluator, cargs); } else { myArgs = cargs; } return new NonEmptyCrossJoinConstraint(myArgs, evaluator); } private CrossJoinArg[] buildArgs( final RolapEvaluator evaluator, final CrossJoinArg[] cargs) { Set joinArgs = crossJoinArgFactory().buildConstraintFromAllAxes(evaluator); joinArgs.addAll(Arrays.asList(cargs)); return joinArgs.toArray(new CrossJoinArg[joinArgs.size()]); } private boolean safeToConstrainByOtherAxes(final FunDef fun) { return !(fun instanceof NonEmptyCrossJoinFunDef); } private void alertCrossJoinNonNative( RolapEvaluator evaluator, FunDef fun, String reason) { if (!(fun instanceof NonEmptyCrossJoinFunDef)) { // Only alert for an explicit NonEmptyCrossJoin, // since query authors use that to indicate that // they expect it to be "wicked fast" return; } if (!evaluator.getQuery().shouldAlertForNonNative(fun)) { return; } RolapUtil.alertNonNative("NonEmptyCrossJoin", reason); } } // End RolapNativeCrossJoin.java mondrian-3.4.1/src/main/mondrian/rolap/GroupingSetsCollector.java0000644000175000017500000000252711735330606025036 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.rolap.agg.GroupingSet; import java.util.ArrayList; import java.util.List; /** *

The GroupingSetsCollector collects the GroupinpSets and pass * the consolidated list to form group by grouping sets sql

* * @author Thiyagu * @since 06-Jun-2007 */ public class GroupingSetsCollector { private final boolean useGroupingSets; private ArrayList groupingSets = new ArrayList(); public GroupingSetsCollector(boolean useGroupingSets) { this.useGroupingSets = useGroupingSets; } public boolean useGroupingSets() { return useGroupingSets; } public void add(GroupingSet aggInfo) { assert groupingSets.isEmpty() || groupingSets.get(0).getColumns().length >= aggInfo.getColumns().length; groupingSets.add(aggInfo); } public List getGroupingSets() { return groupingSets; } } // End GroupingSetsCollector.java mondrian-3.4.1/src/main/mondrian/rolap/TupleReader.java0000644000175000017500000000741711735330606022755 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.TupleList; import java.sql.SQLException; import java.util.List; import javax.sql.DataSource; /** * Describes the public methods of {@link mondrian.rolap.SqlTupleReader}. * * @author av * @since Nov 21, 2005 */ public interface TupleReader { /** * Factory to create new members for a * hierarchy from SQL result. * * @author av * @since Nov 11, 2005 */ public interface MemberBuilder { /** * Returns the MemberCache to look up members before * creating them. * * @return member cache */ MemberCache getMemberCache(); /** * Returns the object which acts as the member cache * synchronization lock. * * @return Object to lock */ Object getMemberCacheLock(); /** * Creates a new member (together with its properties). * * @param parentMember Parent member * @param childLevel Child level * @param value Member value * @param captionValue Caption * @param parentChild Whether a parent-child hierarchy * @param stmt SQL statement * @param key Member key * @param column Column ordinal (0-based) * @return new member * @throws java.sql.SQLException on error */ RolapMember makeMember( RolapMember parentMember, RolapLevel childLevel, Object value, Object captionValue, boolean parentChild, SqlStatement stmt, Object key, int column) throws SQLException; /** * Returns the 'all' member of the hierarchy. * * @return The 'all' member */ RolapMember allMember(); } /** * Adds a hierarchy to retrieve members from. * * @param level level that the members correspond to * @param memberBuilder used to build new members for this level * @param srcMembers if set, array of enumerated members that make up * this level */ void addLevelMembers( RolapLevel level, MemberBuilder memberBuilder, List srcMembers); /** * Performs the read. * * @param dataSource Data source * @param partialResult List of rows from previous pass * @param newPartialResult Populated with a new list of rows * * @return a list of tuples */ TupleList readTuples( DataSource dataSource, TupleList partialResult, List> newPartialResult); /** * Performs the read. * * @param dataSource source for reading tuples * @param partialResult partially cached result that should be used * instead of executing sql query * @param newPartialResult if non-null, return the result of the read; * note that this is a subset of the full return list * @return a list of RolapMember */ TupleList readMembers( DataSource dataSource, TupleList partialResult, List> newPartialResult); /** * Returns an object that uniquely identifies the Result that this * {@link TupleReader} would return. Clients may use this as a key for * caching the result. * * @return Cache key */ Object getCacheKey(); } // End TupleReader.java mondrian-3.4.1/src/main/mondrian/rolap/RolapDimension.java0000644000175000017500000001741011735330606023456 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. // // jhyde, 10 August, 2001 */ package mondrian.rolap; import mondrian.olap.*; import mondrian.resource.MondrianResource; import org.apache.log4j.Logger; import java.util.Collections; import java.util.Map; /** * RolapDimension implements {@link Dimension}for a ROLAP * database. * *

Topic: Dimension ordinals

* * {@link RolapEvaluator} needs each dimension to have an ordinal, so that it * can store the evaluation context as an array of members. * *

* A dimension may be either shared or private to a particular cube. The * dimension object doesn't actually know which; {@link Schema} has a list of * shared hierarchies ({@link Schema#getSharedHierarchies}), and {@link Cube} * has a list of dimensions ({@link Cube#getDimensions}). * *

* If a dimension is shared between several cubes, the {@link Dimension}objects * which represent them may (or may not be) the same. (That's why there's no * getCube() method.) * *

* Furthermore, since members are created by a {@link MemberReader}which * belongs to the {@link RolapHierarchy}, you will the members will be the same * too. For example, if you query [Product].[Beer] from the * Sales and Warehouse cubes, you will get the * same {@link RolapMember}object. * ({@link RolapSchema#mapSharedHierarchyToReader} holds the mapping. I don't * know whether it's still necessary.) * * @author jhyde * @since 10 August, 2001 */ class RolapDimension extends DimensionBase { private static final Logger LOGGER = Logger.getLogger(RolapDimension.class); private final Schema schema; private final Map annotationMap; RolapDimension( Schema schema, String name, String caption, boolean visible, String description, DimensionType dimensionType, final boolean highCardinality, Map annotationMap) { // todo: recognition of a time dimension should be improved // allow multiple time dimensions super( name, caption, visible, description, dimensionType, highCardinality); assert annotationMap != null; this.schema = schema; this.annotationMap = annotationMap; this.hierarchies = new RolapHierarchy[0]; } /** * Creates a dimension from an XML definition. * * @pre schema != null */ RolapDimension( RolapSchema schema, RolapCube cube, MondrianDef.Dimension xmlDimension, MondrianDef.CubeDimension xmlCubeDimension) { this( schema, xmlDimension.name, xmlDimension.caption, xmlDimension.visible, xmlDimension.description, xmlDimension.getDimensionType(), xmlDimension.highCardinality, RolapHierarchy.createAnnotationMap(xmlCubeDimension.annotations)); Util.assertPrecondition(schema != null); if (cube != null) { Util.assertTrue(cube.getSchema() == schema); } if (!Util.isEmpty(xmlDimension.caption)) { setCaption(xmlDimension.caption); } this.hierarchies = new RolapHierarchy[xmlDimension.hierarchies.length]; for (int i = 0; i < xmlDimension.hierarchies.length; i++) { // remaps the xml hierarchy relation to the fact table. // moved out of RolapHierarchy constructor // this should eventually be phased out completely if (xmlDimension.hierarchies[i].relation == null && xmlDimension.hierarchies[i].memberReaderClass == null && cube != null) { xmlDimension.hierarchies[i].relation = cube.fact; } RolapHierarchy hierarchy = new RolapHierarchy( this, xmlDimension.hierarchies[i], xmlCubeDimension); hierarchies[i] = hierarchy; } // if there was no dimension type assigned, determine now. if (dimensionType == null) { for (int i = 0; i < hierarchies.length; i++) { Level[] levels = hierarchies[i].getLevels(); LevLoop: for (int j = 0; j < levels.length; j++) { Level lev = levels[j]; if (lev.isAll()) { continue LevLoop; } if (dimensionType == null) { // not set yet - set it according to current level dimensionType = (lev.getLevelType().isTime()) ? DimensionType.TimeDimension : isMeasures() ? DimensionType.MeasuresDimension : DimensionType.StandardDimension; } else { // Dimension type was set according to first level. // Make sure that other levels fit to definition. if (dimensionType == DimensionType.TimeDimension && !lev.getLevelType().isTime() && !lev.isAll()) { throw MondrianResource.instance() .NonTimeLevelInTimeHierarchy.ex( getUniqueName()); } if (dimensionType != DimensionType.TimeDimension && lev.getLevelType().isTime()) { throw MondrianResource.instance() .TimeLevelInNonTimeHierarchy.ex( getUniqueName()); } } } } } } protected Logger getLogger() { return LOGGER; } /** * Initializes a dimension within the context of a cube. */ void init(MondrianDef.CubeDimension xmlDimension) { for (int i = 0; i < hierarchies.length; i++) { if (hierarchies[i] != null) { ((RolapHierarchy) hierarchies[i]).init(xmlDimension); } } } /** * Creates a hierarchy. * * @param subName Name of this hierarchy. * @param hasAll Whether hierarchy has an 'all' member * @param closureFor Hierarchy for which the new hierarchy is a closure; * null for regular hierarchies * @return Hierarchy */ RolapHierarchy newHierarchy( String subName, boolean hasAll, RolapHierarchy closureFor) { RolapHierarchy hierarchy = new RolapHierarchy( this, subName, caption, visible, description, hasAll, closureFor, Collections.emptyMap()); this.hierarchies = Util.append(this.hierarchies, hierarchy); return hierarchy; } /** * Returns the hierarchy of an expression. * *

In this case, the expression is a dimension, so the hierarchy is the * dimension's default hierarchy (its first). */ public Hierarchy getHierarchy() { return hierarchies[0]; } public Schema getSchema() { return schema; } public Map getAnnotationMap() { return annotationMap; } } // End RolapDimension.java mondrian-3.4.1/src/main/mondrian/rolap/package.html0000644000175000017500000000213211735330606022144 0ustar drazzibdrazzib Implements the data access layer for the olap package.

MemberReader

When a member expression like member.children is evaluated, the function requests the SchemaReader from the Evaluator. The RolapEvaluator uses the RolapSchemaReader which delegates most calls to one of its MemberReaders (one per dimension). In most cases, a SmartMemberReader will be used, which returns the requested members immediately.

CellReader

Cells are evaluated multiple times. For the first time, a FastBatchingCellReader is used. When a cell is evaluated, Evaluator.evaluateCurrent() is called. The FastBatchingCellReader will not compute the cells value but record a CellRequest for that cell and return (not throw) an exception. After all CellRequests for all cells have been recorded, the Aggregation will generate SQL to load all cells with a single SQL query. After that the cells are evaluated again with an AggregatingCellReader that will return the cell values from the cache. mondrian-3.4.1/src/main/mondrian/rolap/RolapAggregationManager.java0000644000175000017500000007441611735330606025264 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. // // jhyde, 30 August, 2001 */ package mondrian.rolap; import mondrian.olap.*; import mondrian.olap.fun.VisualTotalsFunDef.VisualTotalMember; import mondrian.rolap.agg.*; import java.util.*; /** * RolapAggregationManager manages all * {@link mondrian.rolap.agg.Segment}s in the system. * *

The bits of the implementation which depend upon dimensional concepts * RolapMember, etc.) live in this class, and the other bits live * in the derived class, {@link mondrian.rolap.agg.AggregationManager}. * * @author jhyde * @since 30 August, 2001 */ public abstract class RolapAggregationManager { /** * Creates the RolapAggregationManager. */ protected RolapAggregationManager() { } /** * Creates a request to evaluate the cell identified by * members. * *

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 Set of members which constrain the cell * @return Cell request, or null if the requst is unsatisfiable */ public static CellRequest makeRequest(final Member[] members) { return makeCellRequest(members, false, false, null, null); } /** * Creates a request for the fact-table rows underlying the cell identified * by members. * *

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 Set of members which constrain the cell * * @param extendedContext If true, add non-constraining columns to the * query for levels below each current member. * This additional context makes the drill-through * queries easier for humans to understand. * * @param cube Cube * @return Cell request, or null if the requst is unsatisfiable */ public static DrillThroughCellRequest makeDrillThroughRequest( final Member[] members, final boolean extendedContext, RolapCube cube, List fieldsList) { assert cube != null; return (DrillThroughCellRequest) makeCellRequest( members, true, extendedContext, cube, fieldsList); } /** * Creates a request to evaluate the cell identified by the context * specified in evaluator. * *

If any of the members from the context is the null member, returns * null, since there is no cell. If the measure is calculated, returns * null. * * @param evaluator the cell specified by the evaluator context * @return Cell request, or null if the requst is unsatisfiable */ public static CellRequest makeRequest( RolapEvaluator evaluator) { final Member[] currentMembers = evaluator.getNonAllMembers(); final List>> aggregationLists = evaluator.getAggregationLists(); final RolapStoredMeasure measure = (RolapStoredMeasure) currentMembers[0]; final RolapStar.Measure starMeasure = (RolapStar.Measure) measure.getStarMeasure(); assert starMeasure != null; int starColumnCount = starMeasure.getStar().getColumnCount(); CellRequest request = makeCellRequest(currentMembers, false, false, null, null); /* * Now setting the compound keys. * First find out the columns referenced in the aggregateMemberList. * Each list defines a compound member. */ if (aggregationLists == null) { return request; } BitKey compoundBitKey; StarPredicate compoundPredicate; Map> compoundGroupMap; boolean unsatisfiable; /* * For each aggregationList, generate the optimal form of * compoundPredicate. These compoundPredicates are AND'ed together when * sql is generated for them. */ for (List> aggregationList : aggregationLists) { compoundBitKey = BitKey.Factory.makeBitKey(starColumnCount); compoundBitKey.clear(); compoundGroupMap = new LinkedHashMap>(); // Go through the compound members/tuples once and separate them // into groups. List> rolapAggregationList = new ArrayList>(); for (List members : aggregationList) { // REVIEW: do we need to copy? List rolapMembers = Util.cast(members); rolapAggregationList.add(rolapMembers); } unsatisfiable = makeCompoundGroup( starColumnCount, measure.getCube(), rolapAggregationList, compoundGroupMap); if (unsatisfiable) { return null; } compoundPredicate = makeCompoundPredicate(compoundGroupMap, measure.getCube()); if (compoundPredicate != null) { /* * Only add the compound constraint when it is not empty. */ for (BitKey bitKey : compoundGroupMap.keySet()) { compoundBitKey = compoundBitKey.or(bitKey); } request.addAggregateList(compoundBitKey, compoundPredicate); } } return request; } private static CellRequest makeCellRequest( final Member[] members, boolean drillThrough, final boolean extendedContext, RolapCube cube, List fieldsList) { // Need cube for drill-through requests assert drillThrough == (cube != null); if (extendedContext) { assert (drillThrough); } final RolapStoredMeasure measure; if (drillThrough) { cube = RolapCell.chooseDrillThroughCube(members, cube); if (cube == null) { return null; } if (members.length > 0 && members[0] instanceof RolapStoredMeasure) { measure = (RolapStoredMeasure) members[0]; } else { measure = (RolapStoredMeasure) cube.getMeasures().get(0); } } else { if (members.length > 0 && members[0] instanceof RolapStoredMeasure) { measure = (RolapStoredMeasure) members[0]; } else { return null; } } final RolapStar.Measure starMeasure = (RolapStar.Measure) measure.getStarMeasure(); assert starMeasure != null; final CellRequest request; if (drillThrough) { request = new DrillThroughCellRequest(starMeasure, extendedContext); } else { request = new CellRequest(starMeasure, extendedContext, drillThrough); } // Since 'request.extendedContext == false' is a well-worn code path, // we have moved the test outside the loop. if (extendedContext) { if (fieldsList != null) { // If a field list was specified, there will be some columns // to include in the result set, other that we don't. This // happens when the MDX is a DRILLTHROUGH operation and // includes a RETURN clause. final SchemaReader reader = cube.getSchemaReader().withLocus(); for (Exp exp : fieldsList) { final OlapElement member = reader.lookupCompound( cube, Util.parseIdentifier(exp.toString()), true, Category.Unknown); if (member.getHierarchy() instanceof RolapCubeHierarchy && ((RolapCubeHierarchy)member.getHierarchy()) .getRolapHierarchy().closureFor != null) { continue; } addNonConstrainingColumns(member, cube, request); } } for (int i = 1; i < members.length; i++) { final RolapCubeMember member = (RolapCubeMember) members[i]; if (member.getHierarchy().getRolapHierarchy().closureFor != null) { continue; } addNonConstrainingColumns(member, cube, request); final RolapCubeLevel level = member.getLevel(); final boolean needToReturnNull = level.getLevelReader().constrainRequest( member, measure.getCube(), request); if (needToReturnNull) { return null; } } } else { for (int i = 1; i < members.length; i++) { if (!(members[i] instanceof RolapCubeMember)) { continue; } RolapCubeMember member = (RolapCubeMember) members[i]; final RolapCubeLevel level = member.getLevel(); final boolean needToReturnNull = level.getLevelReader().constrainRequest( member, measure.getCube(), request); if (needToReturnNull) { return null; } } } return request; } /** * Adds 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' *
  • State = 'CA' *
  • City not constraining *
* *

Note that [Marital Status] column is present by virtue of * the implicit [Marital Status].[All] member. Hence the SQL * *

     *   select [Marital Status], [City]
     *   from [Star]
     *   where [Gender] = 'M'
     *   and [Nation] = 'USA'
     *   and [State] = 'CA'
     *   
* * @param member Member to constraint * @param baseCube base cube if virtual * @param request Cell request */ private static void addNonConstrainingColumns( final RolapCubeMember member, final RolapCube baseCube, final CellRequest request) { final RolapCubeHierarchy hierarchy = member.getHierarchy(); final RolapCubeLevel[] levels = hierarchy.getLevels(); for (int j = levels.length - 1, depth = member.getLevel().getDepth(); j > depth; j--) { final RolapCubeLevel level = levels[j]; RolapStar.Column column = level.getBaseStarKeyColumn(baseCube); if (column != null) { request.addConstrainedColumn(column, null); if (request.extendedContext && level.getNameExp() != null) { final RolapStar.Column nameColumn = column.getNameColumn(); Util.assertTrue(nameColumn != null); request.addConstrainedColumn(nameColumn, null); } } } } private static void addNonConstrainingColumns( final OlapElement member, final RolapCube baseCube, final CellRequest request) { RolapCubeLevel level; if (member instanceof RolapCubeLevel) { level = (RolapCubeLevel) member; } else if (member instanceof RolapCubeHierarchy || member instanceof RolapCubeDimension) { level = (RolapCubeLevel) member.getHierarchy().getLevels()[0]; if (level.isAll()) { level = level.getChildLevel(); } } else if (member instanceof RolapStar.Measure) { ((DrillThroughCellRequest)request) .addDrillThroughMeasure((RolapStar.Measure)member); return; } else if (member instanceof RolapBaseCubeMeasure) { ((DrillThroughCellRequest)request) .addDrillThroughMeasure( (RolapStar.Measure) ((RolapBaseCubeMeasure)member).getStarMeasure()); return; } else { // FIXME make this better. throw new MondrianException(); } RolapStar.Column column = level.getBaseStarKeyColumn(baseCube); if (column != null) { request.addConstrainedColumn(column, null); ((DrillThroughCellRequest)request).addDrillThroughColumn(column); if (request.extendedContext && level.getNameExp() != null) { final RolapStar.Column nameColumn = column.getNameColumn(); Util.assertTrue(nameColumn != null); request.addConstrainedColumn(nameColumn, null); } } } /** * Groups members (or tuples) from the same compound (i.e. hierarchy) into * groups that are constrained by the same set of columns. * *

E.g. * *

Members
     *     [USA].[CA],
     *     [Canada].[BC],
     *     [USA].[CA].[San Francisco],
     *     [USA].[OR].[Portland]
* * will be grouped into * *
Group 1:
     *     {[USA].[CA], [Canada].[BC]}
     * Group 2:
     *     {[USA].[CA].[San Francisco], [USA].[OR].[Portland]}
* *

This helps with generating optimal form of sql. * *

In case of aggregating over a list of tuples, similar logic also * applies. * *

For example: * *

Tuples:
     *     ([Gender].[M], [Store].[USA].[CA])
     *     ([Gender].[F], [Store].[USA].[CA])
     *     ([Gender].[M], [Store].[USA])
     *     ([Gender].[F], [Store].[Canada])
* * will be grouped into * *
Group 1:
     *     {([Gender].[M], [Store].[USA].[CA]),
     *      ([Gender].[F], [Store].[USA].[CA])}
     * Group 2:
     *     {([Gender].[M], [Store].[USA]),
     *      ([Gender].[F], [Store].[Canada])}
* *

This function returns a boolean value indicating if any constraint * can be created from the aggregationList. It is possible that only part * of the aggregationList can be applied, which still leads to a (partial) * constraint that is represented by the compoundGroupMap. */ private static boolean makeCompoundGroup( int starColumnCount, RolapCube baseCube, List> aggregationList, Map> compoundGroupMap) { // The more generalized aggregation as aggregating over tuples. // The special case is a tuple defined by only one member. int unsatisfiableTupleCount = 0; for (List aggregation : aggregationList) { boolean isTuple; if (aggregation.size() > 0 && (aggregation.get(0) instanceof RolapCubeMember || aggregation.get(0) instanceof VisualTotalMember)) { isTuple = true; } else { ++unsatisfiableTupleCount; continue; } BitKey bitKey = BitKey.Factory.makeBitKey(starColumnCount); RolapCubeMember[] tuple; tuple = new RolapCubeMember[aggregation.size()]; int i = 0; for (Member member : aggregation) { if (member instanceof VisualTotalMember) { tuple[i] = (RolapCubeMember) ((VisualTotalMember) member).getMember(); } else { tuple[i] = (RolapCubeMember)member; } i++; } boolean tupleUnsatisfiable = false; for (RolapCubeMember member : tuple) { // Tuple cannot be constrained if any of the member cannot be. tupleUnsatisfiable = makeCompoundGroupForMember(member, baseCube, bitKey); if (tupleUnsatisfiable) { // If this tuple is unsatisfiable, skip it and try to // constrain the next tuple. unsatisfiableTupleCount ++; break; } } if (!tupleUnsatisfiable && !bitKey.isEmpty()) { // Found tuple(columns) to constrain, // now add it to the compoundGroupMap addTupleToCompoundGroupMap(tuple, bitKey, compoundGroupMap); } } return (unsatisfiableTupleCount == aggregationList.size()); } private static void addTupleToCompoundGroupMap( RolapCubeMember[] tuple, BitKey bitKey, Map> compoundGroupMap) { List compoundGroup = compoundGroupMap.get(bitKey); if (compoundGroup == null) { compoundGroup = new ArrayList(); compoundGroupMap.put(bitKey, compoundGroup); } compoundGroup.add(tuple); } private static boolean makeCompoundGroupForMember( RolapCubeMember member, RolapCube baseCube, BitKey bitKey) { RolapCubeMember levelMember = member; boolean memberUnsatisfiable = false; while (levelMember != null) { RolapCubeLevel level = levelMember.getLevel(); // Only need to constrain the nonAll levels if (!level.isAll()) { RolapStar.Column column = level.getBaseStarKeyColumn(baseCube); if (column != null) { bitKey.set(column.getBitPosition()); } else { // One level in a member causes the member to be // unsatisfiable. memberUnsatisfiable = true; break; } } levelMember = levelMember.getParentMember(); } return memberUnsatisfiable; } /** * Translates a Map<BitKey, List<RolapMember>> of the same * compound member into {@link ListPredicate} by traversing a list of * members or tuples. * *

1. The example below is for list of tuples * *

* group 1: [Gender].[M], [Store].[USA].[CA]
* group 2: [Gender].[F], [Store].[USA].[CA] *
* * is translated into * *
* (Gender=M AND Store_State=CA AND Store_Country=USA)
* OR
* (Gender=F AND Store_State=CA AND Store_Country=USA) *
* *

The caller of this method will translate this representation into * appropriate SQL form as *

* where (gender = 'M'
* and Store_State = 'CA'
* AND Store_Country = 'USA')
* OR (Gender = 'F'
* and Store_State = 'CA'
* AND Store_Country = 'USA') *
* *

2. The example below for a list of members *

* group 1: [USA].[CA], [Canada].[BC]
* group 2: [USA].[CA].[San Francisco], [USA].[OR].[Portland] *
* * is translated into: * *
* (Country=USA AND State=CA)
* OR (Country=Canada AND State=BC)
* OR (Country=USA AND State=CA AND City=San Francisco)
* OR (Country=USA AND State=OR AND City=Portland) *
* *

The caller of this method will translate this representation into * appropriate SQL form. For exmaple, if the underlying DB supports multi * value IN-list, the second group will turn into this predicate: * *

* where (country, state, city) IN ((USA, CA, San Francisco), * (USA, OR, Portland)) *
* * or, if the DB does not support multi-value IN list: * *
* where country=USA AND * ((state=CA AND city = San Francisco) OR * (state=OR AND city=Portland)) *
* * @param compoundGroupMap Map from dimensionality to groups * @param baseCube base cube if virtual * @return compound predicate for a tuple or a member */ private static StarPredicate makeCompoundPredicate( Map> compoundGroupMap, RolapCube baseCube) { List compoundPredicateList = new ArrayList (); for (List group : compoundGroupMap.values()) { /* * e.g. * {[USA].[CA], [Canada].[BC]} * or * { */ StarPredicate compoundGroupPredicate = null; for (RolapCubeMember[] tuple : group) { /* * [USA].[CA] */ StarPredicate tuplePredicate = null; for (RolapCubeMember member : tuple) { tuplePredicate = makeCompoundPredicateForMember( member, baseCube, tuplePredicate); } if (tuplePredicate != null) { if (compoundGroupPredicate == null) { compoundGroupPredicate = tuplePredicate; } else { compoundGroupPredicate = compoundGroupPredicate.or(tuplePredicate); } } } if (compoundGroupPredicate != null) { /* * Sometimes the compound member list does not constrain any * columns; for example, if only AllLevel is present. */ compoundPredicateList.add(compoundGroupPredicate); } } StarPredicate compoundPredicate = null; if (compoundPredicateList.size() > 1) { compoundPredicate = new OrPredicate(compoundPredicateList); } else if (compoundPredicateList.size() == 1) { compoundPredicate = compoundPredicateList.get(0); } return compoundPredicate; } private static StarPredicate makeCompoundPredicateForMember( RolapCubeMember member, RolapCube baseCube, StarPredicate memberPredicate) { while (member != null) { RolapCubeLevel level = member.getLevel(); if (!level.isAll()) { RolapStar.Column column = level.getBaseStarKeyColumn(baseCube); if (memberPredicate == null) { memberPredicate = new ValueColumnPredicate(column, member.getKey()); } else { memberPredicate = memberPredicate.and( new ValueColumnPredicate(column, member.getKey())); } } // Don't need to constrain USA if CA is unique if (member.getLevel().isUnique()) { break; } member = member.getParentMember(); } return memberPredicate; } /** * Retrieves the value of a cell from the cache. * * @param request Cell request * @pre request != null && !request.isUnsatisfiable() * @return Cell value, or null if cell is not in any aggregation in cache, * or {@link Util#nullValue} if cell's value is null */ public abstract Object getCellFromCache(CellRequest request); public abstract Object getCellFromCache( CellRequest request, PinSet pinSet); /** * Generates a SQL statement which will return the rows which contribute to * this request. * * @param request Cell request * @param countOnly If true, return a statment which returns only the count * @param starPredicateSlicer A StarPredicate representing slicer positions * that could not be represented by the CellRequest, or * null if no additional predicate is necessary. * @return SQL statement */ public abstract String getDrillThroughSql( DrillThroughCellRequest request, StarPredicate starPredicateSlicer, List fields, boolean countOnly); public static RolapCacheRegion makeCacheRegion( final RolapStar star, final CacheControl.CellRegion region) { final List measureList = CacheControlImpl.findMeasures(region); final List starMeasureList = new ArrayList(); RolapCube baseCube = null; for (Member measure : measureList) { if (!(measure instanceof RolapStoredMeasure)) { continue; } final RolapStoredMeasure storedMeasure = (RolapStoredMeasure) measure; final RolapStar.Measure starMeasure = (RolapStar.Measure) storedMeasure.getStarMeasure(); assert starMeasure != null; if (star != starMeasure.getStar()) { continue; } // TODO: each time this code executes, baseCube is set. // Should there be a 'break' here? Are all of the // storedMeasure cubes the same cube? Is the measureList always // non-empty so that baseCube is always set? baseCube = storedMeasure.getCube(); starMeasureList.add(starMeasure); } final RolapCacheRegion cacheRegion = new RolapCacheRegion(star, starMeasureList); if (region instanceof CacheControlImpl.CrossjoinCellRegion) { final CacheControlImpl.CrossjoinCellRegion crossjoin = (CacheControlImpl.CrossjoinCellRegion) region; for (CacheControl.CellRegion component : crossjoin.getComponents()) { constrainCacheRegion(cacheRegion, baseCube, component); } } else { constrainCacheRegion(cacheRegion, baseCube, region); } return cacheRegion; } private static void constrainCacheRegion( final RolapCacheRegion cacheRegion, final RolapCube baseCube, final CacheControl.CellRegion region) { if (region instanceof CacheControlImpl.MemberCellRegion) { final CacheControlImpl.MemberCellRegion memberCellRegion = (CacheControlImpl.MemberCellRegion) region; final List memberList = memberCellRegion.getMemberList(); for (Member member : memberList) { if (member.isMeasure()) { continue; } final RolapCubeMember rolapMember; if (member instanceof RolapCubeMember) { rolapMember = (RolapCubeMember) member; } else { rolapMember = (RolapCubeMember) baseCube.getSchemaReader() .getMemberByUniqueName( Util.parseIdentifier(member.getUniqueName()), true); } final RolapCubeLevel level = rolapMember.getLevel(); RolapStar.Column column = level.getBaseStarKeyColumn(baseCube); level.getLevelReader().constrainRegion( new MemberColumnPredicate(column, rolapMember), baseCube, cacheRegion); } } else if (region instanceof CacheControlImpl.MemberRangeCellRegion) { final CacheControlImpl.MemberRangeCellRegion rangeRegion = (CacheControlImpl.MemberRangeCellRegion) region; final RolapCubeLevel level = (RolapCubeLevel)rangeRegion.getLevel(); RolapStar.Column column = level.getBaseStarKeyColumn(baseCube); level.getLevelReader().constrainRegion( new RangeColumnPredicate( column, rangeRegion.getLowerInclusive(), (rangeRegion.getLowerBound() == null ? null : new MemberColumnPredicate( column, rangeRegion.getLowerBound())), rangeRegion.getUpperInclusive(), (rangeRegion.getUpperBound() == null ? null : new MemberColumnPredicate( column, rangeRegion.getUpperBound()))), baseCube, cacheRegion); } else { throw new UnsupportedOperationException(); } } /** * Returns a {@link mondrian.rolap.CellReader} which reads cells from cache. */ public CellReader getCacheCellReader() { return new CellReader() { // implement CellReader public Object get(RolapEvaluator evaluator) { CellRequest request = makeRequest(evaluator); if (request == null || request.isUnsatisfiable()) { // request out of bounds return Util.nullValue; } return getCellFromCache(request); } public int getMissCount() { return 0; // RolapAggregationManager never lies } public boolean isDirty() { return false; } }; } /** * Creates a {@link PinSet}. * * @return a new PinSet */ public abstract PinSet createPinSet(); /** * A set of segments which are pinned (prevented from garbage collection) * for a short duration as a result of a cache inquiry. */ public interface PinSet { } } // End RolapAggregationManager.java mondrian-3.4.1/src/main/mondrian/rolap/Test.java0000644000175000017500000001401311735330606021446 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. // // jhyde, 21 December, 2001 */ package mondrian.rolap; import mondrian.olap.*; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; /** * todo: * * @author jhyde * @since 21 December, 2001 */ public class Test { PrintWriter pw; RolapConnection connection; public static void main(String[] args) { Test test = new Test(args); if (true) { test.run(); } else { try { test.convertFoodMart(); } catch (java.sql.SQLException e) { System.out.println( "Error: " + mondrian.olap.Util.getErrorMessage(e)); } } } Test(String[] args) { pw = new PrintWriter(System.out, true); String connectString = "Data Source=LOCALHOST;Provider=msolap;Catalog=Foodmart"; connection = (RolapConnection) DriverManager.getConnection(connectString, null); } void convertFoodMart() throws java.sql.SQLException { java.sql.Connection connection = null; java.sql.Statement statement = null, statement2 = null; try { try { Class.forName("com.ms.jdbc.odbc.JdbcOdbcDriver"); } catch (ClassNotFoundException e) { } String connectString = "jdbc:odbc:DSN=FoodMart2"; connection = java.sql.DriverManager.getConnection(connectString); statement = connection.createStatement(); statement2 = connection.createStatement(); String sql = "select * from (" + " select *, \"fname\" + ' ' + \"lname\" as \"name\" from \"customer\")" + "order by \"country\", \"state_province\", \"city\", \"name\""; java.sql.ResultSet resultSet = statement.executeQuery(sql); int i = 0; while (resultSet.next()) { int customer_id = resultSet.getInt("customer_id"); statement2.executeUpdate( "update \"customer\" set \"ordinal\" = " + (++i * 3) + " where \"customer_id\" = " + customer_id); } connection.commit(); } finally { if (statement2 != null) { try { statement2.close(); } catch (java.sql.SQLException e) { } } if (statement != null) { try { statement.close(); } catch (java.sql.SQLException e) { } } if (connection != null) { try { connection.close(); } catch (java.sql.SQLException e) { } } } } void run() { RolapCube salesCube = (RolapCube) connection.getSchema().lookupCube("Sales", true); RolapHierarchy measuresHierarchy = (RolapHierarchy) salesCube.getMeasuresHierarchy(); testMemberReader(measuresHierarchy.getMemberReader()); RolapHierarchy genderHierarchy = (RolapHierarchy) salesCube.lookupHierarchy( new Id.Segment("Gender", Id.Quoting.QUOTED), false); testMemberReader(genderHierarchy.getMemberReader()); RolapHierarchy customerHierarchy = (RolapHierarchy) salesCube.lookupHierarchy( new Id.Segment("Customers", Id.Quoting.QUOTED), false); testMemberReader(customerHierarchy.getMemberReader()); } void testMemberReader(MemberReader reader) { pw.println(); pw.println("MemberReader class=" + reader.getClass()); pw.println("Count=" + reader.getMemberCount()); pw.print("Root member(s)="); List rootMembers = reader.getRootMembers(); print(rootMembers); pw.println(); Level[] levels = rootMembers.get(0).getHierarchy().getLevels(); Level level = levels[levels.length > 1 ? 1 : 0]; pw.print("Members at level " + level.getUniqueName() + " are "); List members = reader.getMembersInLevel((RolapLevel)level, 0, Integer.MAX_VALUE); print(members); pw.println(); pw.println("First children of first children: {"); List firstChildren = new ArrayList(); RolapMember member = rootMembers.get(0); while (member != null) { firstChildren.add(member); pw.print("\t"); print(member); List children = new ArrayList(); reader.getMemberChildren(member, children); if (children.isEmpty()) { break; } pw.print(" (" + children.size() + " children)"); RolapMember leadMember = reader.getLeadMember(member, 5); pw.print(", lead(5)="); print(leadMember); if (children.size() > 1) { member = children.get(1); } else if (children.size() > 0) { member = children.get(0); } else { member = null; } pw.println(); } pw.println("}"); } private void print(RolapMember member) { if (member == null) { pw.print("Member(null)"); return; } pw.print("Member(" + member.getUniqueName() + ")"); } private void print(List members) { pw.print("{"); for (int i = 0; i < members.size(); i++) { if (i > 0) { pw.print(", "); } print(members.get(i)); } pw.print("}"); } } // End Test.java mondrian-3.4.1/src/main/mondrian/rolap/RolapSchemaReader.java0000644000175000017500000006251611735330606024063 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. // // jhyde, Feb 24, 2003 */ package mondrian.rolap; import mondrian.calc.*; import mondrian.calc.impl.AbstractCalc; import mondrian.calc.impl.GenericCalc; import mondrian.olap.*; import mondrian.olap.type.StringType; import mondrian.rolap.sql.MemberChildrenConstraint; import mondrian.rolap.sql.TupleConstraint; import org.apache.log4j.Logger; import org.eigenbase.util.property.Property; import org.olap4j.mdx.IdentifierSegment; import java.util.*; import javax.sql.DataSource; /** * A RolapSchemaReader allows you to read schema objects while * observing the access-control profile specified by a given role. * * @author jhyde * @since Feb 24, 2003 */ public class RolapSchemaReader implements SchemaReader, RolapNativeSet.SchemaReaderWithMemberReaderAvailable, NameResolver.Namespace { protected final Role role; private final Map hierarchyReaders = new HashMap(); protected final RolapSchema schema; private final SqlConstraintFactory sqlConstraintFactory = SqlConstraintFactory.instance(); private static final Logger LOGGER = Logger.getLogger(RolapSchemaReader.class); /** * Creates a RolapSchemaReader. * * @param role Role for access control, must not be null * @param schema Schema */ RolapSchemaReader(Role role, RolapSchema schema) { assert role != null : "precondition: role != null"; assert schema != null; this.role = role; this.schema = schema; } public Role getRole() { return role; } public List getHierarchyRootMembers(Hierarchy hierarchy) { final Role.HierarchyAccess hierarchyAccess = role.getAccessDetails(hierarchy); final Level[] levels = hierarchy.getLevels(); final Level firstLevel; if (hierarchyAccess == null) { firstLevel = levels[0]; } else { firstLevel = levels[hierarchyAccess.getTopLevelDepth()]; } return getLevelMembers(firstLevel, true); } public synchronized MemberReader getMemberReader(Hierarchy hierarchy) { MemberReader memberReader = hierarchyReaders.get(hierarchy); if (memberReader == null) { memberReader = ((RolapHierarchy) hierarchy).createMemberReader(role); hierarchyReaders.put(hierarchy, memberReader); } return memberReader; } public Member substitute(Member member) { final MemberReader memberReader = getMemberReader(member.getHierarchy()); return memberReader.substitute((RolapMember) member); } public void getMemberRange( Level level, Member startMember, Member endMember, List list) { getMemberReader(level.getHierarchy()).getMemberRange( (RolapLevel) level, (RolapMember) startMember, (RolapMember) endMember, Util.cast(list)); } public int compareMembersHierarchically(Member m1, Member m2) { RolapMember member1 = (RolapMember) m1; RolapMember member2 = (RolapMember) m2; final RolapHierarchy hierarchy = member1.getHierarchy(); Util.assertPrecondition(hierarchy == m2.getHierarchy()); return getMemberReader(hierarchy).compare(member1, member2, true); } public Member getMemberParent(Member member) { return getMemberReader(member.getHierarchy()).getMemberParent( (RolapMember) member); } public int getMemberDepth(Member member) { final Role.HierarchyAccess hierarchyAccess = role.getAccessDetails(member.getHierarchy()); if (hierarchyAccess != null) { final int memberDepth = member.getLevel().getDepth(); final int topLevelDepth = hierarchyAccess.getTopLevelDepth(); return memberDepth - topLevelDepth; } else if (((RolapLevel) member.getLevel()).isParentChild()) { // For members of parent-child hierarchy, members in the same level // may have different depths. int depth = 0; for (Member m = member.getParentMember(); m != null; m = m.getParentMember()) { depth++; } return depth; } else { return member.getLevel().getDepth(); } } public List getMemberChildren(Member member) { return getMemberChildren(member, null); } public List getMemberChildren(Member member, Evaluator context) { MemberChildrenConstraint constraint = sqlConstraintFactory.getMemberChildrenConstraint(context); List memberList = internalGetMemberChildren(member, constraint); return Util.cast(memberList); } /** * Helper for getMemberChildren. * * @param member Member * @param constraint Constraint * @return List of children */ private List internalGetMemberChildren( Member member, MemberChildrenConstraint constraint) { List children = new ArrayList(); final Hierarchy hierarchy = member.getHierarchy(); final MemberReader memberReader = getMemberReader(hierarchy); memberReader.getMemberChildren( (RolapMember) member, children, constraint); return children; } public void getParentChildContributingChildren( Member dataMember, Hierarchy hierarchy, List list) { final List rolapMemberList = Util.cast(list); list.add(dataMember); ((RolapHierarchy) hierarchy).getMemberReader().getMemberChildren( (RolapMember) dataMember, rolapMemberList); } public int getChildrenCountFromCache(Member member) { final Hierarchy hierarchy = member.getHierarchy(); final MemberReader memberReader = getMemberReader(hierarchy); if (memberReader instanceof RolapCubeHierarchy.RolapCubeHierarchyMemberReader) { List list = ((RolapCubeHierarchy.RolapCubeHierarchyMemberReader) memberReader) .getRolapCubeMemberCacheHelper() .getChildrenFromCache((RolapMember) member, null); if (list == null) { return -1; } return list.size(); } if (memberReader instanceof SmartMemberReader) { List list = ((SmartMemberReader) memberReader).getMemberCache() .getChildrenFromCache((RolapMember) member, null); if (list == null) { return -1; } return list.size(); } if (!(memberReader instanceof MemberCache)) { return -1; } List list = ((MemberCache) memberReader) .getChildrenFromCache((RolapMember) member, null); if (list == null) { return -1; } return list.size(); } /** * Returns number of members in a level, * if the information can be retrieved from cache. * Otherwise {@link Integer#MIN_VALUE}. * * @param level Level * @return number of members in level */ private int getLevelCardinalityFromCache(Level level) { final Hierarchy hierarchy = level.getHierarchy(); final MemberReader memberReader = getMemberReader(hierarchy); if (memberReader instanceof RolapCubeHierarchy.RolapCubeHierarchyMemberReader) { final MemberCacheHelper cache = ((RolapCubeHierarchy.RolapCubeHierarchyMemberReader) memberReader).getRolapCubeMemberCacheHelper(); if (cache == null) { return Integer.MIN_VALUE; } final List list = cache.getLevelMembersFromCache( (RolapLevel) level, null); if (list == null) { return Integer.MIN_VALUE; } return list.size(); } if (memberReader instanceof SmartMemberReader) { List list = ((SmartMemberReader) memberReader) .getMemberCache() .getLevelMembersFromCache( (RolapLevel) level, null); if (list == null) { return Integer.MIN_VALUE; } return list.size(); } if (memberReader instanceof MemberCache) { List list = ((MemberCache) memberReader) .getLevelMembersFromCache( (RolapLevel) level, null); if (list == null) { return Integer.MIN_VALUE; } return list.size(); } return Integer.MIN_VALUE; } public int getLevelCardinality( Level level, boolean approximate, boolean materialize) { if (!this.role.canAccess(level)) { return 1; } int rowCount = Integer.MIN_VALUE; if (approximate) { // See if the schema has an approximation. rowCount = level.getApproxRowCount(); } if (rowCount == Integer.MIN_VALUE) { // See if the precise row count is available in cache. rowCount = getLevelCardinalityFromCache(level); } if (rowCount == Integer.MIN_VALUE) { if (materialize) { // Either the approximate row count hasn't been set, // or they want the precise row count. final MemberReader memberReader = getMemberReader(level.getHierarchy()); rowCount = memberReader.getLevelMemberCount((RolapLevel) level); // Cache it for future. ((RolapLevel) level).setApproxRowCount(rowCount); } } return rowCount; } public List getMemberChildren(List members) { return getMemberChildren(members, null); } public List getMemberChildren( List members, Evaluator context) { if (members.size() == 0) { return Collections.emptyList(); } else { MemberChildrenConstraint constraint = sqlConstraintFactory.getMemberChildrenConstraint(context); final Hierarchy hierarchy = members.get(0).getHierarchy(); final MemberReader memberReader = getMemberReader(hierarchy); final List rolapMemberList = Util.cast(members); final List children = new ArrayList(); memberReader.getMemberChildren( rolapMemberList, children, constraint); return Util.cast(children); } } public void getMemberAncestors(Member member, List ancestorList) { Member parentMember = getMemberParent(member); while (parentMember != null) { ancestorList.add(parentMember); parentMember = getMemberParent(parentMember); } } public Cube getCube() { throw new UnsupportedOperationException(); } public SchemaReader withoutAccessControl() { assert this.getClass() == RolapSchemaReader.class : "Subclass " + getClass() + " must override"; if (role == schema.getDefaultRole()) { return this; } return new RolapSchemaReader(schema.getDefaultRole(), schema); } public OlapElement getElementChild(OlapElement parent, Id.Segment name) { return getElementChild(parent, name, MatchType.EXACT); } public OlapElement getElementChild( OlapElement parent, Id.Segment name, MatchType matchType) { return parent.lookupChild(this, name, matchType); } public final Member getMemberByUniqueName( List uniqueNameParts, boolean failIfNotFound) { return getMemberByUniqueName( uniqueNameParts, failIfNotFound, MatchType.EXACT); } public Member getMemberByUniqueName( List uniqueNameParts, boolean failIfNotFound, MatchType matchType) { // In general, this schema reader doesn't have a cube, so we cannot // start looking up members. return null; } public OlapElement lookupCompound( OlapElement parent, List names, boolean failIfNotFound, int category) { return lookupCompound( parent, names, failIfNotFound, category, MatchType.EXACT); } public final OlapElement lookupCompound( OlapElement parent, List names, boolean failIfNotFound, int category, MatchType matchType) { if (MondrianProperties.instance().SsasCompatibleNaming.get()) { return new NameResolver().resolve( parent, Util.toOlap4j(names), failIfNotFound, category, matchType, getNamespaces()); } return lookupCompoundInternal( parent, names, failIfNotFound, category, matchType); } public final OlapElement lookupCompoundInternal( OlapElement parent, List names, boolean failIfNotFound, int category, MatchType matchType) { return Util.lookupCompound( this, parent, names, failIfNotFound, category, matchType); } public List getNamespaces() { return Collections.singletonList(this); } public OlapElement lookupChild( OlapElement parent, IdentifierSegment segment) { return lookupChild(parent, segment, MatchType.EXACT); } public OlapElement lookupChild( OlapElement parent, IdentifierSegment segment, MatchType matchType) { OlapElement element = getElementChild( parent, Util.convert(segment), matchType); if (element != null) { return element; } if (parent instanceof Cube) { // Named sets defined at the schema level do not, of course, belong // to a cube. But if parent is a cube, this indicates that the name // has not been qualified. element = schema.getNamedSet(segment); } return element; } public Member lookupMemberChildByName( Member parent, Id.Segment childName, MatchType matchType) { if (LOGGER.isDebugEnabled()) { LOGGER.debug( "looking for child \"" + childName + "\" of " + parent); } assert !(parent instanceof RolapHierarchy.LimitedRollupMember); try { MemberChildrenConstraint constraint; if (matchType.isExact()) { constraint = sqlConstraintFactory.getChildByNameConstraint( (RolapMember) parent, childName); } else { constraint = sqlConstraintFactory.getMemberChildrenConstraint(null); } List children = internalGetMemberChildren(parent, constraint); if (children.size() > 0) { return RolapUtil.findBestMemberMatch( children, (RolapMember) parent, children.get(0).getLevel(), childName, matchType, true); } } catch (NumberFormatException e) { // this was thrown in SqlQuery#quote(boolean numeric, Object // value). This happens when Mondrian searches for unqualified Olap // Elements like [Month], because it tries to look up a member with // that name in all dimensions. Then it generates for example // "select .. from time where year = Month" which will result in a // NFE because "Month" can not be parsed as a number. The real bug // is probably, that Mondrian looks at members at all. // // @see RolapCube#lookupChild() LOGGER.debug( "NumberFormatException in lookupMemberChildByName " + "for parent = \"" + parent + "\", childName=\"" + childName + "\", exception: " + e.getMessage()); } return null; } public Member getCalculatedMember(List nameParts) { // There are no calculated members defined against a schema. return null; } public NamedSet getNamedSet(List nameParts) { if (nameParts.size() != 1) { return null; } final String name = nameParts.get(0).name; return schema.getNamedSet(name); } public Member getLeadMember(Member member, int n) { final MemberReader memberReader = getMemberReader(member.getHierarchy()); return memberReader.getLeadMember((RolapMember) member, n); } public List getLevelMembers(Level level, boolean includeCalculated) { List members = getLevelMembers(level, null); if (!includeCalculated) { members = SqlConstraintUtils.removeCalculatedMembers(members); } return members; } public List getLevelMembers(Level level, Evaluator context) { TupleConstraint constraint = sqlConstraintFactory.getLevelMembersConstraint( context, new Level[] {level}); final MemberReader memberReader = getMemberReader(level.getHierarchy()); List membersInLevel = memberReader.getMembersInLevel( (RolapLevel) level, 0, Integer.MAX_VALUE, constraint); return Util.cast(membersInLevel); } public List getCubeDimensions(Cube cube) { assert cube != null; final List dimensions = new ArrayList(); for (Dimension dimension : cube.getDimensions()) { switch (role.getAccess(dimension)) { case NONE: continue; default: dimensions.add(dimension); break; } } return dimensions; } public List getDimensionHierarchies(Dimension dimension) { assert dimension != null; final List hierarchies = new ArrayList(); for (Hierarchy hierarchy : dimension.getHierarchies()) { switch (role.getAccess(hierarchy)) { case NONE: continue; default: hierarchies.add(hierarchy); break; } } return hierarchies; } public List getHierarchyLevels(Hierarchy hierarchy) { assert hierarchy != null; final Role.HierarchyAccess hierarchyAccess = role.getAccessDetails(hierarchy); final Level[] levels = hierarchy.getLevels(); if (hierarchyAccess == null) { return Arrays.asList(levels); } Level topLevel = levels[hierarchyAccess.getTopLevelDepth()]; Level bottomLevel = levels[hierarchyAccess.getBottomLevelDepth()]; List restrictedLevels = Arrays.asList(levels).subList( topLevel.getDepth(), bottomLevel.getDepth() + 1); assert restrictedLevels.size() >= 1 : "postcondition"; return restrictedLevels; } public Member getHierarchyDefaultMember(Hierarchy hierarchy) { assert hierarchy != null; // If the whole hierarchy is inaccessible, return the intrinsic default // member. This is important to construct a evaluator. if (role.getAccess(hierarchy) == Access.NONE) { return hierarchy.getDefaultMember(); } return getMemberReader(hierarchy).getDefaultMember(); } public boolean isDrillable(Member member) { final RolapLevel level = (RolapLevel) member.getLevel(); if (level.getParentExp() != null) { // This is a parent-child level, so its children, if any, come from // the same level. // // todo: More efficient implementation return getMemberChildren(member).size() > 0; } else { // This is a regular level. It has children iff there is a lower // level. final Level childLevel = level.getChildLevel(); return (childLevel != null) && (role.getAccess(childLevel) != Access.NONE); } } public boolean isVisible(Member member) { return !member.isHidden() && role.canAccess(member); } public Cube[] getCubes() { List cubes = schema.getCubeList(); List visibleCubes = new ArrayList(cubes.size()); for (Cube cube : cubes) { if (role.canAccess(cube)) { visibleCubes.add(cube); } } return visibleCubes.toArray(new Cube[visibleCubes.size()]); } public List getCalculatedMembers(Hierarchy hierarchy) { return Collections.emptyList(); } public List getCalculatedMembers(Level level) { return Collections.emptyList(); } public List getCalculatedMembers() { return Collections.emptyList(); } public NativeEvaluator getNativeSetEvaluator( FunDef fun, Exp[] args, Evaluator evaluator, Calc calc) { RolapEvaluator revaluator = (RolapEvaluator) AbstractCalc.simplifyEvaluator(calc, evaluator); if (evaluator.nativeEnabled()) { return schema.getNativeRegistry().createEvaluator( revaluator, fun, args); } return null; } public Parameter getParameter(String name) { // Scan through schema parameters. for (RolapSchemaParameter parameter : schema.parameterList) { if (Util.equalName(parameter.getName(), name)) { return parameter; } } // Scan through mondrian and system properties. List propertyList = MondrianProperties.instance().getPropertyList(); for (Property property : propertyList) { if (property.getPath().equals(name)) { return new SystemPropertyParameter(name, false); } } if (System.getProperty(name) != null) { return new SystemPropertyParameter(name, true); } return null; } public DataSource getDataSource() { return schema.getInternalConnection().getDataSource(); } public RolapSchema getSchema() { return schema; } public SchemaReader withLocus() { return RolapUtil.locusSchemaReader( schema.getInternalConnection(), this); } /** * Implementation of {@link Parameter} which is sourced from system * propertes (see {@link System#getProperties()} or mondrian properties * (see {@link MondrianProperties}. *

*

The name of the property is the same as the key into the * {@link java.util.Properties} object; for example "java.version" or * "mondrian.trace.level". */ private static class SystemPropertyParameter extends ParameterImpl { /** * true if source is a system property; * false if source is a mondrian property. */ private final boolean system; /** * Definition of mondrian property, or null if system property. */ private final Property propertyDefinition; public SystemPropertyParameter(String name, boolean system) { super( name, Literal.nullValue, "System property '" + name + "'", new StringType()); this.system = system; this.propertyDefinition = system ? null : MondrianProperties.instance().getPropertyDefinition(name); } public Scope getScope() { return Scope.System; } public boolean isModifiable() { return false; } public Calc compile(ExpCompiler compiler) { return new GenericCalc(new DummyExp(getType())) { public Calc[] getCalcs() { return new Calc[0]; } public Object evaluate(Evaluator evaluator) { if (system) { final String name = SystemPropertyParameter.this.getName(); return System.getProperty(name); } else { return propertyDefinition.stringValue(); } } }; } } } // End RolapSchemaReader.java mondrian-3.4.1/src/main/mondrian/rolap/RolapMemberInCube.java0000644000175000017500000000220111735330606024016 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2010 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; /** * Extension to {@link RolapMember} that knows the current cube. * *

This is typical of members that occur in queries (where there is always a * current cube) as opposed to members that belong to caches. Members of shared * dimensions might occur in several different cubes, or even several times in * the same virtual cube. * * @author jhyde * @since 20 March, 2010 */ public interface RolapMemberInCube extends RolapMember { /** * Returns the cube this cube member belongs to. * *

This method is not in the {@link RolapMember} interface, because * regular members may be shared, and therefore do not belong to a specific * cube. * * @return Cube this cube member belongs to, never null */ RolapCube getCube(); } // End RolapMemberInCube.java mondrian-3.4.1/src/main/mondrian/rolap/SqlTupleReader.java0000644000175000017500000014301511735330606023430 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.TupleList; import mondrian.calc.impl.ListTupleList; import mondrian.calc.impl.UnaryTupleList; import mondrian.olap.*; import mondrian.olap.fun.FunUtil; import mondrian.resource.MondrianResource; import mondrian.rolap.agg.AggregationManager; import mondrian.rolap.agg.CellRequest; import mondrian.rolap.aggmatcher.AggStar; import mondrian.rolap.sql.*; import mondrian.server.Locus; import mondrian.server.monitor.SqlStatementEvent; import mondrian.spi.Dialect; import mondrian.util.Pair; import org.apache.log4j.Logger; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import javax.sql.DataSource; /** * Reads the members of a single level (level.members) or of multiple levels * (crossjoin). * *

Allows the result to be restricted by a {@link TupleConstraint}. So * the SqlTupleReader can also read Member.Descendants (which is level.members * restricted to a common parent) and member.children (which is a special case * of member.descendants). Other constraints, especially for the current slicer * or evaluation context, are possible. * *

Caching

* *

When a SqlTupleReader reads level.members, it groups the result into * parent/children pairs and puts them into the cache. In order that these can * be found later when the children of a parent are requested, a matching * constraint must be provided for every parent. * *

    * *
  • When reading members from a single level, then the constraint is not * required to join the fact table in * {@link TupleConstraint#addLevelConstraint(mondrian.rolap.sql.SqlQuery, RolapCube, mondrian.rolap.aggmatcher.AggStar, RolapLevel)} * although it may do so to restrict * the result. Also it is permitted to cache the parent/children from all * members in MemberCache, so * {@link TupleConstraint#getMemberChildrenConstraint(RolapMember)} * should not return null.
  • * *
  • When reading multiple levels (i.e. we are performing a crossjoin), * then we can not store the parent/child pairs in the MemberCache and * {@link TupleConstraint#getMemberChildrenConstraint(RolapMember)} * must return null. Also * {@link TupleConstraint#addConstraint(mondrian.rolap.sql.SqlQuery, mondrian.rolap.RolapCube, mondrian.rolap.aggmatcher.AggStar)} * is required to join the fact table for the levels table.
  • *
* * @author av * @since Nov 11, 2005 */ public class SqlTupleReader implements TupleReader { private static final Logger LOGGER = Logger.getLogger(SqlTupleReader.class); protected final TupleConstraint constraint; List targets = new ArrayList(); int maxRows = 0; /** * How many members could not be instantiated in this iteration. This * phenomenon occurs in a parent-child hierarchy, where a member cannot be * created before its parent. Populating the hierarchy will take multiple * passes and will terminate in success when missedMemberCount == 0 at the * end of a pass, or failure if a pass generates failures but does not * manage to load any more members. */ private int missedMemberCount; private static final String UNION = " union "; /** * Helper class for SqlTupleReader; * keeps track of target levels and constraints for adding to sql query. */ private class Target extends TargetBase { final MemberCache cache; RolapLevel[] levels; int levelDepth; boolean parentChild; List members; List> siblings; // if set, the rows for this target come from the array rather // than native sql // current member within the current result set row // for this target public Target( RolapLevel level, MemberBuilder memberBuilder, List srcMembers) { super(srcMembers, level, memberBuilder); this.cache = memberBuilder.getMemberCache(); } public void open() { levels = (RolapLevel[]) level.getHierarchy().getLevels(); setList(new ArrayList()); levelDepth = level.getDepth(); parentChild = level.isParentChild(); // members[i] is the current member of level#i, and siblings[i] // is the current member of level#i plus its siblings members = new ArrayList(); for (int i = 0; i < levels.length; i++) { members.add(null); } siblings = new ArrayList>(); for (int i = 0; i < levels.length + 1; i++) { siblings.add(new ArrayList()); } } int internalAddRow(SqlStatement stmt, int column) throws SQLException { RolapMember member = null; if (getCurrMember() != null) { setCurrMember(member); } else { boolean checkCacheStatus = true; for (int i = 0; i <= levelDepth; i++) { RolapLevel childLevel = levels[i]; if (childLevel.isAll()) { member = memberBuilder.allMember(); continue; } RolapMember parentMember = member; final List accessors = stmt.getAccessors(); if (parentChild) { Object parentValue = accessors.get(column++).get(); if (parentValue == null) { // member is at top of hierarchy; its parent is the // 'all' member. Convert null to placeholder value // for uniformity in hashmaps. parentValue = RolapUtil.sqlNullValue; } else if (parentValue.toString().equals( childLevel.getNullParentValue())) { // member is at top of hierarchy; its parent is the // 'all' member } else { Object parentKey = cache.makeKey( member, parentValue); parentMember = cache.getMember(parentKey); if (parentMember == null) { LOGGER.warn( MondrianResource.instance() .LevelTableParentNotFound.str( childLevel.getUniqueName(), String.valueOf(parentValue))); } } } Object value = accessors.get(column++).get(); if (value == null) { value = RolapUtil.sqlNullValue; } Object captionValue; if (childLevel.hasCaptionColumn()) { captionValue = accessors.get(column++).get(); } else { captionValue = null; } Object key; if (parentChild) { key = cache.makeKey(member, value); } else { key = cache.makeKey(parentMember, value); } member = cache.getMember(key, checkCacheStatus); checkCacheStatus = false; // only check the first time if (member == null) { if (constraint instanceof RolapNativeCrossJoin.NonEmptyCrossJoinConstraint && childLevel.isParentChild()) { member = castToNonEmptyCJConstraint(constraint) .findMember(value); } if (member == null) { member = memberBuilder.makeMember( parentMember, childLevel, value, captionValue, parentChild, stmt, key, column); } } // Skip over the columns consumed by makeMember if (!childLevel.getOrdinalExp().equals( childLevel.getKeyExp())) { ++column; } column += childLevel.getProperties().length; if (member != members.get(i)) { // Flush list we've been building. List children = siblings.get(i + 1); if (children != null) { MemberChildrenConstraint mcc = constraint.getMemberChildrenConstraint( members.get(i)); if (mcc != null) { cache.putChildren( members.get(i), mcc, children); } } // Start a new list, if the cache needs one. (We don't // synchronize, so it's possible that the cache will // have one by the time we complete it.) MemberChildrenConstraint mcc = constraint.getMemberChildrenConstraint(member); // we keep a reference to cachedChildren so they don't // get garbage-collected List cachedChildren = cache.getChildrenFromCache(member, mcc); if (i < levelDepth && cachedChildren == null) { siblings.set(i + 1, new ArrayList()); } else { // don't bother building up a list siblings.set(i + 1, null); } // Record new current member of this level. members.set(i, member); // If we're building a list of siblings at this level, // we haven't seen this one before, so add it. if (siblings.get(i) != null) { if (value == RolapUtil.sqlNullValue) { addAsOldestSibling(siblings.get(i), member); } else { siblings.get(i).add(member); } } } } setCurrMember(member); } getList().add(member); return column; } public List close() { synchronized (cacheLock) { return internalClose(); } } /** * Cleans up after all rows have been processed, and returns the list of * members. * * @return list of members */ public List internalClose() { for (int i = 0; i < members.size(); i++) { RolapMember member = members.get(i); final List children = siblings.get(i + 1); if (member != null && children != null) { // If we are finding the members of a particular level, and // we happen to find some of the children of an ancestor of // that level, we can't be sure that we have found all of // the children, so don't put them in the cache. if (member.getDepth() < level.getDepth()) { continue; } MemberChildrenConstraint mcc = constraint.getMemberChildrenConstraint(member); if (mcc != null) { cache.putChildren(member, mcc, children); } } } return Util.cast(getList()); } /** * Adds member just before the first element in * list which has the same parent. */ private void addAsOldestSibling( List list, RolapMember member) { int i = list.size(); while (--i >= 0) { RolapMember sibling = list.get(i); if (sibling.getParentMember() != member.getParentMember()) { break; } } list.add(i + 1, member); } } public SqlTupleReader(TupleConstraint constraint) { this.constraint = constraint; } public void addLevelMembers( RolapLevel level, MemberBuilder memberBuilder, List srcMembers) { targets.add(new Target(level, memberBuilder, srcMembers)); } public Object getCacheKey() { List key = new ArrayList(); key.add(constraint.getCacheKey()); key.add(SqlTupleReader.class); for (TargetBase target : targets) { // don't include the level in the key if the target isn't // processed through native sql if (target.srcMembers != null) { key.add(target.getLevel()); } } return key; } /** * @return number of targets that contain enumerated sets with calculated * members */ public int getEnumTargetCount() { int enumTargetCount = 0; for (TargetBase target : targets) { if (target.getSrcMembers() != null) { enumTargetCount++; } } return enumTargetCount; } protected void prepareTuples( DataSource dataSource, TupleList partialResult, List> newPartialResult) { String message = "Populating member cache with members for " + targets; SqlStatement stmt = null; final ResultSet resultSet; boolean execQuery = (partialResult == null); try { if (execQuery) { // we're only reading tuples from the targets that are // non-enum targets List partialTargets = new ArrayList(); for (TargetBase target : targets) { if (target.srcMembers == null) { partialTargets.add(target); } } final Pair> pair = makeLevelMembersSql(dataSource); String sql = pair.left; List types = pair.right; assert sql != null && !sql.equals(""); stmt = RolapUtil.executeQuery( dataSource, sql, types, maxRows, 0, new SqlStatement.StatementLocus( Locus.peek().execution, "SqlTupleReader.readTuples " + partialTargets, message, SqlStatementEvent.Purpose.TUPLES, 0), -1, -1); resultSet = stmt.getResultSet(); } else { resultSet = null; } for (TargetBase target : targets) { target.open(); } int limit = MondrianProperties.instance().ResultLimit.get(); int fetchCount = 0; // determine how many enum targets we have int enumTargetCount = getEnumTargetCount(); int[] srcMemberIdxes = null; if (enumTargetCount > 0) { srcMemberIdxes = new int[enumTargetCount]; } boolean moreRows; int currPartialResultIdx = 0; if (execQuery) { moreRows = resultSet.next(); if (moreRows) { ++stmt.rowCount; } } else { moreRows = currPartialResultIdx < partialResult.size(); } while (moreRows) { if (limit > 0 && limit < ++fetchCount) { // result limit exceeded, throw an exception throw MondrianResource.instance().MemberFetchLimitExceeded .ex((long) limit); } if (enumTargetCount == 0) { int column = 0; for (TargetBase target : targets) { target.setCurrMember(null); column = target.addRow(stmt, column); } } else { // find the first enum target, then call addTargets() // to form the cross product of the row from resultSet // with each of the list of members corresponding to // the enumerated targets int firstEnumTarget = 0; for (; firstEnumTarget < targets.size(); firstEnumTarget++) { if (targets.get(firstEnumTarget).srcMembers != null) { break; } } List partialRow; if (execQuery) { partialRow = null; } else { partialRow = Util.cast(partialResult.get(currPartialResultIdx)); } resetCurrMembers(partialRow); addTargets( 0, firstEnumTarget, enumTargetCount, srcMemberIdxes, stmt, message); if (newPartialResult != null) { savePartialResult(newPartialResult); } } if (execQuery) { moreRows = resultSet.next(); if (moreRows) { ++stmt.rowCount; } } else { currPartialResultIdx++; moreRows = currPartialResultIdx < partialResult.size(); } } } catch (SQLException e) { if (stmt == null) { throw Util.newError(e, message); } else { throw stmt.handle(e); } } finally { if (stmt != null) { stmt.close(); } } } public TupleList readMembers( DataSource dataSource, TupleList partialResult, List> newPartialResult) { int memberCount = countMembers(); while (true) { missedMemberCount = 0; int memberCountBefore = memberCount; prepareTuples(dataSource, partialResult, newPartialResult); memberCount = countMembers(); if (missedMemberCount == 0) { // We have successfully read all members. This is always the // case in a regular hierarchy. In a parent-child hierarchy // it may take several passes, because we cannot create a member // before we create its parent. break; } if (memberCount == memberCountBefore) { // This pass made no progress. This must be because of a cycle. throw Util.newError( "Parent-child hierarchy contains cyclic data"); } } assert targets.size() == 1; return new UnaryTupleList(targets.get(0).close()); } /** * Returns the number of members that have been read from all targets. * * @return Number of members that have been read from all targets */ private int countMembers() { int n = 0; for (TargetBase target : targets) { if (target.getList() != null) { n += target.getList().size(); } } return n; } public TupleList readTuples( DataSource jdbcConnection, TupleList partialResult, List> newPartialResult) { prepareTuples(jdbcConnection, partialResult, newPartialResult); // List of tuples final int n = targets.size(); @SuppressWarnings({"unchecked"}) final Iterator[] iter = new Iterator[n]; for (int i = 0; i < n; i++) { TargetBase t = targets.get(i); iter[i] = t.close().iterator(); } List members = new ArrayList(); while (iter[0].hasNext()) { for (int i = 0; i < n; i++) { members.add(iter[i].next()); } } TupleList tupleList = n == 1 ? new UnaryTupleList(members) : new ListTupleList(n, members); // need to hierarchize the columns from the enumerated targets // since we didn't necessarily add them in the order in which // they originally appeared in the cross product int enumTargetCount = getEnumTargetCount(); if (enumTargetCount > 0) { tupleList = FunUtil.hierarchizeTupleList(tupleList, false); } return tupleList; } /** * Sets the current member for those targets that retrieve their column * values from native sql * * @param partialRow if set, previously cached result set */ private void resetCurrMembers(List partialRow) { int nativeTarget = 0; for (TargetBase target : targets) { if (target.srcMembers == null) { // if we have a previously cached row, use that by picking // out the column corresponding to this target; otherwise, // we need to retrieve a new column value from the current // result set if (partialRow != null) { target.setCurrMember(partialRow.get(nativeTarget++)); } else { target.setCurrMember(null); } } } } /** * Recursively forms the cross product of a row retrieved through sql * with each of the targets that contains an enumerated set of members. * * @param currEnumTargetIdx current enum target that recursion * is being applied on * @param currTargetIdx index within the list of a targets that * currEnumTargetIdx corresponds to * @param nEnumTargets number of targets that have enumerated members * @param srcMemberIdxes for each enumerated target, the current member * to be retrieved to form the current cross product row * @param stmt Statement containing the result set corresponding to rows * retrieved through native SQL * @param message Message to issue on failure */ private void addTargets( int currEnumTargetIdx, int currTargetIdx, int nEnumTargets, int[] srcMemberIdxes, SqlStatement stmt, String message) { // loop through the list of members for the current enum target TargetBase currTarget = targets.get(currTargetIdx); for (int i = 0; i < currTarget.srcMembers.size(); i++) { srcMemberIdxes[currEnumTargetIdx] = i; // if we're not on the last enum target, recursively move // to the next one if (currEnumTargetIdx < nEnumTargets - 1) { int nextTargetIdx = currTargetIdx + 1; for (; nextTargetIdx < targets.size(); nextTargetIdx++) { if (targets.get(nextTargetIdx).srcMembers != null) { break; } } addTargets( currEnumTargetIdx + 1, nextTargetIdx, nEnumTargets, srcMemberIdxes, stmt, message); } else { // form a cross product using the columns from the current // result set row and the current members that recursion // has reached for the enum targets int column = 0; int enumTargetIdx = 0; for (TargetBase target : targets) { if (target.srcMembers == null) { try { column = target.addRow(stmt, column); } catch (Throwable e) { throw Util.newError(e, message); } } else { RolapMember member = target.srcMembers.get( srcMemberIdxes[enumTargetIdx++]); target.getList().add(member); } } } } } /** * Retrieves the current members fetched from the targets executed * through sql and form tuples, adding them to partialResult * * @param partialResult list containing the columns and rows corresponding * to data fetched through sql */ private void savePartialResult(List> partialResult) { List row = new ArrayList(); for (TargetBase target : targets) { if (target.srcMembers == null) { row.add(target.getCurrMember()); } } partialResult.add(row); } Pair> makeLevelMembersSql( DataSource dataSource) { // In the case of a virtual cube, if we need to join to the fact // table, we do not necessarily have a single underlying fact table, // as the underlying base cubes in the virtual cube may all reference // different fact tables. // // Therefore, we need to gather the underlying fact tables by going // through the list of measures referenced in the query. And then // we generate one sub-select per fact table, joining against each // underlying fact table, unioning the sub-selects. RolapCube cube = null; boolean virtualCube = false; if (constraint instanceof SqlContextConstraint) { SqlContextConstraint sqlConstraint = (SqlContextConstraint) constraint; Query query = constraint.getEvaluator().getQuery(); cube = (RolapCube) query.getCube(); if (sqlConstraint.isJoinRequired()) { virtualCube = cube.isVirtual(); } } if (virtualCube) { Query query = constraint.getEvaluator().getQuery(); // Make fact table appear in fixed sequence final Collection baseCubes = getBaseCubeCollection(query); Collection fullyJoiningBaseCubes = getFullyJoiningBaseCubes(baseCubes); if (fullyJoiningBaseCubes.size() == 0) { return sqlForEmptyTuple(dataSource, baseCubes); } // generate sub-selects, each one joining with one of // the fact table referenced int k = -1; // Save the original measure in the context Member originalMeasure = constraint.getEvaluator().getMembers()[0]; String prependString = ""; final StringBuilder selectString = new StringBuilder(); List types = null; for (RolapCube baseCube : fullyJoiningBaseCubes) { // Use the measure from the corresponding base cube in the // context to find the correct join path to the base fact // table. // // Any measure is fine since the constraint logic only uses it // to find the correct fact table to join to. Member measureInCurrentbaseCube = baseCube.getMeasures().get(0); constraint.getEvaluator().setContext(measureInCurrentbaseCube); WhichSelect whichSelect = (++k == fullyJoiningBaseCubes.size() - 1) ? WhichSelect.LAST : WhichSelect.NOT_LAST; selectString.append(prependString); final Pair> pair = generateSelectForLevels( dataSource, baseCube, whichSelect); selectString.append(pair.left); types = pair.right; prependString = UNION; } // Restore the original measure member constraint.getEvaluator().setContext(originalMeasure); return Pair.of(selectString.toString(), types); } else { return generateSelectForLevels( dataSource, cube, WhichSelect.ONLY); } } private Collection getFullyJoiningBaseCubes( Collection baseCubes) { final Collection fullyJoiningCubes = new ArrayList(); for (RolapCube baseCube : baseCubes) { boolean allTargetsJoin = true; for (TargetBase target : targets) { if (!targetIsOnBaseCube(target, baseCube)) { allTargetsJoin = false; } } if (allTargetsJoin) { fullyJoiningCubes.add(baseCube); } } return fullyJoiningCubes; } Collection getBaseCubeCollection(final Query query) { RolapCube.CubeComparator cubeComparator = new RolapCube.CubeComparator(); Collection baseCubes = new TreeSet(cubeComparator); baseCubes.addAll(query.getBaseCubes()); return baseCubes; } Pair> sqlForEmptyTuple( DataSource dataSource, final Collection baseCubes) { final SqlQuery sqlQuery = SqlQuery.newQuery(dataSource, null); sqlQuery.addSelect("0", null); sqlQuery.addFrom(baseCubes.iterator().next().getFact(), null, true); sqlQuery.addWhere("1 = 0"); return sqlQuery.toSqlAndTypes(); } /** * Generates the SQL string corresponding to the levels referenced. * * @param dataSource jdbc connection that they query will execute against * @param baseCube this is the cube object for regular cubes, and the * underlying base cube for virtual cubes * @param whichSelect Position of this select statement in a union * @return SQL statement string and types */ Pair> generateSelectForLevels( DataSource dataSource, RolapCube baseCube, WhichSelect whichSelect) { String s = "while generating query to retrieve members of level(s) " + targets; // Allow query to use optimization hints from the table definition SqlQuery sqlQuery = SqlQuery.newQuery(dataSource, s); sqlQuery.setAllowHints(true); Evaluator evaluator = getEvaluator(constraint); AggStar aggStar = chooseAggStar(constraint, evaluator); // add the selects for all levels to fetch for (TargetBase target : targets) { // if we're going to be enumerating the values for this target, // then we don't need to generate sql for it if (target.getSrcMembers() == null) { addLevelMemberSql( sqlQuery, target.getLevel(), baseCube, whichSelect, aggStar); } } constraint.addConstraint(sqlQuery, baseCube, aggStar); return sqlQuery.toSqlAndTypes(); } boolean targetIsOnBaseCube(TargetBase target, RolapCube baseCube) { return baseCube == null || baseCube.findBaseCubeHierarchy( target.getLevel().getHierarchy()) != null; } /** *

Determines whether the GROUP BY clause is required, based on the * schema definitions of the hierarchy and level properties.

* *

The GROUP BY clause may only be eliminated if the level identified by * the uniqueKeyLevelName exists, the query is at a depth to include it, * and all properties in the included levels are functionally dependent on * their level values.

* * * @param sqlQuery The query object being constructed * @param hierarchy Hierarchy of the cube * @param levels Levels in this hierarchy * @param levelDepth Level depth at which the query is occuring * @return whether the GROUP BY is needed * */ private boolean isGroupByNeeded( SqlQuery sqlQuery, RolapHierarchy hierarchy, RolapLevel[] levels, int levelDepth) { /* Figure out if we need to generate GROUP BY at all. It may only be * eliminated if we are at a depth that includes the unique key level, * and all properties of included levels depend on the level value. */ boolean needsGroupBy = false; // figure out if we need GROUP BY at all if (hierarchy.getUniqueKeyLevelName() == null) { needsGroupBy = true; } else { boolean foundUniqueKeyLevelName = false; for (int i = 0; i <= levelDepth; i++) { RolapLevel lvl = levels[i]; // can ignore the "all" level if (!(lvl.isAll())) { if (hierarchy.getUniqueKeyLevelName().equals( lvl.getName())) { foundUniqueKeyLevelName = true; } for (RolapProperty p : lvl.getProperties()) { if (!p.dependsOnLevelValue()) { needsGroupBy = true; // GROUP BY is required, so break out of // properties loop break; } } if (needsGroupBy) { // GROUP BY is required, so break out of levels loop break; } } } if (!foundUniqueKeyLevelName) { // if we're not deep enough to be unique, // then the GROUP BY is required needsGroupBy = true; } } return needsGroupBy; } /** * Generates the SQL statement to access members of level. For * example,
*
SELECT "country", "state_province", "city"
     * FROM "customer"
     * GROUP BY "country", "state_province", "city", "init", "bar"
     * ORDER BY "country", "state_province", "city"
*
accesses the "City" level of the "Customers" * hierarchy. Note that:
    * *
  • "country", "state_province" are the parent keys;
  • * *
  • "city" is the level key;
  • * *
  • "init", "bar" are member properties.
  • *
* * @param sqlQuery the query object being constructed * @param level level to be added to the sql query * @param baseCube this is the cube object for regular cubes, and the * underlying base cube for virtual cubes * @param whichSelect describes whether this select belongs to a larger * @param aggStar aggregate star if available */ protected void addLevelMemberSql( SqlQuery sqlQuery, RolapLevel level, RolapCube baseCube, WhichSelect whichSelect, AggStar aggStar) { RolapHierarchy hierarchy = level.getHierarchy(); // lookup RolapHierarchy of base cube that matches this hierarchy if (hierarchy instanceof RolapCubeHierarchy) { RolapCubeHierarchy cubeHierarchy = (RolapCubeHierarchy)hierarchy; if (baseCube != null && !cubeHierarchy.getCube().equals(baseCube)) { // replace the hierarchy with the underlying base cube hierarchy // in the case of virtual cubes hierarchy = baseCube.findBaseCubeHierarchy(hierarchy); } } RolapLevel[] levels = (RolapLevel[]) hierarchy.getLevels(); int levelDepth = level.getDepth(); boolean needsGroupBy = isGroupByNeeded(sqlQuery, hierarchy, levels, levelDepth); for (int i = 0; i <= levelDepth; i++) { RolapLevel currLevel = levels[i]; if (currLevel.isAll()) { continue; } // Determine if the aggregate table contains the collapsed level boolean levelCollapsed = (aggStar != null) && SqlMemberSource.isLevelCollapsed( aggStar, (RolapCubeLevel)currLevel); boolean multipleCols = SqlMemberSource.levelContainsMultipleColumns(currLevel); if (levelCollapsed && !multipleCols) { // if this is a single column collapsed level, there is // no need to join it with dimension tables RolapStar.Column starColumn = ((RolapCubeLevel) currLevel).getStarKeyColumn(); int bitPos = starColumn.getBitPosition(); AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); String q = aggColumn.generateExprString(sqlQuery); sqlQuery.addSelectGroupBy(q, starColumn.getInternalType()); sqlQuery.addOrderBy(q, true, false, true); aggColumn.getTable().addToFrom(sqlQuery, false, true); continue; } MondrianDef.Expression keyExp = currLevel.getKeyExp(); MondrianDef.Expression ordinalExp = currLevel.getOrdinalExp(); MondrianDef.Expression captionExp = currLevel.getCaptionExp(); MondrianDef.Expression parentExp = currLevel.getParentExp(); if (parentExp != null) { if (!levelCollapsed) { hierarchy.addToFrom(sqlQuery, parentExp); } String parentSql = parentExp.getExpression(sqlQuery); sqlQuery.addSelectGroupBy( parentSql, currLevel.getInternalType()); if (whichSelect == WhichSelect.LAST || whichSelect == WhichSelect.ONLY) { sqlQuery.addOrderBy(parentSql, true, false, true, false); } } String keySql = keyExp.getExpression(sqlQuery); String ordinalSql = ordinalExp.getExpression(sqlQuery); if (!levelCollapsed) { hierarchy.addToFrom(sqlQuery, keyExp); hierarchy.addToFrom(sqlQuery, ordinalExp); } String captionSql = null; if (captionExp != null) { captionSql = captionExp.getExpression(sqlQuery); if (!levelCollapsed) { hierarchy.addToFrom(sqlQuery, captionExp); } } String alias = sqlQuery.addSelect(keySql, currLevel.getInternalType()); if (needsGroupBy) { sqlQuery.addGroupBy(keySql, alias); } if (captionSql != null) { alias = sqlQuery.addSelect(captionSql, null); if (needsGroupBy) { sqlQuery.addGroupBy(captionSql, alias); } } if (!ordinalSql.equals(keySql)) { alias = sqlQuery.addSelect(ordinalSql, null); if (needsGroupBy) { sqlQuery.addGroupBy(ordinalSql, alias); } } constraint.addLevelConstraint( sqlQuery, baseCube, aggStar, currLevel); if (levelCollapsed) { // add join between key and aggstar // join to dimension tables starting // at the lowest granularity and working // towards the fact table hierarchy.addToFromInverse(sqlQuery, keyExp); RolapStar.Column starColumn = ((RolapCubeLevel) currLevel).getStarKeyColumn(); int bitPos = starColumn.getBitPosition(); AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); RolapStar.Condition condition = new RolapStar.Condition(keyExp, aggColumn.getExpression()); sqlQuery.addWhere(condition.toString(sqlQuery)); } // If this is a select on a virtual cube, the query will be // a union, so the order by columns need to be numbers, // not column name strings or expressions. switch (whichSelect) { case LAST: boolean nullable = true; final Dialect dialect = sqlQuery.getDialect(); if (dialect.requiresUnionOrderByExprToBeInSelectClause() || dialect.requiresUnionOrderByOrdinal()) { // If the expression is nullable and the dialect // sorts NULL values first, the dialect will try to // add an expression 'Iif(expr IS NULL, 1, 0)' into // the ORDER BY clause, and that is not allowed by this // dialect. So, pretend that the expression is not // nullable. NULL values, if present, will be sorted // wrong, but that's better than generating an invalid // query. nullable = false; } sqlQuery.addOrderBy( Integer.toString( sqlQuery.getCurrentSelectListSize()), true, false, nullable); break; case ONLY: sqlQuery.addOrderBy(ordinalSql, true, false, true); break; } RolapProperty[] properties = currLevel.getProperties(); for (RolapProperty property : properties) { final MondrianDef.Expression propExp = property.getExp(); final String propSql; if (propExp instanceof MondrianDef.Column) { // When dealing with a column, we must use the same table // alias as the one used by the level. We also assume that // the property lives in the same table as the level. propSql = sqlQuery.getDialect().quoteIdentifier( currLevel.getTableAlias(), ((MondrianDef.Column)propExp).name); } else { propSql = property.getExp().getExpression(sqlQuery); } alias = sqlQuery.addSelect(propSql, null); if (needsGroupBy) { // Certain dialects allow us to eliminate properties // from the group by that are functionally dependent // on the level value if (!sqlQuery.getDialect().allowsSelectNotInGroupBy() || !property.dependsOnLevelValue()) { sqlQuery.addGroupBy(propSql, alias); } } } } } /** * Obtains the evaluator used to find an aggregate table to support * the Tuple constraint. * * @param constraint Constraint * @return evaluator for constraint */ protected Evaluator getEvaluator(TupleConstraint constraint) { if (constraint instanceof SqlContextConstraint) { return constraint.getEvaluator(); } if (constraint instanceof DescendantsConstraint) { DescendantsConstraint descConstraint = (DescendantsConstraint) constraint; MemberChildrenConstraint mcc = descConstraint.getMemberChildrenConstraint(null); if (mcc instanceof SqlContextConstraint) { SqlContextConstraint scc = (SqlContextConstraint) mcc; return scc.getEvaluator(); } } return null; } /** * Obtains the AggStar instance which corresponds to an aggregate table * which can be used to support the member constraint. * * @param constraint * @param evaluator the current evaluator to obtain the cube and members to * be queried @return AggStar for aggregate table */ AggStar chooseAggStar(TupleConstraint constraint, Evaluator evaluator) { if (!MondrianProperties.instance().UseAggregates.get()) { return null; } if (evaluator == null) { return null; } // Current cannot support aggregate tables for virtual cubes RolapCube cube = (RolapCube) evaluator.getCube(); if (cube.isVirtual()) { return null; } RolapStar star = cube.getStar(); final int starColumnCount = star.getColumnCount(); BitKey measureBitKey = BitKey.Factory.makeBitKey(starColumnCount); BitKey levelBitKey = BitKey.Factory.makeBitKey(starColumnCount); // Convert global ordinal to cube based ordinal (the 0th dimension // is always [Measures]). In the case of filter constraint this will // be the measure on which the filter will be done. final Member[] members = evaluator.getNonAllMembers(); // if measure is calculated, we can't continue if (!(members[0] instanceof RolapBaseCubeMeasure)) { return null; } RolapBaseCubeMeasure measure = (RolapBaseCubeMeasure)members[0]; int bitPosition = ((RolapStar.Measure) measure.getStarMeasure()).getBitPosition(); // set a bit for each level which is constrained in the context final CellRequest request = RolapAggregationManager.makeRequest(members); if (request == null) { // One or more calculated members. Cannot use agg table. return null; } // TODO: RME why is this using the array of constrained columns // from the CellRequest rather than just the constrained columns // BitKey (method getConstrainedColumnsBitKey)? RolapStar.Column[] columns = request.getConstrainedColumns(); for (RolapStar.Column column1 : columns) { levelBitKey.set(column1.getBitPosition()); } // set the masks for (TargetBase target : targets) { RolapLevel level = target.level; if (!level.isAll()) { RolapStar.Column column = ((RolapCubeLevel)level).getStarKeyColumn(); if (column != null) { levelBitKey.set(column.getBitPosition()); } } } measureBitKey.set(bitPosition); if (constraint instanceof RolapNativeCrossJoin.NonEmptyCrossJoinConstraint) { // Cannot evaluate NonEmptyCrossJoinConstraint using an agg // table if one of its args is a DescendantsConstraint. RolapNativeCrossJoin.NonEmptyCrossJoinConstraint necj = (RolapNativeCrossJoin.NonEmptyCrossJoinConstraint) constraint; for (CrossJoinArg arg : necj.args) { if (arg instanceof DescendantsCrossJoinArg || arg instanceof MemberListCrossJoinArg) { final RolapLevel level = arg.getLevel(); if (level != null && !level.isAll()) { RolapStar.Column column = ((RolapCubeLevel)level).getStarKeyColumn(); levelBitKey.set(column.getBitPosition()); } } } } // find the aggstar using the masks final AggregationManager aggMgr = cube.getSchema().getInternalConnection().getServer() .getAggregationManager(); return aggMgr.findAgg( star, levelBitKey, measureBitKey, new boolean[] {false}); } int getMaxRows() { return maxRows; } void setMaxRows(int maxRows) { this.maxRows = maxRows; } /** * Description of the position of a SELECT statement in a UNION. Queries * on virtual cubes tend to generate unions. */ enum WhichSelect { /** * Select statement does not belong to a union. */ ONLY, /** * Select statement belongs to a UNION, but is not the last. Typically * this occurs when querying a virtual cube. */ NOT_LAST, /** * Select statement is the last in a UNION. Typically * this occurs when querying a virtual cube. */ LAST } } // End SqlTupleReader.java mondrian-3.4.1/src/main/mondrian/rolap/RolapConnection.java0000644000175000017500000012713611735330606023637 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.*; import mondrian.calc.impl.DelegatingTupleList; import mondrian.olap.*; import mondrian.parser.MdxParserValidator; import mondrian.resource.MondrianResource; import mondrian.server.*; import mondrian.spi.*; import mondrian.spi.impl.JndiDataSourceResolver; import mondrian.util.*; import org.apache.log4j.Logger; import org.eigenbase.util.property.StringProperty; import org.olap4j.Scenario; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.SQLException; import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicInteger; import javax.sql.DataSource; /** * A RolapConnection is a connection to a Mondrian OLAP Server. * *

Typically, you create a connection via * {@link DriverManager#getConnection(String, mondrian.spi.CatalogLocator)}. * {@link RolapConnectionProperties} describes allowable keywords.

* * @see RolapSchema * @see DriverManager * @author jhyde * @since 2 October, 2002 */ public class RolapConnection extends ConnectionBase { private static final Logger LOGGER = Logger.getLogger(RolapConnection.class); private static final AtomicInteger ID_GENERATOR = new AtomicInteger(); private final MondrianServer server; private final Util.PropertyList connectInfo; /** * Factory for JDBC connections to talk to the RDBMS. This factory will * usually use a connection pool. */ private final DataSource dataSource; private final String catalogUrl; private final RolapSchema schema; private SchemaReader schemaReader; protected Role role; private Locale locale = Locale.getDefault(); private Scenario scenario; private boolean closed = false; private static DataSourceResolver dataSourceResolver; private final int id; private final Statement internalStatement; /** * Creates a connection. * * @param server Server instance this connection belongs to * @param connectInfo Connection properties; keywords are described in * {@link RolapConnectionProperties}. * @param dataSource JDBC data source */ public RolapConnection( MondrianServer server, Util.PropertyList connectInfo, DataSource dataSource) { this(server, connectInfo, null, dataSource); } /** * Creates a RolapConnection. * *

Only {@link mondrian.rolap.RolapSchema.Pool#get} calls this with * schema != null (to create a schema's internal connection). * Other uses retrieve a schema from the cache based upon * the Catalog property. * * @param server Server instance this connection belongs to * @param connectInfo Connection properties; keywords are described in * {@link RolapConnectionProperties}. * @param schema Schema for the connection. Must be null unless this is to * be an internal connection. * @param dataSource If not null an external DataSource to be used * by Mondrian */ RolapConnection( MondrianServer server, Util.PropertyList connectInfo, RolapSchema schema, DataSource dataSource) { super(); assert server != null; this.server = server; this.id = ID_GENERATOR.getAndIncrement(); assert connectInfo != null; String provider = connectInfo.get( RolapConnectionProperties.Provider.name(), "mondrian"); Util.assertTrue(provider.equalsIgnoreCase("mondrian")); this.connectInfo = connectInfo; this.catalogUrl = connectInfo.get(RolapConnectionProperties.Catalog.name()); final String jdbcUser = connectInfo.get(RolapConnectionProperties.JdbcUser.name()); final String jdbcConnectString = connectInfo.get(RolapConnectionProperties.Jdbc.name()); final String strDataSource = connectInfo.get(RolapConnectionProperties.DataSource.name()); StringBuilder buf = new StringBuilder(); this.dataSource = createDataSource(dataSource, connectInfo, buf); Role role = null; // Register this connection before we register its internal statement. server.addConnection(this); if (schema == null) { // If RolapSchema.Pool.get were to call this with schema == null, // we would loop. Statement bootstrapStatement = createInternalStatement(false); final Locus locus = new Locus( new Execution(bootstrapStatement, 0), null, "Initializing connection"); Locus.push(locus); try { if (dataSource == null) { // If there is no external data source is passed in, we // expect the properties Jdbc, JdbcUser, DataSource to be // set, as they are used to generate the schema cache key. final String connectionKey = jdbcConnectString + getJdbcProperties(connectInfo).toString(); schema = RolapSchema.Pool.instance().get( catalogUrl, connectionKey, jdbcUser, strDataSource, connectInfo); } else { schema = RolapSchema.Pool.instance().get( catalogUrl, dataSource, connectInfo); } } finally { Locus.pop(locus); bootstrapStatement.close(); } internalStatement = schema.getInternalConnection().getInternalStatement(); String roleNameList = connectInfo.get(RolapConnectionProperties.Role.name()); if (roleNameList != null) { List roleNames = Util.parseCommaList(roleNameList); List roleList = new ArrayList(); for (String roleName : roleNames) { final LockBox.Entry entry = server.getLockBox().get(roleName); Role role1; if (entry != null) { try { role1 = (Role) entry.getValue(); } catch (ClassCastException e) { role1 = null; } } else { role1 = schema.lookupRole(roleName); } if (role1 == null) { throw Util.newError( "Role '" + roleName + "' not found"); } roleList.add(role1); } switch (roleList.size()) { case 0: // If they specify 'Role=;', the list of names will be // empty, and the effect will be as if they did specify // Role at all. role = null; break; case 1: role = roleList.get(0); break; default: role = RoleImpl.union(roleList); break; } } } else { this.internalStatement = createInternalStatement(true); // We are creating an internal connection. Now is a great time to // make sure that the JDBC credentials are valid, for this // connection and for external connections built on top of this. Connection conn = null; java.sql.Statement statement = null; try { conn = this.dataSource.getConnection(); Dialect dialect = DialectManager.createDialect(this.dataSource, conn); if (dialect.getDatabaseProduct() == Dialect.DatabaseProduct.DERBY) { // Derby requires a little extra prodding to do the // validation to detect an error. statement = conn.createStatement(); statement.executeQuery("select * from bogustable"); } } catch (SQLException e) { if (e.getMessage().equals( "Table/View 'BOGUSTABLE' does not exist.")) { // Ignore. This exception comes from Derby when the // connection is valid. If the connection were invalid, we // would receive an error such as "Schema 'BOGUSUSER' does // not exist" } else { throw Util.newError( e, "Error while creating SQL connection: " + buf); } } finally { try { if (statement != null) { statement.close(); } if (conn != null) { conn.close(); } } catch (SQLException e) { // ignore } } } if (role == null) { role = schema.getDefaultRole(); } // Set the locale. String localeString = connectInfo.get(RolapConnectionProperties.Locale.name()); if (localeString != null) { this.locale = Util.parseLocale(localeString); assert locale != null; } this.schema = schema; setRole(role); } @Override protected void finalize() throws Throwable { try { super.finalize(); close(); } catch (Throwable t) { LOGGER.info( MondrianResource.instance() .FinalizerErrorRolapConnection.baseMessage, t); } } /** * Returns the identifier of this connection. Unique within the lifetime of * this JVM. * * @return Identifier of this connection */ public int getId() { return id; } protected Logger getLogger() { return LOGGER; } /** * Creates a JDBC data source from the JDBC credentials contained within a * set of mondrian connection properties. * *

This method is package-level so that it can be called from the * RolapConnectionTest unit test. * * @param dataSource Anonymous data source from user, or null * @param connectInfo Mondrian connection properties * @param buf Into which method writes a description of the JDBC credentials * @return Data source */ static DataSource createDataSource( DataSource dataSource, Util.PropertyList connectInfo, StringBuilder buf) { assert buf != null; final String jdbcConnectString = connectInfo.get(RolapConnectionProperties.Jdbc.name()); final String jdbcUser = connectInfo.get(RolapConnectionProperties.JdbcUser.name()); final String jdbcPassword = connectInfo.get(RolapConnectionProperties.JdbcPassword.name()); final String dataSourceName = connectInfo.get(RolapConnectionProperties.DataSource.name()); if (dataSource != null) { appendKeyValue(buf, "Anonymous data source", dataSource); appendKeyValue( buf, RolapConnectionProperties.JdbcUser.name(), jdbcUser); appendKeyValue( buf, RolapConnectionProperties.JdbcPassword.name(), jdbcPassword); if (jdbcUser != null || jdbcPassword != null) { dataSource = new UserPasswordDataSource( dataSource, jdbcUser, jdbcPassword); } return dataSource; } else if (jdbcConnectString != null) { // Get connection through own pooling datasource appendKeyValue( buf, RolapConnectionProperties.Jdbc.name(), jdbcConnectString); appendKeyValue( buf, RolapConnectionProperties.JdbcUser.name(), jdbcUser); appendKeyValue( buf, RolapConnectionProperties.JdbcPassword.name(), jdbcPassword); String jdbcDrivers = connectInfo.get(RolapConnectionProperties.JdbcDrivers.name()); if (jdbcDrivers != null) { RolapUtil.loadDrivers(jdbcDrivers); } final String jdbcDriversProp = MondrianProperties.instance().JdbcDrivers.get(); RolapUtil.loadDrivers(jdbcDriversProp); Properties jdbcProperties = getJdbcProperties(connectInfo); final Map map = Util.toMap(jdbcProperties); for (Map.Entry entry : map.entrySet()) { // FIXME ordering is non-deterministic appendKeyValue(buf, entry.getKey(), entry.getValue()); } if (jdbcUser != null) { jdbcProperties.put("user", jdbcUser); } if (jdbcPassword != null) { jdbcProperties.put("password", jdbcPassword); } // JDBC connections are dumb beasts, so we assume they're not // pooled. Therefore the default is true. final boolean poolNeeded = connectInfo.get( RolapConnectionProperties.PoolNeeded.name(), "true").equalsIgnoreCase("true"); if (!poolNeeded) { // Connection is already pooled; don't pool it again. return new DriverManagerDataSource( jdbcConnectString, jdbcProperties); } if (jdbcConnectString.toLowerCase().indexOf("mysql") > -1) { // mysql driver needs this autoReconnect parameter jdbcProperties.setProperty("autoReconnect", "true"); } return RolapConnectionPool.instance() .getDriverManagerPoolingDataSource( jdbcConnectString, jdbcProperties); } else if (dataSourceName != null) { appendKeyValue( buf, RolapConnectionProperties.DataSource.name(), dataSourceName); appendKeyValue( buf, RolapConnectionProperties.JdbcUser.name(), jdbcUser); appendKeyValue( buf, RolapConnectionProperties.JdbcPassword.name(), jdbcPassword); // Data sources are fairly smart, so we assume they look after // their own pooling. Therefore the default is false. final boolean poolNeeded = connectInfo.get( RolapConnectionProperties.PoolNeeded.name(), "false").equalsIgnoreCase("true"); // Get connection from datasource. DataSourceResolver dataSourceResolver = getDataSourceResolver(); try { dataSource = dataSourceResolver.lookup(dataSourceName); } catch (Exception e) { throw Util.newInternal( e, "Error while looking up data source (" + dataSourceName + ")"); } if (poolNeeded) { dataSource = RolapConnectionPool.instance() .getDataSourcePoolingDataSource( dataSource, dataSourceName, jdbcUser, jdbcPassword); } else { if (jdbcUser != null || jdbcPassword != null) { dataSource = new UserPasswordDataSource( dataSource, jdbcUser, jdbcPassword); } } return dataSource; } else { throw Util.newInternal( "Connect string '" + connectInfo.toString() + "' must contain either '" + RolapConnectionProperties.Jdbc + "' or '" + RolapConnectionProperties.DataSource + "'"); } } /** * Returns the instance of the {@link mondrian.spi.DataSourceResolver} * plugin. * * @return data source resolver */ private static synchronized DataSourceResolver getDataSourceResolver() { if (dataSourceResolver == null) { final StringProperty property = MondrianProperties.instance().DataSourceResolverClass; final String className = property.get( JndiDataSourceResolver.class.getName()); try { final Class clazz; clazz = Class.forName(className); if (!DataSourceResolver.class.isAssignableFrom(clazz)) { throw Util.newInternal( "Plugin class specified by property " + property.getPath() + " must implement " + DataSourceResolver.class.getName()); } dataSourceResolver = (DataSourceResolver) clazz.newInstance(); } catch (ClassNotFoundException e) { throw Util.newInternal( e, "Error while loading plugin class '" + className + "'"); } catch (IllegalAccessException e) { throw Util.newInternal( e, "Error while loading plugin class '" + className + "'"); } catch (InstantiationException e) { throw Util.newInternal( e, "Error while loading plugin class '" + className + "'"); } } return dataSourceResolver; } /** * Appends "key=value" to a buffer, if value is not null. * * @param buf Buffer * @param key Key * @param value Value */ private static void appendKeyValue( StringBuilder buf, String key, Object value) { if (value != null) { if (buf.length() > 0) { buf.append("; "); } buf.append(key).append('=').append(value); } } /** * Creates a {@link Properties} object containing all of the JDBC * connection properties present in the * {@link mondrian.olap.Util.PropertyList connectInfo}. * * @param connectInfo Connection properties * @return The JDBC connection properties. */ private static Properties getJdbcProperties(Util.PropertyList connectInfo) { Properties jdbcProperties = new Properties(); for (Pair entry : connectInfo) { if (entry.left.startsWith( RolapConnectionProperties.JdbcPropertyPrefix)) { jdbcProperties.put( entry.left.substring( RolapConnectionProperties.JdbcPropertyPrefix.length()), entry.right); } } return jdbcProperties; } public Util.PropertyList getConnectInfo() { return connectInfo; } public void close() { if (!closed) { closed = true; server.removeConnection(this); } } public RolapSchema getSchema() { return schema; } public String getConnectString() { return connectInfo.toString(); } public String getCatalogName() { return catalogUrl; } public Locale getLocale() { return locale; } public void setLocale(Locale locale) { if (locale == null) { throw new IllegalArgumentException("locale must not be null"); } this.locale = locale; } public SchemaReader getSchemaReader() { return schemaReader; } public Object getProperty(String name) { // Mask out the values of certain properties. if (name.equals(RolapConnectionProperties.JdbcPassword.name()) || name.equals(RolapConnectionProperties.CatalogContent.name())) { return ""; } return connectInfo.get(name); } public CacheControl getCacheControl(PrintWriter pw) { return getServer().getAggregationManager().getCacheControl(this, pw); } /** * Executes a Query. * * @param query Query parse tree * * @throws ResourceLimitExceededException if some resource limit specified * in the property file was exceeded * @throws QueryCanceledException if query was canceled during execution * @throws QueryTimeoutException if query exceeded timeout specified in * the property file * * @deprecated Use {@link #execute(mondrian.server.Execution)}; this method * will be removed in mondrian-4.0 */ public Result execute(Query query) { final Statement statement = query.getStatement(); Execution execution = new Execution(statement, statement.getQueryTimeoutMillis()); return execute(execution); } /** * Executes a statement. * * @param execution Execution context (includes statement, query) * * @throws ResourceLimitExceededException if some resource limit specified * in the property file was exceeded * @throws QueryCanceledException if query was canceled during execution * @throws QueryTimeoutException if query exceeded timeout specified in * the property file */ public Result execute(final Execution execution) { execution.copyMDC(); return server.getResultShepherd() .shepherdExecution( execution, new Callable() { public Result call() throws Exception { return executeInternal(execution); } }); } private Result executeInternal(final Execution execution) { execution.setContextMap(); final Statement statement = execution.getMondrianStatement(); // Cleanup any previous executions still running synchronized (statement) { final Execution previousExecution = statement.getCurrentExecution(); if (previousExecution != null) { statement.end(previousExecution); } } final Query query = statement.getQuery(); final MemoryMonitor.Listener listener = new MemoryMonitor.Listener() { public void memoryUsageNotification(long used, long max) { execution.setOutOfMemory( "OutOfMemory used=" + used + ", max=" + max + " for connection: " + getConnectString()); } }; MemoryMonitor mm = MemoryMonitorFactory.getMemoryMonitor(); final long currId = execution.getId(); try { mm.addListener(listener); // Check to see if we must punt execution.checkCancelOrTimeout(); if (LOGGER.isDebugEnabled()) { LOGGER.debug(Util.unparse(query)); } if (RolapUtil.MDX_LOGGER.isDebugEnabled()) { RolapUtil.MDX_LOGGER.debug(currId + ": " + Util.unparse(query)); } final Locus locus = new Locus(execution, null, "Loading cells"); Locus.push(locus); Result result; try { statement.start(execution); ((RolapCube) query.getCube()).clearCachedAggregations(true); result = new RolapResult(execution, true); int i = 0; for (QueryAxis axis : query.getAxes()) { if (axis.isNonEmpty()) { result = new NonEmptyResult(result, execution, i); } ++i; } } finally { Locus.pop(locus); ((RolapCube) query.getCube()).clearCachedAggregations(true); } statement.end(execution); return result; } catch (ResultLimitExceededException e) { // query has been punted throw e; } catch (Exception e) { try { statement.end(execution); } catch (Exception e1) { /* * We can safely ignore that cleanup exception. * If an error is encountered here, it means that * one was already encountered at statement.start() * above and the exception we will throw after the * cleanup is the same as the original one. */ } String queryString; try { queryString = Util.unparse(query); } catch (Exception e1) { queryString = "?"; } throw Util.newError( e, "Error while executing query [" + queryString + "]"); } finally { mm.removeListener(listener); if (RolapUtil.MDX_LOGGER.isDebugEnabled()) { final long elapsed = execution.getElapsedMillis(); RolapUtil.MDX_LOGGER.debug( currId + ": exec: " + elapsed + " ms"); } } } public void setRole(Role role) { assert role != null; this.role = role; this.schemaReader = new RolapSchemaReader(role, schema); } public Role getRole() { Util.assertPostcondition(role != null, "role != null"); return role; } public void setScenario(Scenario scenario) { this.scenario = scenario; } public Scenario getScenario() { return scenario; } /** * Returns the server (mondrian instance) that this connection belongs to. * Usually there is only one server instance in a given JVM. * * @return Server instance; never null */ public MondrianServer getServer() { return server; } public QueryPart parseStatement(String query) { Statement statement = createInternalStatement(false); final Locus locus = new Locus( new Execution(statement, 0), "Parse/validate MDX statement", null); Locus.push(locus); try { QueryPart queryPart = parseStatement(statement, query, null, false); if (queryPart instanceof Query) { ((Query) queryPart).setOwnStatement(true); statement = null; } return queryPart; } finally { Locus.pop(locus); if (statement != null) { statement.close(); } } } public Exp parseExpression(String expr) { boolean debug = false; if (getLogger().isDebugEnabled()) { //debug = true; getLogger().debug( Util.nl + expr); } final Statement statement = getInternalStatement(); try { MdxParserValidator parser = createParser(); final FunTable funTable = getSchema().getFunTable(); return parser.parseExpression(statement, expr, debug, funTable); } catch (Throwable exception) { throw MondrianResource.instance().FailedToParseQuery.ex( expr, exception); } } public Statement getInternalStatement() { if (internalStatement == null) { return schema.getInternalConnection().getInternalStatement(); } else { return internalStatement; } } private Statement createInternalStatement(boolean reentrant) { final Statement statement = reentrant ? new ReentrantInternalStatement() : new InternalStatement(); server.addStatement(statement); return statement; } /** * Implementation of {@link DataSource} which calls the good ol' * {@link java.sql.DriverManager}. * *

Overrides {@link #hashCode()} and {@link #equals(Object)} so that * {@link Dialect} objects can be cached more effectively. */ private static class DriverManagerDataSource implements DataSource { private final String jdbcConnectString; private PrintWriter logWriter; private int loginTimeout; private Properties jdbcProperties; public DriverManagerDataSource( String jdbcConnectString, Properties properties) { this.jdbcConnectString = jdbcConnectString; this.jdbcProperties = properties; } @Override public int hashCode() { int h = loginTimeout; h = Util.hash(h, jdbcConnectString); h = Util.hash(h, jdbcProperties); return h; } @Override public boolean equals(Object obj) { if (obj instanceof DriverManagerDataSource) { DriverManagerDataSource that = (DriverManagerDataSource) obj; return this.loginTimeout == that.loginTimeout && this.jdbcConnectString.equals(that.jdbcConnectString) && this.jdbcProperties.equals(that.jdbcProperties); } return false; } public Connection getConnection() throws SQLException { return new org.apache.commons.dbcp.DelegatingConnection( java.sql.DriverManager.getConnection( jdbcConnectString, jdbcProperties)); } public Connection getConnection(String username, String password) throws SQLException { if (jdbcProperties == null) { return java.sql.DriverManager.getConnection( jdbcConnectString, username, password); } else { Properties temp = (Properties)jdbcProperties.clone(); temp.put("user", username); temp.put("password", password); return java.sql.DriverManager.getConnection( jdbcConnectString, temp); } } public PrintWriter getLogWriter() throws SQLException { return logWriter; } public void setLogWriter(PrintWriter out) throws SQLException { logWriter = out; } public void setLoginTimeout(int seconds) throws SQLException { loginTimeout = seconds; } public int getLoginTimeout() throws SQLException { return loginTimeout; } public java.util.logging.Logger getParentLogger() { return java.util.logging.Logger.getLogger(""); } public T unwrap(Class iface) throws SQLException { throw new SQLException("not a wrapper"); } public boolean isWrapperFor(Class iface) throws SQLException { return false; } } public DataSource getDataSource() { return dataSource; } /** * Helper method to allow olap4j wrappers to implement * {@link org.olap4j.OlapConnection#createScenario()}. * * @return new Scenario */ public ScenarioImpl createScenario() { final ScenarioImpl scenario = new ScenarioImpl(); scenario.register(schema); return scenario; } /** * A NonEmptyResult filters a result by removing empty rows * on a particular axis. */ static class NonEmptyResult extends ResultBase { final Result underlying; private final int axis; private final Map map; /** workspace. Synchronized access only. */ private final int[] pos; /** * Creates a NonEmptyResult. * * @param result Result set * @param execution Execution context * @param axis Which axis to make non-empty */ NonEmptyResult(Result result, Execution execution, int axis) { super(execution, result.getAxes().clone()); this.underlying = result; this.axis = axis; this.map = new HashMap(); int axisCount = underlying.getAxes().length; this.pos = new int[axisCount]; this.slicerAxis = underlying.getSlicerAxis(); TupleList tupleList = ((RolapAxis) underlying.getAxes()[axis]).getTupleList(); final TupleList filteredTupleList; if (!tupleList.isEmpty() && tupleList.get(0).get(0).getDimension().isHighCardinality()) { filteredTupleList = new DelegatingTupleList( tupleList.getArity(), new FilteredIterableList>( tupleList, new FilteredIterableList.Filter>() { public boolean accept(final List p) { return p.get(0) != null; } } )); } else { filteredTupleList = TupleCollections.createList(tupleList.getArity()); int i = -1; TupleCursor tupleCursor = tupleList.tupleCursor(); while (tupleCursor.forward()) { ++i; if (! isEmpty(i, axis)) { map.put(filteredTupleList.size(), i); filteredTupleList.addCurrent(tupleCursor); } } } this.axes[axis] = new RolapAxis(filteredTupleList); } protected Logger getLogger() { return LOGGER; } /** * Returns true if all cells at a given offset on a given axis are * empty. For example, in a 2x2x2 dataset, isEmpty(1,0) * returns true if cells {(1,0,0), (1,0,1), (1,1,0), * (1,1,1)} are all empty. As you can see, we hold the 0th * coordinate fixed at 1, and vary all other coordinates over all * possible values. */ private boolean isEmpty(int offset, int fixedAxis) { int axisCount = getAxes().length; pos[fixedAxis] = offset; return isEmptyRecurse(fixedAxis, axisCount - 1); } private boolean isEmptyRecurse(int fixedAxis, int axis) { if (axis < 0) { RolapCell cell = (RolapCell) underlying.getCell(pos); return cell.isNull(); } else if (axis == fixedAxis) { return isEmptyRecurse(fixedAxis, axis - 1); } else { List positions = getAxes()[axis].getPositions(); final int positionCount = positions.size(); for (int i = 0; i < positionCount; i++) { pos[axis] = i; if (!isEmptyRecurse(fixedAxis, axis - 1)) { return false; } } return true; } } // synchronized because we use 'pos' public synchronized Cell getCell(int[] externalPos) { try { System.arraycopy( externalPos, 0, this.pos, 0, externalPos.length); int offset = externalPos[axis]; int mappedOffset = mapOffsetToUnderlying(offset); this.pos[axis] = mappedOffset; return underlying.getCell(this.pos); } catch (NullPointerException npe) { return underlying.getCell(externalPos); } } private int mapOffsetToUnderlying(int offset) { return map.get(offset); } public void close() { underlying.close(); } } /** * Data source that delegates all methods to an underlying data source. */ private static abstract class DelegatingDataSource implements DataSource { protected final DataSource dataSource; public DelegatingDataSource(DataSource dataSource) { this.dataSource = dataSource; } public Connection getConnection() throws SQLException { return dataSource.getConnection(); } public Connection getConnection( String username, String password) throws SQLException { return dataSource.getConnection(username, password); } public PrintWriter getLogWriter() throws SQLException { return dataSource.getLogWriter(); } public void setLogWriter(PrintWriter out) throws SQLException { dataSource.setLogWriter(out); } public void setLoginTimeout(int seconds) throws SQLException { dataSource.setLoginTimeout(seconds); } public int getLoginTimeout() throws SQLException { return dataSource.getLoginTimeout(); } // JDBC 4.0 support (JDK 1.6 and higher) public T unwrap(Class iface) throws SQLException { if (Util.JdbcVersion >= 0x0400) { // Do // return dataSource.unwrap(iface); // via reflection. try { Method method = DataSource.class.getMethod("unwrap", Class.class); return iface.cast(method.invoke(dataSource, iface)); } catch (IllegalAccessException e) { throw Util.newInternal(e, "While invoking unwrap"); } catch (InvocationTargetException e) { throw Util.newInternal(e, "While invoking unwrap"); } catch (NoSuchMethodException e) { throw Util.newInternal(e, "While invoking unwrap"); } } else { if (iface.isInstance(dataSource)) { return iface.cast(dataSource); } else { return null; } } } // JDBC 4.0 support (JDK 1.6 and higher) public boolean isWrapperFor(Class iface) throws SQLException { if (Util.JdbcVersion >= 0x0400) { // Do // return dataSource.isWrapperFor(iface); // via reflection. try { Method method = DataSource.class.getMethod( "isWrapperFor", boolean.class); return (Boolean) method.invoke(dataSource, iface); } catch (IllegalAccessException e) { throw Util.newInternal(e, "While invoking isWrapperFor"); } catch (InvocationTargetException e) { throw Util.newInternal(e, "While invoking isWrapperFor"); } catch (NoSuchMethodException e) { throw Util.newInternal(e, "While invoking isWrapperFor"); } } else { return iface.isInstance(dataSource); } } // JDBC 4.1 support (JDK 1.7 and higher) public java.util.logging.Logger getParentLogger() { if (Util.JdbcVersion >= 0x0401) { // Do // return dataSource.getParentLogger(); // via reflection. try { Method method = DataSource.class.getMethod("getParentLogger"); return (java.util.logging.Logger) method.invoke(dataSource); } catch (IllegalAccessException e) { throw Util.newInternal(e, "While invoking getParentLogger"); } catch (InvocationTargetException e) { throw Util.newInternal(e, "While invoking getParentLogger"); } catch (NoSuchMethodException e) { throw Util.newInternal(e, "While invoking getParentLogger"); } } else { // Can't throw SQLFeatureNotSupportedException... it doesn't // exist before JDBC 4.1. throw new UnsupportedOperationException(); } } } /** * Data source that gets connections from an underlying data source but * with different user name and password. */ private static class UserPasswordDataSource extends DelegatingDataSource { private final String jdbcUser; private final String jdbcPassword; /** * Creates a UserPasswordDataSource * * @param dataSource Underlying data source * @param jdbcUser User name * @param jdbcPassword Password */ public UserPasswordDataSource( DataSource dataSource, String jdbcUser, String jdbcPassword) { super(dataSource); this.jdbcUser = jdbcUser; this.jdbcPassword = jdbcPassword; } public Connection getConnection() throws SQLException { return dataSource.getConnection(jdbcUser, jdbcPassword); } } /** *

Implementation of {@link Statement} for use when you don't have an * olap4j connection.

*/ private class InternalStatement extends StatementImpl { private boolean closed = false; public void close() { if (!closed) { closed = true; server.removeStatement(this); } } public RolapConnection getMondrianConnection() { return RolapConnection.this; } } /** *

A statement that can be used for all of the various internal * operations, such as resolving MDX identifiers, that require a * {@link Statement} and an {@link Execution}. * *

The statement needs to be reentrant because there are many such * operations; several of these operations might be active at one time. We * don't want to create a new statement for each, but just one internal * statement for each connection. The statement shouldn't have a unique * execution. For this reason, we don't use the inherited {@link #execution} * field.

* *

But there is a drawback. If we can't find the unique execution, the * statement cannot be canceled or time out. If you want that behavior * from an internal statement, use the base class: create a new * {@link InternalStatement} for each operation.

*/ private class ReentrantInternalStatement extends InternalStatement { @Override public void start(Execution execution) { // Unlike StatementImpl, there is not a unique execution. An // internal statement can execute several at the same time. So, // we don't set this.execution. execution.start(); } @Override public void end(Execution execution) { execution.end(); } @Override public void close() { // do not close } } } // End RolapConnection.java mondrian-3.4.1/src/main/mondrian/rolap/RolapNativeSet.java0000644000175000017500000003374311735330606023442 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.ResultStyle; import mondrian.calc.TupleList; import mondrian.calc.impl.DelegatingTupleList; import mondrian.olap.*; import mondrian.rolap.TupleReader.MemberBuilder; import mondrian.rolap.aggmatcher.AggStar; import mondrian.rolap.cache.*; import mondrian.rolap.sql.*; import org.apache.log4j.Logger; import java.util.*; import javax.sql.DataSource; /** * Analyses set expressions and executes them in SQL if possible. * Supports crossjoin, member.children, level.members and member.descendants - * all in non empty mode, i.e. there is a join to the fact table.

* *

TODO: the order of the result is different from the order of the * enumeration. Should sort. * * @author av * @since Nov 12, 2005 */ public abstract class RolapNativeSet extends RolapNative { protected static final Logger LOGGER = Logger.getLogger(RolapNativeSet.class); private SmartCache cache = new SoftSmartCache(); /** * Returns whether certain member types (e.g. calculated members) should * disable native SQL evaluation for expressions containing them. * *

If true, expressions containing calculated members will be evaluated * by the interpreter, instead of using SQL. * *

If false, calc members will be ignored and the computation will be * done in SQL, returning more members than requested. This is ok, if * the superflous members are filtered out in java code afterwards. * * @return whether certain member types should disable native SQL evaluation */ protected abstract boolean restrictMemberTypes(); protected CrossJoinArgFactory crossJoinArgFactory() { return new CrossJoinArgFactory(restrictMemberTypes()); } /** * Constraint for non empty {crossjoin, member.children, * member.descendants, level.members} */ protected static abstract class SetConstraint extends SqlContextConstraint { CrossJoinArg[] args; SetConstraint( CrossJoinArg[] args, RolapEvaluator evaluator, boolean strict) { super(evaluator, strict); this.args = args; } /** * {@inheritDoc} * *

If there is a crossjoin, we need to join the fact table - even if * the evaluator context is empty. */ protected boolean isJoinRequired() { return args.length > 1 || super.isJoinRequired(); } public void addConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar) { super.addConstraint(sqlQuery, baseCube, aggStar); for (CrossJoinArg arg : args) { // if the cross join argument has calculated members in its // enumerated set, ignore the constraint since we won't // produce that set through the native sql and instead // will simply enumerate through the members in the set if (!(arg instanceof MemberListCrossJoinArg) || !((MemberListCrossJoinArg) arg).hasCalcMembers()) { RolapLevel level = arg.getLevel(); if (level == null || levelIsOnBaseCube(baseCube, level)) { arg.addConstraint(sqlQuery, baseCube, aggStar); } } } } private boolean levelIsOnBaseCube( final RolapCube baseCube, final RolapLevel level) { return baseCube.findBaseCubeHierarchy(level.getHierarchy()) != null; } /** * Returns null to prevent the member/childern from being cached. There * exists no valid MemberChildrenConstraint that would fetch those * children that were extracted as a side effect from evaluating a non * empty crossjoin */ public MemberChildrenConstraint getMemberChildrenConstraint( RolapMember parent) { return null; } /** * returns a key to cache the result */ public Object getCacheKey() { List key = new ArrayList(); key.add(super.getCacheKey()); // only add args that will be retrieved through native sql; // args that are sets with calculated members aren't executed // natively for (CrossJoinArg arg : args) { if (!(arg instanceof MemberListCrossJoinArg) || !((MemberListCrossJoinArg) arg).hasCalcMembers()) { key.add(arg); } } return key; } } protected class SetEvaluator implements NativeEvaluator { private final CrossJoinArg[] args; private final SchemaReaderWithMemberReaderAvailable schemaReader; private final TupleConstraint constraint; private int maxRows = 0; public SetEvaluator( CrossJoinArg[] args, SchemaReader schemaReader, TupleConstraint constraint) { this.args = args; if (schemaReader instanceof SchemaReaderWithMemberReaderAvailable) { this.schemaReader = (SchemaReaderWithMemberReaderAvailable) schemaReader; } else { this.schemaReader = new SchemaReaderWithMemberReaderCache(schemaReader); } this.constraint = constraint; } public Object execute(ResultStyle desiredResultStyle) { switch (desiredResultStyle) { case ITERABLE: return executeList(new HighCardSqlTupleReader(constraint)); case MUTABLE_LIST: case LIST: return executeList(new SqlTupleReader(constraint)); } throw ResultStyleException.generate( ResultStyle.ITERABLE_MUTABLELIST_LIST, Collections.singletonList(desiredResultStyle)); } protected TupleList executeList(final SqlTupleReader tr) { tr.setMaxRows(maxRows); for (CrossJoinArg arg : args) { addLevel(tr, arg); } // Look up the result in cache; we can't return the cached // result if the tuple reader contains a target with calculated // members because the cached result does not include those // members; so we still need to cross join the cached result // with those enumerated members. // // The key needs to include the arguments (projection) as well as // the constraint, because it's possible (see bug MONDRIAN-902) // that independent axes have identical constraints but different // args (i.e. projections). REVIEW: In this case, should we use the // same cached result and project different columns? List key = new ArrayList(); key.add(tr.getCacheKey()); key.addAll(Arrays.asList(args)); TupleList result = cache.get(key); boolean hasEnumTargets = (tr.getEnumTargetCount() > 0); if (result != null && !hasEnumTargets) { if (listener != null) { TupleEvent e = new TupleEvent(this, tr); listener.foundInCache(e); } return new DelegatingTupleList( args.length, Util.>cast(result)); } // execute sql and store the result if (result == null && listener != null) { TupleEvent e = new TupleEvent(this, tr); listener.executingSql(e); } // if we don't have a cached result in the case where we have // enumerated targets, then retrieve and cache that partial result TupleList partialResult = result; List> newPartialResult = null; if (hasEnumTargets && partialResult == null) { newPartialResult = new ArrayList>(); } DataSource dataSource = schemaReader.getDataSource(); if (args.length == 1) { result = tr.readMembers( dataSource, partialResult, newPartialResult); } else { result = tr.readTuples( dataSource, partialResult, newPartialResult); } if (hasEnumTargets) { if (newPartialResult != null) { cache.put( key, new DelegatingTupleList( args.length, Util.>cast(newPartialResult))); } } else { cache.put(key, result); } return result; } private void addLevel(TupleReader tr, CrossJoinArg arg) { RolapLevel level = arg.getLevel(); if (level == null) { // Level can be null if the CrossJoinArg represent // an empty set. // This is used to push down the "1 = 0" predicate // into the emerging CJ so that the entire CJ can // be natively evaluated. return; } RolapHierarchy hierarchy = level.getHierarchy(); MemberReader mr = schemaReader.getMemberReader(hierarchy); MemberBuilder mb = mr.getMemberBuilder(); Util.assertTrue(mb != null, "MemberBuilder not found"); if (arg instanceof MemberListCrossJoinArg && ((MemberListCrossJoinArg) arg).hasCalcMembers()) { // only need to keep track of the members in the case // where there are calculated members since in that case, // we produce the values by enumerating through the list // rather than generating the values through native sql tr.addLevelMembers(level, mb, arg.getMembers()); } else { tr.addLevelMembers(level, mb, null); } } int getMaxRows() { return maxRows; } void setMaxRows(int maxRows) { this.maxRows = maxRows; } } /** * Tests whether non-native evaluation is preferred for the * given arguments. * * @param joinArg true if evaluating a cross-join; false if * evaluating a single-input expression such as filter * * @return true if all args prefer the interpreter */ protected boolean isPreferInterpreter( CrossJoinArg[] args, boolean joinArg) { for (CrossJoinArg arg : args) { if (!arg.isPreferInterpreter(joinArg)) { return false; } } return true; } /** disable garbage collection for test */ void useHardCache(boolean hard) { if (hard) { cache = new HardSmartCache(); } else { cache = new SoftSmartCache(); } } /** * Overrides current members in position by default members in * hierarchies which are involved in this filter/topcount. * Stores the RolapStoredMeasure into the context because that is needed to * generate a cell request to constraint the sql. * *

The current context may contain a calculated measure, this measure * was translated into an sql condition (filter/topcount). The measure * is not used to constrain the result but only to access the star. * * @param evaluator Evaluation context to modify * @param cargs Cross join arguments * @param storedMeasure Stored measure * * @see RolapAggregationManager#makeRequest(RolapEvaluator) */ protected void overrideContext( RolapEvaluator evaluator, CrossJoinArg[] cargs, RolapStoredMeasure storedMeasure) { SchemaReader schemaReader = evaluator.getSchemaReader(); for (CrossJoinArg carg : cargs) { RolapLevel level = carg.getLevel(); if (level != null) { Hierarchy hierarchy = level.getHierarchy(); Member defaultMember = schemaReader.getHierarchyDefaultMember(hierarchy); evaluator.setContext(defaultMember); } } if (storedMeasure != null) { evaluator.setContext(storedMeasure); } } public interface SchemaReaderWithMemberReaderAvailable extends SchemaReader { MemberReader getMemberReader(Hierarchy hierarchy); } private static class SchemaReaderWithMemberReaderCache extends DelegatingSchemaReader implements SchemaReaderWithMemberReaderAvailable { private final Map hierarchyReaders = new HashMap(); SchemaReaderWithMemberReaderCache(SchemaReader schemaReader) { super(schemaReader); } public synchronized MemberReader getMemberReader(Hierarchy hierarchy) { MemberReader memberReader = hierarchyReaders.get(hierarchy); if (memberReader == null) { memberReader = ((RolapHierarchy) hierarchy).createMemberReader( schemaReader.getRole()); hierarchyReaders.put(hierarchy, memberReader); } return memberReader; } } } // End RolapNativeSet.java mondrian-3.4.1/src/main/mondrian/rolap/RolapMemberBase.java0000644000175000017500000011075011735330606023534 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.Calc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.fun.AggregateFunDef; import mondrian.olap.fun.VisualTotalsFunDef; import mondrian.server.Locus; import mondrian.spi.PropertyFormatter; import mondrian.util.*; import org.apache.commons.collections.map.Flat3Map; import org.apache.log4j.Logger; import org.eigenbase.util.property.StringProperty; import java.util.*; /** * Basic implementation of a member in a {@link RolapHierarchy}. * * @author jhyde * @since 10 August, 2001 */ public class RolapMemberBase extends MemberBase implements RolapMember { /** * For members of a level with an ordinal expression defined, the * value of that expression for this member as retrieved via JDBC; * otherwise null. */ private Comparable orderKey; private Boolean isParentChildLeaf; private static final Logger LOGGER = Logger.getLogger(RolapMember.class); /** * Sets a member's parent. * *

Can screw up the caching structure. Only to be called by * {@link mondrian.olap.CacheControl#createMoveCommand}. * *

New parent must be in same level as old parent. * * @param parentMember New parent member * * @see #getParentMember() * @see #getParentUniqueName() */ void setParentMember(RolapMember parentMember) { final RolapMember previousParentMember = getParentMember(); if (previousParentMember.getLevel() != parentMember.getLevel()) { throw new IllegalArgumentException( "new parent belongs to different level than old"); } this.parentMember = parentMember; } /** Ordinal of the member within the hierarchy. Some member readers do not * use this property; in which case, they should leave it as its default, * -1. */ private int ordinal; private final Object key; /** * Maps property name to property value. * *

We expect there to be a lot of members, but few of them will * have properties. So to reduce memory usage, when empty, this is set to * an immutable empty set. */ private Map mapPropertyNameToValue; private Boolean containsAggregateFunction = null; /** * Creates a RolapMemberBase. * * @param parentMember Parent member * @param level Level this member belongs to * @param key Key to this member in the underlying RDBMS * @param name Name of this member * @param memberType Type of member */ protected RolapMemberBase( RolapMember parentMember, RolapLevel level, Object key, String name, MemberType memberType) { super(parentMember, level, memberType); assert !(parentMember instanceof RolapCubeMember) || this instanceof RolapCalculatedMember || this instanceof VisualTotalsFunDef.VisualTotalMember; if (key instanceof byte[]) { // Some drivers (e.g. Derby) return byte arrays for binary columns // but byte arrays do not implement Comparable this.key = new String((byte[])key); } else { this.key = key; } this.ordinal = -1; this.mapPropertyNameToValue = Collections.emptyMap(); if (name != null && !(key != null && name.equals(key.toString()))) { // Save memory by only saving the name as a property if it's // different from the key. setProperty(Property.NAME.name, name); } else if (key != null) { setUniqueName(key); } } protected RolapMemberBase() { super(); this.key = null; } RolapMemberBase(RolapMember parentMember, RolapLevel level, Object value) { this(parentMember, level, value, null, MemberType.REGULAR); assert !(level instanceof RolapCubeLevel); } protected Logger getLogger() { return LOGGER; } public RolapLevel getLevel() { return (RolapLevel) level; } public RolapHierarchy getHierarchy() { return (RolapHierarchy) level.getHierarchy(); } public RolapMember getParentMember() { return (RolapMember) super.getParentMember(); } // Regular members do not have annotations. Measures and calculated members // do, so they override this method. public Map getAnnotationMap() { return Collections.emptyMap(); } public int hashCode() { return getUniqueName().hashCode(); } public boolean equals(Object o) { if (o == this) { return true; } if (o instanceof RolapMemberBase && equals((RolapMemberBase) o)) { return true; } if (o instanceof RolapCubeMember && equals(((RolapCubeMember) o).getRolapMember())) { // TODO: remove, RolapCubeMember should never meet RolapMember assert !Bug.BugSegregateRolapCubeMemberFixed; return true; } return false; } public boolean equals(OlapElement o) { return (o instanceof RolapMemberBase) && equals((RolapMemberBase) o); } private boolean equals(RolapMemberBase that) { assert that != null; // public method should have checked // Do not use equalsIgnoreCase; unique names should be identical, and // hashCode assumes this. return this.getUniqueName().equals(that.getUniqueName()); } void makeUniqueName(HierarchyUsage hierarchyUsage) { if (parentMember == null && key != null) { String n = hierarchyUsage.getName(); if (n != null) { String name = keyToString(key); n = Util.quoteMdxIdentifier(n); this.uniqueName = Util.makeFqName(n, name); if (getLogger().isDebugEnabled()) { getLogger().debug( "RolapMember.makeUniqueName: uniqueName=" + uniqueName); } } } } protected void setUniqueName(Object key) { String name = keyToString(key); // Drop the '[All Xxxx]' segment in regular members. // Keep the '[All Xxxx]' segment in the 'all' member. // Keep the '[All Xxxx]' segment in calc members. // Drop it in visual-totals and parent-child members (which are flagged // as calculated, but do have a data member). if (parentMember == null || (parentMember.isAll() && (!isCalculated() || this instanceof VisualTotalsFunDef.VisualTotalMember || getDataMember() != null))) { final RolapHierarchy hierarchy = getHierarchy(); final Dimension dimension = hierarchy.getDimension(); final RolapLevel level = getLevel(); if (dimension.getDimensionType() != null && (dimension.getDimensionType().equals( DimensionType.MeasuresDimension) && hierarchy.getName().equals(dimension.getName()))) { // Kludge to ensure that calc members are called // [Measures].[Foo] not [Measures].[Measures].[Foo]. We can // remove this code when we revisit the scheme to generate // member unique names. this.uniqueName = Util.makeFqName(dimension, name); } else { if (name.equals(level.getName())) { this.uniqueName = Util.makeFqName( Util.makeFqName( hierarchy.getUniqueName(), level.getName()), name); } else { this.uniqueName = Util.makeFqName(hierarchy, name); } } } else { this.uniqueName = Util.makeFqName(parentMember, name); } } public boolean isCalculatedInQuery() { return false; } public String getName() { final Object name = getPropertyValue(Property.NAME.name); return (name != null) ? String.valueOf(name) : keyToString(key); } public void setName(String name) { throw new Error("unsupported"); } /** * Sets a property of this member to a given value. * *

WARNING: Setting system properties such as "$name" may have nasty * side-effects. */ public synchronized void setProperty(String name, Object value) { if (name.equals(Property.CAPTION.name)) { setCaption((String)value); return; } if (mapPropertyNameToValue.isEmpty()) { // the empty map is shared and immutable; create our own PropertyValueMapFactory factory = PropertyValueMapFactoryFactory.getPropertyValueMapFactory(); mapPropertyNameToValue = factory.create(this); } if (name.equals(Property.NAME.name)) { if (value == null) { value = RolapUtil.mdxNullLiteral(); } setUniqueName(value); } if (name.equals(Property.MEMBER_ORDINAL.name)) { String ordinal = (String) value; if (ordinal.startsWith("\"") && ordinal.endsWith("\"")) { ordinal = ordinal.substring(1, ordinal.length() - 1); } final double d = Double.parseDouble(ordinal); setOrdinal((int) d); } mapPropertyNameToValue.put(name, value); } public Object getPropertyValue(String propertyName) { return getPropertyValue(propertyName, true); } public Object getPropertyValue(String propertyName, boolean matchCase) { Property property = Property.lookup(propertyName, matchCase); if (property != null) { Schema schema; Member parentMember; List list; switch (property.ordinal) { case Property.NAME_ORDINAL: // Do NOT call getName() here. This property is internal, // and must fall through to look in the property list. break; case Property.CAPTION_ORDINAL: return getCaption(); case Property.CONTRIBUTING_CHILDREN_ORDINAL: list = new ArrayList(); getHierarchy().getMemberReader().getMemberChildren(this, list); return list; case Property.CATALOG_NAME_ORDINAL: // TODO: can't go from member to connection thence to // Connection.getCatalogName() break; case Property.SCHEMA_NAME_ORDINAL: schema = getHierarchy().getDimension().getSchema(); return schema.getName(); case Property.CUBE_NAME_ORDINAL: // TODO: can't go from member to cube cube yet break; case Property.DIMENSION_UNIQUE_NAME_ORDINAL: return getHierarchy().getDimension().getUniqueName(); case Property.HIERARCHY_UNIQUE_NAME_ORDINAL: return getHierarchy().getUniqueName(); case Property.LEVEL_UNIQUE_NAME_ORDINAL: return getLevel().getUniqueName(); case Property.LEVEL_NUMBER_ORDINAL: return getLevel().getDepth(); case Property.MEMBER_UNIQUE_NAME_ORDINAL: return getUniqueName(); case Property.MEMBER_NAME_ORDINAL: return getName(); case Property.MEMBER_TYPE_ORDINAL: return getMemberType().ordinal(); case Property.MEMBER_GUID_ORDINAL: return null; case Property.MEMBER_CAPTION_ORDINAL: return getCaption(); case Property.MEMBER_ORDINAL_ORDINAL: return getOrdinal(); case Property.CHILDREN_CARDINALITY_ORDINAL: return Locus.execute( ((RolapSchema) level.getDimension().getSchema()) .getInternalConnection(), "Member.CHILDREN_CARDINALITY", new Locus.Action() { public Integer execute() { if (isAll() && childLevelHasApproxRowCount()) { return getLevel().getChildLevel() .getApproxRowCount(); } else { ArrayList list = new ArrayList(); getHierarchy().getMemberReader() .getMemberChildren( RolapMemberBase.this, list); return list.size(); } } } ); case Property.PARENT_LEVEL_ORDINAL: parentMember = getParentMember(); return parentMember == null ? 0 : parentMember.getLevel().getDepth(); case Property.PARENT_UNIQUE_NAME_ORDINAL: parentMember = getParentMember(); return parentMember == null ? null : parentMember.getUniqueName(); case Property.PARENT_COUNT_ORDINAL: parentMember = getParentMember(); return parentMember == null ? 0 : 1; case Property.VISIBLE_ORDINAL: break; case Property.MEMBER_KEY_ORDINAL: case Property.KEY_ORDINAL: return this == this.getHierarchy().getAllMember() ? 0 : getKey(); case Property.SCENARIO_ORDINAL: return ScenarioImpl.forMember(this); default: break; // fall through } } return getPropertyFromMap(propertyName, matchCase); } /** * Returns the value of a property by looking it up in the property map. * * @param propertyName Name of property * @param matchCase Whether to match name case-sensitive * @return Property value */ protected Object getPropertyFromMap( String propertyName, boolean matchCase) { synchronized (this) { if (matchCase) { return mapPropertyNameToValue.get(propertyName); } else { for (String key : mapPropertyNameToValue.keySet()) { if (key.equalsIgnoreCase(propertyName)) { return mapPropertyNameToValue.get(key); } } return null; } } } protected boolean childLevelHasApproxRowCount() { return getLevel().getChildLevel().getApproxRowCount() > Integer.MIN_VALUE; } /** * @deprecated Use {@link #isAll}; will be removed in mondrian-4.0 */ public boolean isAllMember() { return getLevel().getHierarchy().hasAll() && getLevel().getDepth() == 0; } public Property[] getProperties() { return getLevel().getInheritedProperties(); } public int getOrdinal() { return ordinal; } public Comparable getOrderKey() { return orderKey; } void setOrdinal(int ordinal) { if (this.ordinal == -1) { this.ordinal = ordinal; } } void setOrderKey(Comparable orderKey) { this.orderKey = orderKey; } private void resetOrdinal() { this.ordinal = -1; } public Object getKey() { return this.key; } /** * Compares this member to another {@link RolapMemberBase}. * *

The method first compares on keys; null keys always collate last. * If the keys are equal, it compares using unique name. * *

This method does not consider {@link #ordinal} field, because * ordinal is only unique within a parent. If you want to compare * members which may be at any position in the hierarchy, use * {@link mondrian.olap.fun.FunUtil#compareHierarchically}. * * @return -1 if this is less, 0 if this is the same, 1 if this is greater */ public int compareTo(Object o) { RolapMemberBase other = (RolapMemberBase)o; if (this.key != null && other.key == null) { return 1; // not null is greater than null } if (this.key == null && other.key != null) { return -1; // null is less than not null } // compare by unique name, if both keys are null if (this.key == null && other.key == null) { return this.getUniqueName().compareTo(other.getUniqueName()); } // compare by unique name, if one ore both members are null if (this.key == RolapUtil.sqlNullValue || other.key == RolapUtil.sqlNullValue) { return this.getUniqueName().compareTo(other.getUniqueName()); } // as both keys are not null, compare by key // String, Double, Integer should be possible // any key object should be "Comparable" // anyway - keys should be of the same class if (this.key.getClass().equals(other.key.getClass())) { if (this.key instanceof String) { // use a special case sensitive compare name which // first compares w/o case, and if 0 compares with case return Util.caseSensitiveCompareName( (String) this.key, (String) other.key); } else { return Util.compareKey(this.key, other.key); } } // Compare by unique name in case of different key classes. // This is possible, if a new calculated member is created // in a dimension with an Integer key. The calculated member // has a key of type String. return this.getUniqueName().compareTo(other.getUniqueName()); } public boolean isHidden() { final RolapLevel rolapLevel = getLevel(); switch (rolapLevel.getHideMemberCondition()) { case Never: return false; case IfBlankName: { // If the key value in the database is null, then we use // a special key value whose toString() is "null". final String name = getName(); return name.equals(RolapUtil.mdxNullLiteral()) || Util.isBlank(name); } case IfParentsName: { final Member parentMember = getParentMember(); if (parentMember == null) { return false; } final String parentName = parentMember.getName(); final String name = getName(); return (parentName == null ? "" : parentName).equals( name == null ? "" : name); } default: throw Util.badValue(rolapLevel.getHideMemberCondition()); } } public int getDepth() { return getLevel().getDepth(); } public String getPropertyFormattedValue(String propertyName) { // do we have a formatter ? if yes, use it Property[] props = getLevel().getProperties(); Property prop = null; for (Property prop1 : props) { if (prop1.getName().equals(propertyName)) { prop = prop1; break; } } PropertyFormatter pf; if (prop != null && (pf = prop.getFormatter()) != null) { return pf.formatProperty( this, propertyName, getPropertyValue(propertyName)); } Object val = getPropertyValue(propertyName); return (val == null) ? "" : val.toString(); } public boolean isParentChildLeaf() { if (isParentChildLeaf == null) { isParentChildLeaf = getLevel().isParentChild() && getDimension().getSchema().getSchemaReader() .getMemberChildren(this).size() == 0; } return isParentChildLeaf; } /** * Returns a list of member lists where the first member * list is the root members while the last member array is the * leaf members. * *

If you know that you will need to get all or most of the members of * a hierarchy, then calling this which gets all of the hierarchy's * members all at once is much faster than getting members one at * a time. * * @param schemaReader Schema reader * @param hierarchy Hierarchy * @return List of arrays of members */ public static List> getAllMembers( SchemaReader schemaReader, Hierarchy hierarchy) { long start = System.currentTimeMillis(); try { // Getting the members by Level is the fastest way that I could // find for getting all of a hierarchy's members. List> list = new ArrayList>(); Level[] levels = hierarchy.getLevels(); for (Level level : levels) { List members = schemaReader.getLevelMembers(level, true); if (members != null) { list.add(members); } } return list; } finally { if (LOGGER.isDebugEnabled()) { long end = System.currentTimeMillis(); LOGGER.debug( "RolapMember.getAllMembers: time=" + (end - start)); } } } public static int getHierarchyCardinality( SchemaReader schemaReader, Hierarchy hierarchy) { int cardinality = 0; Level[] levels = hierarchy.getLevels(); for (Level level1 : levels) { cardinality += schemaReader.getLevelCardinality(level1, true, true); } return cardinality; } /** * Sets member ordinal values using a Bottom-up/Top-down algorithm. * *

Gets an array of members for each level and traverses * array for the lowest level, setting each member's * parent's parent's etc. member's ordinal if not set working back * down to the leaf member and then going to the next leaf member * and traversing up again. * *

The above algorithm only works for a hierarchy that has all of its * leaf members in the same level (that is, a non-ragged hierarchy), which * is the norm. After all member ordinal values have been set, traverses * the array of members, making sure that all members' ordinals have been * set. If one is found that is not set, then one must to a full Top-down * setting of the ordinals. * *

The Bottom-up/Top-down algorithm is MUCH faster than the Top-down * algorithm. * * @param schemaReader Schema reader * @param seedMember Member */ public static void setOrdinals( SchemaReader schemaReader, Member seedMember) { seedMember = RolapUtil.strip((RolapMember) seedMember); /* * The following are times for executing different set ordinals * algorithms for both the FoodMart Sales cube/Store dimension * and a Large Data set with a dimension with about 250,000 members. * * Times: * Original setOrdinals Top-down * Foodmart: 63ms * Large Data set: 651865ms * Calling getAllMembers before calling original setOrdinals Top-down * Foodmart: 32ms * Large Data set: 73880ms * Bottom-up/Top-down * Foodmart: 17ms * Large Data set: 4241ms */ long start = System.currentTimeMillis(); try { Hierarchy hierarchy = seedMember.getHierarchy(); int ordinal = hierarchy.hasAll() ? 1 : 0; List> levelMembers = getAllMembers(schemaReader, hierarchy); List leafMembers = levelMembers.get(levelMembers.size() - 1); levelMembers = levelMembers.subList(0, levelMembers.size() - 1); // Set all ordinals for (Member child : leafMembers) { ordinal = bottomUpSetParentOrdinals(ordinal, child); ordinal = setOrdinal(child, ordinal); } boolean needsFullTopDown = needsFullTopDown(levelMembers); // If we must to a full Top-down, then first reset all ordinal // values to -1, and then call the Top-down if (needsFullTopDown) { for (List members : levelMembers) { for (Member member : members) { if (member instanceof RolapMemberBase) { ((RolapMemberBase) member).resetOrdinal(); } } } // call full Top-down setOrdinalsTopDown(schemaReader, seedMember); } } finally { if (LOGGER.isDebugEnabled()) { long end = System.currentTimeMillis(); LOGGER.debug("RolapMember.setOrdinals: time=" + (end - start)); } } } /** * Returns whether the ordinal assignment algorithm needs to perform * the more expensive top-down algorithm. If the hierarchy is 'uneven', not * all leaf members are at the same level, then bottom-up setting of * ordinals will have missed some. * * @param levelMembers Array containing the list of members in each level * except the leaf level * @return whether we need to apply the top-down ordinal assignment */ private static boolean needsFullTopDown(List> levelMembers) { for (List members : levelMembers) { for (Member member : members) { if (member.getOrdinal() == -1) { return true; } } } return false; } /** * Walks up the hierarchy, setting the ordinals of ancestors until it * reaches the root or hits an ancestor whose ordinal has already been * assigned. * *

Assigns the given ordinal to the ancestor nearest the root which has * not been assigned an ordinal, and increments by one for each descendant. * * @param ordinal Ordinal to assign to deepest ancestor * @param child Member whose ancestors ordinals to set * @return Ordinal, incremented for each time it was used */ private static int bottomUpSetParentOrdinals(int ordinal, Member child) { Member parent = child.getParentMember(); if ((parent != null) && parent.getOrdinal() == -1) { ordinal = bottomUpSetParentOrdinals(ordinal, parent); ordinal = setOrdinal(parent, ordinal); } return ordinal; } private static int setOrdinal(Member member, int ordinal) { if (member instanceof RolapMemberBase) { ((RolapMemberBase) member).setOrdinal(ordinal++); } else { // TODO LOGGER.warn( "RolapMember.setAllChildren: NOT RolapMember " + "member.name=" + member.getName() + ", member.class=" + member.getClass().getName() + ", ordinal=" + ordinal); ordinal++; } return ordinal; } /** * Sets ordinals of a complete member hierarchy as required by the * MEMBER_ORDINAL XMLA element using a depth-first algorithm. * *

For big hierarchies it takes a bunch of time. SQL Server is * relatively fast in comparison so it might be storing such * information in the DB. * * @param schemaReader Schema reader * @param member Member */ private static void setOrdinalsTopDown( SchemaReader schemaReader, Member member) { long start = System.currentTimeMillis(); try { Member parent = schemaReader.getMemberParent(member); if (parent == null) { // top of the world int ordinal = 0; List siblings = schemaReader.getHierarchyRootMembers(member.getHierarchy()); for (Member sibling : siblings) { ordinal = setAllChildren(ordinal, schemaReader, sibling); } } else { setOrdinalsTopDown(schemaReader, parent); } } finally { if (LOGGER.isDebugEnabled()) { long end = System.currentTimeMillis(); LOGGER.debug( "RolapMember.setOrdinalsTopDown: time=" + (end - start)); } } } private static int setAllChildren( int ordinal, SchemaReader schemaReader, Member member) { ordinal = setOrdinal(member, ordinal); List children = schemaReader.getMemberChildren(member); for (Member child : children) { ordinal = setAllChildren(ordinal, schemaReader, child); } return ordinal; } /** * Converts a key to a string to be used as part of the member's name * and unique name. * *

Usually, it just calls {@link Object#toString}. But if the key is an * integer value represented in a floating-point column, we'd prefer the * integer value. For example, one member of the * [Sales].[Store SQFT] dimension comes out "20319.0" but we'd * like it to be "20319". */ protected static String keyToString(Object key) { String name; if (key == null || RolapUtil.sqlNullValue.equals(key)) { name = RolapUtil.mdxNullLiteral(); } else if (key instanceof Id.Segment) { name = ((Id.Segment) key).name; } else { name = key.toString(); } if ((key instanceof Number) && name.endsWith(".0")) { name = name.substring(0, name.length() - 2); } return name; } /** *

Interface definition for the pluggable factory used to decide * which implementation of {@link java.util.Map} to use to store * property string/value pairs for member properties.

* *

This permits tuning for performance, memory allocation, etcetera. * For example, if a member belongs to a level which has 10 member * properties a HashMap may be preferred, while if the level has * only two member properties a Flat3Map may make more sense.

*/ public interface PropertyValueMapFactory { /** * Creates a {@link java.util.Map} to be used for storing * property string/value pairs for the specified * {@link mondrian.olap.Member}. * * @param member Member * @return the Map instance to store property/value pairs */ Map create(Member member); } /** * Default {@link RolapMemberBase.PropertyValueMapFactory} * implementation, used if * {@link mondrian.olap.MondrianProperties#PropertyValueMapFactoryClass} * is not set. */ public static final class DefaultPropertyValueMapFactory implements PropertyValueMapFactory { /** * {@inheritDoc} *

This factory creates an * {@link org.apache.commons.collections.map.Flat3Map} if * it appears that the provided member has less than 3 properties, * and a {@link java.util.HashMap} if it appears * that it has more than 3.

* *

Guessing the number of properties * can be tricky since some subclasses of * {@link mondrian.olap.Member}

have additional properties * that aren't explicitly declared. The most common offenders * are the (@link mondrian.olap.Measure} implementations, which * often have 4 or more undeclared properties, so if the member * is a measure, the factory will create a {@link java.util.HashMap}. *

* * @param member {@inheritDoc} * @return {@inheritDoc} */ @SuppressWarnings({"unchecked"}) public Map create(Member member) { assert member != null; Property[] props = member.getProperties(); if ((member instanceof RolapMeasure) || (props == null) || (props.length > 3)) { return new HashMap(); } else { return new Flat3Map(); } } } /** *

Creates the PropertyValueMapFactory which is in turn used * to create property-value maps for member properties.

* *

The name of the PropertyValueMapFactory is drawn from * {@link mondrian.olap.MondrianProperties#PropertyValueMapFactoryClass} * in mondrian.properties. If unset, it defaults to * {@link RolapMemberBase.DefaultPropertyValueMapFactory}.

*/ public static final class PropertyValueMapFactoryFactory extends ObjectFactory.Singleton { /** * Single instance of the PropertyValueMapFactory. */ private static final PropertyValueMapFactoryFactory factory; static { factory = new PropertyValueMapFactoryFactory(); } /** * Access the PropertyValueMapFactory instance. * * @return the Map. */ public static PropertyValueMapFactory getPropertyValueMapFactory() { return factory.getObject(); } /** * The constructor for the PropertyValueMapFactoryFactory. * This passes the PropertyValueMapFactory class to the * ObjectFactory base class. */ @SuppressWarnings({"unchecked"}) private PropertyValueMapFactoryFactory() { super((Class) PropertyValueMapFactory.class); } protected StringProperty getStringProperty() { return MondrianProperties.instance().PropertyValueMapFactoryClass; } protected PropertyValueMapFactory getDefault( Class[] parameterTypes, Object[] parameterValues) throws CreationException { return new DefaultPropertyValueMapFactory(); } } public boolean containsAggregateFunction() { // searching for agg functions is expensive, so cache result if (containsAggregateFunction == null) { containsAggregateFunction = foundAggregateFunction(getExpression()); } return containsAggregateFunction; } /** * Returns whether an expression contains a call to an aggregate * function such as "Aggregate" or "Sum". * * @param exp Expression * @return Whether expression contains a call to an aggregate function. */ private static boolean foundAggregateFunction(Exp exp) { if (exp instanceof ResolvedFunCall) { ResolvedFunCall resolvedFunCall = (ResolvedFunCall) exp; if (resolvedFunCall.getFunDef() instanceof AggregateFunDef) { return true; } else { for (Exp argExp : resolvedFunCall.getArgs()) { if (foundAggregateFunction(argExp)) { return true; } } } } return false; } public Calc getCompiledExpression(RolapEvaluatorRoot root) { return root.getCompiled(getExpression(), true, null); } public int getHierarchyOrdinal() { return getHierarchy().getOrdinalInCube(); } public void setContextIn(RolapEvaluator evaluator) { final RolapMember defaultMember = evaluator.root.defaultMembers[getHierarchyOrdinal()]; // This method does not need to call RolapEvaluator.removeCalcMember. // That happens implicitly in setContext. evaluator.setContext(defaultMember); evaluator.setExpanding(this); } } // End RolapMemberBase.java mondrian-3.4.1/src/main/mondrian/rolap/RolapDependencyTestingEvaluator.java0000644000175000017500000003347211735330606027036 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.olap.*; import mondrian.olap.type.SetType; import java.io.PrintWriter; import java.io.StringWriter; import java.util.*; /** * Evaluator which checks dependencies of expressions. * *

For each expression evaluation, this valuator evaluates each * expression more times, and makes sure that the results of the expression * are independent of dimensions which the expression claims to be * independent of. * *

Since it evaluates each expression twice, it also exposes function * implementations which change the context of the evaluator. * * @author jhyde * @since September, 2005 */ public class RolapDependencyTestingEvaluator extends RolapEvaluator { /** * Creates an dependency-testing evaluator. * * @param result Result we are building * @param expDeps Number of dependencies to check */ RolapDependencyTestingEvaluator(RolapResult result, int expDeps) { super(new DteRoot(result, expDeps)); } /** * Creates a child evaluator. * * @param root Root evaluation context * @param evaluator Parent evaluator */ private RolapDependencyTestingEvaluator( RolapEvaluatorRoot root, RolapDependencyTestingEvaluator evaluator, List> aggregationList) { super(root, evaluator, aggregationList); } public Object evaluate( Calc calc, Hierarchy[] independentHierarchies, String mdxString) { final DteRoot dteRoot = (DteRoot) root; if (dteRoot.faking) { ++dteRoot.fakeCallCount; } else { ++dteRoot.callCount; } // Evaluate the call for real. final Object result = calc.evaluate(this); if (dteRoot.result.isDirty()) { return result; } // If the result is a list and says that it is mutable, see whether it // really is. if (calc.getResultStyle() == ResultStyle.MUTABLE_LIST) { List list = (List) result; if (list.size() > 0) { final Object zeroth = list.get(0); list.set(0, zeroth); } } // Change one of the allegedly independent dimensions and evaluate // again. // // Don't do it if the faking is disabled, // or if we're already faking another dimension, // or if we're filtering out nonempty cells (which makes us // dependent on everything), // or if the ratio of fake evals to real evals is too high (which // would make us too slow). if (dteRoot.disabled || dteRoot.faking || isNonEmpty() || (double) dteRoot.fakeCallCount > (double) dteRoot.callCount * dteRoot.random.nextDouble() * 2 * dteRoot.expDeps) { return result; } if (independentHierarchies.length == 0) { return result; } dteRoot.faking = true; ++dteRoot.fakeCount; ++dteRoot.fakeCallCount; final int i = dteRoot.random.nextInt(independentHierarchies.length); final Member saveMember = getContext(independentHierarchies[i]); final Member otherMember = dteRoot.chooseOtherMember( saveMember, getQuery().getSchemaReader(false)); setContext(otherMember); final Object otherResult = calc.evaluate(this); if (false) { System.out.println( "original=" + saveMember.getUniqueName() + ", member=" + otherMember.getUniqueName() + ", originalResult=" + result + ", result=" + otherResult); } if (!equals(otherResult, result)) { final Member[] members = getMembers(); final StringBuilder buf = new StringBuilder(); for (int j = 0; j < members.length; j++) { if (j > 0) { buf.append(", "); } buf.append(members[j].getUniqueName()); } throw Util.newInternal( "Expression '" + mdxString + "' claims to be independent of dimension " + saveMember.getDimension() + " but is not; context is {" + buf.toString() + "}; First result: " + toString(result) + ", Second result: " + toString(otherResult)); } // Restore context. setContext(saveMember); dteRoot.faking = false; return result; } public RolapEvaluator _push(List> aggregationList) { return new RolapDependencyTestingEvaluator(root, this, aggregationList); } private boolean equals(Object o1, Object o2) { if (o1 == null) { return o2 == null; } if (o2 == null) { return false; } if (o1 instanceof Object[]) { if (o2 instanceof Object[]) { Object[] a1 = (Object[]) o1; Object[] a2 = (Object[]) o2; if (a1.length == a2.length) { for (int i = 0; i < a1.length; i++) { if (!equals(a1[i], a2[i])) { return false; } } return true; } } return false; } if (o1 instanceof List) { return o2 instanceof List && equals( ((List) o1).toArray(), ((List) o2).toArray()); } if (o1 instanceof Iterable) { if (o2 instanceof Iterable) { return equals(toList((Iterable) o1), toList((Iterable) o2)); } else { return false; } } return o1.equals(o2); } private String toString(Object o) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); toString(o, pw); return sw.toString(); } private List toList(Iterable iterable) { final ArrayList list = new ArrayList(); for (T t : iterable) { list.add(t); } return list; } private void toString(Object o, PrintWriter pw) { if (o instanceof Object[]) { Object[] a = (Object[]) o; pw.print("{"); for (int i = 0; i < a.length; i++) { Object o1 = a[i]; if (i > 0) { pw.print(", "); } toString(o1, pw); } pw.print("}"); } else if (o instanceof List) { List list = (List) o; toString(list.toArray(), pw); } else if (o instanceof Member) { Member member = (Member) o; pw.print(member.getUniqueName()); } else { pw.print(o); } } /** * Holds context for a tree of {@link RolapDependencyTestingEvaluator}. */ static class DteRoot extends RolapResult.RolapResultEvaluatorRoot { final int expDeps; int callCount; int fakeCallCount; int fakeCount; boolean faking; boolean disabled; final Random random = Util.createRandom( MondrianProperties.instance().TestSeed.get()); DteRoot(RolapResult result, int expDeps) { super(result); this.expDeps = expDeps; } /** * Chooses another member of the same hierarchy. * The member will come from all levels with the same probability. * Calculated members are not included. * * @param save Previous member * @param schemaReader Schema reader * @return other member of same hierarchy */ private Member chooseOtherMember( final Member save, SchemaReader schemaReader) { final Hierarchy hierarchy = save.getHierarchy(); int attempt = 0; while (true) { // Choose a random level. final Level[] levels = hierarchy.getLevels(); final int levelDepth = random.nextInt(levels.length) + 1; Member member = null; for (int i = 0; i < levelDepth; i++) { List members; if (i == 0) { members = schemaReader.getLevelMembers(levels[i], false); } else { members = schemaReader.getMemberChildren(member); } if (members.size() == 0) { break; } member = members.get(random.nextInt(members.size())); } // If the member chosen happens to be the same as the original // member, try again. Give up after 100 attempts (in case the // hierarchy has only one member). if (member != save || ++attempt > 100) { return member; } } } } /** * Expression which checks dependencies of an underlying scalar expression. */ private static class DteScalarCalcImpl extends GenericCalc { private final Calc calc; private final Hierarchy[] independentHierarchies; private final String mdxString; DteScalarCalcImpl( Calc calc, Hierarchy[] independentHierarchies, String mdxString) { super(new DummyExp(calc.getType())); this.calc = calc; this.independentHierarchies = independentHierarchies; this.mdxString = mdxString; } public Calc[] getCalcs() { return new Calc[] {calc}; } public Object evaluate(Evaluator evaluator) { RolapDependencyTestingEvaluator dtEval = (RolapDependencyTestingEvaluator) evaluator; return dtEval.evaluate(calc, independentHierarchies, mdxString); } public ResultStyle getResultStyle() { return calc.getResultStyle(); } } /** * Expression which checks dependencies and list immutability of an * underlying list or iterable expression. */ private static class DteIterCalcImpl extends GenericIterCalc { private final Calc calc; private final Hierarchy[] independentHierarchies; private final boolean mutableList; private final String mdxString; DteIterCalcImpl( Calc calc, Hierarchy[] independentHierarchies, boolean mutableList, String mdxString) { super(new DummyExp(calc.getType())); this.calc = calc; this.independentHierarchies = independentHierarchies; this.mutableList = mutableList; this.mdxString = mdxString; } public Calc[] getCalcs() { return new Calc[] {calc}; } public Object evaluate(Evaluator evaluator) { RolapDependencyTestingEvaluator dtEval = (RolapDependencyTestingEvaluator) evaluator; return dtEval.evaluate(calc, independentHierarchies, mdxString); } public TupleList evaluateList(Evaluator evaluator) { TupleList list = super.evaluateList(evaluator); if (!mutableList) { list = TupleCollections.unmodifiableList(list); } return list; } public ResultStyle getResultStyle() { return calc.getResultStyle(); } } /** * Expression compiler which introduces dependency testing. * *

It also checks that the caller does not modify lists unless it has * explicitly asked for a mutable list. */ static class DteCompiler extends DelegatingExpCompiler { DteCompiler(ExpCompiler compiler) { super(compiler); } protected Calc afterCompile(Exp exp, Calc calc, boolean mutable) { Hierarchy[] dimensions = getIndependentHierarchies(calc); calc = super.afterCompile(exp, calc, mutable); if (calc.getType() instanceof SetType) { return new DteIterCalcImpl( calc, dimensions, mutable, Util.unparse(exp)); } else { return new DteScalarCalcImpl( calc, dimensions, Util.unparse(exp)); } } /** * Returns the dimensions an expression does not depend on. If the * current member of any of these dimensions changes, the expression * will return the same result. * * @param calc Expression * @return List of dimensions that the expression does not depend on */ private Hierarchy[] getIndependentHierarchies(Calc calc) { List list = new ArrayList(); final List hierarchies = ((RolapCube) getValidator().getQuery().getCube()) .getHierarchies(); for (Hierarchy hierarchy : hierarchies) { if (!calc.dependsOn(hierarchy)) { list.add(hierarchy); } } return list.toArray(new Hierarchy[list.size()]); } } } // End RolapDependencyTestingEvaluator.java mondrian-3.4.1/src/main/mondrian/rolap/TargetBase.java0000644000175000017500000000561511735330606022560 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.Member; import mondrian.rolap.sql.TupleConstraint; import java.sql.SQLException; import java.util.List; /** * Base helper class for the SQL tuple readers * {@link mondrian.rolap.HighCardSqlTupleReader} and * {@link mondrian.rolap.SqlTupleReader}. * *

Keeps track of target levels and constraints for adding to the SQL query. * The real work is done in the extending classes, * {@link Target} and * {@link mondrian.rolap.SqlTupleReader.Target}. * * @author Kurtis Walker * @since July 23, 2009 */ public abstract class TargetBase { final List srcMembers; final RolapLevel level; private RolapMember currMember; private List list; final Object cacheLock; final TupleReader.MemberBuilder memberBuilder; public TargetBase( List srcMembers, RolapLevel level, TupleReader.MemberBuilder memberBuilder) { this.srcMembers = srcMembers; this.level = level; cacheLock = memberBuilder.getMemberCacheLock(); this.memberBuilder = memberBuilder; } public void setList(final List list) { this.list = list; } public List getSrcMembers() { return srcMembers; } public RolapLevel getLevel() { return level; } public RolapMember getCurrMember() { return this.currMember; } public void removeCurrMember() { this.currMember = null; } public void setCurrMember(final RolapMember m) { this.currMember = m; } public List getList() { return list; } public String toString() { return level.getUniqueName(); } /** * Adds a row to the collection. * * @param stmt Statement * @param column Column ordinal (0-based) * @return Ordinal of next unconsumed column * @throws SQLException On error */ public final int addRow(SqlStatement stmt, int column) throws SQLException { synchronized (cacheLock) { return internalAddRow(stmt, column); } } public abstract void open(); public abstract List close(); abstract int internalAddRow(SqlStatement stmt, int column) throws SQLException; public void add(final RolapMember member) { this.getList().add(member); } RolapNativeCrossJoin.NonEmptyCrossJoinConstraint castToNonEmptyCJConstraint(TupleConstraint constraint) { return (RolapNativeCrossJoin.NonEmptyCrossJoinConstraint) constraint; } } // End TargetBase.java mondrian-3.4.1/src/main/mondrian/rolap/RolapProperty.java0000644000175000017500000000554311735330606023361 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.MondrianDef; import mondrian.olap.Property; import mondrian.spi.PropertyFormatter; import org.apache.log4j.Logger; /** * RolapProperty is the definition of a member property. * * @author jhyde */ class RolapProperty extends Property { private static final Logger LOGGER = Logger.getLogger(RolapProperty.class); /** Array of RolapProperty of length 0. */ static final RolapProperty[] emptyArray = new RolapProperty[0]; private final PropertyFormatter formatter; private final String caption; private final boolean dependsOnLevelValue; /** The column or expression which yields the property's value. */ private final MondrianDef.Expression exp; /** * Creates a RolapProperty. * * @param name Name of property * @param type Datatype * @param exp Expression for property's value; often a literal * @param formatter A property formatter, or null * @param caption Caption * @param dependsOnLevelValue Whether the property is functionally dependent * on the level with which it is associated * @param internal Whether property is internal */ RolapProperty( String name, Datatype type, MondrianDef.Expression exp, PropertyFormatter formatter, String caption, Boolean dependsOnLevelValue, boolean internal, String description) { super(name, type, -1, internal, false, false, description); this.exp = exp; this.caption = caption; this.formatter = formatter; this.dependsOnLevelValue = dependsOnLevelValue != null && dependsOnLevelValue; } MondrianDef.Expression getExp() { return exp; } public PropertyFormatter getFormatter() { return formatter; } /** * @return Returns the caption. */ public String getCaption() { if (caption == null) { return getName(); } return caption; } /** * @return

Returns the dependsOnLevelValue setting (if unset, * returns false). This indicates whether the property is * functionally dependent on the level with which it is * associated.

* *

If true, then the property column can be eliminated from * the GROUP BY clause for queries on certain databases such * as MySQL.

*/ public boolean dependsOnLevelValue() { return dependsOnLevelValue; } } // End RolapProperty.java mondrian-3.4.1/src/main/mondrian/rolap/cache/0000755000175000017500000000000011735330606020730 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/rolap/cache/package.html0000644000175000017500000000011511735330606023206 0ustar drazzibdrazzib Provides primitives for policy-based caching. mondrian-3.4.1/src/main/mondrian/rolap/cache/CachePool.java0000644000175000017500000000137311735330606023434 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.cache; /** * A CachePool manages the objects in a collection of * caches. * * @author av */ public class CachePool { /** The singleton. */ private static CachePool instance = new CachePool(); /** Returns the singleton. */ public static CachePool instance() { return instance; } private CachePool() { } } // End CachePool.java mondrian-3.4.1/src/main/mondrian/rolap/cache/SmartCache.java0000644000175000017500000000231411735330606023605 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.cache; import java.util.Map; /** * Defines a cache API. Implementations exist for hard and soft references. * *

This interface implements the {@link Iterable}. The {@link #iterator()} * method returns an iterator over all entries in the cache. The iterator * is mutable. * * @author av * @since Nov 21, 2005 */ public interface SmartCache extends Iterable> { /** * Places a key/value pair into the queue. * * @param key Key * @param value Value * @return the previous value of key or null */ V put(K key, V value); V get(K key); /** * Removes a key from the cache. * * @param key Key * * @return Previous value associated with the key */ V remove(K key); void clear(); int size(); } // End SmartCache.java mondrian-3.4.1/src/main/mondrian/rolap/cache/SegmentCacheIndexImpl.java0000644000175000017500000007615411735330606025750 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.cache; import mondrian.rolap.BitKey; import mondrian.rolap.RolapUtil; import mondrian.rolap.agg.*; import mondrian.spi.*; import mondrian.util.*; import java.io.PrintWriter; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.Future; /** * Data structure that identifies which segments contain cells. * *

Not thread safe.

* * @author Julian Hyde */ public class SegmentCacheIndexImpl implements SegmentCacheIndex { private final Map> bitkeyMap = new HashMap>(); /** * The fact map allows us to spot quickly which * segments have facts relating to a given header. */ private final Map factMap = new HashMap(); /** * The fuzzy fact map allows us to spot quickly which * segments have facts relating to a given header, but doesn't * consider the compound predicates in the key. This allows * flush operations to be consistent. */ // TODO Get rid of the fuzzy map once we have a way to parse // compound predicates into rich objects that can be serialized // as part of the SegmentHeader. private final Map fuzzyFactMap = new HashMap(); private final Map headerMap = new HashMap(); private final Thread thread; /** * Creates a SegmentCacheIndexImpl. * * @param thread Thread that must be used to execute commands. */ public SegmentCacheIndexImpl(Thread thread) { this.thread = thread; assert thread != null; } public static List makeConverterKey(SegmentHeader header) { return Arrays.asList( header.schemaName, header.schemaChecksum, header.cubeName, header.rolapStarFactTableName, header.measureName, header.compoundPredicates); } public static List makeConverterKey(CellRequest request, AggregationKey key) { return Arrays.asList( request.getMeasure().getStar().getSchema().getName(), request.getMeasure().getStar().getSchema().getChecksum(), request.getMeasure().getCubeName(), request.getMeasure().getStar().getFactTable().getAlias(), request.getMeasure().getName(), AggregationKey.getCompoundPredicateStringList( key.getStar(), key.getCompoundPredicateList())); } public List locate( String schemaName, ByteString schemaChecksum, String cubeName, String measureName, String rolapStarFactTableName, BitKey constrainedColsBitKey, Map coordinates, List compoundPredicates) { checkThread(); List list = Collections.emptyList(); final List starKey = makeBitkeyKey( schemaName, schemaChecksum, cubeName, rolapStarFactTableName, constrainedColsBitKey, measureName, compoundPredicates); final List headerList = bitkeyMap.get(starKey); if (headerList == null) { return Collections.emptyList(); } for (SegmentHeader header : headerList) { if (matches(header, coordinates, compoundPredicates)) { // Be lazy. Don't allocate a list unless there is at least one // entry. if (list.isEmpty()) { list = new ArrayList(); } list.add(header); } } return list; } public void add( SegmentHeader header, boolean loading, SegmentBuilder.SegmentConverter converter) { checkThread(); final HeaderInfo headerInfo = headerMap.get(header); if (headerInfo != null) { if (loading && headerInfo.slot == null) { headerInfo.slot = new SlotFuture(); } return; } headerMap.put( header, new HeaderInfo( loading ? new SlotFuture() : null)); final List bitkeyKey = makeBitkeyKey(header); List headerList = bitkeyMap.get(bitkeyKey); if (headerList == null) { headerList = new ArrayList(); bitkeyMap.put(bitkeyKey, headerList); } headerList.add(header); final List factKey = makeFactKey(header); FactInfo factInfo = factMap.get(factKey); if (factInfo == null) { factInfo = new FactInfo(); factMap.put(factKey, factInfo); } factInfo.headerList.add(header); factInfo.bitkeyPoset.add(header.getConstrainedColumnsBitKey()); if (converter != null) { factInfo.converter = converter; } final List fuzzyFactKey = makeFuzzyFactKey(header); FuzzyFactInfo fuzzyFactInfo = fuzzyFactMap.get(fuzzyFactKey); if (fuzzyFactInfo == null) { fuzzyFactInfo = new FuzzyFactInfo(); fuzzyFactMap.put(fuzzyFactKey, fuzzyFactInfo); } fuzzyFactInfo.headerList.add(header); } public void loadSucceeded(SegmentHeader header, SegmentBody body) { checkThread(); final HeaderInfo headerInfo = headerMap.get(header); assert headerInfo != null : "segment header " + header.getUniqueID() + " is missing"; assert headerInfo.slot != null : "segment header " + header.getUniqueID() + " is not loading"; headerInfo.slot.put(body); if (headerInfo.removeAfterLoad) { remove(header); } } public void loadFailed(SegmentHeader header, Throwable throwable) { checkThread(); final HeaderInfo headerInfo = headerMap.get(header); assert headerInfo != null : "segment header " + header.getUniqueID() + " is missing"; assert headerInfo.slot != null : "segment header " + header.getUniqueID() + " is not loading"; headerInfo.slot.fail(throwable); remove(header); } public void remove(SegmentHeader header) { checkThread(); final HeaderInfo headerInfo = headerMap.get(header); if (headerInfo == null) { return; } if (headerInfo.slot != null) { // Cannot remove while load is pending; flag for removal after load headerInfo.removeAfterLoad = true; } else { headerMap.remove(header); } final List factKey = makeFactKey(header); final FactInfo factInfo = factMap.get(factKey); if (factInfo != null) { factInfo.headerList.remove(header); if (factInfo.headerList.size() == 0) { factMap.remove(factKey); } } final List fuzzyFactKey = makeFuzzyFactKey(header); final FuzzyFactInfo fuzzyFactInfo = fuzzyFactMap.get(fuzzyFactKey); if (fuzzyFactInfo != null) { fuzzyFactInfo.headerList.remove(header); if (fuzzyFactInfo.headerList.size() == 0) { fuzzyFactMap.remove(fuzzyFactKey); } } final List bitkeyKey = makeBitkeyKey(header); final List headerList = bitkeyMap.get(bitkeyKey); headerList.remove(header); if (headerList.size() == 0) { bitkeyMap.remove(bitkeyKey); factInfo.bitkeyPoset.remove(header.getConstrainedColumnsBitKey()); } headerMap.remove(header); } private void checkThread() { assert thread == Thread.currentThread() : "expected " + thread + ", but was " + Thread.currentThread(); } public static boolean matches( SegmentHeader header, Map coords, List compoundPredicates) { if (!header.compoundPredicates.equals(compoundPredicates)) { return false; } for (Map.Entry entry : coords.entrySet()) { // Check if the segment explicitly excludes this coordinate. final SegmentColumn excludedColumn = header.getExcludedRegion(entry.getKey()); if (excludedColumn != null) { final SortedSet values = excludedColumn.getValues(); if (values == null || values.contains(entry.getValue())) { return false; } } // Check if the dimensionality of the segment intersects // with the coordinate. final SegmentColumn constrainedColumn = header.getConstrainedColumn(entry.getKey()); if (constrainedColumn == null) { // One of the required column/value pairs is not a constraining // column for the header. This will not happen if the header // has been acquired from bitkeyMap, but may happen if a list // of mixed-dimensionality headers is being scanned. return false; } final SortedSet values = constrainedColumn.getValues(); if (values != null && !values.contains(entry.getValue())) { return false; } } return true; } public List intersectRegion( String schemaName, ByteString schemaChecksum, String cubeName, String measureName, String rolapStarFactTableName, SegmentColumn[] region) { checkThread(); final List factKey = makeFuzzyFactKey( schemaName, schemaChecksum, cubeName, rolapStarFactTableName, measureName); final FuzzyFactInfo factInfo = fuzzyFactMap.get(factKey); List list = Collections.emptyList(); if (factInfo == null) { return list; } for (SegmentHeader header : factInfo.headerList) { if (intersects(header, region)) { // Be lazy. Don't allocate a list unless there is at least one // entry. if (list.isEmpty()) { list = new ArrayList(); } list.add(header); } } return list; } private boolean intersects( SegmentHeader header, SegmentColumn[] region) { // most selective condition first if (region.length == 0) { return true; } for (SegmentColumn regionColumn : region) { final SegmentColumn headerColumn = header.getConstrainedColumn(regionColumn.getColumnExpression()); if (headerColumn == null) { // If the segment header doesn't contain a column specified // by the region, then it always implicitly intersects. // This allows flush operations to be valid. return true; } final SortedSet regionValues = regionColumn.getValues(); final SortedSet headerValues = regionColumn.getValues(); if (headerValues == null || regionValues == null) { // This is a wildcard, so it always intersects. return true; } for (Comparable myValue : regionValues) { if (headerValues.contains(myValue)) { return true; } } } return false; } public void printCacheState(PrintWriter pw) { checkThread(); final List> values = new ArrayList>( bitkeyMap.values()); Collections.sort( values, new Comparator>() { public int compare( List o1, List o2) { if (o1.size() == 0) { return -1; } if (o2.size() == 0) { return 1; } return o1.get(0).getUniqueID() .compareTo(o2.get(0).getUniqueID()); } }); for (List key : values) { final List headerList = new ArrayList(key); Collections.sort( headerList, new Comparator() { public int compare(SegmentHeader o1, SegmentHeader o2) { return o1.getUniqueID().compareTo(o2.getUniqueID()); } }); for (SegmentHeader header : headerList) { pw.println(header.getDescription()); } } } public Future getFuture(SegmentHeader header) { checkThread(); return headerMap.get(header).slot; } public SegmentBuilder.SegmentConverter getConverter( String schemaName, ByteString schemaChecksum, String cubeName, String rolapStarFactTableName, String measureName, List compoundPredicates) { checkThread(); final List factKey = makeFactKey( schemaName, schemaChecksum, cubeName, rolapStarFactTableName, measureName, compoundPredicates); final FactInfo factInfo = factMap.get(factKey); if (factInfo == null) { return null; } return factInfo.converter; } public void setConverter( String schemaName, ByteString schemaChecksum, String cubeName, String rolapStarFactTableName, String measureName, List compoundPredicates, SegmentBuilder.SegmentConverter converter) { checkThread(); final List factKey = makeFactKey( schemaName, schemaChecksum, cubeName, rolapStarFactTableName, measureName, compoundPredicates); final FactInfo factInfo = factMap.get(factKey); assert factInfo != null : "should have called 'add' first"; if (factInfo == null) { return; } factInfo.converter = converter; } private List makeBitkeyKey(SegmentHeader header) { return makeBitkeyKey( header.schemaName, header.schemaChecksum, header.cubeName, header.rolapStarFactTableName, header.constrainedColsBitKey, header.measureName, header.compoundPredicates); } private List makeBitkeyKey( String schemaName, ByteString schemaChecksum, String cubeName, String rolapStarFactTableName, BitKey constrainedColsBitKey, String measureName, List compoundPredicates) { return Arrays.asList( schemaName, schemaChecksum, cubeName, rolapStarFactTableName, constrainedColsBitKey, measureName, compoundPredicates); } private List makeFactKey(SegmentHeader header) { return makeFactKey( header.schemaName, header.schemaChecksum, header.cubeName, header.rolapStarFactTableName, header.measureName, header.compoundPredicates); } private List makeFactKey( String schemaName, ByteString schemaChecksum, String cubeName, String rolapStarFactTableName, String measureName, List compoundPredicates) { return Arrays.asList( schemaName, schemaChecksum, cubeName, rolapStarFactTableName, measureName, compoundPredicates); } private List makeFuzzyFactKey(SegmentHeader header) { return makeFuzzyFactKey( header.schemaName, header.schemaChecksum, header.cubeName, header.rolapStarFactTableName, header.measureName); } private List makeFuzzyFactKey( String schemaName, ByteString schemaChecksum, String cubeName, String rolapStarFactTableName, String measureName) { return Arrays.asList( schemaName, schemaChecksum, cubeName, rolapStarFactTableName, measureName); } public List> findRollupCandidates( String schemaName, ByteString schemaChecksum, String cubeName, String measureName, String rolapStarFactTableName, BitKey constrainedColsBitKey, Map coordinates, List compoundPredicates) { final List factKey = makeFactKey( schemaName, schemaChecksum, cubeName, rolapStarFactTableName, measureName, compoundPredicates); final FactInfo factInfo = factMap.get(factKey); if (factInfo == null) { return Collections.emptyList(); } // Iterate over all dimensionalities that are a superset of the desired // columns and for which a segment is known to exist. // // It helps that getAncestors returns dimensionalities with fewer bits // set first. These will contain fewer cells, and therefore be less // effort to roll up. final List> list = new ArrayList>(); final List ancestors = factInfo.bitkeyPoset.getAncestors(constrainedColsBitKey); for (BitKey bitKey : ancestors) { final List bitkeyKey = makeBitkeyKey( schemaName, schemaChecksum, cubeName, rolapStarFactTableName, bitKey, measureName, compoundPredicates); final List headers = bitkeyMap.get(bitkeyKey); assert headers != null : "bitkeyPoset / bitkeyMap inconsistency"; // For columns that are still present after roll up, make sure that // the required value is in the range covered by the segment. // Of the columns that are being aggregated away, are all of // them wildcarded? If so, this segment is a match. If not, we // will need to combine with other segments later. findRollupCandidatesAmong(coordinates, list, headers); } return list; } /** * Finds rollup candidates among a list of headers with the same * dimensionality. * *

For each column that is being aggregated away, we need to ensure that * we have all values of that column. If the column is wildcarded, it's * easy. For example, if we wish to roll up to create Segment1:

* *
Segment1(Year=1997, MaritalStatus=*)
* *

then clearly Segment2:

* *
Segment2(Year=1997, MaritalStatus=*, Gender=*, Nation=*)
* *

has all gender and Nation values. If the values are specified as a * list:

* *
Segment3(Year=1997, MaritalStatus=*, Gender={M, F}, Nation=*)
* *

then we need to check the metadata. We see that Gender has two * distinct values in the database, and we have two values, therefore we * have all of them.

* *

What if we have multiple non-wildcard columns? Consider:

* *
     *     Segment4(Year=1997, MaritalStatus=*, Gender={M},
                    Nation={Mexico, USA})
     *     Segment5(Year=1997, MaritalStatus=*, Gender={F},
                    Nation={USA})
     *     Segment6(Year=1997, MaritalStatus=*, Gender={F, M},
                    Nation={Canada, Mexico, Honduras, Belize})
     * 
* *

The problem is similar to finding whether a collection of rectangular * regions covers a rectangle (or, generalizing to n dimensions, an * n-cube). Or better, find a minimal collection of regions.

* *

Our algorithm solves it by iterating over all combinations of values. * Those combinations are exponential in theory, but tractible in practice, * using the following trick. The algorithm reduces the number of * combinations by looking for values that are always treated the same. In * the above, Canada, Honduras and Belize are always treated the same, so to * prove covering, it is sufficient to prove that all combinations involving * Canada are covered.

* * @param coordinates Coordinates * @param list List to write candidates to * @param headers Headers of candidate segments */ private void findRollupCandidatesAmong( Map coordinates, List> list, List headers) { final List>> matchingHeaders = new ArrayList>>(); headerLoop: for (SegmentHeader header : headers) { // Skip headers that have exclusions. // // TODO: This is a bit harsh. if (!header.getExcludedRegions().isEmpty()) { continue; } List nonWildcards = new ArrayList(); for (SegmentColumn column : header.getConstrainedColumns()) { final SegmentColumn constrainedColumn = header.getConstrainedColumn(column.columnExpression); // REVIEW: How are null key values represented in coordinates? // Assuming that they are represented by null ref. if (coordinates.containsKey(column.columnExpression)) { // Matching column. Will not be aggregated away. Needs // to be in range. Comparable value = coordinates.get(column.columnExpression); if (value == null) { value = RolapUtil.sqlNullValue; } if (constrainedColumn.values != null && !constrainedColumn.values.contains(value)) { continue headerLoop; } } else { // Non-matching column. Will be aggregated away. Needs // to be wildcarded (or some more complicated conditions // to be dealt with later). if (constrainedColumn.values != null) { nonWildcards.add(constrainedColumn); } } } if (nonWildcards.isEmpty()) { list.add(Collections.singletonList(header)); } else { matchingHeaders.add(Pair.of(header, nonWildcards)); } } // Find combinations of segments that can roll up. Need at least two. if (matchingHeaders.size() < 2) { return; } // Collect the list of non-wildcarded columns. final List columnList = new ArrayList(); final List columnNameList = new ArrayList(); for (Pair> pair : matchingHeaders) { for (SegmentColumn column : pair.right) { if (!columnNameList.contains(column.columnExpression)) { final int valueCount = column.getValueCount(); if (valueCount <= 0) { // Impossible to safely roll up. If we don't know the // number of values, we don't know that we have all of // them. return; } columnList.add(column); columnNameList.add(column.columnExpression); } } } // Gather known values of each column. For each value, remember which // segments refer to it. final List> valueLists = new ArrayList>(); for (SegmentColumn column : columnList) { // For each value, which equivalence class it belongs to. final SortedMap valueMap = new TreeMap(RolapUtil.ROLAP_COMPARATOR); int h = -1; for (SegmentHeader header : Pair.leftIter(matchingHeaders)) { ++h; final SegmentColumn column1 = header.getConstrainedColumn( column.columnExpression); if (column1.getValues() == null) { // Wildcard. Mark all values as present. for (Entry entry : valueMap.entrySet()) { for (int pos = 0; pos < entry.getValue().cardinality(); pos++) { entry.getValue().set(pos); } } } else { for (Comparable value : column1.getValues()) { BitSet bitSet = valueMap.get(value); if (bitSet == null) { bitSet = new BitSet(); valueMap.put(value, bitSet); } bitSet.set(h); } } } // Is the number of values discovered equal to the known cardinality // of the column? If not, we can't cover the space. if (valueMap.size() < column.valueCount) { return; } // Build equivalence sets of values. These group together values // that are used identically in segments. // // For instance, given segments Sx over column c, // // S1: c = {1, 2, 3, 4} // S2: c = {3, 4, 5} // S3: c = {3, 6, 7, 8} // // the equivalence classes are: // // E1 = {1, 2} used in {S1} // E2 = {3} used in {S1, S2, S3} // E3 = {4} used in {S1, S2} // E4 = {6, 7, 8} used in {S3} // // The equivalence classes reduce the size of the search space. (In // this case, from 8 values to 4 classes.) We can use any value in a // class to stand for all values. final Map eqclassPrimaryValues = new HashMap(); for (Map.Entry entry : valueMap.entrySet()) { final BitSet bitSet = entry.getValue(); if (!eqclassPrimaryValues.containsKey(bitSet)) { final Comparable value = entry.getKey(); eqclassPrimaryValues.put(bitSet, value); } } valueLists.add( new ArrayList( eqclassPrimaryValues.values())); } // Iterate over every combination of values, and make sure that some // segment can satisfy each. // // TODO: A greedy algorithm would probably be better. Rather than adding // the first segment that contains a particular value combination, add // the segment that contains the most value combinations that we are are // not currently covering. final CartesianProductList tuples = new CartesianProductList(valueLists); final List usedSegments = new ArrayList(); final List unusedSegments = new ArrayList(headers); tupleLoop: for (List tuple : tuples) { // If the value combination is handled by one of the used segments, // great! for (SegmentHeader segment : usedSegments) { if (contains(segment, tuple, columnNameList)) { continue tupleLoop; } } // Does one of the unused segments contain it? Use the first one we // find. for (SegmentHeader segment : unusedSegments) { if (contains(segment, tuple, columnNameList)) { unusedSegments.remove(segment); usedSegments.add(segment); continue tupleLoop; } } // There was a value combination not contained in any of the // segments. Fail. return; } list.add(usedSegments); } private boolean contains( SegmentHeader segment, List values, List columns) { for (int i = 0; i < columns.size(); i++) { String columnName = columns.get(i); final SegmentColumn column = segment.getConstrainedColumn(columnName); final SortedSet valueSet = column.getValues(); if (valueSet != null && !valueSet.contains(values.get(i))) { return false; } } return true; } private static class FactInfo { private static final PartiallyOrderedSet.Ordering ORDERING = new PartiallyOrderedSet.Ordering() { public boolean lessThan(BitKey e1, BitKey e2) { return e2.isSuperSetOf(e1); } }; private final List headerList = new ArrayList(); private final PartiallyOrderedSet bitkeyPoset = new PartiallyOrderedSet(ORDERING); private SegmentBuilder.SegmentConverter converter; FactInfo() { } } private static class FuzzyFactInfo { private final List headerList = new ArrayList(); FuzzyFactInfo() { } } private static class HeaderInfo { private SlotFuture slot; private boolean removeAfterLoad; HeaderInfo(SlotFuture slot) { this.slot = slot; } } } // End SegmentCacheIndexImpl.java mondrian-3.4.1/src/main/mondrian/rolap/cache/HardSmartCache.java0000644000175000017500000000206511735330606024407 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.cache; import java.util.*; /** * An implementation of {@link SmartCache} that uses hard * references. Used for testing. */ public class HardSmartCache implements SmartCache { Map cache = new HashMap(); public V put(K key, V value) { return cache.put(key, value); } public V get(K key) { return cache.get(key); } public V remove(K key) { return cache.remove(key); } public void clear() { cache.clear(); } public int size() { return cache.size(); } public Iterator> iterator() { return cache.entrySet().iterator(); } } // End HardSmartCache.java mondrian-3.4.1/src/main/mondrian/rolap/cache/SegmentCacheIndex.java0000644000175000017500000001527711735330606025125 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.cache; import mondrian.rolap.BitKey; import mondrian.rolap.agg.SegmentBuilder; import mondrian.spi.*; import mondrian.util.ByteString; import java.io.PrintWriter; import java.util.List; import java.util.Map; import java.util.concurrent.Future; /** * Data structure that identifies which segments contain cells. * *

Not thread-safe.

* * @author Julian Hyde */ public interface SegmentCacheIndex { /** * Identifies the segment headers that contain a given cell. * * @param schemaName Schema name * @param schemaChecksum Schema checksum * @param cubeName Cube name * @param measureName Measure name * @param rolapStarFactTableName Fact table table * @param constrainedColsBitKey Bit key * @param coordinates Coordinates * @param compoundPredicates Compound predicates * @return Empty list if not found; never null */ List locate( String schemaName, ByteString schemaChecksum, String cubeName, String measureName, String rolapStarFactTableName, BitKey constrainedColsBitKey, Map coordinates, List compoundPredicates); /** * Returns a list of segments that can be rolled up to satisfy a given * cell request. * * @param schemaName Schema name * @param schemaChecksum Schema checksum * @param cubeName Cube name * @param measureName Measure name * @param rolapStarFactTableName Fact table table * @param constrainedColsBitKey Bit key * @param coordinates Coordinates * @param compoundPredicates Compound predicates * * @return List of candidates; each element is a list of headers that, when * combined using union, are sufficient to answer the given cell request */ List> findRollupCandidates( String schemaName, ByteString schemaChecksum, String cubeName, String measureName, String rolapStarFactTableName, BitKey constrainedColsBitKey, Map coordinates, List compoundPredicates); /** * Finds a list of headers that intersect a given region. * *

This method is used to find out which headers need to be trimmed * or removed during a flush.

* * @param schemaName Schema name * @param schemaChecksum Schema checksum * @param cubeName Cube name * @param measureName Measure name * @param rolapStarFactTableName Fact table table * @param region Region * @return List of intersecting headers */ public List intersectRegion( String schemaName, ByteString schemaChecksum, String cubeName, String measureName, String rolapStarFactTableName, SegmentColumn[] region); /** * Adds a header to the index. * *

If {@code loading} is true, there must follow a call to * {@link #loadSucceeded} or {@link #loadFailed}.

* * @param header Segment header * @param loading Whether segment is pending a load from SQL * @param converter Segment converter */ void add( SegmentHeader header, boolean loading, SegmentBuilder.SegmentConverter converter); /** * Changes the state of a header from loading to loaded. * *

The segment must have previously been added by calling {@link #add} * with a not-null value of the {@code bodyFuture} parameter; * neither {@code loadSucceeded} nor {@link #loadFailed} must have been * called.

* *

Informs anyone waiting on the future supplied with * {@link #add}.

* * @param header Segment header * @param body Segment body */ void loadSucceeded( SegmentHeader header, SegmentBody body); /** * Notifies the segment index that a segment failed to load, and removes the * segment from the index. * *

The segment must have previously been added using {@link #add} * with a not-null value of the {@code bodyFuture} parameter; * neither {@link #loadSucceeded} nor {@code loadFailed} must have been * called.

* *

Informs anyone waiting on the future supplied with * {@link #add}.

* * @param header Header * @param throwable Error message */ void loadFailed( SegmentHeader header, Throwable throwable); /** * Removes a header from the index. * * @param header Segment header */ void remove(SegmentHeader header); /** * Prints the state of the cache to the given writer. * * @param pw Print writer */ void printCacheState(PrintWriter pw); /** * Returns a future slot for a segment body, if a segment is currently * loading, otherwise null. This is the method to use to get segments * 'hot out of the oven'. * * @param header Segment header * @return Slot, or null */ Future getFuture(SegmentHeader header); /** * Returns a converter that can convert the given header to internal * format. * * @param schemaName Schema name * @param schemaChecksum Schema checksum * @param cubeName Cube name * @param rolapStarFactTableName Fact table * @param measureName Measure name * @param compoundPredicates Compound predicates * @return Converter */ SegmentBuilder.SegmentConverter getConverter( String schemaName, ByteString schemaChecksum, String cubeName, String rolapStarFactTableName, String measureName, List compoundPredicates); /** * Sets a converter that can convert headers in for a given measure to * internal format. * * @param schemaName Schema name * @param schemaChecksum Schema checksum * @param cubeName Cube name * @param rolapStarFactTableName Fact table * @param measureName Measure name * @param compoundPredicates Compound predicates * @param converter Converter to store */ void setConverter( String schemaName, ByteString schemaChecksum, String cubeName, String rolapStarFactTableName, String measureName, List compoundPredicates, SegmentBuilder.SegmentConverter converter); } // End SegmentCacheIndex.java mondrian-3.4.1/src/main/mondrian/rolap/cache/MemorySegmentCache.java0000644000175000017500000000764511735330606025326 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.cache; import mondrian.spi.*; import java.lang.ref.SoftReference; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; /** * Implementation of {@link mondrian.spi.SegmentCache} that stores segments * in memory. * *

Segments are held via soft references, so the garbage collector can remove * them if it sees fit.

* *

Not thread safe.

* * @author Julian Hyde */ public class MemorySegmentCache implements SegmentCache { private final Map> map = new HashMap>(); private final List listeners = new CopyOnWriteArrayList(); public SegmentBody get(SegmentHeader header) { final SoftReference ref = map.get(header); if (ref == null) { return null; } final SegmentBody body = ref.get(); if (body == null) { map.remove(header); } return body; } public boolean contains(SegmentHeader header) { final SoftReference ref = map.get(header); if (ref == null) { return false; } final SegmentBody body = ref.get(); if (body == null) { map.remove(header); return false; } return true; } public List getSegmentHeaders() { return new ArrayList(map.keySet()); } public boolean put(final SegmentHeader header, SegmentBody body) { // REVIEW: What's the difference between returning false // and throwing an exception? map.put(header, new SoftReference(body)); fireSegmentCacheEvent( new SegmentCache.SegmentCacheListener.SegmentCacheEvent() { public boolean isLocal() { return true; } public SegmentHeader getSource() { return header; } public EventType getEventType() { return SegmentCacheListener.SegmentCacheEvent .EventType.ENTRY_CREATED; } }); return true; // success } public boolean remove(final SegmentHeader header) { final boolean result = map.remove(header) != null; if (result) { fireSegmentCacheEvent( new SegmentCache.SegmentCacheListener.SegmentCacheEvent() { public boolean isLocal() { return true; } public SegmentHeader getSource() { return header; } public EventType getEventType() { return SegmentCacheListener.SegmentCacheEvent .EventType.ENTRY_DELETED; } }); } return result; } public void tearDown() { map.clear(); listeners.clear(); } public void addListener(SegmentCacheListener listener) { listeners.add(listener); } public void removeListener(SegmentCacheListener listener) { listeners.remove(listener); } public boolean supportsRichIndex() { return true; } public void fireSegmentCacheEvent( SegmentCache.SegmentCacheListener.SegmentCacheEvent evt) { for (SegmentCacheListener listener : listeners) { listener.handle(evt); } } } // End MemorySegmentCache.java mondrian-3.4.1/src/main/mondrian/rolap/cache/SoftSmartCache.java0000644000175000017500000000773011735330606024450 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2012 Pentaho // All Rights Reserved. */ package mondrian.rolap.cache; import mondrian.util.Pair; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.*; /** * A map with soft references that is cleaned up in regular intervals. *

* There is no contains(key) method because it makes no sense - after * contains() returns true, the garbage collector may remove * the value that was contained. Instead the code should call get() and * keep a reference to the value to prevent garbage collection. * * @author av * @since Nov 3, 2005 */ public class SoftSmartCache implements SmartCache { private final Map cache = new HashMap(); private final ReferenceQueue queue = new ReferenceQueue(); /** * an entry in the cache that contains the key for * the cache map to remove the entry when its value * has been garbage collected * * @author rk * @since Nov 7, 2005 */ class CacheReference extends SoftReference { K key; public CacheReference(K key, V value) { super(value, queue); this.key = key; } public String toString() { return String.valueOf(get()); } } /* (non-Javadoc) * @see mondrian.rolap.cache.SmartCache#put(java.lang.Object, java.lang.Object) */ public synchronized V put(K key, V value) { // remove garbage collected entries from cache CacheReference ref; while ((ref = (CacheReference) queue.poll()) != null) { cache.remove(ref.key); } // put new entry into cache ref = new CacheReference(key, value); ref = cache.put(key, ref); if (ref != null) { return ref.get(); } return null; } public synchronized V get(K key) { CacheReference ref = cache.get(key); if (ref == null) { return null; } V value = ref.get(); if (value == null) { cache.remove(key); } return value; } public V remove(K key) { final CacheReference ref = cache.remove(key); return ref == null ? null : ref.get(); } public void clear() { cache.clear(); } public int size() { return cache.size(); } public Iterator> iterator() { final Iterator> cacheIterator = cache.entrySet().iterator(); return new Iterator>() { private Map.Entry entry; public boolean hasNext() { if (entry != null) { return true; } while (cacheIterator.hasNext()) { Map.Entry cacheEntry = cacheIterator.next(); // skip over entries that have been garbage collected final V value = cacheEntry.getValue().get(); if (value != null) { // Would use AbstractMap.SimpleEntry but it's not public // until JDK 1.6. entry = new Pair(cacheEntry.getKey(), value); return true; } } return false; } public Map.Entry next() { Map.Entry entry = this.entry; this.entry = null; return entry; } public void remove() { cacheIterator.remove(); } }; } } // End SoftSmartCache.java mondrian-3.4.1/src/main/mondrian/rolap/RolapProfilingEvaluator.java0000644000175000017500000001747411735330606025357 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.olap.*; import mondrian.olap.type.SetType; import mondrian.olap.type.Type; import java.util.*; /** * Evaluator that collects profiling information as it evaluates expressions. * *

TODO: Cleanup tasks as part of explain/profiling project: * *

1. Obsolete AbstractCalc.calcs member, AbstractCalc.getCalcs(), and * Calc[] constructor parameter to many Calc subclasses. Store the * tree structure (children of a calc, parent of a calc) in * RolapEvaluatorRoot.compiledExps. * *

Rationale: Children calcs are * used in about 50 places, but mostly for dependency-checking (e.g. * {@link mondrian.calc.impl.AbstractCalc#anyDepends}). A few places uses * the calcs array but should use more strongly typed members. e.g. * FilterFunDef.MutableMemberIterCalc should have data members * 'MemberListCalc listCalc' and 'BooleanCalc conditionCalc'. * *

2. Split Query into parse tree, plan, statement. Fits better into the * createStatement - prepare - execute JDBC lifecycle. Currently Query has * aspects of all of these, and some other state is held in RolapResult * (computed in the constructor, unfortunately) and RolapEvaluatorRoot. * This cleanup may not be essential for the explain/profiling task but * should happen soon afterwards. * * @author jhyde * @since October, 2010 */ public class RolapProfilingEvaluator extends RolapEvaluator { /** * Creates a profiling evaluator. * * @param root Shared context between this evaluator and its children */ RolapProfilingEvaluator(RolapEvaluatorRoot root) { super(root); } /** * Creates a child evaluator. * * @param root Root evaluation context * @param evaluator Parent evaluator */ private RolapProfilingEvaluator( RolapEvaluatorRoot root, RolapProfilingEvaluator evaluator, List> aggregationList) { super( root, evaluator, aggregationList); } @Override protected RolapEvaluator _push(List> aggregationList) { return new RolapProfilingEvaluator(root, this, aggregationList); } /** * Expression compiler which introduces dependency testing. * *

It also checks that the caller does not modify lists unless it has * explicitly asked for a mutable list. */ static class ProfilingEvaluatorCompiler extends DelegatingExpCompiler { ProfilingEvaluatorCompiler(ExpCompiler compiler) { super(compiler); } protected Calc afterCompile(Exp exp, Calc calc, boolean mutable) { calc = super.afterCompile(exp, calc, mutable); if (calc == null) { return null; } if (calc.getType() instanceof SetType) { return new ProfilingIterCalc( exp, calc); } else { return new ProfilingScalarCalc( exp, calc); } } } /** * Compiled expression that wraps a list or iterator expression and gathers * profiling information. */ private static class ProfilingIterCalc extends GenericIterCalc { private final Calc calc; private int callCount; private long callMillis; private long elementCount; private long elementSquaredCount; protected ProfilingIterCalc(Exp exp, Calc calc) { super(exp, new Calc[] {calc}); this.calc = calc; } @Override public boolean isWrapperFor(Class iface) { return calc.isWrapperFor(iface); } @Override public T unwrap(Class iface) { return calc.unwrap(iface); } @Override public SetType getType() { return (SetType) calc.getType(); } @Override public ResultStyle getResultStyle() { return calc.getResultStyle(); } @Override public boolean dependsOn(Hierarchy hierarchy) { return calc.dependsOn(hierarchy); } public Object evaluate(Evaluator evaluator) { ++callCount; long start = System.currentTimeMillis(); final Object o = calc.evaluate(evaluator); long end = System.currentTimeMillis(); callMillis += (end - start); if (o instanceof Collection) { long size = ((Collection) o).size(); elementCount += size; elementSquaredCount += size * size; } return o; } @Override public void accept(CalcWriter calcWriter) { // Populate arguments with statistics. final Map argumentMap = new LinkedHashMap(); if (calcWriter.enableProfiling()) { argumentMap.put("callCount", callCount); argumentMap.put("callMillis", callMillis); argumentMap.put("elementCount", elementCount); argumentMap.put("elementSquaredCount", elementSquaredCount); } calcWriter.setParentArgs(calc, argumentMap); // Invoke writer on our child calc. This node won't appear in the // query plan, but the arguments we just created will appear as // arguments of the child calc. calc.accept(calcWriter); } } /** * Compiled expression that wraps a scalar expression and gathers profiling * information. */ private static class ProfilingScalarCalc extends GenericCalc { private final Calc calc; private int callCount; private long callMillis; ProfilingScalarCalc(Exp exp, Calc calc) { super(exp, new Calc[] {calc}); this.calc = calc; } @Override public boolean isWrapperFor(Class iface) { return calc.isWrapperFor(iface); } @Override public T unwrap(Class iface) { return calc.unwrap(iface); } @Override public Type getType() { return calc.getType(); } @Override public Calc[] getCalcs() { return ((AbstractCalc) calc).getCalcs(); } @Override public boolean dependsOn(Hierarchy hierarchy) { return calc.dependsOn(hierarchy); } public Object evaluate(Evaluator evaluator) { ++callCount; long start = System.currentTimeMillis(); final Object o = calc.evaluate(evaluator); long end = System.currentTimeMillis(); callMillis += (end - start); return o; } @Override public void accept(CalcWriter calcWriter) { final Map argumentMap = new LinkedHashMap(); if (calcWriter.enableProfiling()) { argumentMap.put("callCount", callCount); argumentMap.put("callMillis", callMillis); } calcWriter.setParentArgs(calc, argumentMap); // Invoke writer on our child calc. This node won't appear in the // query plan, but the arguments we just created will appear as // arguments of the child calc. calc.accept(calcWriter); } } } // End RolapProfilingEvaluator.java mondrian-3.4.1/src/main/mondrian/rolap/RolapNativeFilter.java0000644000175000017500000001326011735330606024124 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.rolap.aggmatcher.AggStar; import mondrian.rolap.sql.*; import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; /** * Computes a Filter(set, condition) in SQL. * * @author av * @since Nov 21, 2005 */ public class RolapNativeFilter extends RolapNativeSet { public RolapNativeFilter() { super.setEnabled( MondrianProperties.instance().EnableNativeFilter.get()); } static class FilterConstraint extends SetConstraint { Exp filterExpr; public FilterConstraint( CrossJoinArg[] args, RolapEvaluator evaluator, Exp filterExpr) { super(args, evaluator, true); this.filterExpr = filterExpr; } /** * {@inheritDoc} * *

A FilterConstraint always needs to join the fact table because we * want to evaluate the filter expression against a fact. */ protected boolean isJoinRequired() { return true; } public void addConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar) { // Use aggregate table to generate filter condition RolapNativeSql sql = new RolapNativeSql( sqlQuery, aggStar, getEvaluator(), args[0].getLevel()); String filterSql = sql.generateFilterCondition(filterExpr); sqlQuery.addHaving(filterSql); super.addConstraint(sqlQuery, baseCube, aggStar); } public Object getCacheKey() { List key = new ArrayList(); key.add(super.getCacheKey()); // Note required to use string in order for caching to work if (filterExpr != null) { key.add(filterExpr.toString()); } return key; } } protected boolean restrictMemberTypes() { return true; } NativeEvaluator createEvaluator( RolapEvaluator evaluator, FunDef fun, Exp[] args) { if (!isEnabled()) { return null; } if (!FilterConstraint.isValidContext( evaluator, restrictMemberTypes())) { return null; } // is this "Filter(, )" String funName = fun.getName(); if (!"Filter".equalsIgnoreCase(funName)) { return null; } if (args.length != 2) { return null; } // extract the set expression List allArgs = crossJoinArgFactory().checkCrossJoinArg(evaluator, args[0]); // checkCrossJoinArg returns a list of CrossJoinArg arrays. The first // array is the CrossJoin dimensions. The second array, if any, // contains additional constraints on the dimensions. If either the // list or the first array is null, then native cross join is not // feasible. if (allArgs == null || allArgs.isEmpty() || allArgs.get(0) == null) { return null; } CrossJoinArg[] cjArgs = allArgs.get(0); if (isPreferInterpreter(cjArgs, false)) { return null; } // extract "order by" expression SchemaReader schemaReader = evaluator.getSchemaReader(); DataSource ds = schemaReader.getDataSource(); // generate the WHERE condition // Need to generate where condition here to determine whether // or not the filter condition can be created. The filter // condition could change to use an aggregate table later in evaluation SqlQuery sqlQuery = SqlQuery.newQuery(ds, "NativeFilter"); RolapNativeSql sql = new RolapNativeSql( sqlQuery, null, evaluator, cjArgs[0].getLevel()); final Exp filterExpr = args[1]; String filterExprStr = sql.generateFilterCondition(filterExpr); if (filterExprStr == null) { return null; } // Check to see if evaluator contains a calculated member. This is // necessary due to the SqlConstraintsUtils.addContextConstraint() // method which gets called when generating the native SQL. if (SqlConstraintUtils.containsCalculatedMember( evaluator.getNonAllMembers())) { return null; } LOGGER.debug("using native filter"); final int savepoint = evaluator.savepoint(); overrideContext(evaluator, cjArgs, sql.getStoredMeasure()); // Now construct the TupleConstraint that contains both the CJ // dimensions and the additional filter on them. CrossJoinArg[] combinedArgs = cjArgs; if (allArgs.size() == 2) { CrossJoinArg[] predicateArgs = allArgs.get(1); if (predicateArgs != null) { // Combined the CJ and the additional predicate args. combinedArgs = Util.appendArrays(cjArgs, predicateArgs); } } TupleConstraint constraint = new FilterConstraint(combinedArgs, evaluator, filterExpr); evaluator.restore(savepoint); return new SetEvaluator(cjArgs, schemaReader, constraint); } } // End RolapNativeFilter.java mondrian-3.4.1/src/main/mondrian/rolap/RolapCubeLevel.java0000644000175000017500000005106111735330606023377 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.rolap.agg.*; import mondrian.spi.MemberFormatter; /** * RolapCubeLevel wraps a RolapLevel for a specific Cube. * * @author Will Gorman, 19 October 2007 */ public class RolapCubeLevel extends RolapLevel { private final RolapLevel rolapLevel; private RolapStar.Column starKeyColumn = null; /** * For a parent-child hierarchy with a closure provided by the schema, * the equivalent level in the closed hierarchy; otherwise null. */ private RolapCubeLevel closedPeerCubeLevel; protected LevelReader levelReader; private final RolapCubeHierarchy cubeHierarchy; private final RolapCubeDimension cubeDimension; private final RolapCube cube; private final RolapCubeLevel parentCubeLevel; private RolapCubeLevel childCubeLevel; public RolapCubeLevel(RolapLevel level, RolapCubeHierarchy cubeHierarchy) { super( cubeHierarchy, level.getName(), level.getCaption(), level.isVisible(), level.getDescription(), level.getDepth(), level.getKeyExp(), level.getNameExp(), level.getCaptionExp(), level.getOrdinalExp(), level.getParentExp(), level.getNullParentValue(), null, level.getProperties(), level.getFlags(), level.getDatatype(), level.getInternalType(), level.getHideMemberCondition(), level.getLevelType(), "" + level.getApproxRowCount(), level.getAnnotationMap()); this.rolapLevel = level; this.cubeHierarchy = cubeHierarchy; this.cubeDimension = (RolapCubeDimension) cubeHierarchy.getDimension(); cube = cubeDimension.getCube(); parentCubeLevel = (RolapCubeLevel) super.getParentLevel(); if (parentCubeLevel != null) { parentCubeLevel.childCubeLevel = this; } MondrianDef.RelationOrJoin hierarchyRel = cubeHierarchy.getRelation(); keyExp = convertExpression(level.getKeyExp(), hierarchyRel); nameExp = convertExpression(level.getNameExp(), hierarchyRel); captionExp = convertExpression(level.getCaptionExp(), hierarchyRel); ordinalExp = convertExpression(level.getOrdinalExp(), hierarchyRel); parentExp = convertExpression(level.getParentExp(), hierarchyRel); } void init(MondrianDef.CubeDimension xmlDimension) { if (isAll()) { this.levelReader = new AllLevelReaderImpl(); } else if (getLevelType() == LevelType.Null) { this.levelReader = new NullLevelReader(); } else if (rolapLevel.xmlClosure != null) { RolapDimension dimension = (RolapDimension) rolapLevel.getClosedPeer().getHierarchy().getDimension(); RolapCubeDimension cubeDimension = new RolapCubeDimension( getCube(), dimension, xmlDimension, getDimension().getName() + "$Closure", -1, getCube().hierarchyList, getDimension().isHighCardinality()); /* RME HACK WG: Note that the reason for registering this usage is so that when registerDimension is called, the hierarchy is registered successfully to the star. This type of hack will go away once HierarchyUsage is phased out */ if (! getCube().isVirtual()) { getCube().createUsage( (RolapCubeHierarchy) cubeDimension.getHierarchies()[0], xmlDimension); } cubeDimension.init(xmlDimension); getCube().registerDimension(cubeDimension); closedPeerCubeLevel = (RolapCubeLevel) cubeDimension.getHierarchies()[0].getLevels()[1]; if (!getCube().isVirtual()) { getCube().closureColumnBitKey.set( closedPeerCubeLevel.starKeyColumn.getBitPosition()); } this.levelReader = new ParentChildLevelReaderImpl(this); } else { this.levelReader = new RegularLevelReader(this); } } /** * Converts an expression to new aliases if necessary. * * @param exp the expression to convert * @param rel the parent relation * @return returns the converted expression */ private MondrianDef.Expression convertExpression( MondrianDef.Expression exp, MondrianDef.RelationOrJoin rel) { if (getHierarchy().isUsingCubeFact()) { // no conversion necessary return exp; } else if (exp == null || rel == null) { return null; } else if (exp instanceof MondrianDef.Column) { MondrianDef.Column col = (MondrianDef.Column)exp; if (rel instanceof MondrianDef.Table) { return new MondrianDef.Column( ((MondrianDef.Table) rel).getAlias(), col.getColumnName()); } else if (rel instanceof MondrianDef.Join || rel instanceof MondrianDef.Relation) { // need to determine correct name of alias for this level. // this may be defined in level // col.table String alias = getHierarchy().lookupAlias(col.getTableAlias()); return new MondrianDef.Column(alias, col.getColumnName()); } } else if (exp instanceof MondrianDef.ExpressionView) { // this is a limitation, in the future, we may need // to replace the table name in the sql provided // with the new aliased name return exp; } throw new RuntimeException( "conversion of Class " + exp.getClass() + " unsupported at this time"); } public void setStarKeyColumn(RolapStar.Column column) { starKeyColumn = column; } /** * This is the RolapStar.Column that is related to this RolapCubeLevel * * @return the RolapStar.Column related to this RolapCubeLevel */ public RolapStar.Column getStarKeyColumn() { return starKeyColumn; } LevelReader getLevelReader() { return levelReader; } /** * this method returns the RolapStar.Column if non-virtual, * if virtual, find the base cube level and return it's * column * * @param baseCube the base cube for the specificed virtual level * @return the RolapStar.Column related to this RolapCubeLevel */ public RolapStar.Column getBaseStarKeyColumn(RolapCube baseCube) { RolapStar.Column column = null; if (getCube().isVirtual() && baseCube != null) { RolapCubeLevel lvl = baseCube.findBaseCubeLevel(this); if (lvl != null) { column = lvl.getStarKeyColumn(); } } else { column = getStarKeyColumn(); } return column; } /** * Returns the (non virtual) cube this level belongs to. * * @return cube */ public final RolapCube getCube() { return cube; } // override with stricter return type public final RolapCubeDimension getDimension() { return cubeDimension; } // override with stricter return type public final RolapCubeHierarchy getHierarchy() { return cubeHierarchy; } // override with stricter return type public final RolapCubeLevel getChildLevel() { return childCubeLevel; } // override with stricter return type public final RolapCubeLevel getParentLevel() { return parentCubeLevel; } public String getCaption() { return rolapLevel.getCaption(); } public void setCaption(String caption) { // Cannot set the caption on the underlying level; other cube levels // might be using it. throw new UnsupportedOperationException(); } /** * Returns the underlying level. * * @return Underlying level */ public RolapLevel getRolapLevel() { return rolapLevel; } public boolean equals(RolapCubeLevel level) { if (this == level) { return true; } // verify the levels are part of the same hierarchy return super.equals(level) && getCube().equals(level.getCube()); } boolean hasClosedPeer() { return closedPeerCubeLevel != null; } public RolapCubeLevel getClosedPeer() { return closedPeerCubeLevel; } public MemberFormatter getMemberFormatter() { return rolapLevel.getMemberFormatter(); } /** * Encapsulation of the difference between levels in terms of how * constraints are generated. There are implementations for 'all' levels, * the 'null' level, parent-child levels and regular levels. */ interface LevelReader { /** * Adds constraints to a cell request for a member of this level. * * @param member Member to be constrained * @param baseCube base cube if virtual level * @param request Request to be constrained * * @return true if request is unsatisfiable (e.g. if the member is the * null member) */ boolean constrainRequest( RolapCubeMember member, RolapCube baseCube, CellRequest request); /** * Adds constraints to a cache region for a member of this level. * * @param predicate Predicate * @param baseCube base cube if virtual level * @param cacheRegion Cache region to be constrained */ void constrainRegion( StarColumnPredicate predicate, RolapCube baseCube, RolapCacheRegion cacheRegion); } /** * Level reader for a regular level. */ static final class RegularLevelReader implements LevelReader { private RolapCubeLevel cubeLevel; RegularLevelReader( RolapCubeLevel cubeLevel) { this.cubeLevel = cubeLevel; } public boolean constrainRequest( RolapCubeMember member, RolapCube baseCube, CellRequest request) { assert member.getLevel() == cubeLevel; if (member.getKey() == null) { if (member == member.getHierarchy().getNullMember()) { // cannot form a request if one of the members is null return true; } else { throw Util.newInternal("why is key null?"); } } RolapStar.Column column = cubeLevel.getBaseStarKeyColumn(baseCube); 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 (or for the // default member if the hierarchy has no 'all' member) return member != cubeLevel.hierarchy.getDefaultMember() || cubeLevel.hierarchy.hasAll(); } final StarColumnPredicate predicate; if (member.isCalculated() && !member.isParentChildLeaf()) { predicate = null; } else { predicate = new ValueColumnPredicate(column, member.getKey()); } // use the member as constraint; this will give us some // optimization potential request.addConstrainedColumn(column, predicate); if (request.extendedContext && cubeLevel.getNameExp() != null) { final RolapStar.Column nameColumn = column.getNameColumn(); assert nameColumn != null; request.addConstrainedColumn(nameColumn, null); } if (member.isCalculated()) { return false; } // If member is unique without reference to its parent, // no further constraint is required. if (cubeLevel.isUnique()) { return false; } // Constrain the parent member, if any. RolapCubeMember parent = member.getParentMember(); while (true) { if (parent == null) { return false; } RolapCubeLevel level = parent.getLevel(); final LevelReader levelReader = level.levelReader; if (levelReader == this) { // 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. parent = parent.getParentMember(); continue; } return levelReader.constrainRequest( parent, baseCube, request); } } public void constrainRegion( StarColumnPredicate predicate, RolapCube baseCube, RolapCacheRegion cacheRegion) { RolapStar.Column column = cubeLevel.getBaseStarKeyColumn(baseCube); 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 (or for the // default member if the hierarchy has no 'all' member) return; } if (predicate instanceof MemberColumnPredicate) { MemberColumnPredicate memberColumnPredicate = (MemberColumnPredicate) predicate; RolapMember member = memberColumnPredicate.getMember(); assert member.getLevel() == cubeLevel; assert !member.isCalculated(); assert memberColumnPredicate.getMember().getKey() != null; assert !member.isNull(); // use the member as constraint, this will give us some // optimization potential cacheRegion.addPredicate(column, predicate); return; } else if (predicate instanceof RangeColumnPredicate) { RangeColumnPredicate rangeColumnPredicate = (RangeColumnPredicate) predicate; final ValueColumnPredicate lowerBound = rangeColumnPredicate.getLowerBound(); RolapMember lowerMember; if (lowerBound == null) { lowerMember = null; } else if (lowerBound instanceof MemberColumnPredicate) { MemberColumnPredicate memberColumnPredicate = (MemberColumnPredicate) lowerBound; lowerMember = memberColumnPredicate.getMember(); } else { throw new UnsupportedOperationException(); } final ValueColumnPredicate upperBound = rangeColumnPredicate.getUpperBound(); RolapMember upperMember; if (upperBound == null) { upperMember = null; } else if (upperBound instanceof MemberColumnPredicate) { MemberColumnPredicate memberColumnPredicate = (MemberColumnPredicate) upperBound; upperMember = memberColumnPredicate.getMember(); } else { throw new UnsupportedOperationException(); } MemberTuplePredicate predicate2 = new MemberTuplePredicate( baseCube, lowerMember, !rangeColumnPredicate.getLowerInclusive(), upperMember, !rangeColumnPredicate.getUpperInclusive()); // use the member as constraint, this will give us some // optimization potential cacheRegion.addPredicate(predicate2); return; } // Unknown type of constraint. throw new UnsupportedOperationException(); } } /** * Level reader for a parent-child level which has a closed peer level. */ // final for performance static final class ParentChildLevelReaderImpl implements LevelReader { private final RegularLevelReader regularLevelReader; private final RolapCubeLevel closedPeerCubeLevel; private final RolapLevel closedPeerLevel; private final RolapMember wrappedAllMember; private final RolapCubeMember allMember; ParentChildLevelReaderImpl(RolapCubeLevel cubeLevel) { this.regularLevelReader = new RegularLevelReader(cubeLevel); // inline a bunch of fields for performance this.closedPeerCubeLevel = cubeLevel.closedPeerCubeLevel; this.closedPeerLevel = cubeLevel.rolapLevel.getClosedPeer(); this.wrappedAllMember = (RolapMember) closedPeerLevel.getHierarchy().getDefaultMember(); this.allMember = closedPeerCubeLevel.getHierarchy().getDefaultMember(); assert allMember.isAll(); } public boolean constrainRequest( RolapCubeMember member, RolapCube baseCube, CellRequest request) { // Replace a parent/child level by its closed equivalent, when // available; this is always valid, and improves performance by // enabling the database to compute aggregates. if (member.getDataMember() == null) { // Member has no data member because it IS the data // member of a parent-child hierarchy member. Leave // it be. We don't want to aggregate. return regularLevelReader.constrainRequest( member, baseCube, request); } else if (request.drillThrough) { return regularLevelReader.constrainRequest( member.getDataMember(), baseCube, request); } else { // isn't creating a member on the fly a bad idea? RolapMember wrappedMember = new RolapMemberBase( wrappedAllMember, closedPeerLevel, member.getKey()); member = new RolapCubeMember( allMember, wrappedMember, closedPeerCubeLevel); return closedPeerCubeLevel.getLevelReader().constrainRequest( member, baseCube, request); } } public void constrainRegion( StarColumnPredicate predicate, RolapCube baseCube, RolapCacheRegion cacheRegion) { throw new UnsupportedOperationException(); } } /** * Level reader for the level which contains the 'all' member. */ static final class AllLevelReaderImpl implements LevelReader { public boolean constrainRequest( RolapCubeMember member, RolapCube baseCube, CellRequest request) { // We don't need to apply any constraints. return false; } public void constrainRegion( StarColumnPredicate predicate, RolapCube baseCube, RolapCacheRegion cacheRegion) { // We don't need to apply any constraints. } } /** * Level reader for the level which contains the null member. */ static final class NullLevelReader implements LevelReader { public boolean constrainRequest( RolapCubeMember member, RolapCube baseCube, CellRequest request) { return true; } public void constrainRegion( StarColumnPredicate predicate, RolapCube baseCube, RolapCacheRegion cacheRegion) { } } } // End RolapCubeLevel.java mondrian-3.4.1/src/main/mondrian/rolap/MeasureMemberSource.java0000644000175000017500000000205511735330606024444 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. // // jhyde, 21 December, 2001 */ package mondrian.rolap; import java.util.List; /** * A MeasureMemberSource implements the {@link MemberReader} * interface for the special Measures dimension. * *

Usually when a member is added to the context, the resulting SQL * statement has extra filters in its WHERE clause, but for members from this * source, but this implementation columns are added to the SELECT list. * * @author jhyde * @since 21 December, 2001 */ class MeasureMemberSource extends ArrayMemberSource { MeasureMemberSource( RolapHierarchy hierarchy, List members) { super(hierarchy, members); } } // End MeasureMemberSource.java mondrian-3.4.1/src/main/mondrian/rolap/MemberCacheHelper.java0000644000175000017500000002073611735330606024033 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // Copyright (C) 2004-2005 TONBELLER AG // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.Level; import mondrian.olap.Util; import mondrian.rolap.cache.SmartCache; import mondrian.rolap.cache.SoftSmartCache; import mondrian.rolap.sql.MemberChildrenConstraint; import mondrian.rolap.sql.TupleConstraint; import mondrian.spi.DataSourceChangeListener; import mondrian.util.Pair; import java.util.*; /** * Encapsulation of member caching. * * @author Will Gorman */ public class MemberCacheHelper implements MemberCache { private final SqlConstraintFactory sqlConstraintFactory = SqlConstraintFactory.instance(); /** maps a parent member to a list of its children */ final SmartMemberListCache> mapMemberToChildren; /** a cache for all members to ensure uniqueness */ SmartCache mapKeyToMember; RolapHierarchy rolapHierarchy; DataSourceChangeListener changeListener; /** maps a level to its members */ final SmartMemberListCache> mapLevelToMembers; /** * Creates a MemberCacheHelper. * * @param rolapHierarchy Hierarchy */ public MemberCacheHelper(RolapHierarchy rolapHierarchy) { this.rolapHierarchy = rolapHierarchy; this.mapLevelToMembers = new SmartMemberListCache>(); this.mapKeyToMember = new SoftSmartCache(); this.mapMemberToChildren = new SmartMemberListCache>(); if (rolapHierarchy != null) { changeListener = rolapHierarchy.getRolapSchema().getDataSourceChangeListener(); } else { changeListener = null; } } // implement MemberCache // synchronization: Must synchronize, because uses mapKeyToMember public synchronized RolapMember getMember( Object key, boolean mustCheckCacheStatus) { if (mustCheckCacheStatus) { checkCacheStatus(); } return mapKeyToMember.get(key); } // implement MemberCache // synchronization: Must synchronize, because modifies mapKeyToMember public synchronized Object putMember(Object key, RolapMember value) { return mapKeyToMember.put(key, value); } // implement MemberCache public Object makeKey(RolapMember parent, Object key) { return new MemberKey(parent, key); } // implement MemberCache // synchronization: Must synchronize, because modifies mapKeyToMember public synchronized RolapMember getMember(Object key) { return getMember(key, true); } public synchronized void checkCacheStatus() { if (changeListener != null) { if (changeListener.isHierarchyChanged(rolapHierarchy)) { flushCache(); } } } /** * Deprecated in favor of * {@link #putChildren(RolapLevel, TupleConstraint, List)} */ // synchronization: Must synchronize, because modifies mapKeyToMember @Deprecated public synchronized void putLevelMembersInCache( RolapLevel level, TupleConstraint constraint, List members) { putChildren(level, constraint, members); } public synchronized void putChildren( RolapLevel level, TupleConstraint constraint, List members) { mapLevelToMembers.put(level, constraint, members); } public synchronized List getChildrenFromCache( RolapMember member, MemberChildrenConstraint constraint) { if (constraint == null) { constraint = sqlConstraintFactory.getMemberChildrenConstraint(null); } return mapMemberToChildren.get(member, constraint); } public synchronized void putChildren( RolapMember member, MemberChildrenConstraint constraint, List children) { if (constraint == null) { constraint = sqlConstraintFactory.getMemberChildrenConstraint(null); } mapMemberToChildren.put(member, constraint, children); } public synchronized List getLevelMembersFromCache( RolapLevel level, TupleConstraint constraint) { if (constraint == null) { constraint = sqlConstraintFactory.getLevelMembersConstraint(null); } return mapLevelToMembers.get(level, constraint); } public synchronized void flushCache() { mapMemberToChildren.clear(); mapKeyToMember.clear(); mapLevelToMembers.clear(); // We also need to clear the approxRowCount of each level. for (Level level : rolapHierarchy.getLevels()) { ((RolapLevel)level).setApproxRowCount(Integer.MIN_VALUE); } } public DataSourceChangeListener getChangeListener() { return changeListener; } public void setChangeListener(DataSourceChangeListener listener) { changeListener = listener; } public boolean isMutable() { return true; } public synchronized RolapMember removeMember(Object key) { // Flush entries from the level-to-members map // for member's level and all child levels. // Important: Do this even if the member is apparently not in the cache. RolapLevel level = ((MemberKey) key).getLevel(); if (level == null) { level = (RolapLevel) this.rolapHierarchy.getLevels()[0]; } for (Iterator, List>> iterator = mapLevelToMembers.getCache().iterator(); iterator.hasNext();) { Map.Entry, List> entry = iterator.next(); final RolapLevel cacheLevel = entry.getKey().left; if (cacheLevel.equals(level) || (cacheLevel.getHierarchy().equals(level.getHierarchy()) && cacheLevel.getDepth() >= level.getDepth())) { iterator.remove(); } } RolapMember member = getMember(key); if (member == null) { // not in cache return null; } // Drop member from the member-to-children map, wherever it occurs as // a parent or as a child, regardless of the constraint. RolapMember parent = member.getParentMember(); final Iterator, List>> iter = mapMemberToChildren.getCache().iterator(); while (iter.hasNext()) { Map.Entry, List> entry = iter.next(); final RolapMember member1 = entry.getKey().left; final Object constraint = entry.getKey().right; // Cache key is (member's parent, constraint); // cache value is a list of member's siblings; // If constraint is trivial remove member from list of siblings; // otherwise it's safer to nuke the cache entry if (Util.equals(member1, parent)) { if (constraint == DefaultMemberChildrenConstraint.instance()) { List siblings = entry.getValue(); boolean removedIt = siblings.remove(member); Util.discard(removedIt); } else { iter.remove(); } } // cache is (member, some constraint); // cache value is list of member's children; // remove cache entry if (Util.equals(member1, member)) { iter.remove(); } } // drop it from the lookup-cache return mapKeyToMember.put(key, null); } public synchronized RolapMember removeMemberAndDescendants(Object key) { // Can use mapMemberToChildren recursively. No need to update inferior // lists of children. Do need to update inferior lists of level-peers. return null; // STUB } } // End MemberCacheHelper.java mondrian-3.4.1/src/main/mondrian/rolap/RolapStoredMeasure.java0000644000175000017500000000257711735330606024323 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2007 Pentaho and others // All Rights Reserved. // // jhyde, 10 August, 2001 */ package mondrian.rolap; import mondrian.olap.MondrianDef; /** * A measure which is implemented by a SQL column or SQL expression (as opposed * to a {@link RolapCalculatedMember}. * *

Implemented by {@link RolapBaseCubeMeasure} and * {@link RolapVirtualCubeMeasure}. * * @author jhyde * @since 10 August, 2001 */ public interface RolapStoredMeasure extends RolapMeasure { /** * Returns the cube this measure belongs to. */ RolapCube getCube(); /** * Returns the column which holds the value of the measure. */ MondrianDef.Expression getMondrianDefExpression(); /** * Returns the aggregation function which rolls up this measure: "SUM", * "COUNT", etc. */ RolapAggregator getAggregator(); /** * Returns the {@link mondrian.rolap.RolapStar.Measure} from which this * member is computed. Untyped, because another implementation might store * it somewhere else. */ Object getStarMeasure(); } // End RolapStoredMeasure.java mondrian-3.4.1/src/main/mondrian/rolap/RolapAggregator.java0000644000175000017500000003311511735330606023613 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.Calc; import mondrian.calc.TupleList; import mondrian.olap.*; import mondrian.olap.fun.AggregateFunDef; import mondrian.olap.fun.FunUtil; import mondrian.spi.Dialect; import java.util.List; /** * Describes an aggregation operator, such as "sum" or "count". * * @author jhyde * @since Jul 9, 2003 */ public abstract class RolapAggregator extends EnumeratedValues.BasicValue implements Aggregator { private static int index = 0; public static final RolapAggregator Sum = new RolapAggregator("sum", index++, false) { public Object aggregate( Evaluator evaluator, TupleList members, Calc exp) { return FunUtil.sum(evaluator, members, exp); } public boolean supportsFastAggregates(Dialect.Datatype dataType) { switch (dataType) { case Integer: case Numeric: return true; default: return false; } }; public Object aggregate(List rawData) { assert rawData.size() > 0; if (rawData.get(0) instanceof Integer) { int totalValue = 0; for (Object data : rawData) { totalValue += (Integer)data; } return totalValue; } if (rawData.get(0) instanceof Double) { double totalValue = 0d; for (Object data : rawData) { totalValue += (Double)data; } return totalValue; } if (rawData.get(0) instanceof Long) { long totalValue = 0l; for (Object data : rawData) { totalValue += (Long)data; } return totalValue; } throw new IllegalArgumentException(); } }; public static final RolapAggregator Count = new RolapAggregator("count", index++, false) { public Aggregator getRollup() { return Sum; } public Object aggregate( Evaluator evaluator, TupleList members, Calc exp) { return FunUtil.count(evaluator, members, false); } }; public static final RolapAggregator Min = new RolapAggregator("min", index++, false) { public Object aggregate( Evaluator evaluator, TupleList members, Calc exp) { return FunUtil.min(evaluator, members, exp); } public boolean supportsFastAggregates(Dialect.Datatype dataType) { switch (dataType) { case Integer: case Numeric: return true; default: return false; } }; public Object aggregate(List rawData) { assert rawData.size() > 0; if (rawData.get(0) instanceof Integer) { int min = Integer.MAX_VALUE; for (Object data : rawData) { min = Math.min(min, (Integer)data); } return min; } if (rawData.get(0) instanceof Double) { double min = Double.MAX_VALUE; for (Object data : rawData) { min = Math.min(min, (Double)data); } return min; } if (rawData.get(0) instanceof Long) { long min = Long.MAX_VALUE; for (Object data : rawData) { min = Math.min(min, (Long)data); } return min; } throw new IllegalArgumentException(); } }; public static final RolapAggregator Max = new RolapAggregator("max", index++, false) { public Object aggregate( Evaluator evaluator, TupleList members, Calc exp) { return FunUtil.max(evaluator, members, exp); } public boolean supportsFastAggregates(Dialect.Datatype dataType) { switch (dataType) { case Integer: case Numeric: return true; default: return false; } }; public Object aggregate(List rawData) { assert rawData.size() > 0; if (rawData.get(0) instanceof Integer) { int min = Integer.MIN_VALUE; for (Object data : rawData) { min = Math.max(min, (Integer)data); } return min; } if (rawData.get(0) instanceof Double) { double min = Double.MIN_VALUE; for (Object data : rawData) { min = Math.max(min, (Double)data); } return min; } if (rawData.get(0) instanceof Long) { long min = Long.MIN_VALUE; for (Object data : rawData) { min = Math.max(min, (Long)data); } return min; } throw new IllegalArgumentException(); } }; public static final RolapAggregator Avg = new RolapAggregator("avg", index++, false) { public Aggregator getRollup() { return new RolapAggregator("avg", index, false) { public Object aggregate( Evaluator evaluator, TupleList members, Calc calc) { return AggregateFunDef.avg(evaluator, members, calc); } }; } public Object aggregate( Evaluator evaluator, TupleList members, Calc exp) { return AggregateFunDef.avg(evaluator, members, exp); } }; public static final RolapAggregator DistinctCount = new RolapAggregator("distinct-count", index++, true) { public Aggregator getRollup() { // Distinct counts cannot always be rolled up, when they can, // it's using Sum. return Sum; } public RolapAggregator getNonDistinctAggregator() { return Count; } public Object aggregate( Evaluator evaluator, TupleList members, Calc exp) { throw new UnsupportedOperationException(); } public String getExpression(String operand) { return "count(distinct " + operand + ")"; } public boolean supportsFastAggregates( mondrian.spi.Dialect.Datatype dataType) { // We can't rollup using the raw data, because this is // a distinct-count operation. return false; }; }; /** * List of all valid aggregation operators. */ public static final EnumeratedValues enumeration = new EnumeratedValues( new RolapAggregator[] {Sum, Count, Min, Max, Avg, DistinctCount}); /** * This is the base class for implementing aggregators over sum and * average columns in an aggregate table. These differ from the above * aggregators in that these require not oly the operand to create * the aggregation String expression, but also, the aggregate table's * fact count column expression. * These aggregators are NOT singletons like the above aggregators; rather, * each is different because of the fact count column expression. */ protected static abstract class BaseAggor extends RolapAggregator { protected final String factCountExpr; protected BaseAggor(final String name, final String factCountExpr) { super(name, index++, false); this.factCountExpr = factCountExpr; } public Object aggregate( Evaluator evaluator, TupleList members, Calc exp) { throw new UnsupportedOperationException(); } } /** * Aggregator used for aggregate tables implementing the * average aggregator. * *

It uses the aggregate table fact_count column * and a sum measure to create the query used to generate an average: *

* * avg == sum(column_sum) / sum(factcount). * *
* *

If the fact table has both a sum and average over the same column and * the aggregate table only has a sum and fact count column, then the * average aggregator can be generated using this aggregator. */ public static class AvgFromSum extends BaseAggor { public AvgFromSum(String factCountExpr) { super("AvgFromSum", factCountExpr); } public String getExpression(String operand) { StringBuilder buf = new StringBuilder(64); buf.append("sum("); buf.append(operand); buf.append(") / sum("); buf.append(factCountExpr); buf.append(')'); return buf.toString(); } } /** * Aggregator used for aggregate tables implementing the * average aggregator. * *

It uses the aggregate table fact_count column * and an average measure to create the query used to generate an average: *

* * avg == sum(column_sum * factcount) / sum(factcount). * *
* *

If the fact table has both a sum and average over the same column and * the aggregate table only has a average and fact count column, then the * average aggregator can be generated using this aggregator. */ public static class AvgFromAvg extends BaseAggor { public AvgFromAvg(String factCountExpr) { super("AvgFromAvg", factCountExpr); } public String getExpression(String operand) { StringBuilder buf = new StringBuilder(64); buf.append("sum("); buf.append(operand); buf.append(" * "); buf.append(factCountExpr); buf.append(") / sum("); buf.append(factCountExpr); buf.append(')'); return buf.toString(); } } /** * This is an aggregator used for aggregate tables implementing the * sum aggregator. It uses the aggregate table fact_count column * and an average measure to create the query used to generate a sum: *

     *    sum == sum(column_avg * factcount)
     * 
* If the fact table has both a sum and average over the same column and * the aggregate table only has an average and fact count column, then the * sum aggregator can be generated using this aggregator. */ public static class SumFromAvg extends BaseAggor { public SumFromAvg(String factCountExpr) { super("SumFromAvg", factCountExpr); } public String getExpression(String operand) { StringBuilder buf = new StringBuilder(64); buf.append("sum("); buf.append(operand); buf.append(" * "); buf.append(factCountExpr); buf.append(')'); return buf.toString(); } } private final boolean distinct; public RolapAggregator(String name, int ordinal, boolean distinct) { super(name, ordinal, null); this.distinct = distinct; } public boolean isDistinct() { return distinct; } /** * Returns the expression to apply this aggregator to an operand. * For example, getExpression("emp.sal") returns * "sum(emp.sal)". */ public String getExpression(String operand) { StringBuilder buf = new StringBuilder(64); buf.append(name); buf.append('('); if (distinct) { buf.append("distinct "); } buf.append(operand); buf.append(')'); return buf.toString(); } /** * If this is a distinct aggregator, returns the corresponding non-distinct * aggregator, otherwise throws an error. */ public RolapAggregator getNonDistinctAggregator() { throw new UnsupportedOperationException(); } /** * Returns the aggregator used to roll up. By default, aggregators roll up * themselves. */ public Aggregator getRollup() { return this; } /** * By default, fast rollup is not supported for all classes. */ public boolean supportsFastAggregates(Dialect.Datatype dataType) { return false; } public Object aggregate(List rawData) { throw new UnsupportedOperationException(); } } // End RolapAggregator.java mondrian-3.4.1/src/main/mondrian/rolap/RolapCalculatedMember.java0000644000175000017500000000564111735330606024725 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import java.util.Collections; import java.util.Map; /** * A RolapCalculatedMember is a member based upon a * {@link Formula}. * *

It is created before the formula has been resolved; the formula is * responsible for setting the "format_string" property. * * @author jhyde * @since 26 August, 2001 */ public class RolapCalculatedMember extends RolapMemberBase { private final Formula formula; private Map annotationMap; /** * Creates a RolapCalculatedMember. * * @param parentMember Parent member * @param level Level * @param name Name * @param formula Formula */ RolapCalculatedMember( RolapMember parentMember, RolapLevel level, String name, Formula formula) { // A calculated measure has MemberType.FORMULA because FORMULA // overrides MEASURE. super(parentMember, level, name, null, MemberType.FORMULA); this.formula = formula; this.annotationMap = Collections.emptyMap(); } // override RolapMember public int getSolveOrder() { final Number solveOrder = formula.getSolveOrder(); return solveOrder == null ? 0 : solveOrder.intValue(); } public Object getPropertyValue(String propertyName, boolean matchCase) { if (Util.equal(propertyName, Property.FORMULA.name, matchCase)) { return formula; } else if (Util.equal( propertyName, Property.CHILDREN_CARDINALITY.name, matchCase)) { // Looking up children is unnecessary for calculated member. // If do that, SQLException will be thrown. return 0; } else { return super.getPropertyValue(propertyName, matchCase); } } protected boolean computeCalculated(final MemberType memberType) { return true; } public boolean isCalculatedInQuery() { final String memberScope = (String) getPropertyValue(Property.MEMBER_SCOPE.name); return memberScope == null || memberScope.equals("QUERY"); } public Exp getExpression() { return formula.getExpression(); } public Formula getFormula() { return formula; } @Override public Map getAnnotationMap() { return annotationMap; } void setAnnotationMap(Map annotationMap) { assert annotationMap != null; this.annotationMap = annotationMap; } } // End RolapCalculatedMember.java mondrian-3.4.1/src/main/mondrian/rolap/ScenarioImpl.java0000644000175000017500000005037411735330606023126 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.Calc; import mondrian.calc.DummyExp; import mondrian.calc.impl.GenericCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.ScalarType; import org.olap4j.AllocationPolicy; import org.olap4j.Scenario; import java.util.ArrayList; import java.util.List; /** * Implementation of {@link org.olap4j.Scenario}. * * @author jhyde * @since 24 April, 2009 */ public final class ScenarioImpl implements Scenario { private final int id; private final List writebackCells = new ArrayList(); private RolapMember member; private static int nextId; /** * Creates a ScenarioImpl. */ public ScenarioImpl() { id = nextId++; } @Override public int hashCode() { return id; } @Override public boolean equals(Object obj) { return obj instanceof ScenarioImpl && id == ((ScenarioImpl) obj).id; } @Override public String toString() { return "scenario #" + id; } /** * Sets the value of a cell. * * @param connection Connection (not currently used) * @param members Coordinates of cell * @param newValue New value * @param currentValue Current value * @param allocationPolicy Allocation policy * @param allocationArgs Additional arguments of allocation policy */ public void setCellValue( Connection connection, List members, double newValue, double currentValue, AllocationPolicy allocationPolicy, Object[] allocationArgs) { Util.discard(connection); // for future use assert allocationPolicy != null; assert allocationArgs != null; switch (allocationPolicy) { case EQUAL_ALLOCATION: case EQUAL_INCREMENT: if (allocationArgs.length != 0) { throw Util.newError( "Allocation policy " + allocationPolicy + " takes 0 arguments; " + allocationArgs.length + " were supplied"); } break; default: throw Util.newError( "Allocation policy " + allocationPolicy + " is not supported"); } // Compute the set of columns which are constrained by the cell's // coordinates. // // NOTE: This code is very similar to code in // RolapAggregationManager.makeCellRequest. Consider creating a // CellRequest then mining it. It will work better in the presence of // calculated members, compound members, parent-child hierarchies, // hierarchies whose default member is not the 'all' member, and so // forth. final RolapStoredMeasure measure = (RolapStoredMeasure) members.get(0); final RolapCube baseCube = measure.getCube(); final RolapStar.Measure starMeasure = (RolapStar.Measure) measure.getStarMeasure(); assert starMeasure != null; int starColumnCount = starMeasure.getStar().getColumnCount(); final BitKey constrainedColumnsBitKey = BitKey.Factory.makeBitKey(starColumnCount); Object[] keyValues = new Object[starColumnCount]; for (int i = 1; i < members.size(); i++) { Member member = members.get(i); for (RolapCubeMember m = (RolapCubeMember) member; m != null && !m.isAll(); m = m.getParentMember()) { final RolapCubeLevel level = m.getLevel(); RolapStar.Column column = level.getBaseStarKeyColumn(baseCube); if (column != null) { final int bitPos = column.getBitPosition(); keyValues[bitPos] = m.getKey(); constrainedColumnsBitKey.set(bitPos); } if (level.areMembersUnique()) { break; } } } // Squish the values down. We want the compactKeyValues[i] to correspond // to the i'th set bit in the key. This is the same format used by // CellRequest. Object[] compactKeyValues = new Object[constrainedColumnsBitKey.cardinality()]; int k = 0; for (int bitPos : constrainedColumnsBitKey) { compactKeyValues[k++] = keyValues[bitPos]; } // Record the override. // // TODO: add a mechanism for persisting the overrides to a file. // // FIXME: make thread-safe writebackCells.add( new WritebackCell( baseCube, new ArrayList(members), constrainedColumnsBitKey, compactKeyValues, newValue, currentValue, allocationPolicy)); } public String getId() { return Integer.toString(id); } /** * Returns the scenario inside a calculated member in the scenario * dimension. For example, applied to [Scenario].[1], returns the Scenario * object representing scenario #1. * * @param member Wrapper member * @return Wrapped scenario */ static Scenario forMember(final RolapMember member) { final Formula formula = ((RolapCalculatedMember) member).getFormula(); final ResolvedFunCall resolvedFunCall = (ResolvedFunCall) formula.getExpression(); final Calc calc = resolvedFunCall.getFunDef().compileCall(null, null); return ((ScenarioCalc) calc).getScenario(); } /** * Registers this Scenario with a Schema, creating a calulated member * [Scenario].[{id}] for each cube that has writeback enabled. (Currently * a cube has writeback enabled iff it has a dimension called "Scenario".) * * @param schema Schema */ void register(RolapSchema schema) { // Add a value to the [Scenario] dimension of every cube that has // writeback enabled. for (RolapCube cube : schema.getCubeList()) { for (RolapHierarchy hierarchy : cube.getHierarchies()) { if (isScenario(hierarchy)) { member = cube.createCalculatedMember( hierarchy, getId() + "", new ScenarioCalc(this)); assert member != null; } } } } /** * Returns whether a hierarchy is the [Scenario] hierarchy. * *

TODO: use a flag * * @param hierarchy Hierarchy * @return Whether hierarchy is the scenario hierarchy */ public static boolean isScenario(Hierarchy hierarchy) { return hierarchy.getName().equals("Scenario"); } /** * Returns the number of atomic cells that contribute to the current * cell. * * @param evaluator Evaluator * @return Number of atomic cells in the current cell */ private static double evaluateAtomicCellCount(RolapEvaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setContext( evaluator.getCube().getAtomicCellCountMeasure()); final Object o = evaluator.evaluateCurrent(); evaluator.restore(savepoint); return ((Number) o).doubleValue(); } /** * Computes the number of atomic cells in a cell identified by a list * of members. * *

The method may be expensive. If the value is not in the cache, * computes it immediately using a database access. It uses an aggregate * table if applicable, and puts the value into the cache. * *

Compare with {@link #evaluateAtomicCellCount(RolapEvaluator)}, which * gets the value from the cache but may lie (and generate a cache miss) if * the value is not present. * * @param cube Cube * @param memberList Coordinate members of cell * @return Number of atomic cells in cell */ private static double computeAtomicCellCount( RolapCube cube, List memberList) { // Implementation generates and executes a recursive MDX query. This // may not be the most efficient implementation, but achieves the // design goals of (a) immediacy, (b) cache use, (c) aggregate table // use. final StringBuilder buf = new StringBuilder(); buf.append("select from "); buf.append(cube.getUniqueName()); int k = 0; for (Member member : memberList) { if (member.isMeasure()) { member = cube.factCountMeasure; assert member != null : "fact count measure is required for writeback cubes"; } if (!member.equals(member.getHierarchy().getDefaultMember())) { if (k++ > 0) { buf.append(", "); } else { buf.append(" where ("); } buf.append(member.getUniqueName()); } } if (k > 0) { buf.append(")"); } final String mdx = buf.toString(); final RolapConnection connection = cube.getSchema().getInternalConnection(); final Query query = connection.parseQuery(mdx); final Result result = connection.execute(query); final Object o = result.getCell(new int[0]).getValue(); return o instanceof Number ? ((Number) o).doubleValue() : 0d; } /** * Returns the member of the [Scenario] dimension that represents this * scenario. Including that member in the slicer will automatically use * this scenario. * *

The result is not null, provided that {@link #register(RolapSchema)} * has been called. * * @return Scenario member */ public RolapMember getMember() { return member; } /** * Created by a call to * {@link org.olap4j.Cell#setValue(Object, org.olap4j.AllocationPolicy, Object...)}, * records that a cell's value has been changed. * *

From this, other cell values can be modified as they are read into * cache. Only the cells specifically modified by the client have a * {@code CellValueOverride}. * *

In future, a {@link ScenarioImpl} could be persisted by * serializing all {@code WritebackCell}s to a file. */ private static class WritebackCell { private final double newValue; private final double currentValue; private final AllocationPolicy allocationPolicy; private Member[] membersByOrdinal; private final double atomicCellCount; /** * Creates a WritebackCell. * * @param cube Cube * @param members Members that form context * @param constrainedColumnsBitKey Bitmap of columns which have values * @param keyValues List of values, by bit position * @param newValue New value * @param currentValue Current value * @param allocationPolicy Allocation policy */ WritebackCell( RolapCube cube, List members, BitKey constrainedColumnsBitKey, Object[] keyValues, double newValue, double currentValue, AllocationPolicy allocationPolicy) { assert keyValues.length == constrainedColumnsBitKey.cardinality(); Util.discard(cube); // not used currently Util.discard(constrainedColumnsBitKey); // not used currently Util.discard(keyValues); // not used currently this.newValue = newValue; this.currentValue = currentValue; this.allocationPolicy = allocationPolicy; this.atomicCellCount = computeAtomicCellCount(cube, members); // Build the array of members by ordinal. If a member is not // specified for a particular dimension, use the 'all' member (not // necessarily the same as the default member). final List hierarchyList = cube.getHierarchies(); this.membersByOrdinal = new Member[hierarchyList.size()]; for (int i = 0; i < membersByOrdinal.length; i++) { membersByOrdinal[i] = hierarchyList.get(i).getDefaultMember(); } for (RolapMember member : members) { final RolapHierarchy hierarchy = member.getHierarchy(); if (isScenario(hierarchy)) { assert member.isAll(); } // REVIEW The following works because Measures is the only // dimension whose members do not belong to RolapCubeDimension, // just a regular RolapDimension, but has ordinal 0. final int ordinal = hierarchy.getOrdinalInCube(); membersByOrdinal[ordinal] = member; } } /** * Returns the amount by which the cell value has increased with this * override. * * @return Amount by which value has increased */ public double getOffset() { return newValue - currentValue; } /** * Returns the position of this writeback cell relative to another * co-ordinate. * *

Assumes that {@code members} contains an entry for each dimension * in the cube. * * @param members Co-ordinates of another cell * @return Relation of this writeback cell to other co-ordinate, never * null */ CellRelation getRelationTo(Member[] members) { int aboveCount = 0; int belowCount = 0; for (int i = 0; i < members.length; i++) { Member thatMember = members[i]; Member thisMember = membersByOrdinal[i]; // FIXME: isChildOrEqualTo is very inefficient. It should use // level depth as a guideline, at least. if (thatMember.isChildOrEqualTo(thisMember)) { if (thatMember.equals(thisMember)) { // thisMember equals member } else { // thisMember is ancestor of member ++aboveCount; if (belowCount > 0) { return CellRelation.NONE; } } } else if (thisMember.isChildOrEqualTo(thatMember)) { // thisMember is descendant of member ++belowCount; if (aboveCount > 0) { return CellRelation.NONE; } } else { return CellRelation.NONE; } } assert aboveCount == 0 || belowCount == 0; if (aboveCount > 0) { return CellRelation.ABOVE; } else if (belowCount > 0) { return CellRelation.BELOW; } else { return CellRelation.EQUAL; } } } /** * Decribes the relationship between two cells. */ enum CellRelation { ABOVE, EQUAL, BELOW, NONE } /** * Compiled expression to implement a [Scenario].[{name}] calculated member. * *

When evaluated, replaces the value of a cell with the value overridden * by a writeback value, per * {@link org.olap4j.Cell#setValue(Object, org.olap4j.AllocationPolicy, Object...)}, * and modifies the values of ancestors or descendants of such cells * according to the allocation policy. */ private static class ScenarioCalc extends GenericCalc { private final ScenarioImpl scenario; /** * Creates a ScenarioCalc. * * @param scenario Scenario whose writeback values should be substituted * for the values stored in the database. */ public ScenarioCalc(ScenarioImpl scenario) { super(new DummyExp(new ScalarType())); this.scenario = scenario; } /** * Returns the Scenario this writeback cell belongs to. * * @return Scenario, never null */ private Scenario getScenario() { return scenario; } public Object evaluate(Evaluator evaluator) { // Evaluate current member in the given scenario by expanding in // terms of the writeback cells. // First, evaluate in the null scenario. final Member defaultMember = scenario.member.getHierarchy().getDefaultMember(); final int savepoint = evaluator.savepoint(); evaluator.setContext(defaultMember); final Object o = evaluator.evaluateCurrent(); double d = o instanceof Number ? ((Number) o).doubleValue() : 0d; // Look for writeback cells which are equal to, ancestors of, or // descendants of, the current cell. Modify the value accordingly. // // It is possible that the value is modified by several writebacks. // If so, order is important. int changeCount = 0; for (ScenarioImpl.WritebackCell writebackCell : scenario.writebackCells) { CellRelation relation = writebackCell.getRelationTo(evaluator.getMembers()); switch (relation) { case ABOVE: // This cell is below the writeback cell. Value is // determined by allocation policy. double atomicCellCount = evaluateAtomicCellCount((RolapEvaluator) evaluator); if (atomicCellCount == 0d) { // Sometimes the value comes back zero if the cache is // not ready. Switch to 1, which at least does not give // divide-by-zero. We will be invoked again for the // correct answer when the cache has been populated. atomicCellCount = 1d; } switch (writebackCell.allocationPolicy) { case EQUAL_ALLOCATION: d = writebackCell.newValue * atomicCellCount / writebackCell.atomicCellCount; break; case EQUAL_INCREMENT: d += writebackCell.getOffset() * atomicCellCount / writebackCell.atomicCellCount; break; default: throw Util.unexpected(writebackCell.allocationPolicy); } ++changeCount; break; case EQUAL: // This cell is the writeback cell. Value is the value // written back. d = writebackCell.newValue; ++changeCount; break; case BELOW: // This cell is above the writeback cell. Value is the // current value plus the change in the writeback cell. d += writebackCell.getOffset(); ++changeCount; break; case NONE: // Writeback cell is unrelated. It has no effect on cell's // value. break; default: throw Util.unexpected(relation); } } evaluator.restore(savepoint); // Don't create a new object if value has not changed. if (changeCount == 0) { return o; } else { return d; } } } } // End ScenarioImpl.java mondrian-3.4.1/src/main/mondrian/rolap/RolapNativeSql.java0000644000175000017500000005446211735330606023447 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2009 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.mdx.*; import mondrian.olap.*; import mondrian.olap.type.MemberType; import mondrian.olap.type.StringType; import mondrian.rolap.aggmatcher.AggStar; import mondrian.rolap.sql.SqlQuery; import mondrian.spi.Dialect; import java.util.ArrayList; import java.util.List; /** * Creates SQL from parse tree nodes. Currently it creates the SQL that * accesses a measure for the ORDER BY that is generated for a TopCount.

* * @author av * @since Nov 17, 2005 */ public class RolapNativeSql { private SqlQuery sqlQuery; private Dialect dialect; CompositeSqlCompiler numericCompiler; CompositeSqlCompiler booleanCompiler; RolapStoredMeasure storedMeasure; final AggStar aggStar; final Evaluator evaluator; final RolapLevel rolapLevel; /** * We remember one of the measures so we can generate * the constraints from RolapAggregationManager. Also * make sure all measures live in the same star. * * @see RolapAggregationManager#makeRequest(RolapEvaluator) */ private boolean saveStoredMeasure(RolapStoredMeasure m) { if (storedMeasure != null) { RolapStar star1 = getStar(storedMeasure); RolapStar star2 = getStar(m); if (star1 != star2) { return false; } } this.storedMeasure = m; return true; } private RolapStar getStar(RolapStoredMeasure m) { return ((RolapStar.Measure) m.getStarMeasure()).getStar(); } /** * Translates an expression into SQL */ interface SqlCompiler { /** * Returns SQL. If exp can not be compiled into SQL, * returns null. * * @param exp Expression * @return SQL, or null if cannot be converted into SQL */ String compile(Exp exp); } /** * Implementation of {@link SqlCompiler} that uses chain of responsibility * to find a matching sql compiler. */ static class CompositeSqlCompiler implements SqlCompiler { List compilers = new ArrayList(); public void add(SqlCompiler compiler) { compilers.add(compiler); } public String compile(Exp exp) { for (SqlCompiler compiler : compilers) { String s = compiler.compile(exp); if (s != null) { return s; } } return null; } public String toString() { return compilers.toString(); } } /** * Compiles a numeric literal to SQL. */ class NumberSqlCompiler implements SqlCompiler { public String compile(Exp exp) { if (!(exp instanceof Literal)) { return null; } if ((exp.getCategory() & Category.Numeric) == 0) { return null; } Literal literal = (Literal) exp; String expr = String.valueOf(literal.getValue()); if (dialect.getDatabaseProduct().getFamily() == Dialect.DatabaseProduct.DB2) { expr = "FLOAT(" + expr + ")"; } return expr; } public String toString() { return "NumberSqlCompiler"; } } /** * Base class to remove MemberScalarExp. */ abstract class MemberSqlCompiler implements SqlCompiler { protected Exp unwind(Exp exp) { return exp; } } /** * Compiles a measure into SQL, the measure will be aggregated * like sum(measure). */ class StoredMeasureSqlCompiler extends MemberSqlCompiler { public String compile(Exp exp) { exp = unwind(exp); if (!(exp instanceof MemberExpr)) { return null; } final Member member = ((MemberExpr) exp).getMember(); if (!(member instanceof RolapStoredMeasure)) { return null; } RolapStoredMeasure measure = (RolapStoredMeasure) member; if (measure.isCalculated()) { return null; // ?? } if (!saveStoredMeasure(measure)) { return null; } String exprInner; // Use aggregate table to create condition if available if (aggStar != null && measure.getStarMeasure() instanceof RolapStar.Column) { RolapStar.Column column = (RolapStar.Column) measure.getStarMeasure(); int bitPos = column.getBitPosition(); AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); exprInner = aggColumn.generateExprString(sqlQuery); } else { exprInner = measure.getMondrianDefExpression().getExpression(sqlQuery); } String expr = measure.getAggregator().getExpression(exprInner); if (dialect.getDatabaseProduct().getFamily() == Dialect.DatabaseProduct.DB2) { expr = "FLOAT(" + expr + ")"; } return expr; } public String toString() { return "StoredMeasureSqlCompiler"; } } /** * Compiles a MATCHES MDX operator into SQL regular * expression match. */ class MatchingSqlCompiler extends FunCallSqlCompilerBase { protected MatchingSqlCompiler() { super(Category.Logical, "MATCHES", 2); } public String compile(Exp exp) { if (!match(exp)) { return null; } if (!dialect.allowsRegularExpressionInWhereClause() || !(exp instanceof ResolvedFunCall) || evaluator == null) { return null; } final Exp arg0 = ((ResolvedFunCall)exp).getArg(0); final Exp arg1 = ((ResolvedFunCall)exp).getArg(1); // Must finish by ".Caption" or ".Name" if (!(arg0 instanceof ResolvedFunCall) || ((ResolvedFunCall)arg0).getArgCount() != 1 || !(arg0.getType() instanceof StringType) || (!((ResolvedFunCall)arg0).getFunName().equals("Name") && !((ResolvedFunCall)arg0) .getFunName().equals("Caption"))) { return null; } final boolean useCaption; if (((ResolvedFunCall)arg0).getFunName().equals("Name")) { useCaption = false; } else { useCaption = true; } // Must be ".CurrentMember" final Exp currMemberExpr = ((ResolvedFunCall)arg0).getArg(0); if (!(currMemberExpr instanceof ResolvedFunCall) || ((ResolvedFunCall)currMemberExpr).getArgCount() != 1 || !(currMemberExpr.getType() instanceof MemberType) || !((ResolvedFunCall)currMemberExpr) .getFunName().equals("CurrentMember")) { return null; } // Must be a dimension, a hierarchy or a level. final RolapCubeDimension dimension; final Exp dimExpr = ((ResolvedFunCall)currMemberExpr).getArg(0); if (dimExpr instanceof DimensionExpr) { dimension = (RolapCubeDimension) evaluator.getCachedResult( new ExpCacheDescriptor(dimExpr, evaluator)); } else if (dimExpr instanceof HierarchyExpr) { final RolapCubeHierarchy hierarchy = (RolapCubeHierarchy) evaluator.getCachedResult( new ExpCacheDescriptor(dimExpr, evaluator)); dimension = (RolapCubeDimension) hierarchy.getDimension(); } else if (dimExpr instanceof LevelExpr) { final RolapCubeLevel level = (RolapCubeLevel) evaluator.getCachedResult( new ExpCacheDescriptor(dimExpr, evaluator)); dimension = (RolapCubeDimension) level.getDimension(); } else { return null; } if (rolapLevel != null && dimension.equals(rolapLevel.getDimension())) { // We can't use the evaluator because the filter is filtering // a set which is uses same dimension as the predicate. // We must use, in order of priority, // - caption requested: caption->name->key // - name requested: name->key MondrianDef.Expression expression = useCaption ? rolapLevel.captionExp == null ? rolapLevel.nameExp == null ? rolapLevel.keyExp : rolapLevel.nameExp : rolapLevel.captionExp : rolapLevel.nameExp == null ? rolapLevel.keyExp : rolapLevel.nameExp; /* * If an aggregation table is used, it might be more efficient * to use only the aggregate table and not the hierarchy table. * Try to lookup the column bit key. If that fails, we will * link the aggregate table to the hierarchy table. If no * aggregate table is used, we can use the column expression * directly. */ String sourceExp; if (aggStar != null && rolapLevel instanceof RolapCubeLevel && expression == rolapLevel.keyExp) { int bitPos = ((RolapCubeLevel)rolapLevel).getStarKeyColumn() .getBitPosition(); mondrian.rolap.aggmatcher.AggStar.Table.Column col = aggStar.lookupColumn(bitPos); if (col != null) { sourceExp = col.generateExprString(sqlQuery); } else { // Make sure the level table is part of the query. rolapLevel.getHierarchy().addToFrom( sqlQuery, expression); sourceExp = expression.getExpression(sqlQuery); } } else if (aggStar != null) { // Make sure the level table is part of the query. rolapLevel.getHierarchy().addToFrom(sqlQuery, expression); sourceExp = expression.getExpression(sqlQuery); } else { sourceExp = expression.getExpression(sqlQuery); } // The dialect might require the use of the alias rather // then the column exp. if (dialect.requiresHavingAlias()) { sourceExp = sqlQuery.getAlias(sourceExp); } return dialect.generateRegularExpression( sourceExp, String.valueOf( evaluator.getCachedResult( new ExpCacheDescriptor(arg1, evaluator)))); } else { return null; } } public String toString() { return "MatchingSqlCompiler"; } } /** * Compiles the underlying expression of a calculated member. */ class CalculatedMemberSqlCompiler extends MemberSqlCompiler { SqlCompiler compiler; CalculatedMemberSqlCompiler(SqlCompiler argumentCompiler) { this.compiler = argumentCompiler; } public String compile(Exp exp) { exp = unwind(exp); if (!(exp instanceof MemberExpr)) { return null; } final Member member = ((MemberExpr) exp).getMember(); if (!(member instanceof RolapCalculatedMember)) { return null; } exp = member.getExpression(); if (exp == null) { return null; } return compiler.compile(exp); } public String toString() { return "CalculatedMemberSqlCompiler"; } } /** * Contains utility methods to compile FunCall expressions into SQL. */ abstract class FunCallSqlCompilerBase implements SqlCompiler { int category; String mdx; int argCount; FunCallSqlCompilerBase(int category, String mdx, int argCount) { this.category = category; this.mdx = mdx; this.argCount = argCount; } /** * @return true if exp is a matching FunCall */ protected boolean match(Exp exp) { if ((exp.getCategory() & category) == 0) { return false; } if (!(exp instanceof FunCall)) { return false; } FunCall fc = (FunCall) exp; if (!mdx.equalsIgnoreCase(fc.getFunName())) { return false; } Exp[] args = fc.getArgs(); if (args.length != argCount) { return false; } return true; } /** * compiles the arguments of a FunCall * * @return array of expressions or null if either exp does not match or * any argument could not be compiled. */ protected String[] compileArgs(Exp exp, SqlCompiler compiler) { if (!match(exp)) { return null; } Exp[] args = ((FunCall) exp).getArgs(); String[] sqls = new String[args.length]; for (int i = 0; i < args.length; i++) { sqls[i] = compiler.compile(args[i]); if (sqls[i] == null) { return null; } } return sqls; } } /** * Compiles a funcall, e.g. foo(a, b, c). */ class FunCallSqlCompiler extends FunCallSqlCompilerBase { SqlCompiler compiler; String sql; protected FunCallSqlCompiler( int category, String mdx, String sql, int argCount, SqlCompiler argumentCompiler) { super(category, mdx, argCount); this.sql = sql; this.compiler = argumentCompiler; } public String compile(Exp exp) { String[] args = compileArgs(exp, compiler); if (args == null) { return null; } StringBuilder buf = new StringBuilder(); buf.append(sql); buf.append("("); for (int i = 0; i < args.length; i++) { if (i > 0) { buf.append(", "); } buf.append(args[i]); } buf.append(") "); return buf.toString(); } public String toString() { return "FunCallSqlCompiler[" + mdx + "]"; } } /** * Shortcut for an unary operator like NOT(a). */ class UnaryOpSqlCompiler extends FunCallSqlCompiler { protected UnaryOpSqlCompiler( int category, String mdx, String sql, SqlCompiler argumentCompiler) { super(category, mdx, sql, 1, argumentCompiler); } } /** * Shortcut for (). */ class ParenthesisSqlCompiler extends FunCallSqlCompiler { protected ParenthesisSqlCompiler( int category, SqlCompiler argumentCompiler) { super(category, "()", "", 1, argumentCompiler); } public String toString() { return "ParenthesisSqlCompiler"; } } /** * Compiles an infix operator like addition into SQL like (a * + b). */ class InfixOpSqlCompiler extends FunCallSqlCompilerBase { private final String sql; private final SqlCompiler compiler; protected InfixOpSqlCompiler( int category, String mdx, String sql, SqlCompiler argumentCompiler) { super(category, mdx, 2); this.sql = sql; this.compiler = argumentCompiler; } public String compile(Exp exp) { String[] args = compileArgs(exp, compiler); if (args == null) { return null; } return "(" + args[0] + " " + sql + " " + args[1] + ")"; } public String toString() { return "InfixSqlCompiler[" + mdx + "]"; } } /** * Compiles an IsEmpty(measure) * expression into SQL measure is null. */ class IsEmptySqlCompiler extends FunCallSqlCompilerBase { private final SqlCompiler compiler; protected IsEmptySqlCompiler( int category, String mdx, SqlCompiler argumentCompiler) { super(category, mdx, 1); this.compiler = argumentCompiler; } public String compile(Exp exp) { String[] args = compileArgs(exp, compiler); if (args == null) { return null; } return "(" + args[0] + " is null" + ")"; } public String toString() { return "IsEmptySqlCompiler[" + mdx + "]"; } } /** * Compiles an IIF(cond, val1, val2) expression into SQL * CASE WHEN cond THEN val1 ELSE val2 END. */ class IifSqlCompiler extends FunCallSqlCompilerBase { SqlCompiler valueCompiler; IifSqlCompiler(int category, SqlCompiler valueCompiler) { super(category, "iif", 3); this.valueCompiler = valueCompiler; } public String compile(Exp exp) { if (!match(exp)) { return null; } Exp[] args = ((FunCall) exp).getArgs(); String cond = booleanCompiler.compile(args[0]); String val1 = valueCompiler.compile(args[1]); String val2 = valueCompiler.compile(args[2]); if (cond == null || val1 == null || val2 == null) { return null; } return sqlQuery.getDialect().caseWhenElse(cond, val1, val2); } } /** * Creates a RolapNativeSql. * * @param sqlQuery the query which is needed for different SQL dialects - * it is not modified */ public RolapNativeSql( SqlQuery sqlQuery, AggStar aggStar, Evaluator evaluator, RolapLevel rolapLevel) { this.sqlQuery = sqlQuery; this.rolapLevel = rolapLevel; this.evaluator = evaluator; this.dialect = sqlQuery.getDialect(); this.aggStar = aggStar; numericCompiler = new CompositeSqlCompiler(); booleanCompiler = new CompositeSqlCompiler(); numericCompiler.add(new NumberSqlCompiler()); numericCompiler.add(new StoredMeasureSqlCompiler()); numericCompiler.add(new CalculatedMemberSqlCompiler(numericCompiler)); numericCompiler.add( new ParenthesisSqlCompiler(Category.Numeric, numericCompiler)); numericCompiler.add( new InfixOpSqlCompiler( Category.Numeric, "+", "+", numericCompiler)); numericCompiler.add( new InfixOpSqlCompiler( Category.Numeric, "-", "-", numericCompiler)); numericCompiler.add( new InfixOpSqlCompiler( Category.Numeric, "/", "/", numericCompiler)); numericCompiler.add( new InfixOpSqlCompiler( Category.Numeric, "*", "*", numericCompiler)); numericCompiler.add( new IifSqlCompiler(Category.Numeric, numericCompiler)); booleanCompiler.add( new InfixOpSqlCompiler( Category.Logical, "<", "<", numericCompiler)); booleanCompiler.add( new InfixOpSqlCompiler( Category.Logical, "<=", "<=", numericCompiler)); booleanCompiler.add( new InfixOpSqlCompiler( Category.Logical, ">", ">", numericCompiler)); booleanCompiler.add( new InfixOpSqlCompiler( Category.Logical, ">=", ">=", numericCompiler)); booleanCompiler.add( new InfixOpSqlCompiler( Category.Logical, "=", "=", numericCompiler)); booleanCompiler.add( new InfixOpSqlCompiler( Category.Logical, "<>", "<>", numericCompiler)); booleanCompiler.add( new IsEmptySqlCompiler( Category.Logical, "IsEmpty", numericCompiler)); booleanCompiler.add( new InfixOpSqlCompiler( Category.Logical, "and", "AND", booleanCompiler)); booleanCompiler.add( new InfixOpSqlCompiler( Category.Logical, "or", "OR", booleanCompiler)); booleanCompiler.add( new UnaryOpSqlCompiler( Category.Logical, "not", "NOT", booleanCompiler)); booleanCompiler.add( new MatchingSqlCompiler()); booleanCompiler.add( new ParenthesisSqlCompiler(Category.Logical, booleanCompiler)); booleanCompiler.add( new IifSqlCompiler(Category.Logical, booleanCompiler)); } /** * Generates an aggregate of a measure, e.g. "sum(Store_Sales)" for * TopCount. The returned expr will be added to the select list and to the * order by clause. */ public String generateTopCountOrderBy(Exp exp) { return numericCompiler.compile(exp); } public String generateFilterCondition(Exp exp) { return booleanCompiler.compile(exp); } public RolapStoredMeasure getStoredMeasure() { return storedMeasure; } } // End RolapNativeSql.java mondrian-3.4.1/src/main/mondrian/rolap/RolapUtil.java0000644000175000017500000005211411735330606022446 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. // // jhyde, 22 December, 2001 */ package mondrian.rolap; import mondrian.calc.ExpCompiler; import mondrian.olap.*; import mondrian.olap.Member; import mondrian.olap.fun.FunUtil; import mondrian.resource.MondrianResource; import mondrian.server.*; import mondrian.spi.Dialect; import org.apache.log4j.Logger; import org.eigenbase.util.property.StringProperty; import java.io.*; import java.lang.reflect.*; import java.sql.SQLException; import java.util.*; import javax.sql.DataSource; /** * Utility methods for classes in the mondrian.rolap package. * * @author jhyde * @since 22 December, 2001 */ public class RolapUtil { public static final Logger MDX_LOGGER = Logger.getLogger("mondrian.mdx"); public static final Logger SQL_LOGGER = Logger.getLogger("mondrian.sql"); public static final Logger MONITOR_LOGGER = Logger.getLogger("mondrian.server.monitor"); public static final Logger PROFILE_LOGGER = Logger.getLogger("mondrian.profile"); static final Logger LOGGER = Logger.getLogger(RolapUtil.class); private static Semaphore querySemaphore; /** * Special cell value indicates that the value is not in cache yet. */ public static final Object valueNotReadyException = new Double(0); /* * Hook to run when a query is executed. This should not be * used at runtime but only for testing. */ private static ExecuteQueryHook queryHook = null; /** * Special value represents a null key. */ public static final Comparable sqlNullValue = new RolapUtilComparable(); /** * Wraps a schema reader in a proxy so that each call to schema reader * has a locus for profiling purposes. * * @param connection Connection * @param schemaReader Schema reader * @return Wrapped schema reader */ public static SchemaReader locusSchemaReader( RolapConnection connection, final SchemaReader schemaReader) { final Statement statement = connection.getInternalStatement(); final Execution execution = new Execution(statement, 0); final Locus locus = new Locus( execution, "Schema reader", null); return (SchemaReader) Proxy.newProxyInstance( SchemaReader.class.getClassLoader(), new Class[]{SchemaReader.class}, new InvocationHandler() { public Object invoke( Object proxy, Method method, Object[] args) throws Throwable { Locus.push(locus); try { return method.invoke(schemaReader, args); } finally { Locus.pop(locus); } } } ); } /** * Sets the query-execution hook used by tests. This method and * {@link #setHook(mondrian.rolap.RolapUtil.ExecuteQueryHook)} are * synchronized to ensure a memory barrier. * * @return Query execution hook */ public static synchronized ExecuteQueryHook getHook() { return queryHook; } public static synchronized void setHook(ExecuteQueryHook hook) { queryHook = hook; } public static final class RolapUtilComparable implements Comparable, Serializable { private static final long serialVersionUID = -2595758291465179116L; public boolean equals(Object o) { return o == this; } public int hashCode() { return super.hashCode(); } public String toString() { return "#null"; } public int compareTo(Object o) { return o == this ? 0 : -1; } } /** * A comparator singleton instance which can handle the presence of * {@link RolapUtilComparable} instances in a collection. */ public static final Comparator ROLAP_COMPARATOR = new RolapUtilComparator(); private static final class RolapUtilComparator> implements Comparator { public int compare(T o1, T o2) { try { return o1.compareTo(o2); } catch (ClassCastException cce) { if (o1 instanceof RolapUtilComparable) { return -1; } if (o2 instanceof RolapUtilComparable) { return 1; } throw new MondrianException(cce); } } } /** * Runtime NullMemberRepresentation property change not taken into * consideration */ private static String mdxNullLiteral = null; public static final String sqlNullLiteral = "null"; public static String mdxNullLiteral() { if (mdxNullLiteral == null) { reloadNullLiteral(); } return mdxNullLiteral; } public static void reloadNullLiteral() { mdxNullLiteral = MondrianProperties.instance().NullMemberRepresentation.get(); } /** * Names of classes of drivers we've loaded (or have tried to load). * *

NOTE: Synchronization policy: Lock the {@link RolapConnection} class * before modifying or using this member. */ private static final Set loadedDrivers = new HashSet(); static RolapMember[] toArray(List v) { return v.isEmpty() ? new RolapMember[0] : v.toArray(new RolapMember[v.size()]); } static RolapMember lookupMember( MemberReader reader, List uniqueNameParts, boolean failIfNotFound) { RolapMember member = lookupMemberInternal( uniqueNameParts, null, reader, failIfNotFound); if (member != null) { return member; } // If this hierarchy has an 'all' member, we can omit it. // For example, '[Gender].[(All Gender)].[F]' can be abbreviated // '[Gender].[F]'. final List rootMembers = reader.getRootMembers(); if (rootMembers.size() == 1) { final RolapMember rootMember = rootMembers.get(0); if (rootMember.isAll()) { member = lookupMemberInternal( uniqueNameParts, rootMember, reader, failIfNotFound); } } return member; } private static RolapMember lookupMemberInternal( List segments, RolapMember member, MemberReader reader, boolean failIfNotFound) { for (Id.Segment segment : segments) { List children; if (member == null) { children = reader.getRootMembers(); } else { children = new ArrayList(); reader.getMemberChildren(member, children); member = null; } for (RolapMember child : children) { if (child.getName().equals(segment.name)) { member = child; break; } } if (member == null) { break; } } if (member == null && failIfNotFound) { throw MondrianResource.instance().MdxCantFindMember.ex( Util.implode(segments)); } return member; } /** * Executes a query, printing to the trace log if tracing is enabled. * *

If the query fails, it wraps the {@link SQLException} in a runtime * exception with message as description, and closes the result * set. * *

If it succeeds, the caller must call the {@link SqlStatement#close} * method of the returned {@link SqlStatement}. * * @param dataSource DataSource * @param sql SQL string * @param locus Locus of execution * @return ResultSet */ public static SqlStatement executeQuery( DataSource dataSource, String sql, Locus locus) { return executeQuery(dataSource, sql, null, 0, 0, locus, -1, -1); } /** * Executes a query. * *

If the query fails, it wraps the {@link SQLException} in a runtime * exception with message as description, and closes the result * set. * *

If it succeeds, the caller must call the {@link SqlStatement#close} * method of the returned {@link SqlStatement}. * * * @param dataSource DataSource * @param sql SQL string * @param types Suggested types of columns, or null; * if present, must have one element for each SQL column; * each not-null entry overrides deduced JDBC type of the column * @param maxRowCount Maximum number of rows to retrieve, <= 0 if unlimited * @param firstRowOrdinal Ordinal of row to skip to (1-based), or 0 to * start from beginning * @param locus Execution context of this statement * @param resultSetType Result set type, or -1 to use default * @param resultSetConcurrency Result set concurrency, or -1 to use default * @return ResultSet */ public static SqlStatement executeQuery( DataSource dataSource, String sql, List types, int maxRowCount, int firstRowOrdinal, Locus locus, int resultSetType, int resultSetConcurrency) { SqlStatement stmt = new SqlStatement( dataSource, sql, types, maxRowCount, firstRowOrdinal, locus, resultSetType, resultSetConcurrency); stmt.execute(); return stmt; } /** * Raises an alert that native SQL evaluation could not be used * in a case where it might have been beneficial, but some * limitation in Mondrian's implementation prevented it. * (Do not call this in cases where native evaluation would * have been wasted effort.) * * @param functionName name of function for which native evaluation * was skipped * * @param reason reason why native evaluation was skipped */ public static void alertNonNative( String functionName, String reason) throws NativeEvaluationUnsupportedException { // No i18n for log message, but yes for excn String alertMsg = "Unable to use native SQL evaluation for '" + functionName + "'; reason: " + reason; StringProperty alertProperty = MondrianProperties.instance().AlertNativeEvaluationUnsupported; String alertValue = alertProperty.get(); if (alertValue.equalsIgnoreCase( org.apache.log4j.Level.WARN.toString())) { LOGGER.warn(alertMsg); } else if (alertValue.equalsIgnoreCase( org.apache.log4j.Level.ERROR.toString())) { LOGGER.error(alertMsg); throw MondrianResource.instance().NativeEvaluationUnsupported.ex( functionName); } } /** * Loads a set of JDBC drivers. * * @param jdbcDrivers A string consisting of the comma-separated names * of JDBC driver classes. For example * "sun.jdbc.odbc.JdbcOdbcDriver,com.mysql.jdbc.Driver". */ public static synchronized void loadDrivers(String jdbcDrivers) { StringTokenizer tok = new StringTokenizer(jdbcDrivers, ","); while (tok.hasMoreTokens()) { String jdbcDriver = tok.nextToken(); if (loadedDrivers.add(jdbcDriver)) { try { Class.forName(jdbcDriver); LOGGER.info( "Mondrian: JDBC driver " + jdbcDriver + " loaded successfully"); } catch (ClassNotFoundException e) { LOGGER.warn( "Mondrian: Warning: JDBC driver " + jdbcDriver + " not found"); } } } } /** * Creates a compiler which will generate programs which will test * whether the dependencies declared via * {@link mondrian.calc.Calc#dependsOn(Hierarchy)} are accurate. */ public static ExpCompiler createDependencyTestingCompiler( ExpCompiler compiler) { return new RolapDependencyTestingEvaluator.DteCompiler(compiler); } /** * Locates a member specified by its member name, from an array of * members. If an exact match isn't found, but a matchType of BEFORE * or AFTER is specified, then the closest matching member is returned. * * @param members array of members to search from * @param parent parent member corresponding to the member being searched * for * @param level level of the member * @param searchName member name * @param matchType match type * @param caseInsensitive if true, use case insensitive search (if * applicable) when when doing exact searches * * @return matching member (if it exists) or the closest matching one * in the case of a BEFORE or AFTER search */ public static Member findBestMemberMatch( List members, RolapMember parent, RolapLevel level, Id.Segment searchName, MatchType matchType, boolean caseInsensitive) { switch (matchType) { case FIRST: return members.get(0); case LAST: return members.get(members.size() - 1); default: // fall through } // create a member corresponding to the member we're trying // to locate so we can use it to hierarchically compare against // the members array Member searchMember = level.getHierarchy().createMember( parent, level, searchName.name, null); Member bestMatch = null; for (Member member : members) { int rc; if (searchName.quoting == Id.Quoting.KEY && member instanceof RolapMember) { if (((RolapMember) member).getKey().toString().equals( searchName.name)) { return member; } } if (matchType.isExact()) { if (caseInsensitive) { rc = Util.compareName(member.getName(), searchName.name); } else { rc = member.getName().compareTo(searchName.name); } } else { rc = FunUtil.compareSiblingMembers( member, searchMember); } if (rc == 0) { return member; } if (matchType == MatchType.BEFORE) { if (rc < 0 && (bestMatch == null || FunUtil.compareSiblingMembers(member, bestMatch) > 0)) { bestMatch = member; } } else if (matchType == MatchType.AFTER) { if (rc > 0 && (bestMatch == null || FunUtil.compareSiblingMembers(member, bestMatch) < 0)) { bestMatch = member; } } } if (matchType.isExact()) { return null; } return bestMatch; } public static MondrianDef.Relation convertInlineTableToRelation( MondrianDef.InlineTable inlineTable, final Dialect dialect) { MondrianDef.View view = new MondrianDef.View(); view.alias = inlineTable.alias; final int columnCount = inlineTable.columnDefs.array.length; List columnNames = new ArrayList(); List columnTypes = new ArrayList(); for (int i = 0; i < columnCount; i++) { columnNames.add(inlineTable.columnDefs.array[i].name); columnTypes.add(inlineTable.columnDefs.array[i].type); } List valueList = new ArrayList(); for (MondrianDef.Row row : inlineTable.rows.array) { String[] values = new String[columnCount]; for (MondrianDef.Value value : row.values) { final int columnOrdinal = columnNames.indexOf(value.column); if (columnOrdinal < 0) { throw Util.newError( "Unknown column '" + value.column + "'"); } values[columnOrdinal] = value.cdata; } valueList.add(values); } view.addCode( "generic", dialect.generateInline( columnNames, columnTypes, valueList)); return view; } public static RolapMember strip(RolapMember member) { if (member instanceof RolapCubeMember) { return ((RolapCubeMember) member).getRolapMember(); } return member; } public static ExpCompiler createProfilingCompiler(ExpCompiler compiler) { return new RolapProfilingEvaluator.ProfilingEvaluatorCompiler( compiler); } /** * Writes to a string and also to an underlying writer. */ public static class TeeWriter extends FilterWriter { StringWriter buf = new StringWriter(); public TeeWriter(Writer out) { super(out); } /** * Returns everything which has been written so far. */ public String toString() { return buf.toString(); } /** * Returns the underlying writer. */ public Writer getWriter() { return out; } public void write(int c) throws IOException { super.write(c); buf.write(c); } public void write(char cbuf[]) throws IOException { super.write(cbuf); buf.write(cbuf); } public void write(char cbuf[], int off, int len) throws IOException { super.write(cbuf, off, len); buf.write(cbuf, off, len); } public void write(String str) throws IOException { super.write(str); buf.write(str); } public void write(String str, int off, int len) throws IOException { super.write(str, off, len); buf.write(str, off, len); } } /** * Writer which throws away all input. */ private static class NullWriter extends Writer { public void write(char cbuf[], int off, int len) throws IOException { } public void flush() throws IOException { } public void close() throws IOException { } } /** * Gets the semaphore which controls how many people can run queries * simultaneously. */ static synchronized Semaphore getQuerySemaphore() { if (querySemaphore == null) { int queryCount = MondrianProperties.instance().QueryLimit.get(); querySemaphore = new Semaphore(queryCount); } return querySemaphore; } /** * Creates a dummy evaluator. */ public static Evaluator createEvaluator( Statement statement) { Execution dummyExecution = new Execution(statement, 0); final RolapResult result = new RolapResult(dummyExecution, false); return result.getRootEvaluator(); } /** * A Semaphore is a primitive for process synchronization. * *

Given a semaphore initialized with count, no more than * count threads can acquire the semaphore using the * {@link #enter} method. Waiting threads block until enough threads have * called {@link #leave}. */ static class Semaphore { private int count; Semaphore(int count) { if (count < 0) { count = Integer.MAX_VALUE; } this.count = count; } synchronized void enter() { if (count == Integer.MAX_VALUE) { return; } if (count == 0) { try { wait(); } catch (InterruptedException e) { throw Util.newInternal(e, "while waiting for semaphore"); } } Util.assertTrue(count > 0); count--; } synchronized void leave() { if (count == Integer.MAX_VALUE) { return; } count++; notify(); } } static interface ExecuteQueryHook { void onExecuteQuery(String sql); } } // End RolapUtil.java mondrian-3.4.1/src/main/mondrian/rolap/aggmatcher/0000755000175000017500000000000011736560300021764 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/rolap/aggmatcher/AggGen.java0000644000175000017500000007531511735330606023775 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.olap.MondrianDef; import mondrian.olap.Util; import mondrian.rolap.RolapAggregator; import mondrian.rolap.RolapStar; import mondrian.rolap.sql.SqlQuery; import org.apache.log4j.Logger; import java.io.PrintWriter; import java.io.StringWriter; import java.sql.SQLException; import java.sql.Types; import java.util.*; /** * This class is used to create "lost" and "collapsed" aggregate table * creation sql (creates the rdbms table and inserts into it from the base * fact table). * * @author Richard M. Emberson */ public class AggGen { private static final Logger LOGGER = Logger.getLogger(AggGen.class); private final String cubeName; private final RolapStar star; private final RolapStar.Column[] columns; /** map RolapStar.Table to list of JdbcSchema Column Usages */ private final Map> collapsedColumnUsages = new HashMap>(); /** set of JdbcSchema Column Usages */ private final Set notLostColumnUsages = new HashSet(); /** list of JdbcSchema Column Usages */ private final List measures = new ArrayList(); private boolean isReady; public AggGen( String cubeName, RolapStar star, RolapStar.Column[] columns) { this.cubeName = cubeName; this.star = star; this.columns = columns; init(); } private Logger getLogger() { return LOGGER; } /** * Return true if this instance is ready to generate the sql. If false, * then something went wrong as it was trying to understand the columns. */ public boolean isReady() { return isReady; } protected RolapStar.Table getFactTable() { return star.getFactTable(); } protected String getFactTableName() { return getFactTable().getAlias(); } protected SqlQuery getSqlQuery() { return star.getSqlQuery(); } protected String getFactCount() { return "fact_count"; } protected JdbcSchema.Table getTable(JdbcSchema db, RolapStar.Table rt) { JdbcSchema.Table jt = getTable(db, rt.getAlias()); return (jt == null) ? getTable(db, rt.getTableName()) : jt; } protected JdbcSchema.Table getTable(JdbcSchema db, String name) { return db.getTable(name); } protected JdbcSchema.Table.Column getColumn( JdbcSchema.Table table, String name) { return table.getColumn(name); } protected String getRolapStarColumnName(RolapStar.Column rColumn) { MondrianDef.Expression expr = rColumn.getExpression(); if (expr instanceof MondrianDef.Column) { MondrianDef.Column cx = (MondrianDef.Column) expr; return cx.getColumnName(); } return null; } protected void addForeignKeyToNotLostColumnUsages( JdbcSchema.Table.Column column) { // first make sure its not already in String cname = column.getName(); for (JdbcSchema.Table.Column.Usage usage : notLostColumnUsages) { JdbcSchema.Table.Column c = usage.getColumn(); if (cname.equals(c.getName())) { return; } } JdbcSchema.Table.Column.Usage usage; if (column.hasUsage(JdbcSchema.UsageType.FOREIGN_KEY)) { Iterator it = column.getUsages(JdbcSchema.UsageType.FOREIGN_KEY); it.hasNext(); usage = it.next(); } else { usage = column.newUsage(JdbcSchema.UsageType.FOREIGN_KEY); usage.setSymbolicName(JdbcSchema.UsageType.FOREIGN_KEY.name()); } notLostColumnUsages.add(usage); } /** * The columns are the RolapStar columns taking part in an aggregation * request. This is what happens. * First, for each column, walk up the column's table until one level below * the base fact table. The left join condition contains the base fact table * and the foreign key column name. This column should not be lost. * Get the base fact table's measure columns. * With a list of columns that should not be lost and measure, one can * create lost create and insert commands. */ private void init() { JdbcSchema db = JdbcSchema.makeDB(star.getDataSource()); try { db.load(); } catch (SQLException ex) { getLogger().error(ex); return; } JdbcSchema.Table factTable = getTable(db, getFactTableName()); if (factTable == null) { getLogger().warn( "Init: " + "No fact table with name \"" + getFactTableName() + "\""); return; } try { factTable.load(); } catch (SQLException ex) { getLogger().error(ex); return; } if (getLogger().isDebugEnabled()) { getLogger().debug( "Init: " + "RolapStar:" + Util.nl + getFactTable() + Util.nl + "FactTable:" + Util.nl + factTable); } // do foreign keys for (RolapStar.Column column : columns) { if (getLogger().isDebugEnabled()) { getLogger().debug( "Init: " + "Column: " + column); } RolapStar.Table table = column.getTable(); if (table.getParentTable() == null) { // this is for those crazy dimensions which are in the // fact table, you know, non-shared with no table element // How the firetruck to enter information for the // collapsed case. This column is in the base fact table // and can be part of a dimension hierarchy but no where // in the RolapStar is this hiearchy captured - ugg. if (!addSpecialCollapsedColumn(db, column)) { return; } MondrianDef.Expression expr = column.getExpression(); if (expr instanceof MondrianDef.Column) { MondrianDef.Column exprColumn = (MondrianDef.Column) expr; String name = exprColumn.getColumnName(); JdbcSchema.Table.Column c = getColumn(factTable, name); if (c == null) { getLogger().warn( "Init: " + "FactTable:" + getFactTableName() + Util.nl + "No Column with name \"" + name + "\""); return; } if (getLogger().isDebugEnabled()) { getLogger().debug(" Jdbc Column: c=" + c); } addForeignKeyToNotLostColumnUsages(c); } } else { if (!addCollapsedColumn(db, column)) { return; } while (table.getParentTable().getParentTable() != null) { table = table.getParentTable(); } RolapStar.Condition cond = table.getJoinCondition(); if (getLogger().isDebugEnabled()) { getLogger().debug(" RolapStar.Condition: cond=" + cond); } MondrianDef.Expression left = cond.getLeft(); if (left instanceof MondrianDef.Column) { MondrianDef.Column leftColumn = (MondrianDef.Column) left; String name = leftColumn.getColumnName(); JdbcSchema.Table.Column c = getColumn(factTable, name); if (c == null) { getLogger().warn( "Init: " + "FactTable:" + getFactTableName() + Util.nl + "No Column with name \"" + name + "\""); return; } if (getLogger().isDebugEnabled()) { getLogger().debug(" Jdbc Column: c=" + c); } addForeignKeyToNotLostColumnUsages(c); } } } // do measures for (RolapStar.Column rColumn : getFactTable().getColumns()) { String name = getRolapStarColumnName(rColumn); if (name == null) { getLogger().warn( "Init: " + "For fact table \"" + getFactTableName() + "\", could not get column name for RolapStar.Column: " + rColumn); return; } if (!(rColumn instanceof RolapStar.Measure)) { // TODO: whats the solution to this? // its a funky dimension column in the fact table!!! getLogger().warn("not a measure: " + name); continue; } RolapStar.Measure rMeasure = (RolapStar.Measure) rColumn; if (!rMeasure.getCubeName().equals(cubeName)) { continue; } final RolapAggregator aggregator = rMeasure.getAggregator(); JdbcSchema.Table.Column c = getColumn(factTable, name); if (c == null) { getLogger().warn( "For RolapStar: \"" + getFactTable().getAlias() + "\" measure with name, " + name + ", is not a column name. " + "The measure's column name may be an expression" + " and currently AggGen does not handle expressions." + " You will have to add this measure to the" + " aggregate table definition by hand."); continue; } if (getLogger().isDebugEnabled()) { getLogger().debug(" Jdbc Column m=" + c); } JdbcSchema.Table.Column.Usage usage = null; if (c.hasUsage(JdbcSchema.UsageType.MEASURE)) { for (Iterator uit = c.getUsages(JdbcSchema.UsageType.MEASURE); uit.hasNext();) { JdbcSchema.Table.Column.Usage tmpUsage = uit.next(); if ((tmpUsage.getAggregator() == aggregator) && tmpUsage.getSymbolicName().equals(rColumn.getName())) { usage = tmpUsage; break; } } } if (usage == null) { usage = c.newUsage(JdbcSchema.UsageType.MEASURE); usage.setAggregator(aggregator); usage.setSymbolicName(rColumn.getName()); } measures.add(usage); } // If we got to here, then everything is ok. isReady = true; } private boolean addSpecialCollapsedColumn( final JdbcSchema db, final RolapStar.Column rColumn) { String rname = getRolapStarColumnName(rColumn); if (rname == null) { getLogger().warn( "Adding Special Collapsed Column: " + "For fact table \"" + getFactTableName() + "\", could not get column name for RolapStar.Column: " + rColumn); return false; } // this is in fact the fact table. RolapStar.Table rt = rColumn.getTable(); JdbcSchema.Table jt = getTable(db, rt); if (jt == null) { getLogger().warn( "Adding Special Collapsed Column: " + "For fact table \"" + getFactTableName() + "\", could not get jdbc schema table " + "for RolapStar.Table with alias \"" + rt.getAlias() + "\""); return false; } try { jt.load(); } catch (SQLException ex) { getLogger().error(ex); return false; } List list = collapsedColumnUsages.get(rt); if (list == null) { list = new ArrayList(); collapsedColumnUsages.put(rt, list); } JdbcSchema.Table.Column c = getColumn(jt, rname); if (c == null) { getLogger().warn( "Adding Special Collapsed Column: " + "For fact table \"" + getFactTableName() + "\", could not get jdbc schema column " + "for RolapStar.Table with alias \"" + rt.getAlias() + "\" and column name \"" + rname + "\""); return false; } // NOTE: this creates a new usage for the fact table // I do not know if this is a problem is AggGen is run before // Mondrian uses aggregate tables. list.add(c.newUsage(JdbcSchema.UsageType.FOREIGN_KEY)); RolapStar.Column prColumn = rColumn; while (prColumn.getParentColumn() != null) { prColumn = prColumn.getParentColumn(); rname = getRolapStarColumnName(prColumn); if (rname == null) { getLogger().warn( "Adding Special Collapsed Column: " + "For fact table \"" + getFactTableName() + "\", could not get parent column name" + "for RolapStar.Column \"" + prColumn + "\" for RolapStar.Table with alias \"" + rt.getAlias() + "\""); return false; } c = getColumn(jt, rname); if (c == null) { getLogger().warn("Can not find column: " + rname); break; } // NOTE: this creates a new usage for the fact table // I do not know if this is a problem is AggGen is run before // Mondrian uses aggregate tables. list.add(c.newUsage(JdbcSchema.UsageType.FOREIGN_KEY)); } return true; } private boolean addCollapsedColumn( final JdbcSchema db, final RolapStar.Column rColumn) { // TODO: if column is "id" column, then there is no collapse String rname = getRolapStarColumnName(rColumn); if (rname == null) { getLogger().warn( "Adding Collapsed Column: " + "For fact table \"" + getFactTableName() + "\", could not get column name for RolapStar.Column: " + rColumn); return false; } RolapStar.Table rt = rColumn.getTable(); JdbcSchema.Table jt = getTable(db, rt); if (jt == null) { getLogger().warn( "Adding Collapsed Column: " + "For fact table \"" + getFactTableName() + "\", could not get jdbc schema table " + "for RolapStar.Table with alias \"" + rt.getAlias() + "\""); return false; } try { jt.load(); } catch (SQLException ex) { getLogger().error(ex); return false; } //CG guarantee the columns has been loaded before looking up them try { jt.load(); } catch (SQLException sqle) { getLogger().error(sqle); return false; } // if this is a dimension table, then walk down the levels until // we hit the current column List list = new ArrayList(); for (RolapStar.Column rc : rt.getColumns()) { // do not include name columns if (rc.isNameColumn()) { continue; } String name = getRolapStarColumnName(rc); if (name == null) { getLogger().warn( "Adding Collapsed Column: " + "For fact table \"" + getFactTableName() + "\", could not get column name" + " for RolapStar.Column \"" + rc + "\" for RolapStar.Table with alias \"" + rt.getAlias() + "\""); return false; } JdbcSchema.Table.Column c = getColumn(jt, name); if (c == null) { getLogger().warn("Can not find column: " + name); break; } JdbcSchema.Table.Column.Usage usage = c.newUsage(JdbcSchema.UsageType.FOREIGN_KEY); usage.usagePrefix = rc.getUsagePrefix(); list.add(usage); if (rname.equals(name)) { break; } } // may already be there so only enter if new list is bigger List l = collapsedColumnUsages.get(rt); if ((l == null) || (l.size() < list.size())) { collapsedColumnUsages.put(rt, list); } return true; } private static final String AGG_LOST_PREFIX = "agg_l_XXX_"; String makeLostAggregateTableName(String factTableName) { return AGG_LOST_PREFIX + factTableName; } private static final String AGG_COLLAPSED_PREFIX = "agg_c_XXX_"; String makeCollapsedAggregateTableName(String factTableName) { return AGG_COLLAPSED_PREFIX + factTableName; } /** * Return a String containing the sql code to create a lost dimension * table. * * @return lost dimension sql code */ public String createLost() { StringWriter sw = new StringWriter(512); PrintWriter pw = new PrintWriter(sw); String prefix = " "; pw.print("CREATE TABLE "); pw.print(makeLostAggregateTableName(getFactTableName())); pw.println(" ("); // do foreign keys for (JdbcSchema.Table.Column.Usage usage : notLostColumnUsages) { addColumnCreate(pw, prefix, usage); } // do measures for (JdbcSchema.Table.Column.Usage usage : measures) { addColumnCreate(pw, prefix, usage); } // do fact_count pw.print(prefix); pw.print(getFactCount()); pw.println(" INTEGER NOT NULL"); pw.println(");"); return sw.toString(); } /** * Return the sql code to populate a lost dimension table from the fact * table. */ public String insertIntoLost() { StringWriter sw = new StringWriter(512); PrintWriter pw = new PrintWriter(sw); String prefix = " "; String factTableName = getFactTableName(); SqlQuery sqlQuery = getSqlQuery(); pw.print("INSERT INTO "); pw.print(makeLostAggregateTableName(getFactTableName())); pw.println(" ("); for (JdbcSchema.Table.Column.Usage usage : notLostColumnUsages) { JdbcSchema.Table.Column c = usage.getColumn(); pw.print(prefix); pw.print(c.getName()); pw.println(','); } for (JdbcSchema.Table.Column.Usage usage : measures) { JdbcSchema.Table.Column c = usage.getColumn(); pw.print(prefix); String name = getUsageName(usage); pw.print(name); pw.println(','); } // do fact_count pw.print(prefix); pw.print(getFactCount()); pw.println(")"); pw.println("SELECT"); for (JdbcSchema.Table.Column.Usage usage : notLostColumnUsages) { JdbcSchema.Table.Column c = usage.getColumn(); pw.print(prefix); pw.print( sqlQuery.getDialect().quoteIdentifier( factTableName, c.getName())); pw.print(" AS "); pw.print(sqlQuery.getDialect().quoteIdentifier(c.getName())); pw.println(','); } for (JdbcSchema.Table.Column.Usage usage : measures) { JdbcSchema.Table.Column c = usage.getColumn(); RolapAggregator agg = usage.getAggregator(); pw.print(prefix); pw.print( agg.getExpression(sqlQuery.getDialect().quoteIdentifier( factTableName, c.getName()))); pw.print(" AS "); pw.print(sqlQuery.getDialect().quoteIdentifier(c.getName())); pw.println(','); } // do fact_count pw.print(prefix); pw.print("COUNT(*) AS "); pw.println(sqlQuery.getDialect().quoteIdentifier(getFactCount())); pw.println("FROM "); pw.print(prefix); pw.print(sqlQuery.getDialect().quoteIdentifier(factTableName)); pw.print(" "); pw.println(sqlQuery.getDialect().quoteIdentifier(factTableName)); pw.println("GROUP BY "); int k = 0; for (JdbcSchema.Table.Column.Usage notLostColumnUsage : notLostColumnUsages) { if (k++ > 0) { pw.println(","); } JdbcSchema.Table.Column.Usage usage = notLostColumnUsage; JdbcSchema.Table.Column c = usage.getColumn(); pw.print(prefix); pw.print( sqlQuery.getDialect().quoteIdentifier( factTableName, c.getName())); } pw.println(';'); return sw.toString(); } /** * Return a String containing the sql code to create a collapsed dimension * table. * * @return collapsed dimension sql code */ public String createCollapsed() { StringWriter sw = new StringWriter(512); PrintWriter pw = new PrintWriter(sw); String prefix = " "; pw.print("CREATE TABLE "); pw.print(makeCollapsedAggregateTableName(getFactTableName())); pw.println(" ("); // do foreign keys for (List list : collapsedColumnUsages.values()) { for (JdbcSchema.Table.Column.Usage usage : list) { addColumnCreate(pw, prefix, usage); } } // do measures for (JdbcSchema.Table.Column.Usage usage : measures) { addColumnCreate(pw, prefix, usage); } // do fact_count pw.print(prefix); pw.print(getFactCount()); pw.println(" INTEGER NOT NULL"); pw.println(");"); return sw.toString(); } /** * Return the sql code to populate a collapsed dimension table from * the fact table. */ public String insertIntoCollapsed() { StringWriter sw = new StringWriter(512); PrintWriter pw = new PrintWriter(sw); String prefix = " "; String factTableName = getFactTableName(); SqlQuery sqlQuery = getSqlQuery(); pw.print("INSERT INTO "); pw.print(makeCollapsedAggregateTableName(getFactTableName())); pw.println(" ("); for (List list : collapsedColumnUsages.values()) { for (JdbcSchema.Table.Column.Usage usage : list) { JdbcSchema.Table.Column c = usage.getColumn(); pw.print(prefix); if (usage.usagePrefix != null) { pw.print(usage.usagePrefix); } pw.print(c.getName()); pw.println(','); } } for (JdbcSchema.Table.Column.Usage usage : measures) { JdbcSchema.Table.Column c = usage.getColumn(); pw.print(prefix); String name = getUsageName(usage); pw.print(name); //pw.print(c.getName()); pw.println(','); } // do fact_count pw.print(prefix); pw.print(getFactCount()); pw.println(")"); pw.println("SELECT"); for (List list : collapsedColumnUsages.values()) { for (JdbcSchema.Table.Column.Usage usage : list) { JdbcSchema.Table.Column c = usage.getColumn(); JdbcSchema.Table t = c.getTable(); pw.print(prefix); pw.print( sqlQuery.getDialect().quoteIdentifier( t.getName(), c.getName())); pw.print(" AS "); String n = (usage.usagePrefix == null) ? c.getName() : usage.usagePrefix + c.getName(); pw.print(sqlQuery.getDialect().quoteIdentifier(n)); pw.println(','); } } for (JdbcSchema.Table.Column.Usage usage : measures) { JdbcSchema.Table.Column c = usage.getColumn(); JdbcSchema.Table t = c.getTable(); RolapAggregator agg = usage.getAggregator(); pw.print(prefix); pw.print( agg.getExpression(sqlQuery.getDialect().quoteIdentifier( t.getName(), c.getName()))); pw.print(" AS "); pw.print(sqlQuery.getDialect().quoteIdentifier(c.getName())); pw.println(','); } // do fact_count pw.print(prefix); pw.print("COUNT(*) AS "); pw.println(sqlQuery.getDialect().quoteIdentifier(getFactCount())); pw.println("FROM "); pw.print(prefix); pw.print(sqlQuery.getDialect().quoteIdentifier(factTableName)); pw.print(" "); pw.print(sqlQuery.getDialect().quoteIdentifier(factTableName)); pw.println(','); // add dimension tables int k = 0; for (RolapStar.Table rt : collapsedColumnUsages.keySet()) { if (k++ > 0) { pw.println(','); } pw.print(prefix); pw.print(sqlQuery.getDialect().quoteIdentifier(rt.getAlias())); pw.print(" AS "); pw.print(sqlQuery.getDialect().quoteIdentifier(rt.getAlias())); // walk up tables if (rt.getParentTable() != null) { while (rt.getParentTable().getParentTable() != null) { rt = rt.getParentTable(); pw.println(','); pw.print(prefix); pw.print( sqlQuery.getDialect().quoteIdentifier(rt.getAlias())); pw.print(" AS "); pw.print( sqlQuery.getDialect().quoteIdentifier(rt.getAlias())); } } } pw.println(); pw.println("WHERE "); k = 0; for (RolapStar.Table rt : collapsedColumnUsages.keySet()) { if (k++ > 0) { pw.println(" and"); } RolapStar.Condition cond = rt.getJoinCondition(); if (cond == null) { continue; } pw.print(prefix); pw.print(cond.toString(sqlQuery)); if (rt.getParentTable() != null) { while (rt.getParentTable().getParentTable() != null) { rt = rt.getParentTable(); cond = rt.getJoinCondition(); pw.println(" and"); pw.print(prefix); pw.print(cond.toString(sqlQuery)); } } } pw.println(); pw.println("GROUP BY "); k = 0; for (List list : collapsedColumnUsages.values()) { for (JdbcSchema.Table.Column.Usage usage : list) { if (k++ > 0) { pw.println(","); } JdbcSchema.Table.Column c = usage.getColumn(); JdbcSchema.Table t = c.getTable(); String n = (usage.usagePrefix == null) ? c.getName() : usage.usagePrefix + c.getName(); pw.print(prefix); pw.print(sqlQuery.getDialect().quoteIdentifier(t.getName(), n)); } } pw.println(';'); return sw.toString(); } private String getUsageName(final JdbcSchema.Table.Column.Usage usage) { JdbcSchema.Table.Column c = usage.getColumn(); String name = c.getName(); // if its a measure which is based upon a foreign key, then // the foreign key column name is already used (for the foreign key // column) so we must choose a different name. if (usage.getUsageType() == JdbcSchema.UsageType.MEASURE) { if (c.hasUsage(JdbcSchema.UsageType.FOREIGN_KEY)) { name = usage.getSymbolicName().replace(' ', '_').toUpperCase(); } } return name; } private void addColumnCreate( final PrintWriter pw, final String prefix, final JdbcSchema.Table.Column.Usage usage) { JdbcSchema.Table.Column c = usage.getColumn(); String name = getUsageName(usage); pw.print(prefix); if (usage.usagePrefix != null) { pw.print(usage.usagePrefix); } pw.print(name); pw.print(' '); pw.print(c.getTypeName().toUpperCase()); switch (c.getType()) { case Types.NUMERIC: case Types.DECIMAL: pw.print('('); pw.print(c.getNumPrecRadix()); pw.print(","); pw.print(c.getDecimalDigits()); pw.print(')'); break; case Types.CHAR: case Types.VARCHAR: pw.print('('); pw.print(c.getCharOctetLength()); pw.print(')'); break; default: } if (! c.isNullable()) { pw.print(" NOT NULL"); } pw.println(','); } } // End AggGen.java mondrian-3.4.1/src/main/mondrian/rolap/aggmatcher/AggTableManager.java0000644000175000017500000005177311735330606025610 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.olap.*; import mondrian.recorder.*; import mondrian.resource.MondrianResource; import mondrian.rolap.*; import org.apache.log4j.Logger; import org.eigenbase.util.property.Property; import org.eigenbase.util.property.Trigger; import java.sql.SQLException; import java.util.*; import javax.sql.DataSource; /** * Manages aggregate tables. * *

It is used as follows:

    *
  • A {@link mondrian.rolap.RolapSchema} creates an {@link AggTableManager}, * and stores it in a member variable to ensure that it is not * garbage-collected. *
  • The {@link AggTableManager} creates and registers * {@link org.eigenbase.util.property.Trigger} objects, so that it is notified * when properties pertinent to aggregate tables change. *
  • The {@link mondrian.rolap.RolapSchema} calls {@link #initialize()}, * which scans the JDBC catalog and identifies aggregate tables. *
  • For each aggregate table, it creates an {@link AggStar} and calls * {@link RolapStar#addAggStar(AggStar)}. * * @author Richard M. Emberson */ public class AggTableManager { private static final Logger LOGGER = Logger.getLogger(AggTableManager.class); private final RolapSchema schema; private static final MondrianResource mres = MondrianResource.instance(); /** * This is used to create forward references to triggers (so they do not * get reaped until the RolapSchema is reaped). */ private Trigger[] triggers; public AggTableManager(final RolapSchema schema) { this.schema = schema; } /** * This should ONLY be called if the AggTableManager is no longer going * to be used. In fact, it should only be called indirectly by its * associated RolapSchema object. */ public void finalCleanUp() { removeJdbcSchema(); deregisterTriggers(MondrianProperties.instance()); if (getLogger().isDebugEnabled()) { getLogger().debug( "AggTableManager.finalCleanUp: schema=" + schema.getName()); } } /** * Get the Logger. */ public Logger getLogger() { return LOGGER; } /** * Initializes this object, loading all aggregate tables and associating * them with {@link RolapStar}s. * This method should only be called once. */ public void initialize() { if (MondrianProperties.instance().ReadAggregates.get()) { try { loadRolapStarAggregates(); } catch (SQLException ex) { throw mres.AggLoadingError.ex(ex); } } registerTriggers(); printResults(); } private void printResults() { /* * This was too much information at the INFO level, compared to the * rest of Mondrian * * if (getLogger().isInfoEnabled()) { // print just Star table alias and AggStar table names StringBuilder buf = new StringBuilder(1024); buf.append(Util.nl); for (Iterator it = getStars(); it.hasNext();) { RolapStar star = (RolapStar) it.next(); buf.append(star.getFactTable().getAlias()); buf.append(Util.nl); for (Iterator ait = star.getAggStars(); ait.hasNext();) { AggStar aggStar = (AggStar) ait.next(); buf.append(" "); buf.append(aggStar.getFactTable().getName()); buf.append(Util.nl); } } getLogger().info(buf.toString()); } else */ if (getLogger().isDebugEnabled()) { // print everything, Star, subTables, AggStar and subTables // could be a lot StringBuilder buf = new StringBuilder(4096); buf.append(Util.nl); for (RolapStar star : getStars()) { buf.append(star.toString()); buf.append(Util.nl); } getLogger().debug(buf.toString()); } } private void reLoadRolapStarAggregates() { if (MondrianProperties.instance().ReadAggregates.get()) { try { clearJdbcSchema(); loadRolapStarAggregates(); printResults(); } catch (SQLException ex) { throw mres.AggLoadingError.ex(ex); } } } private JdbcSchema getJdbcSchema() { DataSource dataSource = schema.getInternalConnection().getDataSource(); // This actually just does a lookup or simple constructor invocation, // its not expected to fail return JdbcSchema.makeDB(dataSource); } /** * Clear the possibly already loaded snapshot of what is in the database. */ private void clearJdbcSchema() { DataSource dataSource = schema.getInternalConnection().getDataSource(); JdbcSchema.clearDB(dataSource); } /** * Remove the possibly already loaded snapshot of what is in the database. */ private void removeJdbcSchema() { DataSource dataSource = schema.getInternalConnection().getDataSource(); JdbcSchema.removeDB(dataSource); } /** * This method loads and/or reloads the aggregate tables. *

    * NOTE: At this point all RolapStars have been made for this * schema (except for dynamically added cubes which I am going * to ignore for right now). So, All stars have their columns * and their BitKeys can be generated. * * @throws SQLException */ private void loadRolapStarAggregates() throws SQLException { ListRecorder msgRecorder = new ListRecorder(); try { DefaultRules rules = DefaultRules.getInstance(); JdbcSchema db = getJdbcSchema(); // if we don't synchronize this on the db object, // we may end up getting a Concurrency exception due to // calls to other instances of AggTableManager.finalCleanUp() synchronized (db) { // fix for MONDRIAN-496 // flush any existing usages of the jdbc schema, so we // don't accidentally use another star's metadata db.flushUsages(); // loads tables, not their columns db.load(); loop: for (RolapStar star : getStars()) { // This removes any AggStars from any previous invocation of // this method (if any) star.prepareToLoadAggregates(); List aggGroups = getAggGroups(star); for (ExplicitRules.Group group : aggGroups) { group.validate(msgRecorder); } String factTableName = star.getFactTable().getAlias(); JdbcSchema.Table dbFactTable = db.getTable(factTableName); if (dbFactTable == null) { msgRecorder.reportWarning( "No Table found for fact name=" + factTableName); continue loop; } // For each column in the dbFactTable, figure out it they // are measure or foreign key columns bindToStar(dbFactTable, star, msgRecorder); String schema = dbFactTable.table.schema; // Now look at all tables in the database and per table, // first see if it is a match for an aggregate table for // this fact table and second see if its columns match // foreign key and level columns. for (JdbcSchema.Table dbTable : db.getTables()) { String name = dbTable.getName(); // Do the catalog schema aggregate excludes, exclude // this table name. if (ExplicitRules.excludeTable(name, aggGroups)) { continue; } // First see if there is an ExplicitRules match. If so, // then if all of the columns match up, then make an // AggStar. On the other hand, if there is no // ExplicitRules match, see if there is a Default // match. If so and if all the columns match up, then // also make an AggStar. ExplicitRules.TableDef tableDef = ExplicitRules.getIncludeByTableDef(name, aggGroups); boolean makeAggStar = false; int approxRowCount = Integer.MIN_VALUE; // Is it handled by the ExplicitRules if (tableDef != null) { // load columns dbTable.load(); makeAggStar = tableDef.columnsOK( star, dbFactTable, dbTable, msgRecorder); approxRowCount = tableDef.getApproxRowCount(); } if (! makeAggStar) { // Is it handled by the DefaultRules if (rules.matchesTableName(factTableName, name)) { // load columns dbTable.load(); makeAggStar = rules.columnsOK( star, dbFactTable, dbTable, msgRecorder); } } if (makeAggStar) { dbTable.setTableUsageType( JdbcSchema.TableUsageType.AGG); dbTable.table = new MondrianDef.Table( schema, name, null, // null alias null); // don't know about table hints AggStar aggStar = AggStar.makeAggStar( star, dbTable, msgRecorder, approxRowCount); if (aggStar.getSize() > 0) { star.addAggStar(aggStar); } else { getLogger().warn( mres.AggTableZeroSize.str( aggStar.getFactTable().getName(), factTableName)); } } // Note: if the dbTable name matches but the columnsOK // does not, then this is an error and the aggregate // tables can not be loaded. // We do not "reset" the column usages in the dbTable // allowing it maybe to match another rule. } } } } catch (RecorderException ex) { throw new MondrianException(ex); } finally { msgRecorder.logInfoMessage(getLogger()); msgRecorder.logWarningMessage(getLogger()); msgRecorder.logErrorMessage(getLogger()); if (msgRecorder.hasErrors()) { throw mres.AggLoadingExceededErrorCount.ex( msgRecorder.getErrorCount()); } } } private boolean runTrigger() { if (RolapSchema.cacheContains(schema)) { return true; } else { // must remove triggers deregisterTriggers(MondrianProperties.instance()); return false; } } /** * Registers triggers for the following properties: *

      *
    • {@link MondrianProperties#ChooseAggregateByVolume} *
    • {@link MondrianProperties#AggregateRules} *
    • {@link MondrianProperties#AggregateRuleTag} *
    • {@link MondrianProperties#ReadAggregates} *
    */ private void registerTriggers() { final MondrianProperties properties = MondrianProperties.instance(); triggers = new Trigger[] { // When the ordering AggStars property is changed, we must // reorder them, so we create a trigger. // There is no need to provide equals/hashCode methods for this // Trigger since it is never explicitly removed. new Trigger() { public boolean isPersistent() { return false; } public int phase() { return Trigger.SECONDARY_PHASE; } public void execute(Property property, String value) { if (AggTableManager.this.runTrigger()) { reOrderAggStarList(); } } }, // Register to know when the Default resource/url has changed // so that the default aggregate table recognition rules can // be re-loaded. // There is no need to provide equals/hashCode methods for this // Trigger since it is never explicitly removed. new Trigger() { public boolean isPersistent() { return false; } public int phase() { return Trigger.SECONDARY_PHASE; } public void execute(Property property, String value) { if (AggTableManager.this.runTrigger()) { reLoadRolapStarAggregates(); } } }, // If the system started not using aggregates, i.e., the aggregate // tables were not loaded, but then the property // was changed to use aggregates, we must then load the aggregates // if they were never loaded. new Trigger() { public boolean isPersistent() { return false; } public int phase() { return Trigger.SECONDARY_PHASE; } public void execute(Property property, String value) { if (AggTableManager.this.runTrigger()) { reLoadRolapStarAggregates(); } } } }; // Note that for each AggTableManager theses triggers are // added to the properties object. Each trigger has just // been created and "knows" its AggTableManager instance. // The triggers' hashCode and equals methods (those provided // by the Object class) are used when removing the trigger. properties.ChooseAggregateByVolume.addTrigger(triggers[0]); properties.AggregateRules.addTrigger(triggers[1]); properties.AggregateRuleTag.addTrigger(triggers[1]); properties.ReadAggregates.addTrigger(triggers[2]); } private void deregisterTriggers(final MondrianProperties properties) { // Remove this AggTableManager's instance's triggers. final Trigger[] triggers = this.triggers; if (triggers == null) { return; } properties.ChooseAggregateByVolume.removeTrigger(triggers[0]); properties.AggregateRules.removeTrigger(triggers[1]); properties.AggregateRuleTag.removeTrigger(triggers[1]); properties.ReadAggregates.removeTrigger(triggers[2]); } private Collection getStars() { return schema.getStars(); } private void reOrderAggStarList() { for (RolapStar star : getStars()) { star.reOrderAggStarList(); } } /** * Returns a list containing every * {@link mondrian.rolap.aggmatcher.ExplicitRules.Group} in every * cubes in a given {@link RolapStar}. */ protected List getAggGroups(RolapStar star) { List aggGroups = new ArrayList(); for (RolapCube cube : schema.getCubesWithStar(star)) { if (cube.hasAggGroup() && cube.getAggGroup().hasRules()) { aggGroups.add(cube.getAggGroup()); } } return aggGroups; } /** * This method mines the RolapStar and annotes the JdbcSchema.Table * dbFactTable by creating JdbcSchema.Table.Column.Usage instances. For * example, a measure in the RolapStar becomes a measure usage for the * column with the same name and a RolapStar foreign key column becomes a * foreign key usage for the column with the same name. * * @param dbFactTable * @param star * @param msgRecorder */ void bindToStar( final JdbcSchema.Table dbFactTable, final RolapStar star, final MessageRecorder msgRecorder) throws SQLException { msgRecorder.pushContextName("AggTableManager.bindToStar"); try { // load columns dbFactTable.load(); dbFactTable.setTableUsageType(JdbcSchema.TableUsageType.FACT); MondrianDef.RelationOrJoin relation = star.getFactTable().getRelation(); String schema = null; MondrianDef.Hint[] tableHints = null; if (relation instanceof MondrianDef.Table) { schema = ((MondrianDef.Table) relation).schema; tableHints = ((MondrianDef.Table) relation).tableHints; } String tableName = dbFactTable.getName(); String alias = null; dbFactTable.table = new MondrianDef.Table( schema, tableName, alias, tableHints); for (JdbcSchema.Table.Column factColumn : dbFactTable.getColumns()) { String cname = factColumn.getName(); RolapStar.Column[] rcs = star.getFactTable().lookupColumns(cname); for (RolapStar.Column rc : rcs) { // its a measure if (rc instanceof RolapStar.Measure) { RolapStar.Measure rm = (RolapStar.Measure) rc; JdbcSchema.Table.Column.Usage usage = factColumn.newUsage(JdbcSchema.UsageType.MEASURE); usage.setSymbolicName(rm.getName()); usage.setAggregator(rm.getAggregator()); usage.rMeasure = rm; } } // it still might be a foreign key RolapStar.Table rTable = star.getFactTable().findTableWithLeftJoinCondition(cname); if (rTable != null) { JdbcSchema.Table.Column.Usage usage = factColumn.newUsage(JdbcSchema.UsageType.FOREIGN_KEY); usage.setSymbolicName("FOREIGN_KEY"); usage.rTable = rTable; } else { RolapStar.Column rColumn = star.getFactTable().lookupColumn(cname); if ((rColumn != null) && !(rColumn instanceof RolapStar.Measure)) { // Ok, maybe its used in a non-shared dimension // This is a column in the fact table which is // (not necessarily) a measure but is also not // a foreign key to an external dimension table. JdbcSchema.Table.Column.Usage usage = factColumn.newUsage( JdbcSchema.UsageType.FOREIGN_KEY); usage.setSymbolicName("FOREIGN_KEY"); usage.rColumn = rColumn; } } // warn if it has not been identified if (!factColumn.hasUsage() && getLogger().isDebugEnabled()) { getLogger().debug( mres.UnknownFactTableColumn.str( msgRecorder.getContext(), dbFactTable.getName(), factColumn.getName())); } } } finally { msgRecorder.popContextName(); } } } // End AggTableManager.java mondrian-3.4.1/src/main/mondrian/rolap/aggmatcher/DefaultRules.java0000644000175000017500000003532411735330606025240 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.olap.MondrianProperties; import mondrian.recorder.*; import mondrian.resource.MondrianResource; import mondrian.rolap.RolapStar; import org.apache.log4j.Logger; import org.eigenbase.util.property.Property; import org.eigenbase.util.property.Trigger; import org.eigenbase.xom.*; import java.io.*; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; import java.util.Map; /** * Container for the default aggregate recognition rules. * It is generated by parsing the default rule xml information found * in the {@link MondrianProperties#AggregateRules} value which normally is * a resource in the jar file (but can be a url). * *

    It is a singleton since it is used to recognize tables independent of * database connection (each {@link mondrian.rolap.RolapSchema} uses the same * instance). * * @author Richard M. Emberson */ public class DefaultRules { private static final Logger LOGGER = Logger.getLogger(DefaultRules.class); private static final MondrianResource mres = MondrianResource.instance(); /** * There is a single instance of the {@link DefaultRecognizer} and the * {@link DefaultRules} class is a container of that instance. */ public static synchronized DefaultRules getInstance() { if (instance == null) { InputStream inStream = getAggRuleInputStream(); if (inStream == null) { return null; } DefaultDef.AggRules defs = makeAggRules(inStream); // validate the DefaultDef.AggRules object ListRecorder reclists = new ListRecorder(); try { defs.validate(reclists); } catch (RecorderException e) { // ignore } reclists.logWarningMessage(LOGGER); reclists.logErrorMessage(LOGGER); if (reclists.hasErrors()) { reclists.throwRTException(); } // make sure the tag name exists String tag = MondrianProperties.instance().AggregateRuleTag.get(); DefaultDef.AggRule aggrule = defs.getAggRule(tag); if (aggrule == null) { throw mres.MissingDefaultAggRule.ex(tag); } DefaultRules rules = new DefaultRules(defs); rules.setTag(tag); instance = rules; } return instance; } private static InputStream getAggRuleInputStream() { String aggRules = MondrianProperties.instance().AggregateRules.get(); InputStream inStream = DefaultRules.class.getResourceAsStream(aggRules); if (inStream == null) { try { URL url = new URL(aggRules); inStream = url.openStream(); } catch (MalformedURLException e) { // ignore } catch (IOException e) { // ignore } } if (inStream == null) { LOGGER.warn(mres.CouldNotLoadDefaultAggregateRules.str(aggRules)); } return inStream; } private static DefaultRules instance = null; static { // When the value of the AggregateRules property is changed, force // system to reload the DefaultRules. // There is no need to provide equals/hashCode methods for this // Trigger since it is a singleton and is never removed. Trigger trigger = new Trigger() { public boolean isPersistent() { return true; } public int phase() { return Trigger.PRIMARY_PHASE; } public void execute(Property property, String value) { synchronized (DefaultRules.class) { DefaultRules oldInstance = DefaultRules.instance; DefaultRules.instance = null; DefaultRules newInstance = null; Exception ex = null; try { newInstance = DefaultRules.getInstance(); } catch (Exception e) { ex = e; } if (ex != null) { DefaultRules.instance = oldInstance; throw new Trigger.VetoRT(ex); } else if (newInstance == null) { DefaultRules.instance = oldInstance; String msg = mres.FailedCreateNewDefaultAggregateRules.str( property.getPath(), value); throw new Trigger.VetoRT(msg); } else { instance = newInstance; } } } }; final MondrianProperties properties = MondrianProperties.instance(); properties.AggregateRules.addTrigger(trigger); properties.AggregateRuleTag.addTrigger(trigger); } protected static DefaultDef.AggRules makeAggRules(final File file) { DOMWrapper def = makeDOMWrapper(file); try { DefaultDef.AggRules rules = new DefaultDef.AggRules(def); return rules; } catch (XOMException e) { throw mres.AggRuleParse.ex(file.getName(), e); } } protected static DefaultDef.AggRules makeAggRules(final URL url) { DOMWrapper def = makeDOMWrapper(url); try { DefaultDef.AggRules rules = new DefaultDef.AggRules(def); return rules; } catch (XOMException e) { throw mres.AggRuleParse.ex(url.toString(), e); } } protected static DefaultDef.AggRules makeAggRules( final InputStream inStream) { DOMWrapper def = makeDOMWrapper(inStream); try { DefaultDef.AggRules rules = new DefaultDef.AggRules(def); return rules; } catch (XOMException e) { throw mres.AggRuleParse.ex("InputStream", e); } } protected static DefaultDef.AggRules makeAggRules( final String text, final String name) { DOMWrapper def = makeDOMWrapper(text, name); try { DefaultDef.AggRules rules = new DefaultDef.AggRules(def); return rules; } catch (XOMException e) { throw mres.AggRuleParse.ex(name, e); } } protected static DOMWrapper makeDOMWrapper(final File file) { try { return makeDOMWrapper(file.toURL()); } catch (MalformedURLException e) { throw mres.AggRuleParse.ex(file.getName(), e); } } protected static DOMWrapper makeDOMWrapper(final URL url) { try { final Parser xmlParser = XOMUtil.createDefaultParser(); DOMWrapper def = xmlParser.parse(url); return def; } catch (XOMException e) { throw mres.AggRuleParse.ex(url.toString(), e); } } protected static DOMWrapper makeDOMWrapper(final InputStream inStream) { try { final Parser xmlParser = XOMUtil.createDefaultParser(); DOMWrapper def = xmlParser.parse(inStream); return def; } catch (XOMException e) { throw mres.AggRuleParse.ex("InputStream", e); } } protected static DOMWrapper makeDOMWrapper( final String text, final String name) { try { final Parser xmlParser = XOMUtil.createDefaultParser(); DOMWrapper def = xmlParser.parse(text); return def; } catch (XOMException e) { throw mres.AggRuleParse.ex(name, e); } } private final DefaultDef.AggRules rules; private final Map factToPattern; private final Map foreignKeyMatcherMap; private Recognizer.Matcher ignoreMatcherMap; private Recognizer.Matcher factCountMatcher; private String tag; private DefaultRules(final DefaultDef.AggRules rules) { this.rules = rules; this.factToPattern = new HashMap(); this.foreignKeyMatcherMap = new HashMap(); this.tag = MondrianProperties.instance().AggregateRuleTag.getDefaultValue(); } public void validate(MessageRecorder msgRecorder) { rules.validate(msgRecorder); } /** * Sets the name (tag) of this rule. * * @param tag */ private void setTag(final String tag) { this.tag = tag; } /** * Gets the tag of this rule (this is the value of the * {@link MondrianProperties#AggregateRuleTag} property). */ public String getTag() { return this.tag; } /** * Returns the {@link mondrian.rolap.aggmatcher.DefaultDef.AggRule} whose * tag equals this rule's tag. */ public DefaultDef.AggRule getAggRule() { return getAggRule(getTag()); } /** * Returns the {@link mondrian.rolap.aggmatcher.DefaultDef.AggRule} whose * tag equals the parameter tag, or null if not found. * * @param tag * @return the AggRule with tag value equal to tag parameter, or null. */ public DefaultDef.AggRule getAggRule(final String tag) { return this.rules.getAggRule(tag); } /** * Gets the {@link mondrian.rolap.aggmatcher.Recognizer.Matcher} for this * tableName. * * @param tableName */ public Recognizer.Matcher getTableMatcher(final String tableName) { Recognizer.Matcher matcher = factToPattern.get(tableName); if (matcher == null) { // get default AggRule DefaultDef.AggRule rule = getAggRule(); DefaultDef.TableMatch tableMatch = rule.getTableMatch(); matcher = tableMatch.getMatcher(tableName); factToPattern.put(tableName, matcher); } return matcher; } /** * Gets the {@link mondrian.rolap.aggmatcher.Recognizer.Matcher} for the * fact count column. */ public Recognizer.Matcher getIgnoreMatcher() { if (ignoreMatcherMap == null) { // get default AggRule DefaultDef.AggRule rule = getAggRule(); DefaultDef.IgnoreMap ignoreMatch = rule.getIgnoreMap(); if (ignoreMatch == null) { ignoreMatcherMap = new Recognizer.Matcher() { public boolean matches(String name) { return false; } }; } else { ignoreMatcherMap = ignoreMatch.getMatcher(); } } return ignoreMatcherMap; } /** * Gets the {@link mondrian.rolap.aggmatcher.Recognizer.Matcher} for * columns that should be ignored. * * @return the {@link mondrian.rolap.aggmatcher.Recognizer.Matcher} for * columns that should be ignored. */ public Recognizer.Matcher getFactCountMatcher() { if (factCountMatcher == null) { // get default AggRule DefaultDef.AggRule rule = getAggRule(); DefaultDef.FactCountMatch factCountMatch = rule.getFactCountMatch(); factCountMatcher = factCountMatch.getMatcher(); } return factCountMatcher; } /** * Gets the {@link mondrian.rolap.aggmatcher.Recognizer.Matcher} for this * foreign key column name. * * @param foreignKeyName Name of a foreign key column */ public Recognizer.Matcher getForeignKeyMatcher(String foreignKeyName) { Recognizer.Matcher matcher = foreignKeyMatcherMap.get(foreignKeyName); if (matcher == null) { // get default AggRule DefaultDef.AggRule rule = getAggRule(); DefaultDef.ForeignKeyMatch foreignKeyMatch = rule.getForeignKeyMatch(); matcher = foreignKeyMatch.getMatcher(foreignKeyName); foreignKeyMatcherMap.put(foreignKeyName, matcher); } return matcher; } /** * Returns true if this candidate aggregate table name "matches" the * factTableName. * * @param factTableName Name of the fact table * @param name candidate aggregate table name */ public boolean matchesTableName( final String factTableName, final String name) { Recognizer.Matcher matcher = getTableMatcher(factTableName); return matcher.matches(name); } /** * Creates a {@link mondrian.rolap.aggmatcher.Recognizer.Matcher} for the * given measure name (symbolic name), column name and aggregate name * (sum, count, etc.). */ public Recognizer.Matcher getMeasureMatcher( final String measureName, final String measureColumnName, final String aggregateName) { DefaultDef.AggRule rule = getAggRule(); Recognizer.Matcher matcher = rule.getMeasureMap().getMatcher( measureName, measureColumnName, aggregateName); return matcher; } /** * Gets a {@link mondrian.rolap.aggmatcher.Recognizer.Matcher} for a given * level's hierarchy's name, level name and column name. */ public Recognizer.Matcher getLevelMatcher( final String usagePrefix, final String hierarchyName, final String levelName, final String levelColumnName) { DefaultDef.AggRule rule = getAggRule(); Recognizer.Matcher matcher = rule.getLevelMap().getMatcher( usagePrefix, hierarchyName, levelName, levelColumnName); return matcher; } /** * Uses the {@link DefaultRecognizer} Recognizer to determine if the * given aggTable's columns all match upto the dbFactTable's columns (where * present) making the column usages as a result. */ public boolean columnsOK( final RolapStar star, final JdbcSchema.Table dbFactTable, final JdbcSchema.Table aggTable, final MessageRecorder msgRecorder) { Recognizer cb = new DefaultRecognizer( this, star, dbFactTable, aggTable, msgRecorder); return cb.check(); } } // End DefaultRules.java mondrian-3.4.1/src/main/mondrian/rolap/aggmatcher/DefaultDef.java0000644000175000017500000030003711736560300024635 0ustar drazzibdrazzib/* // This java file was automatically generated // from XOM model 'aggregates' // on Tue Apr 03 13:36:32 EST 2012 // Do not edit this file by hand. */ package mondrian.rolap.aggmatcher; /** * This is the XML model for defining default aggregate table recognition * and level/measure mapping. *

    This class was generated from XOM model 'aggregates' on Tue Apr 03 13:36:32 EST 2012 */ public class DefaultDef { public static java.lang.Class getXMLDefClass() { return DefaultDef.class; } public static String[] _elements = { "AggRules", "Base", "CaseMatcher", "NameMatcher", "FactCountMatch", "ForeignKeyMatch", "TableMatch", "Mapper", "Regex", "RegexMapper", "Ref", "LevelMapRef", "MeasureMapRef", "IgnoreMapRef", "FactCountMatchRef", "ForeignKeyMatchRef", "TableMatchRef", "LevelMap", "MeasureMap", "IgnoreMap", "AggRule" }; /** * The set of "named" rules for matching aggregate tables. * Only one rule can be applied to a given connection. In * addition, one rule must be set as the default - this rule * is always the choice when not selecting by name. * It is very important that the AggRules validate method is called * prior to using any of the object. */ public static class AggRules extends org.eigenbase.xom.ElementDef { public AggRules() { } public AggRules(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); org.eigenbase.xom.NodeDef[] _tempArray; tag = (String)_parser.getAttribute("tag", "String", null, null, true); _tempArray = _parser.getArray(TableMatch.class, 0, 0); tableMatches = new TableMatch[_tempArray.length]; for (int _i = 0; _i < tableMatches.length; _i++) tableMatches[_i] = (TableMatch)_tempArray[_i]; _tempArray = _parser.getArray(FactCountMatch.class, 0, 0); factCountMatches = new FactCountMatch[_tempArray.length]; for (int _i = 0; _i < factCountMatches.length; _i++) factCountMatches[_i] = (FactCountMatch)_tempArray[_i]; _tempArray = _parser.getArray(ForeignKeyMatch.class, 0, 0); foreignKeyMatches = new ForeignKeyMatch[_tempArray.length]; for (int _i = 0; _i < foreignKeyMatches.length; _i++) foreignKeyMatches[_i] = (ForeignKeyMatch)_tempArray[_i]; _tempArray = _parser.getArray(LevelMap.class, 0, 0); levelMaps = new LevelMap[_tempArray.length]; for (int _i = 0; _i < levelMaps.length; _i++) levelMaps[_i] = (LevelMap)_tempArray[_i]; _tempArray = _parser.getArray(MeasureMap.class, 0, 0); measureMaps = new MeasureMap[_tempArray.length]; for (int _i = 0; _i < measureMaps.length; _i++) measureMaps[_i] = (MeasureMap)_tempArray[_i]; _tempArray = _parser.getArray(IgnoreMap.class, 0, 0); ignoreMaps = new IgnoreMap[_tempArray.length]; for (int _i = 0; _i < ignoreMaps.length; _i++) ignoreMaps[_i] = (IgnoreMap)_tempArray[_i]; _tempArray = _parser.getArray(AggRule.class, 1, 0); aggRules = new AggRule[_tempArray.length]; for (int _i = 0; _i < aggRules.length; _i++) aggRules[_i] = (AggRule)_tempArray[_i]; } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String tag; // required attribute /** * All shared TableMatches. */ public TableMatch[] tableMatches; //optional array /** * All shared FactCountMatches. */ public FactCountMatch[] factCountMatches; //optional array /** * All shared ForeignKeyMatches. */ public ForeignKeyMatch[] foreignKeyMatches; //optional array /** * All shared LevelMap. */ public LevelMap[] levelMaps; //optional array /** * All shared MeasureMap. */ public MeasureMap[] measureMaps; //optional array /** * All shared IgnoreMap. */ public IgnoreMap[] ignoreMaps; //optional array /** * All AggRules (at least one). * Also, one of them must be marked with default=true. */ public AggRule[] aggRules; //min 1 public String getName() { return "AggRules"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "tag", tag, _indent+1); displayElementArray(_out, "tableMatches", tableMatches, _indent+1); displayElementArray(_out, "factCountMatches", factCountMatches, _indent+1); displayElementArray(_out, "foreignKeyMatches", foreignKeyMatches, _indent+1); displayElementArray(_out, "levelMaps", levelMaps, _indent+1); displayElementArray(_out, "measureMaps", measureMaps, _indent+1); displayElementArray(_out, "ignoreMaps", ignoreMaps, _indent+1); displayElementArray(_out, "aggRules", aggRules, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("AggRules", new org.eigenbase.xom.XMLAttrVector() .add("tag", tag) ); displayXMLElementArray(_out, tableMatches); displayXMLElementArray(_out, factCountMatches); displayXMLElementArray(_out, foreignKeyMatches); displayXMLElementArray(_out, levelMaps); displayXMLElementArray(_out, measureMaps); displayXMLElementArray(_out, ignoreMaps); displayXMLElementArray(_out, aggRules); _out.endTag("AggRules"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { AggRules _cother = (AggRules)_other; boolean _diff = displayAttributeDiff("tag", tag, _cother.tag, _out, _indent+1); _diff = _diff && displayElementArrayDiff("tableMatches", tableMatches, _cother.tableMatches, _out, _indent+1); _diff = _diff && displayElementArrayDiff("factCountMatches", factCountMatches, _cother.factCountMatches, _out, _indent+1); _diff = _diff && displayElementArrayDiff("foreignKeyMatches", foreignKeyMatches, _cother.foreignKeyMatches, _out, _indent+1); _diff = _diff && displayElementArrayDiff("levelMaps", levelMaps, _cother.levelMaps, _out, _indent+1); _diff = _diff && displayElementArrayDiff("measureMaps", measureMaps, _cother.measureMaps, _out, _indent+1); _diff = _diff && displayElementArrayDiff("ignoreMaps", ignoreMaps, _cother.ignoreMaps, _out, _indent+1); _diff = _diff && displayElementArrayDiff("aggRules", aggRules, _cother.aggRules, _out, _indent+1); return _diff; } // BEGIN pass-through code block --- private static final org.apache.log4j.Logger LOGGER = org.apache.log4j.Logger.getLogger(DefaultDef.class); protected static org.apache.log4j.Logger getLogger() { return LOGGER; } public String getTag() { return tag; } public AggRule getAggRule(String tag) { for (int i = 0; i < aggRules.length; i++) { AggRule aggRule = aggRules[i]; if (aggRule.isEnabled() && aggRule.getTag().equals(tag)) { return aggRule; } } return null; } public void validate(final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { validate(factCountMatches, msgRecorder); validate(tableMatches, msgRecorder); validate(levelMaps, msgRecorder); validate(measureMaps, msgRecorder); validate(ignoreMaps, msgRecorder); validate(aggRules, msgRecorder); } finally { msgRecorder.popContextName(); } } private void validate(final Base[] bases, final mondrian.recorder.MessageRecorder msgRecorder) { for (int i = 0; i < bases.length; i++) { Base base = bases[i]; if (base.isEnabled()) { base.validate(this, msgRecorder); } } } public boolean hasIgnoreMap(String id) { return (lookupIgnoreMap(id) != null); } public IgnoreMap lookupIgnoreMap(String id) { return (IgnoreMap) lookupBase(id, ignoreMaps); } public boolean hasFactCountMatch(String id) { return (lookupFactCountMatch(id) != null); } public FactCountMatch lookupFactCountMatch(String id) { return (FactCountMatch) lookupBase(id, factCountMatches); } public boolean hasForeignKeyMatch(String id) { return (lookupForeignKeyMatch(id) != null); } public ForeignKeyMatch lookupForeignKeyMatch(String id) { return (ForeignKeyMatch) lookupBase(id, foreignKeyMatches); } public boolean hasTableMatch(String id) { return (lookupTableMatch(id) != null); } public TableMatch lookupTableMatch(String id) { return (TableMatch) lookupBase(id, tableMatches); } public boolean hasLevelMap(String id) { return (lookupLevelMap(id) != null); } public LevelMap lookupLevelMap(String id) { return (LevelMap) lookupBase(id, levelMaps); } public boolean hasMeasureMap(String id) { return (lookupMeasureMap(id) != null); } public MeasureMap lookupMeasureMap(String id) { return (MeasureMap) lookupBase(id, measureMaps); } public boolean hasAggRule(String id) { return (lookupAggRule(id) != null); } public AggRule lookupAggRule(String id) { return (AggRule) lookupBase(id, aggRules); } private Base lookupBase(String tag, Base[] bases) { for (int i = 0; i < bases.length; i++) { Base base = bases[i]; if (base.isEnabled() && base.getTag().equals(tag)) { return base; } } return null; } public IgnoreMap[] getIgnoreMaps() { return ignoreMaps; } public FactCountMatch[] getFactCountMatches() { return factCountMatches; } public ForeignKeyMatch[] getForeignKeyMatches() { return foreignKeyMatches; } public TableMatch[] getTableMatches() { return tableMatches; } public LevelMap[] getLevelMaps() { return levelMaps; } public MeasureMap[] getMeasureMaps() { return measureMaps; } public AggRule[] getAggRules() { return aggRules; } // END pass-through code block --- } /** * Base is the base class for all of the elements. * All elements can be enabled or not, have a tag, and * can be validated. */ public static abstract class Base extends org.eigenbase.xom.ElementDef { public Base() { } public Base(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public Boolean enabled; // attribute default: true public String getName() { return "Base"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "enabled", enabled, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("Base", new org.eigenbase.xom.XMLAttrVector() .add("enabled", enabled) ); _out.endTag("Base"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { Base _cother = (Base)_other; boolean _diff = displayAttributeDiff("enabled", enabled, _cother.enabled, _out, _indent+1); return _diff; } // BEGIN pass-through code block --- public boolean isEnabled() { return enabled.booleanValue(); } protected abstract String getTag(); public abstract void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder); // END pass-through code block --- } /** * This is a base class for all elements that can match strings * where the case of the string is important. In addition, * it has an id which services as its tag. */ public static abstract class CaseMatcher extends Base { public CaseMatcher() { } public CaseMatcher(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); id = (String)_parser.getAttribute("id", "String", null, null, true); charcase = (String)_parser.getAttribute("charcase", "String", "ignore", _charcase_values, false); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String id; // required attribute /** Allowable values for {@link #charcase}. */ public static final String[] _charcase_values = {"ignore", "exact", "upper", "lower"}; public String charcase; // attribute default: ignore public String getName() { return "CaseMatcher"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "id", id, _indent+1); displayAttribute(_out, "charcase", charcase, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("CaseMatcher", new org.eigenbase.xom.XMLAttrVector() .add("id", id) .add("charcase", charcase) .add("enabled", enabled) ); _out.endTag("CaseMatcher"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { CaseMatcher _cother = (CaseMatcher)_other; boolean _diff = displayAttributeDiff("id", id, _cother.id, _out, _indent+1); _diff = _diff && displayAttributeDiff("charcase", charcase, _cother.charcase, _out, _indent+1); return _diff; } // BEGIN pass-through code block --- public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { // empty } protected String getTag() { return getId(); } public String getId() { return id; } public String getCharCase() { return charcase; } // END pass-through code block --- } /** * A NameMatcher is a CaseMatcher that prepends and appends * regular expressions to a given string as part of creating * the matching regular expression. Both the pre/post * regular expression can be null in which case matches are * applied simply against the name (modulo case considerations). * The purpose of this class is to allow aggregate tables to * be identified when their table names are formed by placing * text before and/or after the base fact table name. */ public static abstract class NameMatcher extends CaseMatcher { public NameMatcher() { } public NameMatcher(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); pretemplate = (String)_parser.getAttribute("pretemplate", "String", null, null, false); posttemplate = (String)_parser.getAttribute("posttemplate", "String", null, null, false); basename = (String)_parser.getAttribute("basename", "String", null, null, false); id = (String)_parser.getAttribute("id", "String", null, null, true); charcase = (String)_parser.getAttribute("charcase", "String", "ignore", _charcase_values, false); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String pretemplate; // optional attribute public String posttemplate; // optional attribute public String basename; // optional attribute public String getName() { return "NameMatcher"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "pretemplate", pretemplate, _indent+1); displayAttribute(_out, "posttemplate", posttemplate, _indent+1); displayAttribute(_out, "basename", basename, _indent+1); displayAttribute(_out, "id", id, _indent+1); displayAttribute(_out, "charcase", charcase, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("NameMatcher", new org.eigenbase.xom.XMLAttrVector() .add("pretemplate", pretemplate) .add("posttemplate", posttemplate) .add("basename", basename) .add("id", id) .add("charcase", charcase) .add("enabled", enabled) ); _out.endTag("NameMatcher"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { NameMatcher _cother = (NameMatcher)_other; boolean _diff = displayAttributeDiff("pretemplate", pretemplate, _cother.pretemplate, _out, _indent+1); _diff = _diff && displayAttributeDiff("posttemplate", posttemplate, _cother.posttemplate, _out, _indent+1); _diff = _diff && displayAttributeDiff("basename", basename, _cother.basename, _out, _indent+1); return _diff; } // BEGIN pass-through code block --- java.util.regex.Pattern baseNamePattern = null; public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { super.validate(rules, msgRecorder); if (basename != null) { baseNamePattern = java.util.regex.Pattern.compile(basename); } } finally { msgRecorder.popContextName(); } } /** * Generates a regular expression string by prepending and * appending regular expression to the parameter tableName. * * @param name Table name * @return regular expression */ public String getRegex(final String name) { StringBuilder buf = new StringBuilder(); if (pretemplate != null) { buf.append(pretemplate); } if (name != null) { String n = name; if (baseNamePattern != null) { java.util.regex.Matcher matcher = baseNamePattern.matcher(name); if (matcher.matches() && matcher.groupCount() > 0) { n = matcher.group(1); } else { if (AggRules.getLogger().isDebugEnabled()) { StringBuilder bf = new StringBuilder(64); bf.append(getName()); bf.append(".getRegex: for name \""); bf.append(name); bf.append("\" regex is null because basename \""); bf.append(basename); bf.append("\" is not matched."); String msg = bf.toString(); AggRules.getLogger().debug(msg); } // If the table name does not match the basename // pattern, then return null for regex. return null; } } buf.append(n); } if (posttemplate != null) { buf.append(posttemplate); } String regex = buf.toString(); if (AggRules.getLogger().isDebugEnabled()) { StringBuilder bf = new StringBuilder(64); bf.append(getName()); bf.append(".getRegex: for name \""); bf.append(name); bf.append("\" regex is \""); bf.append(regex); bf.append('"'); String msg = bf.toString(); AggRules.getLogger().debug(msg); } return regex; } protected Recognizer.Matcher getMatcher(String name) { final String charcase = getCharCase(); final String regex; int flag = 0; if (charcase.equals("ignore")) { // the case of name does not matter // since the Pattern will be create to ignore case regex = getRegex(name); flag = java.util.regex.Pattern.CASE_INSENSITIVE; } else if (charcase.equals("exact")) { // the case of name is not changed // since we are interested in exact case matching regex = getRegex(name); } else if (charcase.equals("upper")) { // convert name to upper case regex = getRegex(name.toUpperCase()); } else { // lower // convert name to lower case regex = getRegex(name.toLowerCase()); } // If regex is null, then return a matcher that matches nothing if (regex == null) { return new Recognizer.Matcher() { public boolean matches(String name) { return false; } }; } final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regex, flag); return new Recognizer.Matcher() { public boolean matches(String name) { boolean b = pattern.matcher(name).matches(); if (AggRules.getLogger().isDebugEnabled()) { debug(name); } return b; } private void debug(String name) { StringBuilder bf = new StringBuilder(64); bf.append(NameMatcher.this.getName()); bf.append(".Matcher.matches:"); bf.append(" name \""); bf.append(name); bf.append("\" pattern \""); bf.append(pattern.pattern()); bf.append("\""); if ((pattern.flags() & java.util.regex.Pattern.CASE_INSENSITIVE) != 0) { bf.append(" case_insensitive"); } String msg = bf.toString(); AggRules.getLogger().debug(msg); } }; } // END pass-through code block --- } /** * This is used to identify the "fact_count" column in an aggregate * table. It allows one to match using regular exprssions. * The default is that the name of the fact count colum is simply * the string "fact_count". */ public static class FactCountMatch extends NameMatcher { public FactCountMatch() { } public FactCountMatch(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); factCountName = (String)_parser.getAttribute("factCountName", "String", "fact_count", null, true); pretemplate = (String)_parser.getAttribute("pretemplate", "String", null, null, false); posttemplate = (String)_parser.getAttribute("posttemplate", "String", null, null, false); basename = (String)_parser.getAttribute("basename", "String", null, null, false); id = (String)_parser.getAttribute("id", "String", null, null, true); charcase = (String)_parser.getAttribute("charcase", "String", "ignore", _charcase_values, false); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String factCountName; // attribute default: fact_count public String getName() { return "FactCountMatch"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "factCountName", factCountName, _indent+1); displayAttribute(_out, "pretemplate", pretemplate, _indent+1); displayAttribute(_out, "posttemplate", posttemplate, _indent+1); displayAttribute(_out, "basename", basename, _indent+1); displayAttribute(_out, "id", id, _indent+1); displayAttribute(_out, "charcase", charcase, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("FactCountMatch", new org.eigenbase.xom.XMLAttrVector() .add("factCountName", factCountName) .add("pretemplate", pretemplate) .add("posttemplate", posttemplate) .add("basename", basename) .add("id", id) .add("charcase", charcase) .add("enabled", enabled) ); _out.endTag("FactCountMatch"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { FactCountMatch _cother = (FactCountMatch)_other; boolean _diff = displayAttributeDiff("factCountName", factCountName, _cother.factCountName, _out, _indent+1); return _diff; } // BEGIN pass-through code block --- public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { super.validate(rules, msgRecorder); } finally { msgRecorder.popContextName(); } } public Recognizer.Matcher getMatcher() { return super.getMatcher(factCountName); } // END pass-through code block --- } /** * This is used to identify foreign key columns in a candidate * aggregate table given the name of a foreign key column of the * base fact table. This allows such foreign keys to be identified * by using a regular exprsssion. The default is to simply * match the base fact table's foreign key column name. */ public static class ForeignKeyMatch extends NameMatcher { public ForeignKeyMatch() { } public ForeignKeyMatch(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); pretemplate = (String)_parser.getAttribute("pretemplate", "String", null, null, false); posttemplate = (String)_parser.getAttribute("posttemplate", "String", null, null, false); basename = (String)_parser.getAttribute("basename", "String", null, null, false); id = (String)_parser.getAttribute("id", "String", null, null, true); charcase = (String)_parser.getAttribute("charcase", "String", "ignore", _charcase_values, false); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String getName() { return "ForeignKeyMatch"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "pretemplate", pretemplate, _indent+1); displayAttribute(_out, "posttemplate", posttemplate, _indent+1); displayAttribute(_out, "basename", basename, _indent+1); displayAttribute(_out, "id", id, _indent+1); displayAttribute(_out, "charcase", charcase, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("ForeignKeyMatch", new org.eigenbase.xom.XMLAttrVector() .add("pretemplate", pretemplate) .add("posttemplate", posttemplate) .add("basename", basename) .add("id", id) .add("charcase", charcase) .add("enabled", enabled) ); _out.endTag("ForeignKeyMatch"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { ForeignKeyMatch _cother = (ForeignKeyMatch)_other; return true; } // BEGIN pass-through code block --- public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { super.validate(rules, msgRecorder); } finally { msgRecorder.popContextName(); } } public Recognizer.Matcher getMatcher(final String foreignKeyName) { return super.getMatcher(foreignKeyName); } // END pass-through code block --- } /** * This is used to identify which tables in the database might * be aggregate table of a given fact table. * It is expected that aggregate table names will include the * base fact table name with additional text before and/or * after. * It is not allow for both the prepending and appending * regular expression text to be null (if it were, then only * aggregate tables who names were the same as (modulo case) * would match - which is surely not allowed). */ public static class TableMatch extends NameMatcher { public TableMatch() { } public TableMatch(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); pretemplate = (String)_parser.getAttribute("pretemplate", "String", null, null, false); posttemplate = (String)_parser.getAttribute("posttemplate", "String", null, null, false); basename = (String)_parser.getAttribute("basename", "String", null, null, false); id = (String)_parser.getAttribute("id", "String", null, null, true); charcase = (String)_parser.getAttribute("charcase", "String", "ignore", _charcase_values, false); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String getName() { return "TableMatch"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "pretemplate", pretemplate, _indent+1); displayAttribute(_out, "posttemplate", posttemplate, _indent+1); displayAttribute(_out, "basename", basename, _indent+1); displayAttribute(_out, "id", id, _indent+1); displayAttribute(_out, "charcase", charcase, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("TableMatch", new org.eigenbase.xom.XMLAttrVector() .add("pretemplate", pretemplate) .add("posttemplate", posttemplate) .add("basename", basename) .add("id", id) .add("charcase", charcase) .add("enabled", enabled) ); _out.endTag("TableMatch"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { TableMatch _cother = (TableMatch)_other; return true; } // BEGIN pass-through code block --- public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { if ((pretemplate == null) && (posttemplate == null)) { String msg = "Must have at least one template non-null"; msgRecorder.reportError(msg); } super.validate(rules, msgRecorder); } finally { msgRecorder.popContextName(); } } public Recognizer.Matcher getMatcher(final String name) { return super.getMatcher(name); } // END pass-through code block --- } /** * This allows one to create an element that matches against a * single template, where the template is an attribute. * While much loved, this is currently not used. */ public static abstract class Mapper extends CaseMatcher { public Mapper() { } public Mapper(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); template = (String)_parser.getAttribute("template", "String", null, null, true); space = (String)_parser.getAttribute("space", "String", "_", null, false); dot = (String)_parser.getAttribute("dot", "String", "_", null, false); id = (String)_parser.getAttribute("id", "String", null, null, true); charcase = (String)_parser.getAttribute("charcase", "String", "ignore", _charcase_values, false); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String template; // required attribute public String space; // attribute default: _ public String dot; // attribute default: _ public String getName() { return "Mapper"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "template", template, _indent+1); displayAttribute(_out, "space", space, _indent+1); displayAttribute(_out, "dot", dot, _indent+1); displayAttribute(_out, "id", id, _indent+1); displayAttribute(_out, "charcase", charcase, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("Mapper", new org.eigenbase.xom.XMLAttrVector() .add("template", template) .add("space", space) .add("dot", dot) .add("id", id) .add("charcase", charcase) .add("enabled", enabled) ); _out.endTag("Mapper"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { Mapper _cother = (Mapper)_other; boolean _diff = displayAttributeDiff("template", template, _cother.template, _out, _indent+1); _diff = _diff && displayAttributeDiff("space", space, _cother.space, _out, _indent+1); _diff = _diff && displayAttributeDiff("dot", dot, _cother.dot, _out, _indent+1); return _diff; } // BEGIN pass-through code block --- public String getTemplate() { return template; } public String getSpace() { return space; } public String getDot() { return dot; } protected static final int BAD_ID = -1; protected String[] templateParts; protected int[] templateNamePos; /** * It is hoped that no one will need to match more than 50 names * in a template. Currently, this implementation, requires only 3. */ private static final int MAX_SIZE = 50; public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { super.validate(rules, msgRecorder); String[] ss = new String[MAX_SIZE+1]; int[] poss = new int[MAX_SIZE]; String template = getTemplate(); int count = 0; int end = 0; int previousEnd = 0; int start = template.indexOf("${", end); while (count < MAX_SIZE) { if (start == -1) { if (count == 0) { // there are no ${} in template which // is an error String msg = "Bad template \"" + template + "\", no ${} entries"; msgRecorder.reportError(msg); return; } // its OK, there are "count" ${} templateNamePos = new int[count]; System.arraycopy(poss, 0, templateNamePos, 0, count); ss[count++] = template.substring(end, template.length()); templateParts = new String[count]; System.arraycopy(ss, 0, templateParts, 0, count); return; } previousEnd = end; end = template.indexOf('}', start); if (end == -1) { // there was a "${" but not '}' in template String msg = "Bad template \"" + template + "\", it had a \"${\", but no '}'"; msgRecorder.reportError(msg); return; } String name = template.substring(start+2, end); int pos = convertNameToID(name, msgRecorder); if (pos == BAD_ID) { return; } poss[count] = pos; ss[count] = template.substring(previousEnd, start); start = template.indexOf("${", end); end++; count++; } } finally { msgRecorder.popContextName(); } } protected abstract String[] getTemplateNames(); private int convertNameToID(final String name, final mondrian.recorder.MessageRecorder msgRecorder) { if (name == null) { String msg = "Template name is null"; msgRecorder.reportError(msg); return BAD_ID; } String[] names = getTemplateNames(); for (int i = 0; i < names.length; i++) { if (names[i].equals(name)) { return i; } } String msg = "Bad template name \"" + name + "\""; msgRecorder.reportError(msg); return BAD_ID; } public String getRegex(final String[] names) { final String space = getSpace(); final String dot = getDot(); final StringBuilder buf = new StringBuilder(); // // Remember that: // templateParts.length == templateNamePos.length+1 // buf.append(templateParts[0]); for (int i = 0; i < templateNamePos.length; i++) { String n = names[templateNamePos[i]]; if (space != null) { n = n.replaceAll(" ", space); } if (dot != null) { n = n.replaceAll("\\.", dot); } buf.append(n); buf.append(templateParts[i+1]); } String regex = buf.toString(); if (AggRules.getLogger().isDebugEnabled()) { StringBuilder bf = new StringBuilder(64); bf.append(getName()); bf.append(".getRegex:"); bf.append(" for names "); for (int i = 0; i < names.length; i++) { bf.append('"'); bf.append(names[i]); bf.append('"'); if (i+1 < names.length) { bf.append(", "); } } bf.append(" regex is \""); bf.append(regex); bf.append('"'); String msg = bf.toString(); AggRules.getLogger().debug(msg); } return regex; } protected Recognizer.Matcher getMatcher(final String[] names) { final String charcase = getCharCase(); final String regex; int flag = 0; if (charcase.equals("ignore")) { // the case of name does not matter // since the Pattern will be create to ignore case regex = getRegex(names); flag = java.util.regex.Pattern.CASE_INSENSITIVE; } else if (charcase.equals("exact")) { // the case of name is not changed // since we are interested in exact case matching regex = getRegex(names); } else if (charcase.equals("upper")) { // convert name to upper case String[] ucNames = new String[names.length]; for (int i = 0; i < names.length; i++) { ucNames[i] = names[i].toUpperCase(); } regex = getRegex(ucNames); } else { // lower // convert name to lower case String[] lcNames = new String[names.length]; for (int i = 0; i < names.length; i++) { lcNames[i] = names[i].toLowerCase(); } regex = getRegex(lcNames); } final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regex, flag); return new Recognizer.Matcher() { public boolean matches(String name) { boolean b = pattern.matcher(name).matches(); if (AggRules.getLogger().isDebugEnabled()) { debug(name); } return b; } private void debug(String name) { StringBuilder bf = new StringBuilder(64); bf.append(Mapper.this.getName()); bf.append(".Matcher.matches:"); bf.append(" name \""); bf.append(name); bf.append("\" pattern \""); bf.append(pattern.pattern()); bf.append("\""); if ((pattern.flags() & java.util.regex.Pattern.CASE_INSENSITIVE) != 0) { bf.append(" case_insensitive"); } String msg = bf.toString(); AggRules.getLogger().debug(msg); } }; } // END pass-through code block --- } /** * This element is used in a vector of child elements when * one wishes to have one or more regular expressions associated * with matching a given string. The parent element must * initialize Regex object by calling its validate method * passing in an array of template names. * The cdata content is a regular expression with embedded * template names. Each name must be surrounded by "${" and "}". * Each time this is used for a new set of names, the names * replace the template names in the regular expression. * For example, if the charcase="lower", the attribute * dot="-" (the default dot value is "_"), the template names are: * "city", "state", and "country" * and the cdata is: * .*_${country}_.*_${city} * Then when the names: * "San Francisco", "California", and "U.S.A" * are passed in, the regular expression becomes: * .*_u-s-a_.*_san_francisco * Note that a given template name can only appear ONCE in the * template content, the cdata content. As an example, the * following cdata template is not supported: * .*_${country}_.*_${city}_${country} */ public static class Regex extends CaseMatcher { public Regex() { } public Regex(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); space = (String)_parser.getAttribute("space", "String", "_", null, false); dot = (String)_parser.getAttribute("dot", "String", "_", null, false); id = (String)_parser.getAttribute("id", "String", null, null, true); charcase = (String)_parser.getAttribute("charcase", "String", "ignore", _charcase_values, false); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); cdata = _parser.getText(); } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String space; // attribute default: _ public String dot; // attribute default: _ public String cdata; // All text goes here public String getName() { return "Regex"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "space", space, _indent+1); displayAttribute(_out, "dot", dot, _indent+1); displayAttribute(_out, "id", id, _indent+1); displayAttribute(_out, "charcase", charcase, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); displayString(_out, "cdata", cdata, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("Regex", new org.eigenbase.xom.XMLAttrVector() .add("space", space) .add("dot", dot) .add("id", id) .add("charcase", charcase) .add("enabled", enabled) ); _out.cdata(cdata); _out.endTag("Regex"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { Regex _cother = (Regex)_other; boolean _diff = displayAttributeDiff("space", space, _cother.space, _out, _indent+1); _diff = _diff && displayAttributeDiff("dot", dot, _cother.dot, _out, _indent+1); _diff = _diff && displayStringDiff("cdata", cdata, _cother.cdata, _out, _indent+1); return _diff; } // BEGIN pass-through code block --- public String getSpace() { return space; } public String getDot() { return dot; } public String getTemplate() { return cdata; } protected static final int BAD_ID = -1; protected String[] templateParts; /** * This is a one-to-one mapping, each template name can appear * at most once. */ protected int[] templateNamePos; /** * It is hoped that no one will need to match more than 50 names * in a template. Currently, this implementation, requires only 3. */ private static final int MAX_SIZE = 50; public void validate(final AggRules rules, final String[] templateNames, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { super.validate(rules, msgRecorder); String[] ss = new String[MAX_SIZE+1]; int[] poss = new int[MAX_SIZE]; String template = getTemplate(); int count = 0; int end = 0; int previousEnd = 0; int start = template.indexOf("${", end); // if no templateNames, then there better not be // any ${}s if (templateNames.length == 0) { if (start == -1) { // everything is ok templateParts = new String[1]; templateParts[0] = template; templateNamePos = new int[0]; } else { String msg = "Bad template \"" + template + "\", no ${} entries but there are "+ "template names" ; msgRecorder.reportError(msg); } return; } while (count < MAX_SIZE) { if (start == -1) { if (count == 0) { // there are no ${} in template which // is an error String msg = "Bad template \"" + template + "\", no ${} entries"; msgRecorder.reportError(msg); return; } // its OK, there are "count" ${} templateNamePos = new int[count]; System.arraycopy(poss, 0, templateNamePos, 0, count); ss[count++] = template.substring(end, template.length()); templateParts = new String[count]; System.arraycopy(ss, 0, templateParts, 0, count); return; } previousEnd = end; end = template.indexOf('}', start); if (end == -1) { // there was a "${" but not '}' in template String msg = "Bad template \"" + template + "\", it had a \"${\", but no '}'"; msgRecorder.reportError(msg); return; } String name = template.substring(start+2, end); int pos = convertNameToID(name, templateNames, msgRecorder); if (pos == BAD_ID) { return; } poss[count] = pos; ss[count] = template.substring(previousEnd, start); start = template.indexOf("${", end); end++; count++; } } finally { msgRecorder.popContextName(); } } private int convertNameToID(final String name, final String[] templateNames, final mondrian.recorder.MessageRecorder msgRecorder) { if (name == null) { String msg = "Template name is null"; msgRecorder.reportError(msg); return BAD_ID; } for (int i = 0; i < templateNames.length; i++) { if (templateNames[i].equals(name)) { return i; } } String msg = "Bad template name \"" + name + "\""; msgRecorder.reportError(msg); return BAD_ID; } public String getRegex(final String[] names) { final String space = getSpace(); final String dot = getDot(); final StringBuilder buf = new StringBuilder(); // // Remember that: // templateParts.length == templateNamePos.length+1 // buf.append(templateParts[0]); for (int i = 0; i < templateNamePos.length; i++) { String n = names[templateNamePos[i]]; if (n == null) { // its ok for a name to be null, it just // eliminates the current regex from consideration return null; } if (space != null) { n = n.replaceAll(" ", space); } if (dot != null) { n = n.replaceAll("\\.", dot); } buf.append(n); buf.append(templateParts[i+1]); } String regex = buf.toString(); if (AggRules.getLogger().isDebugEnabled()) { StringBuilder bf = new StringBuilder(64); bf.append(getName()); bf.append(".getRegex:"); bf.append(" for names "); for (int i = 0; i < names.length; i++) { bf.append('"'); bf.append(names[i]); bf.append('"'); if (i+1 < names.length) { bf.append(", "); } } bf.append(" regex is \""); bf.append(regex); bf.append('"'); String msg = bf.toString(); AggRules.getLogger().debug(msg); } return regex; } protected java.util.regex.Pattern getPattern(final String[] names) { final String charcase = getCharCase(); if (charcase.equals("ignore")) { // the case of name does not matter // since the Pattern will be create to ignore case final String regex = getRegex(names); if (regex == null) { return null; } final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regex, java.util.regex.Pattern.CASE_INSENSITIVE); return pattern; } else if (charcase.equals("exact")) { // the case of name is not changed // since we are interested in exact case matching final String regex = getRegex(names); if (regex == null) { return null; } final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regex); return pattern; } else if (charcase.equals("upper")) { // convert name to upper case String[] ucNames = new String[names.length]; for (int i = 0; i < names.length; i++) { String name = names[i]; ucNames[i] = (name == null) ? null: name.toUpperCase(); } final String regex = getRegex(ucNames); if (regex == null) { return null; } final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regex); return pattern; } else { // lower // convert name to lower case String[] lcNames = new String[names.length]; for (int i = 0; i < names.length; i++) { String name = names[i]; lcNames[i] = (name == null) ? null: name.toLowerCase(); } final String regex = getRegex(lcNames); if (regex == null) { return null; } final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regex); return pattern; } } // END pass-through code block --- } /** */ public static abstract class RegexMapper extends Base { public RegexMapper() { } public RegexMapper(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); org.eigenbase.xom.NodeDef[] _tempArray; id = (String)_parser.getAttribute("id", "String", null, null, true); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); _tempArray = _parser.getArray(Regex.class, 0, 0); regexs = new Regex[_tempArray.length]; for (int _i = 0; _i < regexs.length; _i++) regexs[_i] = (Regex)_tempArray[_i]; } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String id; // required attribute /** * This is an array of Regex. A match occurs if any one of * the Regex matches; it is the equivalent of or-ing the * regular expressions together. A candidate string is processed * sequentially by each Regex in their document order until * one matches. In none match, well, none match. */ public Regex[] regexs; //optional array public String getName() { return "RegexMapper"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "id", id, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); displayElementArray(_out, "regexs", regexs, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("RegexMapper", new org.eigenbase.xom.XMLAttrVector() .add("id", id) .add("enabled", enabled) ); displayXMLElementArray(_out, regexs); _out.endTag("RegexMapper"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { RegexMapper _cother = (RegexMapper)_other; boolean _diff = displayAttributeDiff("id", id, _cother.id, _out, _indent+1); _diff = _diff && displayElementArrayDiff("regexs", regexs, _cother.regexs, _out, _indent+1); return _diff; } // BEGIN pass-through code block --- protected String getTag() { return id; } public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { String[] templateNames = getTemplateNames(); for (int i = 0; i < regexs.length; i++) { Regex regex = regexs[i]; regex.validate(rules, templateNames, msgRecorder); } } finally { msgRecorder.popContextName(); } } /** * This must be defined in derived classes. It returns an array of * symbolic names that are the symbolic names allowed to appear * in the regular expression templates. * * @return array of symbol names */ protected abstract String[] getTemplateNames(); protected Recognizer.Matcher getMatcher(final String[] names) { final java.util.regex.Pattern[] patterns = new java.util.regex.Pattern[regexs.length]; for (int i = 0; i < regexs.length; i++) { Regex regex = regexs[i]; patterns[i] = regex.getPattern(names); } return new Recognizer.Matcher() { public boolean matches(String name) { for (int i = 0; i < patterns.length; i++) { java.util.regex.Pattern pattern = patterns[i]; if ((pattern != null) && pattern.matcher(name).matches()) { if (AggRules.getLogger().isDebugEnabled()) { debug(name, pattern); } return true; } } return false; } private void debug(String name, java.util.regex.Pattern p) { StringBuilder bf = new StringBuilder(64); bf.append("DefaultDef.RegexMapper"); bf.append(".Matcher.matches:"); bf.append(" name \""); bf.append(name); bf.append("\" matches regex \""); bf.append(p.pattern()); bf.append("\""); if ((p.flags() & java.util.regex.Pattern.CASE_INSENSITIVE) != 0) { bf.append(" case_insensitive"); } String msg = bf.toString(); AggRules.getLogger().debug(msg); } }; } // END pass-through code block --- } public static abstract class Ref extends Base { public Ref() { } public Ref(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); refId = (String)_parser.getAttribute("refId", "String", null, null, true); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String refId; // required attribute public String getName() { return "Ref"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "refId", refId, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("Ref", new org.eigenbase.xom.XMLAttrVector() .add("refId", refId) .add("enabled", enabled) ); _out.endTag("Ref"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { Ref _cother = (Ref)_other; boolean _diff = displayAttributeDiff("refId", refId, _cother.refId, _out, _indent+1); return _diff; } // BEGIN pass-through code block --- protected String getTag() { return getRefId(); } public String getRefId() { return refId; } // END pass-through code block --- } public static class LevelMapRef extends Ref { public LevelMapRef() { } public LevelMapRef(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); refId = (String)_parser.getAttribute("refId", "String", null, null, true); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String getName() { return "LevelMapRef"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "refId", refId, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("LevelMapRef", new org.eigenbase.xom.XMLAttrVector() .add("refId", refId) .add("enabled", enabled) ); _out.endTag("LevelMapRef"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { LevelMapRef _cother = (LevelMapRef)_other; return true; } // BEGIN pass-through code block --- public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { if (! rules.hasLevelMap(getRefId())) { String msg = "No LevelMap has id equal to refid \"" + getRefId() + "\""; msgRecorder.reportError(msg); } } finally { msgRecorder.popContextName(); } } // END pass-through code block --- } public static class MeasureMapRef extends Ref { public MeasureMapRef() { } public MeasureMapRef(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); refId = (String)_parser.getAttribute("refId", "String", null, null, true); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String getName() { return "MeasureMapRef"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "refId", refId, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("MeasureMapRef", new org.eigenbase.xom.XMLAttrVector() .add("refId", refId) .add("enabled", enabled) ); _out.endTag("MeasureMapRef"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { MeasureMapRef _cother = (MeasureMapRef)_other; return true; } // BEGIN pass-through code block --- public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { if (! rules.hasMeasureMap(getRefId())) { String msg = "No MeasureMap has id equal to refid \"" + getRefId() + "\""; msgRecorder.reportError(msg); } } finally { msgRecorder.popContextName(); } } // END pass-through code block --- } public static class IgnoreMapRef extends Ref { public IgnoreMapRef() { } public IgnoreMapRef(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); refId = (String)_parser.getAttribute("refId", "String", null, null, true); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String getName() { return "IgnoreMapRef"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "refId", refId, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("IgnoreMapRef", new org.eigenbase.xom.XMLAttrVector() .add("refId", refId) .add("enabled", enabled) ); _out.endTag("IgnoreMapRef"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { IgnoreMapRef _cother = (IgnoreMapRef)_other; return true; } // BEGIN pass-through code block --- public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { if (! rules.hasIgnoreMap(getRefId())) { String msg = "No IgnoreMap has id equal to refid \"" + getRefId() + "\""; msgRecorder.reportError(msg); } } finally { msgRecorder.popContextName(); } } // END pass-through code block --- } public static class FactCountMatchRef extends Ref { public FactCountMatchRef() { } public FactCountMatchRef(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); refId = (String)_parser.getAttribute("refId", "String", null, null, true); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String getName() { return "FactCountMatchRef"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "refId", refId, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("FactCountMatchRef", new org.eigenbase.xom.XMLAttrVector() .add("refId", refId) .add("enabled", enabled) ); _out.endTag("FactCountMatchRef"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { FactCountMatchRef _cother = (FactCountMatchRef)_other; return true; } // BEGIN pass-through code block --- public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { if (! rules.hasFactCountMatch(getRefId())) { String msg = "No FactCountMatch has id equal to refid \"" + getRefId() + "\""; msgRecorder.reportError(msg); } } finally { msgRecorder.popContextName(); } } // END pass-through code block --- } public static class ForeignKeyMatchRef extends Ref { public ForeignKeyMatchRef() { } public ForeignKeyMatchRef(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); refId = (String)_parser.getAttribute("refId", "String", null, null, true); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String getName() { return "ForeignKeyMatchRef"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "refId", refId, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("ForeignKeyMatchRef", new org.eigenbase.xom.XMLAttrVector() .add("refId", refId) .add("enabled", enabled) ); _out.endTag("ForeignKeyMatchRef"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { ForeignKeyMatchRef _cother = (ForeignKeyMatchRef)_other; return true; } // BEGIN pass-through code block --- public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { if (! rules.hasForeignKeyMatch(getRefId())) { String msg = "No ForeignKeyMatch has id equal to refid \"" + getRefId() + "\""; msgRecorder.reportError(msg); } } finally { msgRecorder.popContextName(); } } // END pass-through code block --- } public static class TableMatchRef extends Ref { public TableMatchRef() { } public TableMatchRef(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); refId = (String)_parser.getAttribute("refId", "String", null, null, true); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String getName() { return "TableMatchRef"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "refId", refId, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("TableMatchRef", new org.eigenbase.xom.XMLAttrVector() .add("refId", refId) .add("enabled", enabled) ); _out.endTag("TableMatchRef"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { TableMatchRef _cother = (TableMatchRef)_other; return true; } // BEGIN pass-through code block --- public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { if (! rules.hasTableMatch(getRefId())) { String msg = "No TableMatch has id equal to refid \"" + getRefId() + "\""; msgRecorder.reportError(msg); } } finally { msgRecorder.popContextName(); } } // END pass-through code block --- } /** * This is the template that maps from a combination of level * usage_prefix * hierarchy_name * level_name * level_column_name */ public static class LevelMap extends RegexMapper { public LevelMap() { } public LevelMap(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); org.eigenbase.xom.NodeDef[] _tempArray; id = (String)_parser.getAttribute("id", "String", null, null, true); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); _tempArray = _parser.getArray(Regex.class, 0, 0); regexs = new Regex[_tempArray.length]; for (int _i = 0; _i < regexs.length; _i++) regexs[_i] = (Regex)_tempArray[_i]; } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String getName() { return "LevelMap"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "id", id, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); displayElementArray(_out, "regexs", regexs, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("LevelMap", new org.eigenbase.xom.XMLAttrVector() .add("id", id) .add("enabled", enabled) ); displayXMLElementArray(_out, regexs); _out.endTag("LevelMap"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { LevelMap _cother = (LevelMap)_other; boolean _diff = displayElementArrayDiff("regexs", regexs, _cother.regexs, _out, _indent+1); return _diff; } // BEGIN pass-through code block --- private static final String[] TEMPLATE_NAMES = new String[] { "usage_prefix", "hierarchy_name", "level_name", "level_column_name" }; protected String[] getTemplateNames() { return TEMPLATE_NAMES; } protected Recognizer.Matcher getMatcher( final String usagePrefix, final String hierarchyName, final String levelName, final String levelColumnName) { return getMatcher(new String[] { usagePrefix, hierarchyName, levelName, levelColumnName }); } // END pass-through code block --- } /** * This is the template that maps from a combination of measure * measure_name, * measure_column_name, and * aggregate_name ("count", "sum", "avg", "min", "max", * "distinct-count"). */ public static class MeasureMap extends RegexMapper { public MeasureMap() { } public MeasureMap(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); org.eigenbase.xom.NodeDef[] _tempArray; id = (String)_parser.getAttribute("id", "String", null, null, true); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); _tempArray = _parser.getArray(Regex.class, 0, 0); regexs = new Regex[_tempArray.length]; for (int _i = 0; _i < regexs.length; _i++) regexs[_i] = (Regex)_tempArray[_i]; } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String getName() { return "MeasureMap"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "id", id, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); displayElementArray(_out, "regexs", regexs, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("MeasureMap", new org.eigenbase.xom.XMLAttrVector() .add("id", id) .add("enabled", enabled) ); displayXMLElementArray(_out, regexs); _out.endTag("MeasureMap"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { MeasureMap _cother = (MeasureMap)_other; boolean _diff = displayElementArrayDiff("regexs", regexs, _cother.regexs, _out, _indent+1); return _diff; } // BEGIN pass-through code block --- private static final String[] TEMPLATE_NAMES = new String[] { "measure_name", "measure_column_name", "aggregate_name" }; protected String[] getTemplateNames() { return TEMPLATE_NAMES; } protected Recognizer.Matcher getMatcher( final String measureName, final String measuerColumnName, final String aggregateName) { return getMatcher(new String[] { measureName, measuerColumnName, aggregateName }); } // END pass-through code block --- } /** * This is the template used to specify columns to be ignored. * There are NO template names. One simply uses a regular * expression. */ public static class IgnoreMap extends RegexMapper { public IgnoreMap() { } public IgnoreMap(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); org.eigenbase.xom.NodeDef[] _tempArray; id = (String)_parser.getAttribute("id", "String", null, null, true); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); _tempArray = _parser.getArray(Regex.class, 0, 0); regexs = new Regex[_tempArray.length]; for (int _i = 0; _i < regexs.length; _i++) regexs[_i] = (Regex)_tempArray[_i]; } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String getName() { return "IgnoreMap"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "id", id, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); displayElementArray(_out, "regexs", regexs, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("IgnoreMap", new org.eigenbase.xom.XMLAttrVector() .add("id", id) .add("enabled", enabled) ); displayXMLElementArray(_out, regexs); _out.endTag("IgnoreMap"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { IgnoreMap _cother = (IgnoreMap)_other; boolean _diff = displayElementArrayDiff("regexs", regexs, _cother.regexs, _out, _indent+1); return _diff; } // BEGIN pass-through code block --- private static final String[] TEMPLATE_NAMES = new String[] { }; protected String[] getTemplateNames() { return TEMPLATE_NAMES; } protected Recognizer.Matcher getMatcher() { return getMatcher(new String[]{}); } // END pass-through code block --- } /** * A RolapConnection uses one AggRule. If no name is specified, then * the AggRule which is marked as default==true is used (validation * fails if one and only one AggRule is not marked as the default). * An AggRule has manditory child elements for matching the * aggregate table names, aggregate table fact count column, * foreign key columns, the measure columns, and the hierarchy level * columns. These child elements can be specified as direct children * of an AggRule element or by reference to elements defined as a * pier to the AggRule (using references allows reuse of the child * elements and with one quick edit the reference to use can be * changed by changing the refid attribute value). */ public static class AggRule extends Base { public AggRule() { } public AggRule(org.eigenbase.xom.DOMWrapper _def) throws org.eigenbase.xom.XOMException { try { org.eigenbase.xom.DOMElementParser _parser = new org.eigenbase.xom.DOMElementParser(_def, "", DefaultDef.class); tag = (String)_parser.getAttribute("tag", "String", null, null, true); countColumn = (String)_parser.getAttribute("countColumn", "String", "fact_count", null, true); enabled = (Boolean)_parser.getAttribute("enabled", "Boolean", "true", null, false); ignoreMap = (IgnoreMap)_parser.getElement(IgnoreMap.class, false); ignoreMapRef = (IgnoreMapRef)_parser.getElement(IgnoreMapRef.class, false); factCountMatch = (FactCountMatch)_parser.getElement(FactCountMatch.class, false); factCountMatchRef = (FactCountMatchRef)_parser.getElement(FactCountMatchRef.class, false); foreignKeyMatch = (ForeignKeyMatch)_parser.getElement(ForeignKeyMatch.class, false); foreignKeyMatchRef = (ForeignKeyMatchRef)_parser.getElement(ForeignKeyMatchRef.class, false); tableMatch = (TableMatch)_parser.getElement(TableMatch.class, false); tableMatchRef = (TableMatchRef)_parser.getElement(TableMatchRef.class, false); levelMap = (LevelMap)_parser.getElement(LevelMap.class, false); levelMapRef = (LevelMapRef)_parser.getElement(LevelMapRef.class, false); measureMap = (MeasureMap)_parser.getElement(MeasureMap.class, false); measureMapRef = (MeasureMapRef)_parser.getElement(MeasureMapRef.class, false); } catch(org.eigenbase.xom.XOMException _ex) { throw new org.eigenbase.xom.XOMException("In " + getName() + ": " + _ex.getMessage()); } } public String tag; // required attribute public String countColumn; // attribute default: fact_count /** */ public IgnoreMap ignoreMap; //optional element /** */ public IgnoreMapRef ignoreMapRef; //optional element /** */ public FactCountMatch factCountMatch; //optional element /** */ public FactCountMatchRef factCountMatchRef; //optional element /** */ public ForeignKeyMatch foreignKeyMatch; //optional element /** */ public ForeignKeyMatchRef foreignKeyMatchRef; //optional element /** */ public TableMatch tableMatch; //optional element /** */ public TableMatchRef tableMatchRef; //optional element /** */ public LevelMap levelMap; //optional element /** */ public LevelMapRef levelMapRef; //optional element /** */ public MeasureMap measureMap; //optional element /** */ public MeasureMapRef measureMapRef; //optional element public String getName() { return "AggRule"; } public void display(java.io.PrintWriter _out, int _indent) { _out.println(getName()); displayAttribute(_out, "tag", tag, _indent+1); displayAttribute(_out, "countColumn", countColumn, _indent+1); displayAttribute(_out, "enabled", enabled, _indent+1); displayElement(_out, "ignoreMap", (org.eigenbase.xom.ElementDef) ignoreMap, _indent+1); displayElement(_out, "ignoreMapRef", (org.eigenbase.xom.ElementDef) ignoreMapRef, _indent+1); displayElement(_out, "factCountMatch", (org.eigenbase.xom.ElementDef) factCountMatch, _indent+1); displayElement(_out, "factCountMatchRef", (org.eigenbase.xom.ElementDef) factCountMatchRef, _indent+1); displayElement(_out, "foreignKeyMatch", (org.eigenbase.xom.ElementDef) foreignKeyMatch, _indent+1); displayElement(_out, "foreignKeyMatchRef", (org.eigenbase.xom.ElementDef) foreignKeyMatchRef, _indent+1); displayElement(_out, "tableMatch", (org.eigenbase.xom.ElementDef) tableMatch, _indent+1); displayElement(_out, "tableMatchRef", (org.eigenbase.xom.ElementDef) tableMatchRef, _indent+1); displayElement(_out, "levelMap", (org.eigenbase.xom.ElementDef) levelMap, _indent+1); displayElement(_out, "levelMapRef", (org.eigenbase.xom.ElementDef) levelMapRef, _indent+1); displayElement(_out, "measureMap", (org.eigenbase.xom.ElementDef) measureMap, _indent+1); displayElement(_out, "measureMapRef", (org.eigenbase.xom.ElementDef) measureMapRef, _indent+1); } public void displayXML(org.eigenbase.xom.XMLOutput _out, int _indent) { _out.beginTag("AggRule", new org.eigenbase.xom.XMLAttrVector() .add("tag", tag) .add("countColumn", countColumn) .add("enabled", enabled) ); displayXMLElement(_out, (org.eigenbase.xom.ElementDef) ignoreMap); displayXMLElement(_out, (org.eigenbase.xom.ElementDef) ignoreMapRef); displayXMLElement(_out, (org.eigenbase.xom.ElementDef) factCountMatch); displayXMLElement(_out, (org.eigenbase.xom.ElementDef) factCountMatchRef); displayXMLElement(_out, (org.eigenbase.xom.ElementDef) foreignKeyMatch); displayXMLElement(_out, (org.eigenbase.xom.ElementDef) foreignKeyMatchRef); displayXMLElement(_out, (org.eigenbase.xom.ElementDef) tableMatch); displayXMLElement(_out, (org.eigenbase.xom.ElementDef) tableMatchRef); displayXMLElement(_out, (org.eigenbase.xom.ElementDef) levelMap); displayXMLElement(_out, (org.eigenbase.xom.ElementDef) levelMapRef); displayXMLElement(_out, (org.eigenbase.xom.ElementDef) measureMap); displayXMLElement(_out, (org.eigenbase.xom.ElementDef) measureMapRef); _out.endTag("AggRule"); } public boolean displayDiff(org.eigenbase.xom.ElementDef _other, java.io.PrintWriter _out, int _indent) { AggRule _cother = (AggRule)_other; boolean _diff = displayAttributeDiff("tag", tag, _cother.tag, _out, _indent+1); _diff = _diff && displayAttributeDiff("countColumn", countColumn, _cother.countColumn, _out, _indent+1); _diff = _diff && displayElementDiff("ignoreMap", ignoreMap, _cother.ignoreMap, _out, _indent+1); _diff = _diff && displayElementDiff("ignoreMapRef", ignoreMapRef, _cother.ignoreMapRef, _out, _indent+1); _diff = _diff && displayElementDiff("factCountMatch", factCountMatch, _cother.factCountMatch, _out, _indent+1); _diff = _diff && displayElementDiff("factCountMatchRef", factCountMatchRef, _cother.factCountMatchRef, _out, _indent+1); _diff = _diff && displayElementDiff("foreignKeyMatch", foreignKeyMatch, _cother.foreignKeyMatch, _out, _indent+1); _diff = _diff && displayElementDiff("foreignKeyMatchRef", foreignKeyMatchRef, _cother.foreignKeyMatchRef, _out, _indent+1); _diff = _diff && displayElementDiff("tableMatch", tableMatch, _cother.tableMatch, _out, _indent+1); _diff = _diff && displayElementDiff("tableMatchRef", tableMatchRef, _cother.tableMatchRef, _out, _indent+1); _diff = _diff && displayElementDiff("levelMap", levelMap, _cother.levelMap, _out, _indent+1); _diff = _diff && displayElementDiff("levelMapRef", levelMapRef, _cother.levelMapRef, _out, _indent+1); _diff = _diff && displayElementDiff("measureMap", measureMap, _cother.measureMap, _out, _indent+1); _diff = _diff && displayElementDiff("measureMapRef", measureMapRef, _cother.measureMapRef, _out, _indent+1); return _diff; } // BEGIN pass-through code block --- private boolean isOk(final Base base) { return ((base != null) && base.isEnabled()); } private boolean isRef(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder, final Base base, final Base baseRef, final String baseName) { if (! isOk(base)) { if (isOk(baseRef)) { baseRef.validate(rules, msgRecorder); return true; } else { String msg = "Neither base " + baseName + " or baseref " + baseName + "Ref is ok"; msgRecorder.reportError(msg); return false; } } else if (isOk(baseRef)) { String msg = "Both base " + base.getName() + " and baseref " + baseRef.getName() + " are ok"; msgRecorder.reportError(msg); return false; } else { base.validate(rules, msgRecorder); return false; } } // called after a constructor is called public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { // IgnoreMap is optional if (ignoreMap != null) { ignoreMap.validate(rules, msgRecorder); } else if (ignoreMapRef != null) { ignoreMapRef.validate(rules, msgRecorder); ignoreMap = rules.lookupIgnoreMap(ignoreMapRef.getRefId()); } if (isRef(rules, msgRecorder, factCountMatch, factCountMatchRef, "FactCountMatch")) { factCountMatch = rules.lookupFactCountMatch( factCountMatchRef.getRefId()); } if (isRef(rules, msgRecorder, foreignKeyMatch, foreignKeyMatchRef, "ForeignKeyMatch")) { foreignKeyMatch = rules.lookupForeignKeyMatch( foreignKeyMatchRef.getRefId()); } if (isRef(rules, msgRecorder, tableMatch, tableMatchRef, "TableMatch")) { tableMatch = rules.lookupTableMatch(tableMatchRef.getRefId()); } if (isRef(rules, msgRecorder, levelMap, levelMapRef, "LevelMap")) { levelMap = rules.lookupLevelMap(levelMapRef.getRefId()); } if (isRef(rules, msgRecorder, measureMap, measureMapRef, "MeasureMap")) { measureMap = rules.lookupMeasureMap(measureMapRef.getRefId()); } } finally { msgRecorder.popContextName(); } } public String getTag() { return tag; } public String getCountColumn() { return countColumn; } public FactCountMatch getFactCountMatch() { return factCountMatch; } public ForeignKeyMatch getForeignKeyMatch() { return foreignKeyMatch; } public TableMatch getTableMatch() { return tableMatch; } public LevelMap getLevelMap() { return levelMap; } public MeasureMap getMeasureMap() { return measureMap; } public IgnoreMap getIgnoreMap() { return ignoreMap; } // END pass-through code block --- } } mondrian-3.4.1/src/main/mondrian/rolap/aggmatcher/package.html0000644000175000017500000000015611735330606024252 0ustar drazzibdrazzib Defines a 'matcher' which scans the schema to find candidate aggregate tables. mondrian-3.4.1/src/main/mondrian/rolap/aggmatcher/JdbcSchema.java0000644000175000017500000011751311735330606024625 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.olap.MondrianDef; import mondrian.olap.MondrianProperties; import mondrian.resource.MondrianResource; import mondrian.rolap.RolapAggregator; import mondrian.rolap.RolapLevel; import mondrian.rolap.RolapStar; import mondrian.spi.Dialect; import org.apache.log4j.Logger; import org.olap4j.impl.Olap4jUtil; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.ref.SoftReference; import java.sql.*; import java.util.*; import javax.sql.DataSource; /** * Metadata gleaned from JDBC about the tables and columns in the star schema. * This class is used to scrape a database and store information about its * tables and columnIter. * *

    The structure of this information is as follows: A database has tables. A * table has columnIter. A column has one or more usages. A usage might be a * column being used as a foreign key or as part of a measure. * *

    Tables are created when calling code requests the set of available * tables. This call getTables() causes all tables to be loaded. * But a table's columnIter are not loaded until, on a table-by-table basis, * a request is made to get the set of columnIter associated with the table. * Since, the AggTableManager first attempts table name matches (recognition) * most tables do not match, so why load their columnIter. * Of course, as a result, there are a host of methods that can throw an * {@link SQLException}, rats. * * @author Richard M. Emberson */ public class JdbcSchema { private static final Logger LOGGER = Logger.getLogger(JdbcSchema.class); private static final MondrianResource mres = MondrianResource.instance(); /** * Returns the Logger. */ public Logger getLogger() { return LOGGER; } public interface Factory { JdbcSchema makeDB(DataSource dataSource); void clearDB(JdbcSchema db); void removeDB(JdbcSchema db); } private static final Map> dbMap = new HashMap>(); /** * How often between sweeping through the dbMap looking for nulls. */ private static final int SWEEP_COUNT = 10; private static int sweepDBCount = 0; public static class StdFactory implements Factory { StdFactory() { } public JdbcSchema makeDB(DataSource dataSource) { return new JdbcSchema(dataSource); } public void clearDB(JdbcSchema db) { // NoOp } public void removeDB(JdbcSchema db) { // NoOp } } private static Factory factory; private static void makeFactory() { if (factory == null) { String classname = MondrianProperties.instance().JdbcFactoryClass.get(); if (classname == null) { factory = new StdFactory(); } else { try { Class clz = Class.forName(classname); factory = (Factory) clz.newInstance(); } catch (ClassNotFoundException ex) { throw mres.BadJdbcFactoryClassName.ex(classname); } catch (InstantiationException ex) { throw mres.BadJdbcFactoryInstantiation.ex(classname); } catch (IllegalAccessException ex) { throw mres.BadJdbcFactoryAccess.ex(classname); } } } } /** * Creates or retrieves an instance of the JdbcSchema for the given * DataSource. * * @param dataSource DataSource * @return instance of the JdbcSchema for the given DataSource */ public static synchronized JdbcSchema makeDB(DataSource dataSource) { makeFactory(); JdbcSchema db = null; SoftReference ref = dbMap.get(dataSource); if (ref != null) { db = ref.get(); } if (db == null) { db = factory.makeDB(dataSource); dbMap.put(dataSource, new SoftReference(db)); } sweepDB(); return db; } /** * Clears information in a JdbcSchema associated with a DataSource. * * @param dataSource DataSource */ public static synchronized void clearDB(DataSource dataSource) { makeFactory(); SoftReference ref = dbMap.get(dataSource); if (ref != null) { JdbcSchema db = ref.get(); if (db != null) { factory.clearDB(db); db.clear(); } else { dbMap.remove(dataSource); } } sweepDB(); } /** * Removes a JdbcSchema associated with a DataSource. * * @param dataSource DataSource */ public static synchronized void removeDB(DataSource dataSource) { makeFactory(); SoftReference ref = dbMap.remove(dataSource); if (ref != null) { JdbcSchema db = ref.get(); if (db != null) { factory.removeDB(db); db.remove(); } } sweepDB(); } /** * Every SWEEP_COUNT calls to this method, go through all elements of * the dbMap removing all that either have null values (null SoftReference) * or those with SoftReference with null content. */ private static void sweepDB() { if (sweepDBCount++ > SWEEP_COUNT) { Iterator> it = dbMap.values().iterator(); while (it.hasNext()) { SoftReference ref = it.next(); if ((ref == null) || (ref.get() == null)) { try { it.remove(); } catch (Exception ex) { // Should not happen, but might still like to // know that something's funky. LOGGER.warn(ex); } } } // reset sweepDBCount = 0; } } // // Types of column usages. // public static final int UNKNOWN_COLUMN_USAGE = 0x0001; public static final int FOREIGN_KEY_COLUMN_USAGE = 0x0002; public static final int MEASURE_COLUMN_USAGE = 0x0004; public static final int LEVEL_COLUMN_USAGE = 0x0008; public static final int FACT_COUNT_COLUMN_USAGE = 0x0010; public static final int IGNORE_COLUMN_USAGE = 0x0020; public static final String UNKNOWN_COLUMN_NAME = "UNKNOWN"; public static final String FOREIGN_KEY_COLUMN_NAME = "FOREIGN_KEY"; public static final String MEASURE_COLUMN_NAME = "MEASURE"; public static final String LEVEL_COLUMN_NAME = "LEVEL"; public static final String FACT_COUNT_COLUMN_NAME = "FACT_COUNT"; public static final String IGNORE_COLUMN_NAME = "IGNORE"; /** * Enumeration of ways that an aggregate table can use a column. */ enum UsageType { UNKNOWN, FOREIGN_KEY, MEASURE, LEVEL, FACT_COUNT, IGNORE } /** * Determine if the parameter represents a single column type, i.e., the * column only has one usage. * * @param columnType Column types * @return true if column has only one usage. */ public static boolean isUniqueColumnType(Set columnType) { return columnType.size() == 1; } /** * Maps from column type enum to column type name or list of names if the * parameter represents more than on usage. */ public static String convertColumnTypeToName(Set columnType) { if (columnType.size() == 1) { return columnType.iterator().next().name(); } // it's a multi-purpose column StringBuilder buf = new StringBuilder(); int k = 0; for (UsageType usage : columnType) { if (k++ > 0) { buf.append('|'); } buf.append(usage.name()); } return buf.toString(); } /** * Converts a {@link java.sql.Types} value to a * {@link mondrian.spi.Dialect.Datatype}. * * @param javaType JDBC type code, as per {@link java.sql.Types} * @return Datatype */ public static Dialect.Datatype getDatatype(int javaType) { switch (javaType) { case Types.TINYINT: case Types.SMALLINT: case Types.INTEGER: case Types.BIGINT: return Dialect.Datatype.Integer; case Types.FLOAT: case Types.REAL: case Types.DOUBLE: case Types.NUMERIC: case Types.DECIMAL: return Dialect.Datatype.Numeric; case Types.BOOLEAN: return Dialect.Datatype.Boolean; case Types.DATE: return Dialect.Datatype.Date; case Types.TIME: return Dialect.Datatype.Time; case Types.TIMESTAMP: return Dialect.Datatype.Timestamp; case Types.CHAR: case Types.VARCHAR: default: return Dialect.Datatype.String; } } /** * Returns true if the parameter is a java.sql.Type text type. */ public static boolean isText(int javaType) { switch (javaType) { case Types.CHAR: case Types.VARCHAR: case Types.LONGVARCHAR: return true; default: return false; } } enum TableUsageType { UNKNOWN, FACT, AGG } /** * A table in a database. */ public class Table { /** * A column in a table. */ public class Column { /** * A usage of a column. */ public class Usage { private final UsageType usageType; private String symbolicName; private RolapAggregator aggregator; //////////////////////////////////////////////////// // // These instance variables are used to hold // stuff which is determines at one place and // then used somewhere else. Generally, a usage // is created before all of its "stuff" can be // determined, hence, usage is not a set of classes, // rather its one class with a bunch of instance // variables which may or may not be used. // // measure stuff public RolapStar.Measure rMeasure; // hierarchy stuff public MondrianDef.Relation relation; public MondrianDef.Expression joinExp; public String levelColumnName; // level public RolapStar.Column rColumn; // agg stuff public boolean collapsed = false; public RolapLevel level = null; // for subtables public RolapStar.Table rTable; public String rightJoinConditionColumnName; /** * The prefix (possibly null) to use during aggregate table * generation (See AggGen). */ public String usagePrefix; /** * Creates a Usage. * * @param usageType Usage type */ Usage(UsageType usageType) { this.usageType = usageType; } /** * Returns the column with which this usage is associated. * * @return the usage's column. */ public Column getColumn() { return JdbcSchema.Table.Column.this; } /** * Returns the column usage type. */ public UsageType getUsageType() { return usageType; } /** * Sets the symbolic (logical) name associated with this usage. * For example, this might be the measure's name. * * @param symbolicName Symbolic name */ public void setSymbolicName(final String symbolicName) { this.symbolicName = symbolicName; } /** * Returns the usage's symbolic name. */ public String getSymbolicName() { return symbolicName; } /** * Sets the aggregator associated with this usage (if it is a * measure usage). * * @param aggregator Aggregator */ public void setAggregator(final RolapAggregator aggregator) { this.aggregator = aggregator; } /** * Returns the aggregator associated with this usage (if its a * measure usage, otherwise null). */ public RolapAggregator getAggregator() { return aggregator; } public String toString() { StringWriter sw = new StringWriter(64); PrintWriter pw = new PrintWriter(sw); print(pw, ""); pw.flush(); return sw.toString(); } public void print(final PrintWriter pw, final String prefix) { if (getSymbolicName() != null) { pw.print("symbolicName="); pw.print(getSymbolicName()); } if (getAggregator() != null) { pw.print(", aggregator="); pw.print(getAggregator().getName()); } pw.print(", columnType="); pw.print(getUsageType().name()); } } /** This is the name of the column. */ private final String name; /** This is the java.sql.Type enum of the column in the database. */ private int type; /** * This is the java.sql.Type name of the column in the database. */ private String typeName; /** This is the size of the column in the database. */ private int columnSize; /** The number of fractional digits. */ private int decimalDigits; /** Radix (typically either 10 or 2). */ private int numPrecRadix; /** For char types the maximum number of bytes in the column. */ private int charOctetLength; /** * False means the column definitely does not allow NULL values. */ private boolean isNullable; public final MondrianDef.Column column; private final List usages; /** * This contains the enums of all of the column's usages. */ private final Set usageTypes = Olap4jUtil.enumSetNoneOf(UsageType.class); private Column(final String name) { this.name = name; this.column = new MondrianDef.Column( JdbcSchema.Table.this.getName(), name); this.usages = new ArrayList(); } /** * Returns the column's name in the database, not a symbolic name. */ public String getName() { return name; } /** * Sets the columnIter java.sql.Type enun of the column. * * @param type */ private void setType(final int type) { this.type = type; } /** * Returns the columnIter java.sql.Type enun of the column. */ public int getType() { return type; } /** * Sets the columnIter java.sql.Type name. * * @param typeName */ private void setTypeName(final String typeName) { this.typeName = typeName; } /** * Returns the columnIter java.sql.Type name. */ public String getTypeName() { return typeName; } /** * Returns this column's table. */ public Table getTable() { return JdbcSchema.Table.this; } /** * Return true if this column is numeric. */ public Dialect.Datatype getDatatype() { return JdbcSchema.getDatatype(getType()); } /** * Sets the size in bytes of the column in the database. * * @param columnSize */ private void setColumnSize(final int columnSize) { this.columnSize = columnSize; } /** * Returns the size in bytes of the column in the database. * */ public int getColumnSize() { return columnSize; } /** * Sets number of fractional digits. * * @param decimalDigits */ private void setDecimalDigits(final int decimalDigits) { this.decimalDigits = decimalDigits; } /** * Returns number of fractional digits. */ public int getDecimalDigits() { return decimalDigits; } /** * Sets Radix (typically either 10 or 2). * * @param numPrecRadix */ private void setNumPrecRadix(final int numPrecRadix) { this.numPrecRadix = numPrecRadix; } /** * Returns Radix (typically either 10 or 2). */ public int getNumPrecRadix() { return numPrecRadix; } /** * For char types the maximum number of bytes in the column. * * @param charOctetLength */ private void setCharOctetLength(final int charOctetLength) { this.charOctetLength = charOctetLength; } /** * For char types the maximum number of bytes in the column. */ public int getCharOctetLength() { return charOctetLength; } /** * False means the column definitely does not allow NULL values. * * @param isNullable */ private void setIsNullable(final boolean isNullable) { this.isNullable = isNullable; } /** * False means the column definitely does not allow NULL values. */ public boolean isNullable() { return isNullable; } /** * How many usages does this column have. A column has * between 0 and N usages. It has no usages if usages is some * administrative column. It has one usage if, for example, its * the fact_count column or a level column (for a collapsed * dimension aggregate). It might have 2 usages if its a foreign key * that is also used as a measure. If its a column used in N * measures, then usages will have N usages. */ public int numberOfUsages() { return usages.size(); } /** * flushes all star usage references */ public void flushUsages() { usages.clear(); usageTypes.clear(); } /** * Return true if the column has at least one usage. */ public boolean hasUsage() { return (usages.size() != 0); } /** * Return true if the column has at least one usage of the given * column type. */ public boolean hasUsage(UsageType columnType) { return usageTypes.contains(columnType); } /** * Returns an iterator over all usages. */ public List getUsages() { return usages; } /** * Returns an iterator over all usages of the given column type. */ public Iterator getUsages(UsageType usageType) { // Yes, this is legal. class ColumnTypeIterator implements Iterator { private final Iterator usageIter; private final UsageType usageType; private Usage nextUsage; ColumnTypeIterator( final List usages, final UsageType columnType) { this.usageIter = usages.iterator(); this.usageType = columnType; } public boolean hasNext() { while (usageIter.hasNext()) { Usage usage = usageIter.next(); if (usage.getUsageType() == this.usageType) { nextUsage = usage; return true; } } nextUsage = null; return false; } public Usage next() { return nextUsage; } public void remove() { usageIter.remove(); } } return new ColumnTypeIterator(getUsages(), usageType); } /** * Create a new usage of a given column type. */ public Usage newUsage(UsageType usageType) { this.usageTypes.add(usageType); Usage usage = new Usage(usageType); usages.add(usage); return usage; } public String toString() { StringWriter sw = new StringWriter(256); PrintWriter pw = new PrintWriter(sw); print(pw, ""); pw.flush(); return sw.toString(); } public void print(final PrintWriter pw, final String prefix) { pw.print(prefix); pw.print("name="); pw.print(getName()); pw.print(", typename="); pw.print(getTypeName()); pw.print(", size="); pw.print(getColumnSize()); switch (getType()) { case Types.TINYINT: case Types.SMALLINT: case Types.INTEGER: case Types.BIGINT: case Types.FLOAT: case Types.REAL: case Types.DOUBLE: break; case Types.NUMERIC: case Types.DECIMAL: pw.print(", decimalDigits="); pw.print(getDecimalDigits()); pw.print(", numPrecRadix="); pw.print(getNumPrecRadix()); break; case Types.CHAR: case Types.VARCHAR: pw.print(", charOctetLength="); pw.print(getCharOctetLength()); break; case Types.LONGVARCHAR: case Types.DATE: case Types.TIME: case Types.TIMESTAMP: case Types.BINARY: case Types.VARBINARY: case Types.LONGVARBINARY: default: break; } pw.print(", isNullable="); pw.print(isNullable()); if (hasUsage()) { pw.print(" Usages ["); for (Usage usage : getUsages()) { pw.print('('); usage.print(pw, prefix); pw.print(')'); } pw.println("]"); } } } /** Name of table. */ private final String name; /** Map from column name to column. */ private Map columnMap; /** Sum of all of the table's column's column sizes. */ private int totalColumnSize; /** * Whether the table is a fact, aggregate or other table type. * Note: this assumes that a table has only ONE usage. */ private TableUsageType tableUsageType; /** * Typical table types are: "TABLE", "VIEW", "SYSTEM TABLE", * "GLOBAL TEMPORARY", "LOCAL TEMPORARY", "ALIAS", "SYNONYM". * (Depends what comes out of JDBC.) */ private final String tableType; // mondriandef stuff public MondrianDef.Table table; private boolean allColumnsLoaded; private Table(final String name, String tableType) { this.name = name; this.tableUsageType = TableUsageType.UNKNOWN; this.tableType = tableType; } public void load() throws SQLException { loadColumns(); } /** * flushes all star usage references */ public void flushUsages() { tableUsageType = TableUsageType.UNKNOWN; for (Table.Column col : getColumns()) { col.flushUsages(); } } /** * Returns the name of the table. */ public String getName() { return name; } /** * Returns the total size of a row (sum of the column sizes). */ public int getTotalColumnSize() { return totalColumnSize; } /** * Returns the number of rows in the table. */ public int getNumberOfRows() { return -1; } /** * Returns the collection of columns in this Table. */ public Collection getColumns() { return getColumnMap().values(); } /** * Returns an iterator over all column usages of a given type. */ public Iterator getColumnUsages( final UsageType usageType) { class CTIterator implements Iterator { private final Iterator columnIter; private final UsageType columnType; private Iterator usageIter; private JdbcSchema.Table.Column.Usage nextObject; CTIterator(Collection columns, UsageType columnType) { this.columnIter = columns.iterator(); this.columnType = columnType; } public boolean hasNext() { while (true) { while ((usageIter == null) || ! usageIter.hasNext()) { if (! columnIter.hasNext()) { nextObject = null; return false; } Column c = columnIter.next(); usageIter = c.getUsages().iterator(); } JdbcSchema.Table.Column.Usage usage = usageIter.next(); if (usage.getUsageType() == columnType) { nextObject = usage; return true; } } } public JdbcSchema.Table.Column.Usage next() { return nextObject; } public void remove() { usageIter.remove(); } } return new CTIterator(getColumns(), usageType); } /** * Returns a column by its name. */ public Column getColumn(final String columnName) { return getColumnMap().get(columnName); } /** * Return true if this table contains a column with the given name. */ public boolean constainsColumn(final String columnName) { return getColumnMap().containsKey(columnName); } /** * Sets the table usage (fact, aggregate or other). * * @param tableUsageType */ public void setTableUsageType(final TableUsageType tableUsageType) { // if usageIter has already been set, then usageIter can NOT be // reset if ((this.tableUsageType != TableUsageType.UNKNOWN) && (this.tableUsageType != tableUsageType)) { throw mres.AttemptToChangeTableUsage.ex( getName(), this.tableUsageType.name(), tableUsageType.name()); } this.tableUsageType = tableUsageType; } /** * Returns the table's usage type. */ public TableUsageType getTableUsageType() { return tableUsageType; } /** * Returns the table's type. */ public String getTableType() { return tableType; } public String toString() { StringWriter sw = new StringWriter(256); PrintWriter pw = new PrintWriter(sw); print(pw, ""); pw.flush(); return sw.toString(); } public void print(final PrintWriter pw, final String prefix) { pw.print(prefix); pw.println("Table:"); String subprefix = prefix + " "; String subsubprefix = subprefix + " "; pw.print(subprefix); pw.print("name="); pw.print(getName()); pw.print(", type="); pw.print(getTableType()); pw.print(", usage="); pw.println(getTableUsageType().name()); pw.print(subprefix); pw.print("totalColumnSize="); pw.println(getTotalColumnSize()); pw.print(subprefix); pw.println("Columns: ["); for (Column column : getColumnMap().values()) { column.print(pw, subsubprefix); pw.println(); } pw.print(subprefix); pw.println("]"); } /** * Returns all of the columnIter associated with a table and creates * Column objects with the column's name, type, type name and column * size. * * @throws SQLException */ private void loadColumns() throws SQLException { if (! allColumnsLoaded) { Connection conn = getDataSource().getConnection(); try { DatabaseMetaData dmd = conn.getMetaData(); String schema = JdbcSchema.this.getSchemaName(); String catalog = JdbcSchema.this.getCatalogName(); String tableName = getName(); String columnNamePattern = "%"; ResultSet rs = null; try { Map map = getColumnMap(); rs = dmd.getColumns( catalog, schema, tableName, columnNamePattern); while (rs.next()) { String name = rs.getString(4); int type = rs.getInt(5); String typeName = rs.getString(6); int columnSize = rs.getInt(7); int decimalDigits = rs.getInt(9); int numPrecRadix = rs.getInt(10); int charOctetLength = rs.getInt(16); String isNullable = rs.getString(18); Column column = new Column(name); column.setType(type); column.setTypeName(typeName); column.setColumnSize(columnSize); column.setDecimalDigits(decimalDigits); column.setNumPrecRadix(numPrecRadix); column.setCharOctetLength(charOctetLength); column.setIsNullable(!"NO".equals(isNullable)); map.put(name, column); totalColumnSize += column.getColumnSize(); } } finally { if (rs != null) { rs.close(); } } } finally { try { conn.close(); } catch (SQLException e) { //ignore } } allColumnsLoaded = true; } } private Map getColumnMap() { if (columnMap == null) { columnMap = new HashMap(); } return columnMap; } } private DataSource dataSource; private String schema; private String catalog; private boolean allTablesLoaded; /** * Tables by name. We use a sorted map so {@link #getTables()}'s output * is in deterministic order. */ private final SortedMap tables = new TreeMap(); JdbcSchema(final DataSource dataSource) { this.dataSource = dataSource; } /** * This forces the tables to be loaded. * * @throws SQLException */ public void load() throws SQLException { loadTables(); } protected synchronized void clear() { // keep the DataSource, clear/reset everything else allTablesLoaded = false; schema = null; catalog = null; tables.clear(); } protected void remove() { // set ALL instance variables to null clear(); dataSource = null; } /** * Used for testing allowing one to load tables and their columnIter * from more than one datasource */ void resetAllTablesLoaded() { allTablesLoaded = false; } public DataSource getDataSource() { return dataSource; } protected void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * Sets the database's schema name. * * @param schema Schema name */ public void setSchemaName(final String schema) { this.schema = schema; } /** * Returns the database's schema name. */ public String getSchemaName() { return schema; } /** * Sets the database's catalog name. */ public void setCatalogName(final String catalog) { this.catalog = catalog; } /** * Returns the database's catalog name. */ public String getCatalogName() { return catalog; } /** * Returns the database's tables. The collection is sorted by table name. */ public synchronized Collection

getTables() { return getTablesMap().values(); } /** * flushes all star usage references */ public synchronized void flushUsages() { for (Table table : getTables()) { table.flushUsages(); } } /** * Gets a table by name. */ public synchronized Table getTable(final String tableName) { return getTablesMap().get(tableName); } public String toString() { StringWriter sw = new StringWriter(256); PrintWriter pw = new PrintWriter(sw); print(pw, ""); pw.flush(); return sw.toString(); } public void print(final PrintWriter pw, final String prefix) { pw.print(prefix); pw.println("JdbcSchema:"); String subprefix = prefix + " "; String subsubprefix = subprefix + " "; pw.print(subprefix); pw.println("Tables: ["); for (Table table : getTablesMap().values()) { table.print(pw, subsubprefix); } pw.print(subprefix); pw.println("]"); } /** * Gets all of the tables (and views) in the database. * If called a second time, this method is a no-op. * * @throws SQLException */ private void loadTables() throws SQLException { if (allTablesLoaded) { return; } Connection conn = null; try { conn = getDataSource().getConnection(); final DatabaseMetaData databaseMetaData = conn.getMetaData(); String[] tableTypes = { "TABLE", "VIEW" }; if (databaseMetaData.getDatabaseProductName().toUpperCase().indexOf( "VERTICA") >= 0) { for (String tableType : tableTypes) { loadTablesOfType(databaseMetaData, new String[]{tableType}); } } else { loadTablesOfType(databaseMetaData, tableTypes); } allTablesLoaded = true; } finally { if (conn != null) { conn.close(); } } } /** * Loads definition of tables of a given set of table types ("TABLE", "VIEW" * etc.) */ private void loadTablesOfType( DatabaseMetaData databaseMetaData, String[] tableTypes) throws SQLException { final String schema = getSchemaName(); final String catalog = getCatalogName(); final String tableName = "%"; ResultSet rs = null; try { rs = databaseMetaData.getTables( catalog, schema, tableName, tableTypes); if (rs == null) { getLogger().debug("ERROR: rs == null"); return; } while (rs.next()) { addTable(rs); } } finally { if (rs != null) { rs.close(); } } } /** * Makes a Table from an ResultSet: the table's name is the ResultSet third * entry. * * @param rs Result set * @throws SQLException */ protected void addTable(final ResultSet rs) throws SQLException { String name = rs.getString(3); String tableType = rs.getString(4); Table table = new Table(name, tableType); tables.put(table.getName(), table); } private SortedMap getTablesMap() { return tables; } public static synchronized void clearAllDBs() { factory = null; makeFactory(); } } // End JdbcSchema.java mondrian-3.4.1/src/main/mondrian/rolap/aggmatcher/DefaultRules.xml0000644000175000017500000000433411735330606025114 0ustar drazzibdrazzib ${hierarchy_name}_${level_name} ${hierarchy_name}_${level_column_name} ${usage_prefix}${level_column_name} ${level_column_name} ${measure_name} ${measure_column_name} ${measure_column_name}_${aggregate_name} mondrian-3.4.1/src/main/mondrian/rolap/aggmatcher/DefaultRecognizer.java0000644000175000017500000002563111735330606026255 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.olap.Hierarchy; import mondrian.olap.Level; import mondrian.recorder.MessageRecorder; import mondrian.resource.MondrianResource; import mondrian.rolap.*; import mondrian.rolap.aggmatcher.JdbcSchema.Table.Column; import mondrian.util.Pair; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; /** * This is the default Recognizer. It uses the rules found in the file * DefaultRules.xml to find aggregate tables and there columns. * * @author Richard M. Emberson */ class DefaultRecognizer extends Recognizer { private static final MondrianResource mres = MondrianResource.instance(); private final DefaultRules aggDefault; DefaultRecognizer( final DefaultRules aggDefault, final RolapStar star, final JdbcSchema.Table dbFactTable, final JdbcSchema.Table aggTable, final MessageRecorder msgRecorder) { super(star, dbFactTable, aggTable, msgRecorder); this.aggDefault = aggDefault; } /** * Get the DefaultRules instance associated with this object. */ DefaultRules getRules() { return aggDefault; } /** * Get the Matcher to be used to match columns to be ignored. */ protected Recognizer.Matcher getIgnoreMatcher() { return getRules().getIgnoreMatcher(); } /** * Get the Matcher to be used to match the column which is the fact count * column. */ protected Recognizer.Matcher getFactCountMatcher() { return getRules().getFactCountMatcher(); } /** * Get the Match used to identify columns that are measures. */ protected Recognizer.Matcher getMeasureMatcher( JdbcSchema.Table.Column.Usage factUsage) { String measureName = factUsage.getSymbolicName(); String measureColumnName = factUsage.getColumn().getName(); String aggregateName = factUsage.getAggregator().getName(); return getRules().getMeasureMatcher( measureName, measureColumnName, aggregateName); } /** * Create measures for an aggregate table. *

* First, iterator through all fact table measure usages. * Create a Matcher for each such usage. * Iterate through all aggregate table columns. * For each column that matches create a measure usage. *

* Per fact table measure usage, at most only one aggregate measure should * be created. * * @return number of measures created. */ protected int checkMeasures() { msgRecorder.pushContextName("DefaultRecognizer.checkMeasures"); try { int measureCountCount = 0; for (Iterator it = dbFactTable.getColumnUsages(JdbcSchema.UsageType.MEASURE); it.hasNext();) { JdbcSchema.Table.Column.Usage factUsage = it.next(); Matcher matcher = getMeasureMatcher(factUsage); int matchCount = 0; for (JdbcSchema.Table.Column aggColumn : aggTable.getColumns()) { // if marked as ignore, then do not consider if (aggColumn.hasUsage(JdbcSchema.UsageType.IGNORE)) { continue; } if (matcher.matches(aggColumn.getName())) { makeMeasure(factUsage, aggColumn); measureCountCount++; matchCount++; } } if (matchCount > 1) { String msg = mres.AggMultipleMatchingMeasure.str( msgRecorder.getContext(), aggTable.getName(), dbFactTable.getName(), matchCount, factUsage.getSymbolicName(), factUsage.getColumn().getName(), factUsage.getAggregator().getName()); msgRecorder.reportError(msg); returnValue = false; } } return measureCountCount; } finally { msgRecorder.popContextName(); } } /** * This creates a foreign key usage. * *

Using the foreign key Matcher with the fact usage's column name the * aggregate table's columns are searched for one that matches. For each * that matches a foreign key usage is created (thought if more than one is * created its is an error which is handled in the calling code. */ protected int matchForeignKey(JdbcSchema.Table.Column.Usage factUsage) { JdbcSchema.Table.Column factColumn = factUsage.getColumn(); // search to see if any of the aggTable's columns match Recognizer.Matcher matcher = getRules().getForeignKeyMatcher(factColumn.getName()); int matchCount = 0; for (JdbcSchema.Table.Column aggColumn : aggTable.getColumns()) { // if marked as ignore, then do not consider if (aggColumn.hasUsage(JdbcSchema.UsageType.IGNORE)) { continue; } if (matcher.matches(aggColumn.getName())) { makeForeignKey(factUsage, aggColumn, null); matchCount++; } } return matchCount; } /** * Create level usages. * *

A Matcher is created using the Hierarchy's name, the RolapLevel * name, and the column name associated with the RolapLevel's key * expression. The aggregate table columns are search for the first match * and, if found, a level usage is created for that column. */ protected void matchLevels( final Hierarchy hierarchy, final HierarchyUsage hierarchyUsage) { msgRecorder.pushContextName("DefaultRecognizer.matchLevel"); try { List> levelMatches = new ArrayList>(); level_loop: for (Level level : hierarchy.getLevels()) { if (level.isAll()) { continue; } final RolapLevel rLevel = (RolapLevel) level; String usagePrefix = hierarchyUsage.getUsagePrefix(); String hierName = hierarchy.getName(); String levelName = rLevel.getName(); String levelColumnName = getColumnName(rLevel.getKeyExp()); Recognizer.Matcher matcher = getRules().getLevelMatcher( usagePrefix, hierName, levelName, levelColumnName); for (JdbcSchema.Table.Column aggColumn : aggTable.getColumns()) { if (matcher.matches(aggColumn.getName())) { levelMatches.add( new Pair( rLevel, aggColumn)); continue level_loop; } } } if (levelMatches.size() == 0) { return; } // Sort the matches by level depth. Collections.sort( levelMatches, new Comparator>() { public int compare( Pair o1, Pair o2) { return Integer.valueOf(o1.left.getDepth()).compareTo( Integer.valueOf(o2.left.getDepth())); } }); // Validate by iterating. for (Pair pair : levelMatches) { boolean collapsed = true; if (levelMatches.indexOf(pair) == 0 && pair.left.getDepth() > 1) { collapsed = false; } // Fail if the level is not the first match // but the one before is not its parent. if (levelMatches.indexOf(pair) > 0 && pair.left.getDepth() - 1 != levelMatches.get( levelMatches.indexOf(pair) - 1).left.getDepth()) { msgRecorder.reportError( "The aggregate table " + aggTable.getName() + " contains the column " + pair.right.getName() + " which maps to the level " + pair.left.getUniqueName() + " but its parent level is not part of that aggregation."); } // Fail if the level is non-collapsed but its members // are not unique. if (!collapsed && !pair.left.isUnique()) { msgRecorder.reportError( "The aggregate table " + aggTable.getName() + " contains the column " + pair.right.getName() + " which maps to the level " + pair.left.getUniqueName() + " but that level doesn't have unique members and this level is marked as non collapsed."); } } if (msgRecorder.hasErrors()) { return; } // All checks out. Let's create the levels. for (Pair pair : levelMatches) { boolean collapsed = true; if (levelMatches.indexOf(pair) == 0 && pair.left.getDepth() > 1) { collapsed = false; } makeLevel( pair.right, hierarchy, hierarchyUsage, pair.right.column.name, getColumnName(pair.left.getKeyExp()), pair.left.getName(), collapsed, pair.left); } } finally { msgRecorder.popContextName(); } } } // End DefaultRecognizer.java mondrian-3.4.1/src/main/mondrian/rolap/aggmatcher/AggStar.java0000644000175000017500000014502311735330606024167 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.olap.*; import mondrian.olap.MondrianDef.AggLevel; import mondrian.recorder.MessageRecorder; import mondrian.resource.MondrianResource; import mondrian.rolap.*; import mondrian.rolap.sql.SqlQuery; import mondrian.server.Execution; import mondrian.server.Locus; import mondrian.spi.Dialect; import org.apache.log4j.Logger; import java.io.PrintWriter; import java.io.StringWriter; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; import javax.sql.DataSource; /** * Aggregate table version of a RolapStar for a fact table. * *

There is the following class structure: *

 * AggStar
 *   Table
 *     JoinCondition
 *     Column
 *     Level extends Column
 *   FactTable extends Table
 *     Measure extends Table.Column
 *   DimTable extends Table
 * 
* *

Each inner class is non-static meaning that instances have implied * references to the enclosing object. * * @author Richard M. Emberson */ public class AggStar { private static final Logger LOGGER = Logger.getLogger(AggStar.class); static Logger getLogger() { return LOGGER; } private static final MondrianResource mres = MondrianResource.instance(); /** * Creates an AggStar and all of its {@link Table}, {@link Table.Column}s, * etc. */ public static AggStar makeAggStar( final RolapStar star, final JdbcSchema.Table dbTable, final MessageRecorder msgRecorder, final int approxRowCount) { AggStar aggStar = new AggStar(star, dbTable, approxRowCount); AggStar.FactTable aggStarFactTable = aggStar.getFactTable(); // 1. load fact count for (Iterator it = dbTable.getColumnUsages(JdbcSchema.UsageType.FACT_COUNT); it.hasNext();) { JdbcSchema.Table.Column.Usage usage = it.next(); aggStarFactTable.loadFactCount(usage); } // 2. load measures for (Iterator it = dbTable.getColumnUsages(JdbcSchema.UsageType.MEASURE); it.hasNext();) { JdbcSchema.Table.Column.Usage usage = it.next(); aggStarFactTable.loadMeasure(usage); } // 3. load foreign keys for (Iterator it = dbTable.getColumnUsages(JdbcSchema.UsageType.FOREIGN_KEY); it.hasNext();) { JdbcSchema.Table.Column.Usage usage = it.next(); aggStarFactTable.loadForeignKey(usage); } // 4. load levels for (Iterator it = dbTable.getColumnUsages(JdbcSchema.UsageType.LEVEL); it.hasNext();) { JdbcSchema.Table.Column.Usage usage = it.next(); aggStarFactTable.loadLevel(usage); } // 5. for each distinct-count measure, populate a list of the levels // which it is OK to roll up for (FactTable.Measure measure : aggStarFactTable.measures) { if (measure.aggregator.isDistinct() && measure.argument instanceof MondrianDef.Column) { setLevelBits( measure.rollableLevelBitKey, aggStarFactTable, (MondrianDef.Column) measure.argument, star.getFactTable()); } } return aggStar; } /** * Sets bits in the bitmap for all levels reachable from a given table. * This allows us to compute which levels can be safely aggregated away * when rolling up a distinct-count measure. * *

For example, when rolling up a measure based on * 'COUNT(DISTINCT customer_id)', all levels in the Customers table * and the Regions table reached via the Customers table can be rolled up. * So method sets the bit for all of these levels. * * @param bitKey Bit key of levels which can be rolled up * @param aggTable Fact or dimension table which is the start point for * the navigation * @param column Foreign-key column which constraints which dimension * @param table */ private static void setLevelBits( final BitKey bitKey, Table aggTable, MondrianDef.Column column, RolapStar.Table table) { final Set columns = new HashSet(); RolapStar.collectColumns(columns, table, column); final List levelList = new ArrayList(); collectLevels(levelList, aggTable, null); for (Table.Level level : levelList) { if (columns.contains(level.starColumn)) { bitKey.set(level.getBitPosition()); } } } private static void collectLevels( List levelList, Table table, MondrianDef.Column joinColumn) { if (joinColumn == null) { levelList.addAll(table.levels); } for (Table dimTable : table.children) { if (joinColumn != null && !dimTable.getJoinCondition().left.equals(joinColumn)) { continue; } collectLevels(levelList, dimTable, null); } } private final RolapStar star; private final AggStar.FactTable aggTable; /** * This BitKey is for all of the columns in the AggStar (levels and * measures). */ private final BitKey bitKey; /** * BitKey of the levels (levels and foreign keys) of this AggStar. */ private final BitKey levelBitKey; /** * BitKey of the measures of this AggStar. */ private final BitKey measureBitKey; /** * BitKey of the foreign keys of this AggStar. */ private final BitKey foreignKeyBitKey; /** * BitKey of those measures of this AggStar that are distinct count * aggregates. */ private final BitKey distinctMeasureBitKey; private final AggStar.Table.Column[] columns; /** * A map of bit positions to columns which need to * be joined and are not collapsed. If the aggregate table * includes an {@link AggLevel} element which is not * collapsed, it will appear in that list. We use this * list later on to create the join paths in AggQuerySpec. */ private final Map levelColumnsToJoin; /** * An approximate number of rows present in this aggregate table. */ private final int approxRowCount; AggStar( final RolapStar star, final JdbcSchema.Table aggTable, final int approxRowCount) { this.star = star; this.approxRowCount = approxRowCount; this.bitKey = BitKey.Factory.makeBitKey(star.getColumnCount()); this.levelBitKey = bitKey.emptyCopy(); this.measureBitKey = bitKey.emptyCopy(); this.foreignKeyBitKey = bitKey.emptyCopy(); this.distinctMeasureBitKey = bitKey.emptyCopy(); this.aggTable = new AggStar.FactTable(aggTable); this.columns = new AggStar.Table.Column[star.getColumnCount()]; this.levelColumnsToJoin = new HashMap(); } /** * Get the fact table. * * @return the fact table */ public AggStar.FactTable getFactTable() { return aggTable; } /** * Find a table by name (alias) that is a descendant of the base * fact table. * * @param name the table to find * @return the table or null */ public Table findTable(String name) { AggStar.FactTable table = getFactTable(); return table.findDescendant(name); } /** * Returns a measure of the IO cost of querying this table. It can be * either the row count or the row count times the size of a row. * If the property {@link MondrianProperties#ChooseAggregateByVolume} * is true, then volume is returned, otherwise row count. */ public int getSize() { return MondrianProperties.instance().ChooseAggregateByVolume.get() ? getFactTable().getVolume() : getFactTable().getNumberOfRows(); } void setForeignKey(int index) { this.foreignKeyBitKey.set(index); } public BitKey getForeignKeyBitKey() { return this.foreignKeyBitKey; } /** * Is this AggStar's BitKey a super set (proper or not) of the BitKey * parameter. * * @param bitKey * @return true if it is a super set */ public boolean superSetMatch(final BitKey bitKey) { return getBitKey().isSuperSetOf(bitKey); } /** * Return true if this AggStar's level BitKey equals the * levelBitKey parameter * and if this AggStar's measure BitKey is a super set * (proper or not) of the measureBitKey parameter. */ public boolean select( final BitKey levelBitKey, final BitKey coreLevelBitKey, final BitKey measureBitKey) { if (!getMeasureBitKey().isSuperSetOf(measureBitKey)) { return false; } if (getLevelBitKey().equals(levelBitKey)) { return true; } else if (getLevelBitKey().isSuperSetOf(levelBitKey) && getLevelBitKey().andNot(coreLevelBitKey).equals( levelBitKey.andNot(coreLevelBitKey))) { // It's OK to roll up levels which are orthogonal to the distinct // measure. return true; } else { return false; } } /** * Get this AggStar's RolapStar. */ public RolapStar getStar() { return star; } /** * Return true if AggStar has measures */ public boolean hasMeasures() { return getFactTable().hasMeasures(); } /** * Return true if AggStar has levels */ public boolean hasLevels() { return getFactTable().hasLevels(); } /** * Returns whether this AggStar has foreign keys. */ public boolean hasForeignKeys() { return getFactTable().hasChildren(); } /** * Returns the BitKey. */ public BitKey getBitKey() { return bitKey; } /** * Get the foreign-key/level BitKey. */ public BitKey getLevelBitKey() { return levelBitKey; } /** * Returns a BitKey of all measures. */ public BitKey getMeasureBitKey() { return measureBitKey; } /** * Returns a BitKey containing only distinct measures. */ public BitKey getDistinctMeasureBitKey() { return distinctMeasureBitKey; } /** * Get an SqlQuery instance. */ private SqlQuery getSqlQuery() { return getStar().getSqlQuery(); } /** * Get the Measure at the given bit position or return null. * Note that there is no check that the bit position is within the range of * the array of columns. * Nor is there a check that the column type at that position is a Measure. * * @param bitPos * @return A Measure or null. */ public AggStar.FactTable.Measure lookupMeasure(final int bitPos) { AggStar.Table.Column column = lookupColumn(bitPos); return (column instanceof AggStar.FactTable.Measure) ? (AggStar.FactTable.Measure) column : null; } /** * Get the Level at the given bit position or return null. * Note that there is no check that the bit position is within the range of * the array of columns. * Nor is there a check that the column type at that position is a Level. * * @param bitPos * @return A Level or null. */ public AggStar.Table.Level lookupLevel(final int bitPos) { AggStar.Table.Column column = lookupColumn(bitPos); return (column instanceof AggStar.FactTable.Level) ? (AggStar.FactTable.Level) column : null; } /** * Get the Column at the bit position. * Note that there is no check that the bit position is within the range of * the array of columns. */ public AggStar.Table.Column lookupColumn(final int bitPos) { if (columns[bitPos] != null) { return columns[bitPos]; } return levelColumnsToJoin.get(bitPos); } /** * This is called by within the Column constructor. * * @param column */ private void addColumn(final AggStar.Table.Column column) { columns[column.getBitPosition()] = column; } private static final Logger JOIN_CONDITION_LOGGER = Logger.getLogger(AggStar.Table.JoinCondition.class); /** * Base Table class for the FactTable and DimTable classes. * This class parallels the RolapStar.Table class. * */ public abstract class Table { /** * The query join condition between a base table and this table (the * table that owns the join condition). */ public class JoinCondition { // I think this is always a MondrianDef.Column private final MondrianDef.Expression left; private final MondrianDef.Expression right; private JoinCondition( final MondrianDef.Expression left, final MondrianDef.Expression right) { if (!(left instanceof MondrianDef.Column)) { JOIN_CONDITION_LOGGER.debug( "JoinCondition.left NOT Column: " + left.getClass().getName()); } this.left = left; this.right = right; } /** * Get the enclosing AggStar.Table. */ public Table getTable() { return AggStar.Table.this; } /** * Return the left join expression. */ public MondrianDef.Expression getLeft() { return this.left; } /** * Return the left join expression as string. */ public String getLeft(final SqlQuery query) { return this.left.getExpression(query); } /** * Return the right join expression. */ public MondrianDef.Expression getRight() { return this.right; } /** * This is used to create part of a SQL where clause. */ String toString(final SqlQuery query) { StringBuilder buf = new StringBuilder(64); buf.append(left.getExpression(query)); buf.append(" = "); buf.append(right.getExpression(query)); return buf.toString(); } public String toString() { StringWriter sw = new StringWriter(128); PrintWriter pw = new PrintWriter(sw); print(pw, ""); pw.flush(); return sw.toString(); } /** * Prints this table and its children. */ public void print(final PrintWriter pw, final String prefix) { SqlQuery sqlQueuy = getTable().getSqlQuery(); pw.print(prefix); pw.println("JoinCondition:"); String subprefix = prefix + " "; pw.print(subprefix); pw.print("left="); if (left instanceof MondrianDef.Column) { MondrianDef.Column c = (MondrianDef.Column) left; mondrian.rolap.RolapStar.Column col = getTable().getAggStar().getStar().getFactTable() .lookupColumn(c.name); if (col != null) { pw.print(" ("); pw.print(col.getBitPosition()); pw.print(") "); } } pw.println(left.getExpression(sqlQueuy)); pw.print(subprefix); pw.print("right="); pw.println(right.getExpression(sqlQueuy)); } } /** * Base class for Level and Measure classes */ public class Column { private final String name; private final MondrianDef.Expression expression; private final Dialect.Datatype datatype; /** * This is only used in RolapAggregationManager and adds * non-constraining columns making the drill-through queries * easier for humans to understand. */ private final Column nameColumn; /** this has a unique value per star */ private final int bitPosition; protected Column( final String name, final MondrianDef.Expression expression, final Dialect.Datatype datatype, final int bitPosition) { this.name = name; this.expression = expression; this.datatype = datatype; this.bitPosition = bitPosition; this.nameColumn = null; // do not count the fact_count column if (bitPosition >= 0) { AggStar.this.bitKey.set(bitPosition); AggStar.this.addColumn(this); } } /** * Get the name of the column (this is the name in the database). */ public String getName() { return name; } /** * Get the enclosing AggStar.Table. */ public AggStar.Table getTable() { return AggStar.Table.this; } /** * Get the bit possition associted with this column. This has the * same value as this column's RolapStar.Column. */ public int getBitPosition() { return bitPosition; } /** * Returns the datatype of this column. */ public Dialect.Datatype getDatatype() { return datatype; } public SqlQuery getSqlQuery() { return getTable().getAggStar().getSqlQuery(); } public MondrianDef.Expression getExpression() { return expression; } /** * Generates a SQL expression, which typically this looks like * this: tableName.columnName. */ public String generateExprString(final SqlQuery query) { return getExpression().getExpression(query); } public String toString() { StringWriter sw = new StringWriter(256); PrintWriter pw = new PrintWriter(sw); print(pw, ""); pw.flush(); return sw.toString(); } public void print(final PrintWriter pw, final String prefix) { SqlQuery sqlQuery = getSqlQuery(); pw.print(prefix); pw.print(getName()); pw.print(" ("); pw.print(getBitPosition()); pw.print("): "); pw.print(generateExprString(sqlQuery)); } public SqlStatement.Type getInternalType() { return null; } } /** * This class is used for holding foreign key columns. * Both DimTables and FactTables can have Level columns. */ final class ForeignKey extends Column { private ForeignKey( final String name, final MondrianDef.Expression expression, final Dialect.Datatype datatype, final int bitPosition) { super(name, expression, datatype, bitPosition); AggStar.this.levelBitKey.set(bitPosition); } } /** * This class is used for holding dimension level information. * Both DimTables and FactTables can have Level columns. */ final class Level extends Column { private final RolapStar.Column starColumn; private final boolean collapsed; private Level( final String name, final MondrianDef.Expression expression, final int bitPosition, RolapStar.Column starColumn, boolean collapsed) { super(name, expression, starColumn.getDatatype(), bitPosition); this.starColumn = starColumn; this.collapsed = collapsed; AggStar.this.levelBitKey.set(bitPosition); } @Override public SqlStatement.Type getInternalType() { return starColumn.getInternalType(); } public boolean isCollapsed() { return collapsed; } } /** The name of the table in the database. */ private final String name; private final MondrianDef.Relation relation; protected final List levels = new ArrayList(); protected List children; Table(final String name, final MondrianDef.Relation relation) { this.name = name; this.relation = relation; this.children = Collections.emptyList(); } /** * Return the name of the table in the database. */ public String getName() { return name; } /** * Return true if this table has a parent table (FactTable instances * do not have parent tables, all other do). */ public abstract boolean hasParent(); /** * Get the parent table (returns null if this table is a FactTable). */ public abstract Table getParent(); /** * Return true if this table has a join condition (only DimTables have * join conditions, FactTable instances do not). */ public abstract boolean hasJoinCondition(); public abstract Table.JoinCondition getJoinCondition(); public MondrianDef.Relation getRelation() { return relation; } /** * Get this table's enclosing AggStar. */ protected AggStar getAggStar() { return AggStar.this; } /** * Get a SqlQuery object. */ protected SqlQuery getSqlQuery() { return getAggStar().getSqlQuery(); } /** * Add a Level column. * * @param level */ protected void addLevel(final AggStar.Table.Level level) { this.levels.add(level); } /** * Returns all level columns. */ public List getLevels() { return levels; } /** * Return true if table has levels. */ public boolean hasLevels() { return ! levels.isEmpty(); } /** * Add a child DimTable table. * * @param child */ protected void addTable(final DimTable child) { if (children == Collections.EMPTY_LIST) { children = new ArrayList(); } children.add(child); } /** * Returns a list of child {@link Table} objects. */ public List getChildTables() { return children; } /** * Find descendant of fact table with given name or return null. * * @param name the child table name (alias). * @return the child table or null. */ public Table findDescendant(String name) { if (getName().equals(name)) { return this; } for (Table child : getChildTables()) { Table found = child.findDescendant(name); if (found != null) { return found; } } return null; } /** * Return true if this table has one or more child tables. */ public boolean hasChildren() { return ! children.isEmpty(); } /** * Converts a {@link mondrian.rolap.RolapStar.Table} into a * {@link AggStar.DimTable} as well as converting all columns and * child tables. If the rightJoinConditionColumnName parameter * is null, then the table's namd and the rTable parameter's * condition left condition's column name are used to form the * join condition's left expression. */ protected AggStar.DimTable convertTable( final RolapStar.Table rTable, final String rightJoinConditionColumnName) { String tableName = rTable.getAlias(); MondrianDef.Relation relation = rTable.getRelation(); RolapStar.Condition rjoinCondition = rTable.getJoinCondition(); MondrianDef.Expression rleft = rjoinCondition.getLeft(); MondrianDef.Expression rright = rjoinCondition.getRight(); MondrianDef.Column left = null; if (rightJoinConditionColumnName != null) { left = new MondrianDef.Column( getName(), rightJoinConditionColumnName); } else { if (rleft instanceof MondrianDef.Column) { MondrianDef.Column lcolumn = (MondrianDef.Column) rleft; left = new MondrianDef.Column(getName(), lcolumn.name); } else { throw Util.newInternal("not implemented: rleft=" + rleft); /* // RME TODO can we catch this during validation String msg = mres.BadRolapStarLeftJoinCondition.str( "AggStar.Table", rleft.getClass().getName(), left.toString()); getLogger().warn(msg); */ } } // Explicitly set which columns are foreign keys in the // AggStar. This lets us later determine if a measure is // based upon a foreign key (see AggregationManager findAgg // method). mondrian.rolap.RolapStar.Column col = getAggStar().getStar().getFactTable().lookupColumn(left.name); if (col != null) { getAggStar().setForeignKey(col.getBitPosition()); } JoinCondition joinCondition = new JoinCondition(left, rright); DimTable dimTable = new DimTable(this, tableName, relation, joinCondition); dimTable.convertColumns(rTable); dimTable.convertChildren(rTable); return dimTable; } /** * Convert a RolapStar.Table table's columns into * AggStar.Table.Level columns. * * @param rTable */ protected void convertColumns(final RolapStar.Table rTable) { // add level columns for (RolapStar.Column column : rTable.getColumns()) { String name = column.getName(); MondrianDef.Expression expression = column.getExpression(); int bitPosition = column.getBitPosition(); Level level = new Level( name, expression, bitPosition, column, false); addLevel(level); } } /** * Convert the child tables of a RolapStar.Table into * child AggStar.DimTable tables. * * @param rTable */ protected void convertChildren(final RolapStar.Table rTable) { // add children tables for (RolapStar.Table rTableChild : rTable.getChildren()) { DimTable dimChild = convertTable(rTableChild, null); addTable(dimChild); } } /** * This is a copy of the code found in RolapStar used to generate an SQL * query. * * @param query * @param failIfExists * @param joinToParent */ public void addToFrom( final SqlQuery query, final boolean failIfExists, final boolean joinToParent) { query.addFrom(relation, name, failIfExists); if (joinToParent) { if (hasParent()) { getParent().addToFrom(query, failIfExists, joinToParent); } if (hasJoinCondition()) { query.addWhere(getJoinCondition().toString(query)); } } } public String toString() { StringWriter sw = new StringWriter(256); PrintWriter pw = new PrintWriter(sw); print(pw, ""); pw.flush(); return sw.toString(); } public abstract void print(final PrintWriter pw, final String prefix); } /** * This is an aggregate fact table. */ public class FactTable extends Table { /** * This is a Column that is a Measure (contains an aggregator). */ public class Measure extends Table.Column { private final RolapAggregator aggregator; /** * The fact table column which is being aggregated. */ private final MondrianDef.Expression argument; /** * For distinct-count measures, contains a bitKey of levels which * it is OK to roll up. For regular measures, this is empty, since * all levels can be rolled up. */ private final BitKey rollableLevelBitKey; private Measure( final String name, final MondrianDef.Expression expression, final Dialect.Datatype datatype, final int bitPosition, final RolapAggregator aggregator, final MondrianDef.Expression argument) { super(name, expression, datatype, bitPosition); this.aggregator = aggregator; this.argument = argument; assert (argument != null) == aggregator.isDistinct(); this.rollableLevelBitKey = BitKey.Factory.makeBitKey(star.getColumnCount()); AggStar.this.measureBitKey.set(bitPosition); } public boolean isDistinct() { return aggregator.isDistinct(); } /** * Get this Measure's RolapAggregator. */ public RolapAggregator getAggregator() { return aggregator; } /** * Returns a BitKey of the levels which can be * safely rolled up. (For distinct-count measures, most can't.) */ public BitKey getRollableLevelBitKey() { return rollableLevelBitKey; } /** * Generates an expression to rollup this measure from a previous * result. For example, a "COUNT" and "DISTINCT COUNT" measures * rollup using "SUM". */ /* public String generateRollupString(SqlQuery query) { String expr = generateExprString(query); final Aggregator rollup = getAggregator().getRollup(); return ((RolapAggregator) rollup).getExpression(expr); } */ /* public String generateRollupString(SqlQuery query) { String expr = generateExprString(query); // final Aggregator rollup = getAggregator().getRollup(); Aggregator rollup = (getAggregator().isDistinct()) ? getAggregator().getNonDistinctAggregator() : getAggregator().getRollup(); String s = ((RolapAggregator) rollup).getExpression(expr); return s; } */ public String generateRollupString(SqlQuery query) { String expr = generateExprString(query); Aggregator rollup = null; BitKey fkbk = AggStar.this.getForeignKeyBitKey(); // When rolling up and the aggregator is distinct and // the measure is based upon a foreign key, then // one must use "count" rather than "sum" if (fkbk.get(getBitPosition())) { rollup = (getAggregator().isDistinct()) ? getAggregator().getNonDistinctAggregator() : getAggregator().getRollup(); } else { rollup = getAggregator().getRollup(); } String s = ((RolapAggregator) rollup).getExpression(expr); return s; } public void print(final PrintWriter pw, final String prefix) { SqlQuery sqlQuery = getSqlQuery(); pw.print(prefix); pw.print(getName()); pw.print(" ("); pw.print(getBitPosition()); pw.print("): "); pw.print(generateRollupString(sqlQuery)); } } private Column factCountColumn; private final List measures; private final int totalColumnSize; private int numberOfRows; FactTable(final JdbcSchema.Table aggTable) { this( aggTable.getName(), aggTable.table, aggTable.getTotalColumnSize(), aggTable.getNumberOfRows()); } FactTable( final String name, final MondrianDef.Relation relation, final int totalColumnSize, final int numberOfRows) { super(name, relation); this.totalColumnSize = totalColumnSize; this.measures = new ArrayList(); this.numberOfRows = numberOfRows; } public Table getParent() { return null; } public boolean hasParent() { return false; } public boolean hasJoinCondition() { return false; } public Table.JoinCondition getJoinCondition() { return null; } /** * Get the volume of the table (now of rows * size of a row). */ public int getVolume() { return getTotalColumnSize() * getNumberOfRows(); } /** * Get the total size of all columns in a row. */ public int getTotalColumnSize() { return totalColumnSize; } /** * Get the number of rows in this aggregate table. */ public int getNumberOfRows() { if (numberOfRows < 0) { makeNumberOfRows(); } return numberOfRows; } /** * This is for testing ONLY. * * @param numberOfRows */ void setNumberOfRows(int numberOfRows) { this.numberOfRows = numberOfRows; } /** * Returns a list of all measures. */ public List getMeasures() { return measures; } /** * Return true it table has measures */ public boolean hasMeasures() { return ! measures.isEmpty(); } /** * Returns a list of the columns in this table. */ public List getColumns() { List list = new ArrayList(); list.addAll(measures); list.addAll(levels); for (DimTable dimTable : getChildTables()) { dimTable.addColumnsToList(list); } return list; } /** * For a foreign key usage create a child DimTable table. * * @param usage */ private void loadForeignKey(final JdbcSchema.Table.Column.Usage usage) { if (usage.rTable != null) { DimTable child = convertTable( usage.rTable, usage.rightJoinConditionColumnName); addTable(child); } else { // it a column thats not a measure or foreign key - it must be // a non-shared dimension // See: AggTableManager.java JdbcSchema.Table.Column column = usage.getColumn(); String name = column.getName(); String symbolicName = usage.getSymbolicName(); if (symbolicName == null) { symbolicName = name; } MondrianDef.Expression expression = new MondrianDef.Column(getName(), name); Dialect.Datatype datatype = column.getDatatype(); RolapStar.Column rColumn = usage.rColumn; if (rColumn == null) { getLogger().warn( "loadForeignKey: for column " + name + ", rColumn == null"); } else { int bitPosition = rColumn.getBitPosition(); ForeignKey c = new ForeignKey( symbolicName, expression, datatype, bitPosition); getAggStar().setForeignKey(c.getBitPosition()); } } } /** * Given a usage of type measure, create a Measure column. * * @param usage */ private void loadMeasure(final JdbcSchema.Table.Column.Usage usage) { final JdbcSchema.Table.Column column = usage.getColumn(); String name = column.getName(); String symbolicName = usage.getSymbolicName(); if (symbolicName == null) { symbolicName = name; } Dialect.Datatype datatype = column.getDatatype(); RolapAggregator aggregator = usage.getAggregator(); MondrianDef.Expression expression; if (column.hasUsage(JdbcSchema.UsageType.FOREIGN_KEY) && !aggregator.isDistinct()) { expression = factCountColumn.getExpression(); } else { expression = new MondrianDef.Column(getName(), name); } MondrianDef.Expression argument; if (aggregator.isDistinct()) { argument = usage.rMeasure.getExpression(); } else { argument = null; } int bitPosition = usage.rMeasure.getBitPosition(); Measure aggMeasure = new Measure( symbolicName, expression, datatype, bitPosition, aggregator, argument); measures.add(aggMeasure); if (aggMeasure.aggregator.isDistinct()) { distinctMeasureBitKey.set(bitPosition); } } /** * Create a fact_count column for a usage of type fact count. * * @param usage */ private void loadFactCount(final JdbcSchema.Table.Column.Usage usage) { String name = usage.getColumn().getName(); String symbolicName = usage.getSymbolicName(); if (symbolicName == null) { symbolicName = name; } MondrianDef.Expression expression = new MondrianDef.Column(getName(), name); Dialect.Datatype datatype = usage.getColumn().getDatatype(); int bitPosition = -1; Column aggColumn = new Column( symbolicName, expression, datatype, bitPosition); factCountColumn = aggColumn; } /** * Given a usage of type level, create a Level column. * * @param usage */ private void loadLevel(final JdbcSchema.Table.Column.Usage usage) { String name = usage.getSymbolicName(); MondrianDef.Expression expression = new MondrianDef.Column(getName(), usage.levelColumnName); int bitPosition = usage.rColumn.getBitPosition(); Level level = new Level( name, expression, bitPosition, usage.rColumn, usage.collapsed); addLevel(level); /* * If we are dealing with a non-collapsed level, we have to * modify the bit key of the AggStar and create a column * object for each parent level so that the AggQuerySpec * can correctly link up to the other tables. */ if (!usage.collapsed) { // We must also update the bit key with // the parent levels of any non-collapsed level. RolapLevel parentLevel = (RolapLevel)usage.level.getParentLevel(); while (!parentLevel.isAll()) { /* * Find the bit for this AggStar's bit key for each parent * level. There is no need to modify the AggStar's bit key * directly here, because the constructor of Column * will do that for us later on. */ final BitKey bk = AggStar.this.star.getBitKey( new String[] { parentLevel.getKeyExp().getTableAlias()}, new String[] { ((MondrianDef.Column)parentLevel.getKeyExp()) .getColumnName()}); final int bitPos = bk.nextSetBit(0); if (bitPos == -1) { throw new MondrianException( "Failed to match non-collapsed aggregate level with a column from the RolapStar."); } /* * Now we will create the Column object to return to the * AggQuerySpec. We will use the convertTable() method * because it is convenient and it is capable to convert * our base table into a series of parent-child tables * with their join paths figured out. */ DimTable columnTable = convertTable( AggStar.this.star.getColumn(bitPosition) .getTable(), null); /* * Make sure to return the last child table, since * AggQuerySpec will take care of going up the * parent-child hierarchy and do all the work for us. */ while (columnTable.getChildTables().size() > 0) { columnTable = columnTable.getChildTables().get(0); } final DimTable finalColumnTable = columnTable; levelColumnsToJoin.put( bitPos, new Column( ((MondrianDef.Column)parentLevel.getKeyExp()) .getColumnName(), parentLevel.getKeyExp(), AggStar.this.star.getColumn(bitPos).getDatatype(), bitPos) { public Table getTable() { return finalColumnTable; } }); // Do the next parent level. parentLevel = (RolapLevel) parentLevel.getParentLevel(); } } } public void print(final PrintWriter pw, final String prefix) { pw.print(prefix); pw.println("Table:"); String subprefix = prefix + " "; String subsubprefix = subprefix + " "; pw.print(subprefix); pw.print("name="); pw.println(getName()); if (getRelation() != null) { pw.print(subprefix); pw.print("relation="); pw.println(getRelation()); } pw.print(subprefix); pw.print("numberofrows="); pw.println(getNumberOfRows()); pw.print(subprefix); pw.println("FactCount:"); factCountColumn.print(pw, subsubprefix); pw.println(); pw.print(subprefix); pw.println("Measures:"); for (Measure column : getMeasures()) { column.print(pw, subsubprefix); pw.println(); } pw.print(subprefix); pw.println("Levels:"); for (Level level : getLevels()) { level.print(pw, subsubprefix); pw.println(); } for (DimTable child : getChildTables()) { child.print(pw, subprefix); } } private void makeNumberOfRows() { if (approxRowCount >= 0) { numberOfRows = approxRowCount; return; } SqlQuery query = getSqlQuery(); query.addSelect("count(*)", null); query.addFrom(getRelation(), getName(), false); DataSource dataSource = getAggStar().getStar().getDataSource(); SqlStatement stmt = RolapUtil.executeQuery( dataSource, query.toString(), new Locus( new Execution( star.getSchema().getInternalConnection() .getInternalStatement(), 0), "AggStar.FactTable.makeNumberOfRows", "Counting rows in aggregate table")); try { ResultSet resultSet = stmt.getResultSet(); if (resultSet.next()) { ++stmt.rowCount; numberOfRows = resultSet.getInt(1); } else { getLogger().warn( mres.SqlQueryFailed.str( "AggStar.FactTable.makeNumberOfRows", query.toString())); // set to large number so that this table is never used numberOfRows = Integer.MAX_VALUE / getTotalColumnSize(); } } catch (SQLException e) { throw stmt.handle(e); } finally { stmt.close(); } } } /** * This class represents a dimension table. */ public class DimTable extends Table { private final Table parent; private final JoinCondition joinCondition; DimTable( final Table parent, final String name, final MondrianDef.Relation relation, final JoinCondition joinCondition) { super(name, relation); this.parent = parent; this.joinCondition = joinCondition; } public Table getParent() { return parent; } public boolean hasParent() { return true; } public boolean hasJoinCondition() { return true; } public Table.JoinCondition getJoinCondition() { return joinCondition; } /** * Add all of this Table's columns to the list parameter and then add * all child table columns. * * @param list */ public void addColumnsToList(final List list) { list.addAll(levels); for (DimTable dimTable : getChildTables()) { dimTable.addColumnsToList(list); } } public void print(final PrintWriter pw, final String prefix) { pw.print(prefix); pw.println("Table:"); String subprefix = prefix + " "; String subsubprefix = subprefix + " "; pw.print(subprefix); pw.print("name="); pw.println(getName()); if (getRelation() != null) { pw.print(subprefix); pw.print("relation="); pw.println(getRelation()); } pw.print(subprefix); pw.println("Levels:"); for (Level level : getLevels()) { level.print(pw, subsubprefix); pw.println(); } joinCondition.print(pw, subprefix); for (DimTable child : getChildTables()) { child.print(pw, subprefix); } } } public String toString() { StringWriter sw = new StringWriter(256); PrintWriter pw = new PrintWriter(sw); print(pw, ""); pw.flush(); return sw.toString(); } /** * Print this AggStar. * * @param pw * @param prefix */ public void print(final PrintWriter pw, final String prefix) { pw.print(prefix); pw.println("AggStar:" + getFactTable().getName()); String subprefix = prefix + " "; pw.print(subprefix); pw.print(" bk="); pw.println(bitKey); pw.print(subprefix); pw.print("fbk="); pw.println(levelBitKey); pw.print(subprefix); pw.print("mbk="); pw.println(measureBitKey); pw.print(subprefix); pw.print("has foreign key="); pw.println(aggTable.hasChildren()); for (AggStar.Table.Column column : getFactTable().getColumns()) { pw.print(" "); pw.println(column); } aggTable.print(pw, subprefix); } } // End AggStar.java mondrian-3.4.1/src/main/mondrian/rolap/aggmatcher/ExplicitRecognizer.java0000644000175000017500000004022311735330606026444 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.olap.*; import mondrian.recorder.MessageRecorder; import mondrian.rolap.*; import mondrian.rolap.aggmatcher.JdbcSchema.Table.Column; import mondrian.util.Pair; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; /** * This is the Recognizer for the aggregate table descriptions that appear in * the catalog schema files; the user explicitly defines the aggregate. * * @author Richard M. Emberson */ class ExplicitRecognizer extends Recognizer { private ExplicitRules.TableDef tableDef; private RolapCube cube; ExplicitRecognizer( final ExplicitRules.TableDef tableDef, final RolapStar star, RolapCube cube, final JdbcSchema.Table dbFactTable, final JdbcSchema.Table aggTable, final MessageRecorder msgRecorder) { super(star, dbFactTable, aggTable, msgRecorder); this.tableDef = tableDef; this.cube = cube; } /** * Get the ExplicitRules.TableDef associated with this instance. */ protected ExplicitRules.TableDef getTableDef() { return tableDef; } /** * Get the Matcher to be used to match columns to be ignored. */ protected Recognizer.Matcher getIgnoreMatcher() { return getTableDef().getIgnoreMatcher(); } /** * Get the Matcher to be used to match the column which is the fact count * column. */ protected Recognizer.Matcher getFactCountMatcher() { return getTableDef().getFactCountMatcher(); } /** * Make the measures for this aggregate table. *

* First, iterate through all of the columns in the table. * For each column, iterate through all of the tableDef measures, the * explicit definitions of a measure. * If the table's column name matches the column name in the measure * definition, then make a measure. * Next, look through all of the fact table column usage measures. * For each such measure usage that has a sibling foreign key usage * see if the tableDef has a foreign key defined with the same name. * If so, then, for free, we can make a measure for the aggregate using * its foreign key. *

* * @return number of measures created. */ protected int checkMeasures() { msgRecorder.pushContextName("ExplicitRecognizer.checkMeasures"); try { int measureColumnCounts = 0; // Look at each aggregate table column. For each measure defined, // see if the measure's column name equals the column's name. // If so, make the aggregate measure usage for that column. for (JdbcSchema.Table.Column aggColumn : aggTable.getColumns()) { // if marked as ignore, then do not consider if (aggColumn.hasUsage(JdbcSchema.UsageType.IGNORE)) { continue; } String aggColumnName = aggColumn.getName(); for (ExplicitRules.TableDef.Measure measure : getTableDef().getMeasures()) { // Column name match is case insensitive if (measure.getColumnName().equalsIgnoreCase(aggColumnName)) { String name = measure.getName(); List parts = Util.parseIdentifier(name); String nameLast = parts.get(parts.size() - 1).name; RolapStar.Measure m = star.getFactTable().lookupMeasureByName( cube.getName(), nameLast); RolapAggregator agg = null; if (m != null) { agg = m.getAggregator(); } // Ok, got a match, so now make a measure makeMeasure(measure, agg, aggColumn); measureColumnCounts++; } } } // Ok, now look at all of the fact table columns with measure usage // that have a sibling foreign key usage. These can be automagically // generated for the aggregate table as long as it still has the // foreign key. for (Iterator it = dbFactTable.getColumnUsages(JdbcSchema.UsageType.MEASURE); it.hasNext();) { JdbcSchema.Table.Column.Usage factUsage = it.next(); JdbcSchema.Table.Column factColumn = factUsage.getColumn(); if (factColumn.hasUsage(JdbcSchema.UsageType.FOREIGN_KEY)) { // What we've got here is a measure based upon a foreign key String aggFK = getTableDef().getAggregateFK(factColumn.getName()); // OK, not a lost dimension if (aggFK != null) { JdbcSchema.Table.Column aggColumn = aggTable.getColumn(aggFK); // Column name match is case insensitive if (aggColumn == null) { aggColumn = aggTable.getColumn(aggFK.toLowerCase()); } if (aggColumn == null) { aggColumn = aggTable.getColumn(aggFK.toUpperCase()); } if (aggColumn != null) { makeMeasure(factUsage, aggColumn); measureColumnCounts++; } } } } return measureColumnCounts; } finally { msgRecorder.popContextName(); } } /** * Make a measure. This makes a measure usage using the Aggregator found in * the RolapStar.Measure associated with the ExplicitRules.TableDef.Measure. * * @param measure * @param aggColumn */ protected void makeMeasure( final ExplicitRules.TableDef.Measure measure, RolapAggregator factAgg, final JdbcSchema.Table.Column aggColumn) { RolapStar.Measure rm = measure.getRolapStarMeasure(); JdbcSchema.Table.Column.Usage aggUsage = aggColumn.newUsage(JdbcSchema.UsageType.MEASURE); aggUsage.setSymbolicName(measure.getSymbolicName()); RolapAggregator ra = (factAgg == null) ? convertAggregator(aggUsage, rm.getAggregator()) : convertAggregator(aggUsage, factAgg, rm.getAggregator()); aggUsage.setAggregator(ra); aggUsage.rMeasure = rm; } /** * Creates a foreign key usage. * *

First the column name of the fact usage which is a foreign key is * used to search for a foreign key definition in the * ExplicitRules.tableDef. If not found, thats ok, it is just a lost * dimension. If found, look for a column in the aggregate table with that * name and make a foreign key usage. */ protected int matchForeignKey( final JdbcSchema.Table.Column.Usage factUsage) { JdbcSchema.Table.Column factColumn = factUsage.getColumn(); String aggFK = getTableDef().getAggregateFK(factColumn.getName()); // OK, a lost dimension if (aggFK == null) { return 0; } int matchCount = 0; for (JdbcSchema.Table.Column aggColumn : aggTable.getColumns()) { // if marked as ignore, then do not consider if (aggColumn.hasUsage(JdbcSchema.UsageType.IGNORE)) { continue; } if (aggFK.equals(aggColumn.getName())) { makeForeignKey(factUsage, aggColumn, aggFK); matchCount++; } } return matchCount; } /** * Creates a level usage. A level usage is a column that is used in a * collapsed dimension aggregate table. * *

First, iterate through the ExplicitRules.TableDef's level * definitions for one with a name equal to the RolapLevel unique name, * i.e., [Time].[Quarter]. Now, using the level's column name, search * through the aggregate table's columns for one with that name and make a * level usage for the column. */ protected void matchLevels( final Hierarchy hierarchy, final HierarchyUsage hierarchyUsage) { msgRecorder.pushContextName("ExplicitRecognizer.matchLevel"); try { // Try to match a Level's name against the RolapLevel // unique name. List> levelMatches = new ArrayList>(); List aggLevels = new ArrayList(); level_loop: for (Level hLevel : hierarchy.getLevels()) { if (hLevel.isAll()) { continue; } final RolapLevel rLevel = (RolapLevel) hLevel; String levelUniqueName = rLevel.getUniqueName(); for (ExplicitRules.TableDef.Level level : getTableDef().getLevels()) { if (level.getName().equals(levelUniqueName)) { // Now can we find a column in the aggTable //that matches the Level's column final String columnName = level.getColumnName(); for (JdbcSchema.Table.Column aggColumn : aggTable.getColumns()) { if (aggColumn.getName().equals(columnName)) { levelMatches.add( new Pair( rLevel, aggColumn)); aggLevels.add(level); continue level_loop; } } } } } if (levelMatches.size() == 0) { return; } // Sort the matches by level depth. Collections.sort( levelMatches, new Comparator>() { public int compare( Pair o1, Pair o2) { return Integer.valueOf( o1.left.getDepth()).compareTo( Integer.valueOf(o2.left.getDepth())); } }); Collections.sort( aggLevels, new Comparator() { public int compare( mondrian.rolap.aggmatcher .ExplicitRules.TableDef.Level o1, mondrian.rolap.aggmatcher .ExplicitRules.TableDef.Level o2) { return Integer.valueOf(o1.getRolapLevel().getDepth()) .compareTo( Integer.valueOf( o2.getRolapLevel().getDepth())); } }); // Validate by iterating. boolean forceCollapse = false; for (Pair pair : levelMatches) { // Fail if the level is not the first match // but the one before is not its parent. if (levelMatches.indexOf(pair) > 0 && pair.left.getDepth() - 1 != levelMatches.get( levelMatches.indexOf(pair) - 1).left.getDepth()) { msgRecorder.reportError( "The aggregate table " + aggTable.getName() + " contains the column " + pair.right.getName() + " which maps to the level " + pair.left.getUniqueName() + " but its parent level is not part of that aggregation."); } /* * Warn if this level is marked as non-collapsed but the level * above it is present in this agg table. */ if (levelMatches.indexOf(pair) > 0 && !aggLevels.get(levelMatches.indexOf(pair)).isCollapsed()) { forceCollapse = true; msgRecorder.reportWarning( "The aggregate table " + aggTable.getName() + " contains the column " + pair.right.getName() + " which maps to the level " + pair.left.getUniqueName() + " and is marked as non-collapsed, but its parent column is already present."); } /* * Fail if the level is the first, it isn't at the top, * but it is marked as collapsed. */ if (levelMatches.indexOf(pair) == 0 && pair.left.getDepth() > 1 && aggLevels.get(levelMatches.indexOf(pair)).isCollapsed()) { msgRecorder.reportError( "The aggregate table " + aggTable.getName() + " contains the column " + pair.right.getName() + " which maps to the level " + pair.left.getUniqueName() + " but its parent level is not part of that aggregation and this level is marked as collapsed."); } // Fail if the level is non-collapsed but its members // are not unique. if (!aggLevels.get( levelMatches.indexOf(pair)).isCollapsed() && !pair.left.isUnique()) { msgRecorder.reportError( "The aggregate table " + aggTable.getName() + " contains the column " + pair.right.getName() + " which maps to the level " + pair.left.getUniqueName() + " but that level doesn't have unique members and this level is marked as non collapsed."); } } if (msgRecorder.hasErrors()) { return; } // All checks out. Let's create the levels. for (Pair pair : levelMatches) { makeLevel( pair.right, hierarchy, hierarchyUsage, getColumnName(pair.left.getKeyExp()), aggLevels.get(levelMatches.indexOf(pair)).getColumnName(), pair.left.getName(), forceCollapse ? true : aggLevels.get(levelMatches.indexOf(pair)) .isCollapsed(), pair.left); } } finally { msgRecorder.popContextName(); } } } // End ExplicitRecognizer.java mondrian-3.4.1/src/main/mondrian/rolap/aggmatcher/Recognizer.java0000644000175000017500000010366311735330606024752 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.olap.*; import mondrian.recorder.MessageRecorder; import mondrian.resource.MondrianResource; import mondrian.rolap.*; import mondrian.rolap.sql.SqlQuery; import org.apache.log4j.Logger; import java.util.*; /** * Abstract Recognizer class used to determine if a candidate aggregate table * has the column categories: "fact_count" column, measure columns, foreign key * and level columns. * *

Derived classes use either the default or explicit column descriptions in * matching column categories. The basic matching algorithm is in this class * while some specific column category matching and column building must be * specified in derived classes. * *

A Recognizer is created per candidate aggregate table. The tables columns * are then categorized. All errors and warnings are added to a MessageRecorder. * *

This class is less about defining a type and more about code sharing. * * @author Richard M. Emberson */ abstract class Recognizer { private static final MondrianResource mres = MondrianResource.instance(); private static final Logger LOGGER = Logger.getLogger(Recognizer.class); /** * This is used to wrap column name matching rules. */ public interface Matcher { /** * Return true it the name matches and false otherwise. */ boolean matches(String name); } protected final RolapStar star; protected final JdbcSchema.Table dbFactTable; protected final JdbcSchema.Table aggTable; protected final MessageRecorder msgRecorder; protected boolean returnValue; protected Recognizer( final RolapStar star, final JdbcSchema.Table dbFactTable, final JdbcSchema.Table aggTable, final MessageRecorder msgRecorder) { this.star = star; this.dbFactTable = dbFactTable; this.aggTable = aggTable; this.msgRecorder = msgRecorder; returnValue = true; } /** * Return true if the candidate aggregate table was successfully mapped into * the fact table. This is the top-level checking method. *

* It first checks the ignore columns. *

* Next, the existence of a fact count column is checked. *

* Then the measures are checked. First the specified (defined, * explicit) measures are all determined. There must be at least one such * measure. This if followed by checking for implied measures (e.g., if base * fact table as both sum and average of a column and the aggregate has a * sum measure, the there is an implied average measure in the aggregate). *

* Now the levels are checked. This is in two parts. First, foreign keys are * checked followed by level columns (for collapsed dimension aggregates). *

* If eveything checks out, returns true. */ public boolean check() { checkIgnores(); checkFactCount(); // Check measures int nosMeasures = checkMeasures(); // There must be at least one measure checkNosMeasures(nosMeasures); generateImpliedMeasures(); // Check levels List notSeenForeignKeys = checkForeignKeys(); //printNotSeenForeignKeys(notSeenForeignKeys); checkLevels(notSeenForeignKeys); if (returnValue) { // Add all unused columns as warning to the MessageRecorder checkUnusedColumns(); } return returnValue; } /** * Return the ignore column Matcher. */ protected abstract Matcher getIgnoreMatcher(); /** * Check all columns to be marked as ignore. */ protected void checkIgnores() { Matcher ignoreMatcher = getIgnoreMatcher(); for (JdbcSchema.Table.Column aggColumn : aggTable.getColumns()) { if (ignoreMatcher.matches(aggColumn.getName())) { makeIgnore(aggColumn); } } } /** * Create an ignore usage for the aggColumn. * * @param aggColumn */ protected void makeIgnore(final JdbcSchema.Table.Column aggColumn) { JdbcSchema.Table.Column.Usage usage = aggColumn.newUsage(JdbcSchema.UsageType.IGNORE); usage.setSymbolicName("Ignore"); } /** * Return the fact count column Matcher. */ protected abstract Matcher getFactCountMatcher(); /** * Make sure that the aggregate table has one fact count column and that its * type is numeric. */ protected void checkFactCount() { msgRecorder.pushContextName("Recognizer.checkFactCount"); try { Matcher factCountMatcher = getFactCountMatcher(); int nosOfFactCounts = 0; for (JdbcSchema.Table.Column aggColumn : aggTable.getColumns()) { // if marked as ignore, then do not consider if (aggColumn.hasUsage(JdbcSchema.UsageType.IGNORE)) { continue; } if (factCountMatcher.matches(aggColumn.getName())) { if (aggColumn.getDatatype().isNumeric()) { makeFactCount(aggColumn); nosOfFactCounts++; } else { String msg = mres.NonNumericFactCountColumn.str( aggTable.getName(), dbFactTable.getName(), aggColumn.getName(), aggColumn.getTypeName()); msgRecorder.reportError(msg); returnValue = false; } } } if (nosOfFactCounts == 0) { String msg = mres.NoFactCountColumns.str( aggTable.getName(), dbFactTable.getName()); msgRecorder.reportError(msg); returnValue = false; } else if (nosOfFactCounts > 1) { String msg = mres.TooManyFactCountColumns.str( aggTable.getName(), dbFactTable.getName(), nosOfFactCounts); msgRecorder.reportError(msg); returnValue = false; } } finally { msgRecorder.popContextName(); } } /** * Check all measure columns returning the number of measure columns. */ protected abstract int checkMeasures(); /** * Create a fact count usage for the aggColumn. * * @param aggColumn */ protected void makeFactCount(final JdbcSchema.Table.Column aggColumn) { JdbcSchema.Table.Column.Usage usage = aggColumn.newUsage(JdbcSchema.UsageType.FACT_COUNT); usage.setSymbolicName("Fact Count"); } /** * Make sure there was at least one measure column identified. * * @param nosMeasures */ protected void checkNosMeasures(int nosMeasures) { msgRecorder.pushContextName("Recognizer.checkNosMeasures"); try { if (nosMeasures == 0) { String msg = mres.NoMeasureColumns.str( aggTable.getName(), dbFactTable.getName()); msgRecorder.reportError(msg); returnValue = false; } } finally { msgRecorder.popContextName(); } } /** * An implied measure in an aggregate table is one where there is both a sum * and average measures in the base fact table and the aggregate table has * either a sum or average, the other measure is implied and can be * generated from the measure and the fact_count column. *

* For each column in the fact table, get its measure usages. If there is * both an average and sum aggregator associated with the column, then * iterator over all of the column usage of type measure of the aggregator * table. If only one aggregate column usage measure is found and this * RolapStar.Measure measure instance variable is the same as the * the fact table's usage's instance variable, then the other measure is * implied and the measure is created for the aggregate table. */ protected void generateImpliedMeasures() { for (JdbcSchema.Table.Column factColumn : aggTable.getColumns()) { JdbcSchema.Table.Column.Usage sumFactUsage = null; JdbcSchema.Table.Column.Usage avgFactUsage = null; for (Iterator mit = factColumn.getUsages(JdbcSchema.UsageType.MEASURE); mit.hasNext();) { JdbcSchema.Table.Column.Usage factUsage = mit.next(); if (factUsage.getAggregator() == RolapAggregator.Avg) { avgFactUsage = factUsage; } else if (factUsage.getAggregator() == RolapAggregator.Sum) { sumFactUsage = factUsage; } } if (avgFactUsage != null && sumFactUsage != null) { JdbcSchema.Table.Column.Usage sumAggUsage = null; JdbcSchema.Table.Column.Usage avgAggUsage = null; int seenCount = 0; for (Iterator mit = aggTable.getColumnUsages(JdbcSchema.UsageType.MEASURE); mit.hasNext();) { JdbcSchema.Table.Column.Usage aggUsage = mit.next(); if (aggUsage.rMeasure == avgFactUsage.rMeasure) { avgAggUsage = aggUsage; seenCount++; } else if (aggUsage.rMeasure == sumFactUsage.rMeasure) { sumAggUsage = aggUsage; seenCount++; } } if (seenCount == 1) { if (avgAggUsage != null) { makeMeasure(sumFactUsage, avgAggUsage); } if (sumAggUsage != null) { makeMeasure(avgFactUsage, sumAggUsage); } } } } } /** * Here we have the fact usage of either sum or avg and an aggregate usage * of the opposite type. We wish to make a new aggregate usage based * on the existing usage's column of the same type as the fact usage. * * @param factUsage fact usage * @param aggSiblingUsage existing sibling usage */ protected void makeMeasure( final JdbcSchema.Table.Column.Usage factUsage, final JdbcSchema.Table.Column.Usage aggSiblingUsage) { JdbcSchema.Table.Column aggColumn = aggSiblingUsage.getColumn(); JdbcSchema.Table.Column.Usage aggUsage = aggColumn.newUsage(JdbcSchema.UsageType.MEASURE); aggUsage.setSymbolicName(factUsage.getSymbolicName()); RolapAggregator ra = convertAggregator( aggUsage, factUsage.getAggregator(), aggSiblingUsage.getAggregator()); aggUsage.setAggregator(ra); aggUsage.rMeasure = factUsage.rMeasure; } /** * Creates an aggregate table column measure usage from a fact * table column measure usage. * * @param factUsage * @param aggColumn */ protected void makeMeasure( final JdbcSchema.Table.Column.Usage factUsage, final JdbcSchema.Table.Column aggColumn) { JdbcSchema.Table.Column.Usage aggUsage = aggColumn.newUsage(JdbcSchema.UsageType.MEASURE); aggUsage.setSymbolicName(factUsage.getSymbolicName()); RolapAggregator ra = convertAggregator(aggUsage, factUsage.getAggregator()); aggUsage.setAggregator(ra); aggUsage.rMeasure = factUsage.rMeasure; } /** * This method determine how may aggregate table column's match the fact * table foreign key column return in the number matched. For each matching * column a foreign key usage is created. */ protected abstract int matchForeignKey( JdbcSchema.Table.Column.Usage factUsage); /** * This method checks the foreign key columns. *

* For each foreign key column usage in the fact table, determine how many * aggregate table columns match that column usage. If there is more than * one match, then that is an error. If there were no matches, then the * foreign key usage is added to the list of fact column foreign key that * were not in the aggregate table. This list is returned by this method. *

* This matches foreign keys that were not "lost" or "collapsed". * * @return list on not seen foreign key column usages */ protected List checkForeignKeys() { msgRecorder.pushContextName("Recognizer.checkForeignKeys"); try { List notSeenForeignKeys = Collections.emptyList(); for (Iterator it = dbFactTable.getColumnUsages(JdbcSchema.UsageType.FOREIGN_KEY); it.hasNext();) { JdbcSchema.Table.Column.Usage factUsage = it.next(); int matchCount = matchForeignKey(factUsage); if (matchCount > 1) { String msg = mres.TooManyMatchingForeignKeyColumns.str( aggTable.getName(), dbFactTable.getName(), matchCount, factUsage.getColumn().getName()); msgRecorder.reportError(msg); returnValue = false; } else if (matchCount == 0) { if (notSeenForeignKeys.isEmpty()) { notSeenForeignKeys = new ArrayList(); } notSeenForeignKeys.add(factUsage); } } return notSeenForeignKeys; } finally { msgRecorder.popContextName(); } } /** * This method identifies those columns in the aggregate table that match * "collapsed" dimension columns. Remember that a collapsed dimension is one * where the higher levels of some hierarchy are columns in the aggregate * table (and all of the lower levels are missing - it has aggregated up to * the first existing level). *

* Here, we do not start from the fact table, we iterator over each cube. * For each of the cube's dimensions, the dimension's hirarchies are * iterated over. In turn, each hierarchy's usage is iterated over. * if the hierarchy's usage's foreign key is not in the list of not seen * foreign keys (the notSeenForeignKeys parameter), then that hierarchy is * not considered. If the hierarchy's usage's foreign key is in the not seen * list, then starting with the hierarchy's top level, it is determined if * the combination of hierarchy, hierarchy usage, and level matches an * aggregated table column. If so, then a level usage is created for that * column and the hierarchy's next level is considered and so on until a * for a level an aggregate table column does not match. Then we continue * iterating over the hierarchy usages. *

* This check is different. The others mine the fact table usages. This * looks through the fact table's cubes' dimension, hierarchy, * hiearchy usages, levels to match up symbolic names for levels. The other * checks match on "physical" characteristics, the column name; this matches * on "logical" characteristics. *

* Note: Levels should not be created for foreign keys that WERE seen. * Currently, this is NOT checked explicitly. For the explicit rules any * extra columns MUST ge declared ignored or one gets an error. * * @param notSeenForeignKeys */ protected void checkLevels( List notSeenForeignKeys) { // These are the factTable that do not appear in the aggTable. // 1) find all cubes with this given factTable // 1) per cube, find all usages with the column as foreign key // 2) for each usage, find dimension and its levels // 3) determine if level columns are represented // In generaly, there is only one cube. for (RolapCube cube : findCubes()) { Dimension[] dims = cube.getDimensions(); // start dimensions at 1 (0 is measures) for (int j = 1; j < dims.length; j++) { Dimension dim = dims[j]; // Ok, got dimension. // See if any of the levels exist as columns in the // aggTable. This requires applying a map from: // hierarchyName // levelName // levelColumnName // to each "unassigned" column in the aggTable. // Remember that the rule is if a level does appear, // then all of the higher levels must also appear. String dimName = dim.getName(); Hierarchy[] hierarchies = dim.getHierarchies(); for (Hierarchy hierarchy : hierarchies) { HierarchyUsage[] hierarchyUsages = cube.getUsages(hierarchy); for (HierarchyUsage hierarchyUsage : hierarchyUsages) { // Search through the notSeenForeignKeys list // making sure that this HierarchyUsage's // foreign key is not in the list. String foreignKey = hierarchyUsage.getForeignKey(); boolean b = foreignKey == null || inNotSeenForeignKeys( foreignKey, notSeenForeignKeys); if (!b) { // It was not in the not seen list, so ignore continue; } matchLevels(hierarchy, hierarchyUsage); } } } } } /** * Return true if the foreignKey column name is in the list of not seen * foreign keys. */ boolean inNotSeenForeignKeys( String foreignKey, List notSeenForeignKeys) { for (JdbcSchema.Table.Column.Usage usage : notSeenForeignKeys) { if (usage.getColumn().getName().equals(foreignKey)) { return true; } } return false; } /** * Debug method: Print out not seen foreign key list. * * @param notSeenForeignKeys */ private void printNotSeenForeignKeys(List notSeenForeignKeys) { LOGGER.debug( "Recognizer.printNotSeenForeignKeys: " + aggTable.getName()); for (Iterator it = notSeenForeignKeys.iterator(); it.hasNext();) { JdbcSchema.Table.Column.Usage usage = (JdbcSchema.Table.Column.Usage) it.next(); LOGGER.debug(" " + usage.getColumn().getName()); } } /** * Here a measure ussage is created and the right join condition is * explicitly supplied. This is needed is when the aggregate table's column * names may not match those found in the RolapStar. * * @param factUsage * @param aggColumn * @param rightJoinConditionColumnName */ protected void makeForeignKey( final JdbcSchema.Table.Column.Usage factUsage, final JdbcSchema.Table.Column aggColumn, final String rightJoinConditionColumnName) { JdbcSchema.Table.Column.Usage aggUsage = aggColumn.newUsage(JdbcSchema.UsageType.FOREIGN_KEY); aggUsage.setSymbolicName("FOREIGN_KEY"); // Extract from RolapStar enough stuff to build // AggStar subtable except the column name of the right join // condition might be different aggUsage.rTable = factUsage.rTable; aggUsage.rightJoinConditionColumnName = rightJoinConditionColumnName; aggUsage.rColumn = factUsage.rColumn; } /** * Match a aggregate table column given the hierarchy and hierarchy usage. */ protected abstract void matchLevels( final Hierarchy hierarchy, final HierarchyUsage hierarchyUsage); /** * Make a level column usage. * *

Note there is a check in this code. If a given aggregate table * column has already has a level usage, then that usage must all refer to * the same hierarchy usage join table and column name as the one that * calling this method was to create. If there is an existing level usage * for the column and it matches something else, then it is an error. */ protected void makeLevel( final JdbcSchema.Table.Column aggColumn, final Hierarchy hierarchy, final HierarchyUsage hierarchyUsage, final String factColumnName, final String levelColumnName, final String symbolicName, final boolean isCollapsed, final RolapLevel rLevel) { msgRecorder.pushContextName("Recognizer.makeLevel"); try { if (aggColumn.hasUsage(JdbcSchema.UsageType.LEVEL)) { // The column has at least one usage of level type // make sure we are looking at the // same table and column for (Iterator uit = aggColumn.getUsages(JdbcSchema.UsageType.LEVEL); uit.hasNext();) { JdbcSchema.Table.Column.Usage aggUsage = uit.next(); MondrianDef.Relation rel = hierarchyUsage.getJoinTable(); String cName = levelColumnName; if (! aggUsage.relation.equals(rel) || ! aggColumn.column.name.equals(cName)) { // this is an error so return String msg = mres.DoubleMatchForLevel.str( aggTable.getName(), dbFactTable.getName(), aggColumn.getName(), aggUsage.relation.toString(), aggColumn.column.name, rel.toString(), cName); msgRecorder.reportError(msg); returnValue = false; msgRecorder.throwRTException(); } } } else { JdbcSchema.Table.Column.Usage aggUsage = aggColumn.newUsage(JdbcSchema.UsageType.LEVEL); // Cache table and column for the above // check aggUsage.relation = hierarchyUsage.getJoinTable(); aggUsage.joinExp = hierarchyUsage.getJoinExp(); aggUsage.levelColumnName = levelColumnName; aggUsage.collapsed = isCollapsed; aggUsage.level = rLevel; aggUsage.setSymbolicName(symbolicName); String tableAlias; if (aggUsage.joinExp instanceof MondrianDef.Column) { MondrianDef.Column mcolumn = (MondrianDef.Column) aggUsage.joinExp; tableAlias = mcolumn.table; } else { tableAlias = aggUsage.relation.getAlias(); } RolapStar.Table factTable = star.getFactTable(); RolapStar.Table descTable = factTable.findDescendant(tableAlias); if (descTable == null) { // TODO: what to do here??? StringBuilder buf = new StringBuilder(256); buf.append("descendant table is null for factTable="); buf.append(factTable.getAlias()); buf.append(", tableAlias="); buf.append(tableAlias); msgRecorder.reportError(buf.toString()); returnValue = false; msgRecorder.throwRTException(); } RolapStar.Column rc = descTable.lookupColumn(factColumnName); if (rc == null) { rc = lookupInChildren(descTable, factColumnName); } if (rc == null) { StringBuilder buf = new StringBuilder(256); buf.append("Rolap.Column not found (null) for tableAlias="); buf.append(tableAlias); buf.append(", factColumnName="); buf.append(factColumnName); buf.append(", levelColumnName="); buf.append(levelColumnName); buf.append(", symbolicName="); buf.append(symbolicName); msgRecorder.reportError(buf.toString()); returnValue = false; msgRecorder.throwRTException(); } else { aggUsage.rColumn = rc; } } } finally { msgRecorder.popContextName(); } } protected RolapStar.Column lookupInChildren( final RolapStar.Table table, final String factColumnName) { // This can happen if we are looking at a collapsed dimension // table, and the collapsed dimension in question in the // fact table is a snowflake (not just a star), so we // must look deeper... for (RolapStar.Table child : table.getChildren()) { RolapStar.Column rc = child.lookupColumn(factColumnName); if (rc != null) { return rc; } else { rc = lookupInChildren(child, factColumnName); if (rc != null) { return rc; } } } return null; } // Question: what if foreign key is seen, but there are also level // columns - is this at least is a warning. /** * If everything is ok, issue warning for each aggTable column * that has not been identified as a FACT_COLUMN, MEASURE_COLUMN or * LEVEL_COLUMN. */ protected void checkUnusedColumns() { msgRecorder.pushContextName("Recognizer.checkUnusedColumns"); // Collection of messages for unused columns, sorted by column name // so that tests are deterministic. SortedMap unusedColumnMsgs = new TreeMap(); for (JdbcSchema.Table.Column aggColumn : aggTable.getColumns()) { if (! aggColumn.hasUsage()) { String msg = mres.AggUnknownColumn.str( aggTable.getName(), dbFactTable.getName(), aggColumn.getName()); unusedColumnMsgs.put(aggColumn.getName(), msg); } } for (String msg : unusedColumnMsgs.values()) { msgRecorder.reportWarning(msg); } msgRecorder.popContextName(); } /** * Figure out what aggregator should be associated with a column usage. * Generally, this aggregator is simply the RolapAggregator returned by * calling the getRollup() method of the fact table column's * RolapAggregator. But in the case that the fact table column's * RolapAggregator is the "Avg" aggregator, then the special * RolapAggregator.AvgFromSum is used. *

* Note: this code assumes that the aggregate table does not have an * explicit average aggregation column. * * @param aggUsage * @param factAgg */ protected RolapAggregator convertAggregator( final JdbcSchema.Table.Column.Usage aggUsage, final RolapAggregator factAgg) { // NOTE: This assumes that the aggregate table does not have an explicit // average column. if (factAgg == RolapAggregator.Avg) { String columnExpr = getFactCountExpr(aggUsage); return new RolapAggregator.AvgFromSum(columnExpr); } else if (factAgg == RolapAggregator.DistinctCount) { //return RolapAggregator.Count; return RolapAggregator.DistinctCount; } else { return factAgg; } } /** * The method chooses a special aggregator for the aggregate table column's * usage. *

     * If the fact table column's aggregator was "Avg":
     *   then if the sibling aggregator was "Avg":
     *      the new aggregator is RolapAggregator.AvgFromAvg
     *   else if the sibling aggregator was "Sum":
     *      the new aggregator is RolapAggregator.AvgFromSum
     * else if the fact table column's aggregator was "Sum":
     *   if the sibling aggregator was "Avg":
     *      the new aggregator is RolapAggregator.SumFromAvg
     * 
* Note that there is no SumFromSum since that is not a special case * requiring a special aggregator. *

* if no new aggregator was selected, then the fact table's aggregator * rollup aggregator is used. * * @param aggUsage * @param factAgg * @param siblingAgg */ protected RolapAggregator convertAggregator( final JdbcSchema.Table.Column.Usage aggUsage, final RolapAggregator factAgg, final RolapAggregator siblingAgg) { msgRecorder.pushContextName("Recognizer.convertAggregator"); RolapAggregator rollupAgg = null; String columnExpr = getFactCountExpr(aggUsage); if (factAgg == RolapAggregator.Avg) { if (siblingAgg == RolapAggregator.Avg) { rollupAgg = new RolapAggregator.AvgFromAvg(columnExpr); } else if (siblingAgg == RolapAggregator.Sum) { rollupAgg = new RolapAggregator.AvgFromSum(columnExpr); } } else if (factAgg == RolapAggregator.Sum) { if (siblingAgg == RolapAggregator.Avg) { rollupAgg = new RolapAggregator.SumFromAvg(columnExpr); } else if (siblingAgg instanceof RolapAggregator.AvgFromAvg) { // needed for BUG_1541077.testTotalAmount rollupAgg = new RolapAggregator.SumFromAvg(columnExpr); } } if (rollupAgg == null) { rollupAgg = (RolapAggregator) factAgg.getRollup(); } if (rollupAgg == null) { String msg = mres.NoAggregatorFound.str( aggUsage.getSymbolicName(), factAgg.getName(), siblingAgg.getName()); msgRecorder.reportError(msg); } msgRecorder.popContextName(); return rollupAgg; } /** * Given an aggregate table column usage, find the column name of the * table's fact count column usage. * * @param aggUsage Aggregate table column usage * @return The name of the column which holds the fact count. */ private String getFactCountExpr( final JdbcSchema.Table.Column.Usage aggUsage) { // get the fact count column name. JdbcSchema.Table aggTable = aggUsage.getColumn().getTable(); // iterator over fact count usages - in the end there can be only one!! Iterator it = aggTable.getColumnUsages(JdbcSchema.UsageType.FACT_COUNT); it.hasNext(); JdbcSchema.Table.Column.Usage usage = it.next(); // get the columns name String factCountColumnName = usage.getColumn().getName(); String tableName = aggTable.getName(); // we want the fact count expression MondrianDef.Column column = new MondrianDef.Column(tableName, factCountColumnName); SqlQuery sqlQuery = star.getSqlQuery(); return column.getExpression(sqlQuery); } /** * Finds all cubes that use this fact table. */ protected List findCubes() { String name = dbFactTable.getName(); List list = new ArrayList(); RolapSchema schema = star.getSchema(); for (RolapCube cube : schema.getCubeList()) { if (cube.isVirtual()) { continue; } RolapStar cubeStar = cube.getStar(); String factTableName = cubeStar.getFactTable().getAlias(); if (name.equals(factTableName)) { list.add(cube); } } return list; } /** * Given a {@link mondrian.olap.MondrianDef.Expression}, returns * the associated column name. * *

Note: if the {@link mondrian.olap.MondrianDef.Expression} is * not a {@link mondrian.olap.MondrianDef.Column} or {@link * mondrian.olap.MondrianDef.KeyExpression}, returns null. This * will result in an error. */ protected String getColumnName(MondrianDef.Expression expr) { msgRecorder.pushContextName("Recognizer.getColumnName"); try { if (expr instanceof MondrianDef.Column) { MondrianDef.Column column = (MondrianDef.Column) expr; return column.getColumnName(); } else if (expr instanceof MondrianDef.KeyExpression) { MondrianDef.KeyExpression key = (MondrianDef.KeyExpression) expr; return key.toString(); } String msg = mres.NoColumnNameFromExpression.str( expr.toString()); msgRecorder.reportError(msg); return null; } finally { msgRecorder.popContextName(); } } } // End Recognizer.java mondrian-3.4.1/src/main/mondrian/rolap/aggmatcher/DefaultRulesSchema.xml0000644000175000017500000017347211735330606026247 0ustar drazzibdrazzib This is the XML model for defining default aggregate table recognition and level/measure mapping. The set of "named" rules for matching aggregate tables. Only one rule can be applied to a given connection. In addition, one rule must be set as the default - this rule is always the choice when not selecting by name. It is very important that the AggRules validate method is called prior to using any of the object. The identifying tag for a schema. All shared TableMatches. All shared FactCountMatches. All shared ForeignKeyMatches. All shared LevelMap. All shared MeasureMap. All shared IgnoreMap. All AggRules (at least one). Also, one of them must be marked with default=true. Base is the base class for all of the elements. All elements can be enabled or not, have a tag, and can be validated. Is this element enabled - if true, then Mondrian can consider using it otherwise it ignored. public boolean isEnabled() { return enabled.booleanValue(); } protected abstract String getTag(); public abstract void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder); This is a base class for all elements that can match strings where the case of the string is important. In addition, it has an id which services as its tag. The unique identifier for this Matcher. How should the case of the item being matched be treated. If "ignore" then any combination of the source string where the characters are upper or lower case will match a target string. If "exact" then the exact match is made. If "upper" then all characters must be upper-case. If "lower" then all characters must be lower-case. ignore exact upper lower public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { // empty } protected String getTag() { return getId(); } public String getId() { return id; } public String getCharCase() { return charcase; } A NameMatcher is a CaseMatcher that prepends and appends regular expressions to a given string as part of creating the matching regular expression. Both the pre/post regular expression can be null in which case matches are applied simply against the name (modulo case considerations). The purpose of this class is to allow aggregate tables to be identified when their table names are formed by placing text before and/or after the base fact table name. The regular expression to preppend to the table name. The regular expression to append to the table name. The regular expression used to extract the fact table's base name from its full name. For instance, if the DBA allways prepends "RF_" before each fact table name, i.e., "RF_SHIPPING", but you want only the base part ("SHIPPING") to be used in recognizing aggregates, then one defines a regular expression with ONE and ONLY one group, in this case "RF_(.*)" with which the base name can be extracted from the full fact table name. In Sun terms, the "()" are a capture group. Note, if a Matcher is requested from a NameMatcher which has the basename attribute set, and the name used in the request does not match the basename pattern, then the Matcher return NEVER MATCHES ANYTHING. 0) { n = matcher.group(1); } else { if (AggRules.getLogger().isDebugEnabled()) { StringBuilder bf = new StringBuilder(64); bf.append(getName()); bf.append(".getRegex: for name \""); bf.append(name); bf.append("\" regex is null because basename \""); bf.append(basename); bf.append("\" is not matched."); String msg = bf.toString(); AggRules.getLogger().debug(msg); } // If the table name does not match the basename // pattern, then return null for regex. return null; } } buf.append(n); } if (posttemplate != null) { buf.append(posttemplate); } String regex = buf.toString(); if (AggRules.getLogger().isDebugEnabled()) { StringBuilder bf = new StringBuilder(64); bf.append(getName()); bf.append(".getRegex: for name \""); bf.append(name); bf.append("\" regex is \""); bf.append(regex); bf.append('"'); String msg = bf.toString(); AggRules.getLogger().debug(msg); } return regex; } protected Recognizer.Matcher getMatcher(String name) { final String charcase = getCharCase(); final String regex; int flag = 0; if (charcase.equals("ignore")) { // the case of name does not matter // since the Pattern will be create to ignore case regex = getRegex(name); flag = java.util.regex.Pattern.CASE_INSENSITIVE; } else if (charcase.equals("exact")) { // the case of name is not changed // since we are interested in exact case matching regex = getRegex(name); } else if (charcase.equals("upper")) { // convert name to upper case regex = getRegex(name.toUpperCase()); } else { // lower // convert name to lower case regex = getRegex(name.toLowerCase()); } // If regex is null, then return a matcher that matches nothing if (regex == null) { return new Recognizer.Matcher() { public boolean matches(String name) { return false; } }; } final java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regex, flag); return new Recognizer.Matcher() { public boolean matches(String name) { boolean b = pattern.matcher(name).matches(); if (AggRules.getLogger().isDebugEnabled()) { debug(name); } return b; } private void debug(String name) { StringBuilder bf = new StringBuilder(64); bf.append(NameMatcher.this.getName()); bf.append(".Matcher.matches:"); bf.append(" name \""); bf.append(name); bf.append("\" pattern \""); bf.append(pattern.pattern()); bf.append("\""); if ((pattern.flags() & java.util.regex.Pattern.CASE_INSENSITIVE) != 0) { bf.append(" case_insensitive"); } String msg = bf.toString(); AggRules.getLogger().debug(msg); } }; } ]]> This is used to identify the "fact_count" column in an aggregate table. It allows one to match using regular exprssions. The default is that the name of the fact count colum is simply the string "fact_count". The "base" name for a fact count column. This is used to identify foreign key columns in a candidate aggregate table given the name of a foreign key column of the base fact table. This allows such foreign keys to be identified by using a regular exprsssion. The default is to simply match the base fact table's foreign key column name. This is used to identify which tables in the database might be aggregate table of a given fact table. It is expected that aggregate table names will include the base fact table name with additional text before and/or after. It is not allow for both the prepending and appending regular expression text to be null (if it were, then only aggregate tables who names were the same as (modulo case) would match - which is surely not allowed). This allows one to create an element that matches against a single template, where the template is an attribute. While much loved, this is currently not used. This is used by Elements to create a regex string. How to translate the space character. For example, if the space=='_' and the source string is "Product Family", then the target string is "Product_Family". How to translate the dot character. For example, if the dot=='_' and the source string is "Time.Time Weekly", then the target string is "Time_Time Weekly". This element is used in a vector of child elements when one wishes to have one or more regular expressions associated with matching a given string. The parent element must initialize Regex object by calling its validate method passing in an array of template names. The cdata content is a regular expression with embedded template names. Each name must be surrounded by "${" and "}". Each time this is used for a new set of names, the names replace the template names in the regular expression. For example, if the charcase="lower", the attribute dot="-" (the default dot value is "_"), the template names are: "city", "state", and "country" and the cdata is: .*_${country}_.*_${city} Then when the names: "San Francisco", "California", and "U.S.A" are passed in, the regular expression becomes: .*_u-s-a_.*_san_francisco Note that a given template name can only appear ONCE in the template content, the cdata content. As an example, the following cdata template is not supported: .*_${country}_.*_${city}_${country} How to translate the space character. For example, if the space=='_' and the source string is "Product Family", then the target string is "Product_Family". How to translate the dot character. For example, if the dot=='_' and the source string is "Time.Time Weekly", then the target string is "Time_Time Weekly". The unique identifier for this Matcher. This is an array of Regex. A match occurs if any one of the Regex matches; it is the equivalent of or-ing the regular expressions together. A candidate string is processed sequentially by each Regex in their document order until one matches. In none match, well, none match. . protected String getTag() { return getRefId(); } public String getRefId() { return refId; } public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { if (! rules.hasLevelMap(getRefId())) { String msg = "No LevelMap has id equal to refid \"" + getRefId() + "\""; msgRecorder.reportError(msg); } } finally { msgRecorder.popContextName(); } } public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { if (! rules.hasMeasureMap(getRefId())) { String msg = "No MeasureMap has id equal to refid \"" + getRefId() + "\""; msgRecorder.reportError(msg); } } finally { msgRecorder.popContextName(); } } public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { if (! rules.hasIgnoreMap(getRefId())) { String msg = "No IgnoreMap has id equal to refid \"" + getRefId() + "\""; msgRecorder.reportError(msg); } } finally { msgRecorder.popContextName(); } } public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { if (! rules.hasFactCountMatch(getRefId())) { String msg = "No FactCountMatch has id equal to refid \"" + getRefId() + "\""; msgRecorder.reportError(msg); } } finally { msgRecorder.popContextName(); } } public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { if (! rules.hasForeignKeyMatch(getRefId())) { String msg = "No ForeignKeyMatch has id equal to refid \"" + getRefId() + "\""; msgRecorder.reportError(msg); } } finally { msgRecorder.popContextName(); } } public void validate(final AggRules rules, final mondrian.recorder.MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { if (! rules.hasTableMatch(getRefId())) { String msg = "No TableMatch has id equal to refid \"" + getRefId() + "\""; msgRecorder.reportError(msg); } } finally { msgRecorder.popContextName(); } } This is the template that maps from a combination of level usage_prefix hierarchy_name level_name level_column_name This is the template that maps from a combination of measure measure_name, measure_column_name, and aggregate_name ("count", "sum", "avg", "min", "max", "distinct-count"). This is the template used to specify columns to be ignored. There are NO template names. One simply uses a regular expression. A RolapConnection uses one AggRule. If no name is specified, then the AggRule which is marked as default==true is used (validation fails if one and only one AggRule is not marked as the default). An AggRule has manditory child elements for matching the aggregate table names, aggregate table fact count column, foreign key columns, the measure columns, and the hierarchy level columns. These child elements can be specified as direct children of an AggRule element or by reference to elements defined as a pier to the AggRule (using references allows reuse of the child elements and with one quick edit the reference to use can be changed by changing the refid attribute value). Name of this AggRule Name of the aggregate column containing the count for the row. mondrian-3.4.1/src/main/mondrian/rolap/aggmatcher/ExplicitRules.java0000644000175000017500000014357711735330606025447 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.aggmatcher; import mondrian.olap.*; import mondrian.recorder.MessageRecorder; import mondrian.resource.MondrianResource; import mondrian.rolap.*; import org.apache.log4j.Logger; import java.io.PrintWriter; import java.io.StringWriter; import java.util.*; import java.util.regex.Pattern; /** * A class containing a RolapCube's Aggregate tables exclude/include * criteria. * * @author Richard M. Emberson */ public class ExplicitRules { private static final Logger LOGGER = Logger.getLogger(ExplicitRules.class); private static final MondrianResource mres = MondrianResource.instance(); /** * Returns whether the given is tableName explicitly excluded from * consideration as a candidate aggregate table. */ public static boolean excludeTable( final String tableName, final List aggGroups) { for (Group group : aggGroups) { if (group.excludeTable(tableName)) { return true; } } return false; } /** * Returns the {@link TableDef} for a tableName that is a candidate * aggregate table. If null is returned, then the default rules are used * otherwise if not null, then the ExplicitRules.TableDef is used. */ public static ExplicitRules.TableDef getIncludeByTableDef( final String tableName, final List aggGroups) { for (Group group : aggGroups) { TableDef tableDef = group.getIncludeByTableDef(tableName); if (tableDef != null) { return tableDef; } } return null; } /** * This class forms a collection of aggregate table explicit rules for a * given cube. * */ public static class Group { /** * Make an ExplicitRules.Group for a given RolapCube given the * MondrianDef.Cube associated with that cube. */ public static ExplicitRules.Group make( final RolapCube cube, final MondrianDef.Cube xmlCube) { Group group = new Group(cube); MondrianDef.Relation relation = xmlCube.fact; if (relation instanceof MondrianDef.Table) { MondrianDef.AggExclude[] aggExcludes = ((MondrianDef.Table) relation).getAggExcludes(); if (aggExcludes != null) { for (MondrianDef.AggExclude aggExclude : aggExcludes) { Exclude exclude = ExplicitRules.make(aggExclude); group.addExclude(exclude); } } MondrianDef.AggTable[] aggTables = ((MondrianDef.Table) relation).getAggTables(); if (aggTables != null) { for (MondrianDef.AggTable aggTable : aggTables) { TableDef tableDef = TableDef.make(aggTable, group); group.addTableDef(tableDef); } } } else { LOGGER.warn( mres.CubeRelationNotTable.str( cube.getName(), relation.getClass().getName())); } if (LOGGER.isDebugEnabled()) { LOGGER.debug(Util.nl + group); } return group; } private final RolapCube cube; private List tableDefs; private List excludes; public Group(final RolapCube cube) { this.cube = cube; this.excludes = Collections.emptyList(); this.tableDefs = Collections.emptyList(); } /** * Get the RolapCube associated with this Group. */ public RolapCube getCube() { return cube; } /** * Get the RolapStar associated with this Group's RolapCube. */ public RolapStar getStar() { return getCube().getStar(); } /** * Get the name of this Group (its the name of its RolapCube). */ public String getName() { return getCube().getName(); } /** * Are there any rules associated with this Group. */ public boolean hasRules() { return (excludes != Collections.EMPTY_LIST) || (tableDefs != Collections.EMPTY_LIST); } /** * Add an exclude rule. * * @param exclude */ public void addExclude(final ExplicitRules.Exclude exclude) { if (excludes == Collections.EMPTY_LIST) { excludes = new ArrayList(); } excludes.add(exclude); } /** * Add a name or pattern (table) rule. * * @param tableDef */ public void addTableDef(final ExplicitRules.TableDef tableDef) { if (tableDefs == Collections.EMPTY_LIST) { tableDefs = new ArrayList(); } tableDefs.add(tableDef); } /** * Returns whether the given tableName is excluded. */ public boolean excludeTable(final String tableName) { // See if the table is explicitly, by name, excluded for (Exclude exclude : excludes) { if (exclude.isExcluded(tableName)) { return true; } } return false; } /** * Is the given tableName included either by exact name or by pattern. */ public ExplicitRules.TableDef getIncludeByTableDef( final String tableName) { // An exact match on a NameTableDef takes precedences over a // fuzzy match on a PatternTableDef, so // first look throught NameTableDef then PatternTableDef for (ExplicitRules.TableDef tableDef : tableDefs) { if (tableDef instanceof NameTableDef) { if (tableDef.matches(tableName)) { return tableDef; } } } for (ExplicitRules.TableDef tableDef : tableDefs) { if (tableDef instanceof PatternTableDef) { if (tableDef.matches(tableName)) { return tableDef; } } } return null; } /** * Get the database table name associated with this Group's RolapStar's * fact table. */ public String getTableName() { RolapStar.Table table = getStar().getFactTable(); MondrianDef.Relation relation = table.getRelation(); return relation.getAlias(); } /** * Get the database schema name associated with this Group's RolapStar's * fact table. */ public String getSchemaName() { String schema = null; RolapStar.Table table = getStar().getFactTable(); MondrianDef.Relation relation = table.getRelation(); if (relation instanceof MondrianDef.Table) { MondrianDef.Table mtable = (MondrianDef.Table) relation; schema = mtable.schema; } return schema; } /** * Get the database catalog name associated with this Group's * RolapStar's fact table. * Note: this currently this always returns null. */ public String getCatalogName() { return null; } /** * Validate the content and structure of this Group. */ public void validate(final MessageRecorder msgRecorder) { msgRecorder.pushContextName(getName()); try { for (ExplicitRules.TableDef tableDef : tableDefs) { tableDef.validate(msgRecorder); } } finally { msgRecorder.popContextName(); } } public String toString() { StringWriter sw = new StringWriter(256); PrintWriter pw = new PrintWriter(sw); print(pw, ""); pw.flush(); return sw.toString(); } public void print(final PrintWriter pw, final String prefix) { pw.print(prefix); pw.println("ExplicitRules.Group:"); String subprefix = prefix + " "; String subsubprefix = subprefix + " "; pw.print(subprefix); pw.print("name="); pw.println(getStar().getFactTable().getRelation()); pw.print(subprefix); pw.println("TableDefs: ["); for (ExplicitRules.TableDef tableDef : tableDefs) { tableDef.print(pw, subsubprefix); } pw.print(subprefix); pw.println("]"); } } private static Exclude make(final MondrianDef.AggExclude aggExclude) { return (aggExclude.getNameAttribute() != null) ? new ExcludeName( aggExclude.getNameAttribute(), aggExclude.isIgnoreCase()) : (Exclude) new ExcludePattern( aggExclude.getPattern(), aggExclude.isIgnoreCase()); } /** * Interface of an Exclude type. There are two implementations, one that * excludes by exact name match (as an option, ignore case) and the second * that matches a regular expression. */ private interface Exclude { /** * Return true if the tableName is excluded. * * @param tableName Table name * @return whether table name is excluded */ boolean isExcluded(final String tableName); /** * Validate that the exclude name matches the table pattern. * * @param msgRecorder Message recorder */ void validate(final MessageRecorder msgRecorder); /** * Prints this rule to a PrintWriter. * * @param pw PrintWriter * @param prefix Line prefix, for indentation */ void print(final PrintWriter pw, final String prefix); } /** * Implementation of Exclude which matches names exactly. */ private static class ExcludeName implements Exclude { private final String name; private final boolean ignoreCase; private ExcludeName(final String name, final boolean ignoreCase) { this.name = name; this.ignoreCase = ignoreCase; } /** * Returns the name to be matched. */ public String getName() { return name; } /** * Returns true if the matching can ignore case. */ public boolean isIgnoreCase() { return ignoreCase; } public boolean isExcluded(final String tableName) { return (this.ignoreCase) ? this.name.equals(tableName) : this.name.equalsIgnoreCase(tableName); } public void validate(final MessageRecorder msgRecorder) { msgRecorder.pushContextName("ExcludeName"); try { String name = getName(); checkAttributeString(msgRecorder, name, "name"); /* RME TODO // If name does not match the PatternTableDef pattern, // then issue warning. // Why, because no table with the exclude's name will // ever match the pattern, so the exclude is superfluous. // This is best effort. Pattern pattern = ExplicitRules.PatternTableDef.this.getPattern(); boolean patternIgnoreCase = ExplicitRules.PatternTableDef.this.isIgnoreCase(); boolean ignoreCase = isIgnoreCase(); // If pattern is ignoreCase and name is any case or pattern // is not ignoreCase and name is not ignoreCase, then simply // see if name matches. // Else pattern in not ignoreCase and name is ignoreCase, // then pattern could be "AB.*" and name "abc". // Here "abc" would name, but not pattern - but who cares if (patternIgnoreCase || ! ignoreCase) { if (! pattern.matcher(name).matches()) { msgRecorder.reportWarning( mres.getSuperfluousExludeName( msgRecorder.getContext(), name, pattern.pattern())); } } */ } finally { msgRecorder.popContextName(); } } public void print(final PrintWriter pw, final String prefix) { pw.print(prefix); pw.println("ExplicitRules.PatternTableDef.ExcludeName:"); String subprefix = prefix + " "; pw.print(subprefix); pw.print("name="); pw.println(this.name); pw.print(subprefix); pw.print("ignoreCase="); pw.println(this.ignoreCase); } } /** * This class is a regular expression base name matching Exclude * implementation. */ private static class ExcludePattern implements Exclude { private final Pattern pattern; private ExcludePattern( final String pattern, final boolean ignoreCase) { this.pattern = (ignoreCase) ? Pattern.compile(pattern, Pattern.CASE_INSENSITIVE) : Pattern.compile(pattern); } public boolean isExcluded(final String tableName) { return pattern.matcher(tableName).matches(); } public void validate(final MessageRecorder msgRecorder) { msgRecorder.pushContextName("ExcludePattern"); try { checkAttributeString( msgRecorder, pattern.pattern(), "pattern"); //String context = msgRecorder.getContext(); // Is there any way to determine if the exclude pattern // is never a sub-set of the table pattern. // I will have to think about this. // Until then, this method is empty. } finally { msgRecorder.popContextName(); } } public void print(final PrintWriter pw, final String prefix) { pw.print(prefix); pw.println("ExplicitRules.PatternTableDef.ExcludePattern:"); String subprefix = prefix + " "; pw.print(subprefix); pw.print("pattern="); pw.print(this.pattern.pattern()); pw.print(":"); pw.println(this.pattern.flags()); } } /** * This is the base class for the exact name based and name pattern based * aggregate table mapping definitions. It contains the mappings for the * fact count column, optional ignore columns, foreign key mappings, * measure column mappings and level column mappings. */ public static abstract class TableDef { /** * Given a MondrianDef.AggTable instance create a TableDef instance * which is either a NameTableDef or PatternTableDef. */ static ExplicitRules.TableDef make( final MondrianDef.AggTable aggTable, final ExplicitRules.Group group) { return (aggTable instanceof MondrianDef.AggName) ? ExplicitRules.NameTableDef.make( (MondrianDef.AggName) aggTable, group) : (ExplicitRules.TableDef) ExplicitRules.PatternTableDef.make( (MondrianDef.AggPattern) aggTable, group); } /** * This method extracts information from the MondrianDef.AggTable and * places it in the ExplicitRules.TableDef. This code is used for both * the NameTableDef and PatternTableDef subclasses of TableDef (it * extracts information common to both). * * @param tableDef * @param aggTable */ private static void add( final ExplicitRules.TableDef tableDef, final MondrianDef.AggTable aggTable) { if (aggTable.getAggFactCount() != null) { tableDef.setFactCountName( aggTable.getAggFactCount().getColumnName()); } MondrianDef.AggIgnoreColumn[] ignores = aggTable.getAggIgnoreColumns(); if (ignores != null) { for (MondrianDef.AggIgnoreColumn ignore : ignores) { tableDef.addIgnoreColumnName(ignore.getColumnName()); } } MondrianDef.AggForeignKey[] fks = aggTable.getAggForeignKeys(); if (fks != null) { for (MondrianDef.AggForeignKey fk : fks) { tableDef.addFK(fk); } } MondrianDef.AggMeasure[] measures = aggTable.getAggMeasures(); if (measures != null) { for (MondrianDef.AggMeasure measure : measures) { addTo(tableDef, measure); } } MondrianDef.AggLevel[] levels = aggTable.getAggLevels(); if (levels != null) { for (MondrianDef.AggLevel level : levels) { addTo(tableDef, level); } } } private static void addTo( final ExplicitRules.TableDef tableDef, final MondrianDef.AggLevel aggLevel) { addLevelTo( tableDef, aggLevel.getNameAttribute(), aggLevel.getColumnName(), aggLevel.isCollapsed()); } private static void addTo( final ExplicitRules.TableDef tableDef, final MondrianDef.AggMeasure aggMeasure) { addMeasureTo( tableDef, aggMeasure.getNameAttribute(), aggMeasure.getColumn()); } public static void addLevelTo( final ExplicitRules.TableDef tableDef, final String name, final String columnName, final boolean collapsed) { Level level = tableDef.new Level(name, columnName, collapsed); tableDef.add(level); } public static void addMeasureTo( final ExplicitRules.TableDef tableDef, final String name, final String column) { Measure measure = tableDef.new Measure(name, column); tableDef.add(measure); } /** * This class is used to map from a Level's symbolic name, * [Time].[Year] to the aggregate table's column name, TIME_YEAR. */ class Level { private final String name; private final String columnName; private final boolean collapsed; private RolapLevel rlevel; Level( final String name, final String columnName, final boolean collapsed) { this.name = name; this.columnName = columnName; this.collapsed = collapsed; } /** * Get the symbolic name, the level name. */ public String getName() { return name; } /** * Get the foreign key column name of the aggregate table. */ public String getColumnName() { return columnName; } /** * Returns whether this level is collapsed (includes * parent levels in the agg table). */ public boolean isCollapsed() { return collapsed; } /** * Get the RolapLevel associated with level name. */ public RolapLevel getRolapLevel() { return rlevel; } /** * Validates a level's name. * *

The level name must be of the form [hierarchy usage * name].[level name]. * *

This method checks that is of length 2, starts with a * hierarchy and the "level name" exists. */ public void validate(final MessageRecorder msgRecorder) { msgRecorder.pushContextName("Level"); try { String name = getName(); String columnName = getColumnName(); checkAttributeString(msgRecorder, name, "name"); checkAttributeString(msgRecorder, columnName, "column"); List names = Util.parseIdentifier(name); // must be [hierarchy usage name].[level name] if (!(names.size() == 2 || MondrianProperties.instance().SsasCompatibleNaming .get() && names.size() == 3)) { msgRecorder.reportError( mres.BadLevelNameFormat.str( msgRecorder.getContext(), name)); } else { RolapCube cube = ExplicitRules.TableDef.this.getCube(); SchemaReader schemaReader = cube.getSchemaReader(); RolapLevel level = (RolapLevel) schemaReader.lookupCompound( cube, names, false, Category.Level); if (level == null) { Hierarchy hierarchy = (Hierarchy) schemaReader.lookupCompound( cube, names.subList(0, 1), false, Category.Hierarchy); if (hierarchy == null) { msgRecorder.reportError( mres.UnknownHierarchyName.str( msgRecorder.getContext(), names.get(0).name)); } else { msgRecorder.reportError( mres.UnknownLevelName.str( msgRecorder.getContext(), names.get(0).name, names.get(1).name)); } } rlevel = level; } } finally { msgRecorder.popContextName(); } } public String toString() { StringWriter sw = new StringWriter(256); PrintWriter pw = new PrintWriter(sw); print(pw, ""); pw.flush(); return sw.toString(); } public void print(final PrintWriter pw, final String prefix) { pw.print(prefix); pw.println("Level:"); String subprefix = prefix + " "; pw.print(subprefix); pw.print("name="); pw.println(this.name); pw.print(subprefix); pw.print("columnName="); pw.println(this.columnName); } } /** * This class is used to map from a measure's symbolic name, * [Measures]&#46;[Unit Sales] to the aggregate table's column * name, UNIT_SALES_SUM. */ class Measure { private final String name; private String symbolicName; private final String columnName; private RolapStar.Measure rolapMeasure; Measure(final String name, final String columnName) { this.name = name; this.columnName = columnName; } /** * Get the symbolic name, the measure name, i.e., * [Measures].[Unit Sales]. */ public String getName() { return name; } /** * Get the symbolic name, the measure name, i.e., [Unit Sales]. */ public String getSymbolicName() { return symbolicName; } /** * Get the aggregate table column name. */ public String getColumnName() { return columnName; } /** * Get the RolapStar.Measure associated with this symbolic name. */ public RolapStar.Measure getRolapStarMeasure() { return rolapMeasure; } /** * Validates a measure's name. * *

The measure name must be of the form *

[Measures].[measure name]
* *

This method checks that is of length 2, starts * with "Measures" and the "measure name" exists. */ public void validate(final MessageRecorder msgRecorder) { msgRecorder.pushContextName("Measure"); try { String name = getName(); String column = getColumnName(); checkAttributeString(msgRecorder, name, "name"); checkAttributeString(msgRecorder, column, "column"); List names = Util.parseIdentifier(name); if (names.size() != 2) { msgRecorder.reportError( mres.BadMeasureNameFormat.str( msgRecorder.getContext(), name)); } else { RolapCube cube = ExplicitRules.TableDef.this.getCube(); SchemaReader schemaReader = cube.getSchemaReader(); Member member = (Member) schemaReader.lookupCompound( cube, names, false, Category.Member); if (member == null) { if (! names.get(0).name.equals("Measures")) { msgRecorder.reportError( mres.BadMeasures.str( msgRecorder.getContext(), names.get(0).name)); } else { msgRecorder.reportError( mres.UnknownMeasureName.str( msgRecorder.getContext(), names.get(1).name)); } } RolapStar star = cube.getStar(); rolapMeasure = star.getFactTable().lookupMeasureByName( cube.getName(), names.get(1).name); if (rolapMeasure == null) { msgRecorder.reportError( mres.BadMeasureName.str( msgRecorder.getContext(), names.get(1).name, cube.getName())); } symbolicName = names.get(1).name; } } finally { msgRecorder.popContextName(); } } public String toString() { StringWriter sw = new StringWriter(256); PrintWriter pw = new PrintWriter(sw); print(pw, ""); pw.flush(); return sw.toString(); } public void print(final PrintWriter pw, final String prefix) { pw.print(prefix); pw.println("Measure:"); String subprefix = prefix + " "; pw.print(subprefix); pw.print("name="); pw.println(this.name); pw.print(subprefix); pw.print("column="); pw.println(this.columnName); } } private static int idCount = 0; private static int nextId() { return idCount++; } protected final int id; protected final boolean ignoreCase; protected final ExplicitRules.Group aggGroup; protected String factCountName; protected List ignoreColumnNames; private Map foreignKeyMap; private List levels; private List measures; protected int approxRowCount = Integer.MIN_VALUE; protected TableDef( final boolean ignoreCase, final ExplicitRules.Group aggGroup) { this.id = nextId(); this.ignoreCase = ignoreCase; this.aggGroup = aggGroup; this.foreignKeyMap = Collections.emptyMap(); this.levels = Collections.emptyList(); this.measures = Collections.emptyList(); this.ignoreColumnNames = Collections.emptyList(); } /** * Returns an approximate number of rows in this table. * A negative value indicates that no estimate is available. * @return An estimated row count, or a negative value if no * row count approximation was available. */ public int getApproxRowCount() { return approxRowCount; } /** * Return true if this name/pattern matching ignores case. */ public boolean isIgnoreCase() { return this.ignoreCase; } /** * Get the RolapStar associated with this cube. */ public RolapStar getStar() { return getAggGroup().getStar(); } /** * Get the Group with which is a part. */ public ExplicitRules.Group getAggGroup() { return this.aggGroup; } /** * Get the name of the fact count column. */ protected String getFactCountName() { return factCountName; } /** * Set the name of the fact count column. * * @param factCountName */ protected void setFactCountName(final String factCountName) { this.factCountName = factCountName; } /** * Get an Iterator over all ignore column name entries. */ protected Iterator getIgnoreColumnNames() { return ignoreColumnNames.iterator(); } /** * Gets all level mappings. */ public List getLevels() { return levels; } /** * Gets all level mappings. */ public List getMeasures() { return measures; } /** * Get Matcher for ignore columns. */ protected Recognizer.Matcher getIgnoreMatcher() { return new Recognizer.Matcher() { public boolean matches(final String name) { for (Iterator it = ExplicitRules.TableDef.this.getIgnoreColumnNames(); it.hasNext();) { String ignoreName = it.next(); if (ignoreName.equals(name)) { return true; } } return false; } }; } /** * Get Matcher for the fact count column. */ protected Recognizer.Matcher getFactCountMatcher() { return new Recognizer.Matcher() { public boolean matches(String name) { // Match is case insensitive final String factCountName = TableDef.this.factCountName; return factCountName != null && factCountName.equalsIgnoreCase(name); } }; } /** * Get the RolapCube associated with this mapping. */ RolapCube getCube() { return aggGroup.getCube(); } /** * Checks that ALL of the columns in the dbTable have a mapping in the * tableDef. * *

It is an error if there is a column that does not have a mapping. */ public boolean columnsOK( final RolapStar star, final JdbcSchema.Table dbFactTable, final JdbcSchema.Table dbTable, final MessageRecorder msgRecorder) { Recognizer cb = new ExplicitRecognizer( this, star, getCube(), dbFactTable, dbTable, msgRecorder); return cb.check(); } /** * Adds the name of an aggregate table column that is to be ignored. */ protected void addIgnoreColumnName(final String ignoreName) { if (this.ignoreColumnNames == Collections.EMPTY_LIST) { this.ignoreColumnNames = new ArrayList(); } this.ignoreColumnNames.add(ignoreName); } /** * Add foreign key mapping entry (maps from fact table foreign key * column name to aggregate table foreign key column name). */ protected void addFK(final MondrianDef.AggForeignKey fk) { if (this.foreignKeyMap == Collections.EMPTY_MAP) { this.foreignKeyMap = new HashMap(); } this.foreignKeyMap.put( fk.getFactFKColumnName(), fk.getAggregateFKColumnName()); } /** * Get the name of the aggregate table's foreign key column that matches * the base fact table's foreign key column or return null. */ protected String getAggregateFK(final String baseFK) { return this.foreignKeyMap.get(baseFK); } /** * Adds a Level. */ protected void add(final Level level) { if (this.levels == Collections.EMPTY_LIST) { this.levels = new ArrayList(); } this.levels.add(level); } /** * Adds a Measure. */ protected void add(final Measure measure) { if (this.measures == Collections.EMPTY_LIST) { this.measures = new ArrayList(); } this.measures.add(measure); } /** * Does the TableDef match a table with name tableName. */ public abstract boolean matches(final String tableName); /** * Validate the Levels and Measures, also make sure each definition * is different, both name and column. */ public void validate(final MessageRecorder msgRecorder) { msgRecorder.pushContextName("TableDef"); try { // used to detect duplicates Map namesToObjects = new HashMap(); // used to detect duplicates Map columnsToObjects = new HashMap(); for (Level level : levels) { level.validate(msgRecorder); // Is the level name a duplicate if (namesToObjects.containsKey(level.getName())) { msgRecorder.reportError( mres.DuplicateLevelNames.str( msgRecorder.getContext(), level.getName())); } else { namesToObjects.put(level.getName(), level); } // Is the level foreign key name a duplicate if (columnsToObjects.containsKey(level.getColumnName())) { Level l = (Level) columnsToObjects.get(level.getColumnName()); msgRecorder.reportError( mres.DuplicateLevelColumnNames.str( msgRecorder.getContext(), level.getName(), l.getName(), level.getColumnName())); } else { columnsToObjects.put(level.getColumnName(), level); } } // reset names map, but keep the columns from levels namesToObjects.clear(); for (Measure measure : measures) { measure.validate(msgRecorder); if (namesToObjects.containsKey(measure.getName())) { msgRecorder.reportError( mres.DuplicateMeasureNames.str( msgRecorder.getContext(), measure.getName())); continue; } else { namesToObjects.put(measure.getName(), measure); } if (columnsToObjects.containsKey(measure.getColumnName())) { Object o = columnsToObjects.get(measure.getColumnName()); if (o instanceof Measure) { Measure m = (Measure) o; msgRecorder.reportError( mres.DuplicateMeasureColumnNames.str( msgRecorder.getContext(), measure.getName(), m.getName(), measure.getColumnName())); } else { Level l = (Level) o; msgRecorder.reportError( mres.DuplicateLevelMeasureColumnNames.str( msgRecorder.getContext(), l.getName(), measure.getName(), measure.getColumnName())); } } else { columnsToObjects.put(measure.getColumnName(), measure); } } // reset both namesToObjects.clear(); columnsToObjects.clear(); // Make sure that the base fact table foreign key names match // real columns RolapStar star = getStar(); RolapStar.Table factTable = star.getFactTable(); String tableName = factTable.getAlias(); for (Map.Entry e : foreignKeyMap.entrySet()) { String baseFKName = e.getKey(); String aggFKName = e.getValue(); if (namesToObjects.containsKey(baseFKName)) { msgRecorder.reportError( mres.DuplicateFactForeignKey.str( msgRecorder.getContext(), baseFKName, aggFKName)); } else { namesToObjects.put(baseFKName, aggFKName); } if (columnsToObjects.containsKey(aggFKName)) { msgRecorder.reportError( mres.DuplicateFactForeignKey.str( msgRecorder.getContext(), baseFKName, aggFKName)); } else { columnsToObjects.put(aggFKName, baseFKName); } MondrianDef.Column c = new MondrianDef.Column(tableName, baseFKName); if (factTable.findTableWithLeftCondition(c) == null) { msgRecorder.reportError( mres.UnknownLeftJoinCondition.str( msgRecorder.getContext(), tableName, baseFKName)); } } } finally { msgRecorder.popContextName(); } } public String toString() { StringWriter sw = new StringWriter(256); PrintWriter pw = new PrintWriter(sw); print(pw, ""); pw.flush(); return sw.toString(); } public void print(final PrintWriter pw, final String prefix) { String subprefix = prefix + " "; String subsubprefix = subprefix + " "; pw.print(subprefix); pw.print("id="); pw.println(this.id); pw.print(subprefix); pw.print("ignoreCase="); pw.println(this.ignoreCase); pw.print(subprefix); pw.println("Levels: ["); for (Level level : this.levels) { level.print(pw, subsubprefix); } pw.print(subprefix); pw.println("]"); pw.print(subprefix); pw.println("Measures: ["); for (Measure measure : this.measures) { measure.print(pw, subsubprefix); } pw.print(subprefix); pw.println("]"); } } static class NameTableDef extends ExplicitRules.TableDef { /** * Makes a NameTableDef from the catalog schema. */ static ExplicitRules.NameTableDef make( final MondrianDef.AggName aggName, final ExplicitRules.Group group) { ExplicitRules.NameTableDef name = new ExplicitRules.NameTableDef( aggName.getNameAttribute(), aggName.getApproxRowCountAttribute(), aggName.isIgnoreCase(), group); ExplicitRules.TableDef.add(name, aggName); return name; } private final String name; public NameTableDef( final String name, final String approxRowCount, final boolean ignoreCase, final ExplicitRules.Group group) { super(ignoreCase, group); this.name = name; this.approxRowCount = loadApproxRowCount(approxRowCount); } private int loadApproxRowCount(String approxRowCount) { boolean notNullAndNumeric = approxRowCount != null && approxRowCount.matches("^\\d+$"); if (notNullAndNumeric) { return Integer.parseInt(approxRowCount); } else { // if approxRowCount is not set, return MIN_VALUE to indicate return Integer.MIN_VALUE; } } /** * Does the given tableName match this NameTableDef (either exact match * or, if set, a case insensitive match). */ public boolean matches(final String tableName) { return (this.ignoreCase) ? this.name.equalsIgnoreCase(tableName) : this.name.equals(tableName); } /** * Validate name and base class. * * @param msgRecorder */ public void validate(final MessageRecorder msgRecorder) { msgRecorder.pushContextName("NameTableDef"); try { checkAttributeString(msgRecorder, name, "name"); super.validate(msgRecorder); } finally { msgRecorder.popContextName(); } } public void print(final PrintWriter pw, final String prefix) { pw.print(prefix); pw.println("ExplicitRules.NameTableDef:"); super.print(pw, prefix); String subprefix = prefix + " "; pw.print(subprefix); pw.print("name="); pw.println(this.name); } } /** * This class matches candidate aggregate table name with a pattern. */ public static class PatternTableDef extends ExplicitRules.TableDef { /** * Make a PatternTableDef from the catalog schema. */ static ExplicitRules.PatternTableDef make( final MondrianDef.AggPattern aggPattern, final ExplicitRules.Group group) { ExplicitRules.PatternTableDef pattern = new ExplicitRules.PatternTableDef( aggPattern.getPattern(), aggPattern.isIgnoreCase(), group); MondrianDef.AggExclude[] excludes = aggPattern.getAggExcludes(); if (excludes != null) { for (MondrianDef.AggExclude exclude1 : excludes) { Exclude exclude = ExplicitRules.make(exclude1); pattern.add(exclude); } } ExplicitRules.TableDef.add(pattern, aggPattern); return pattern; } private final Pattern pattern; private List excludes; public PatternTableDef( final String pattern, final boolean ignoreCase, final ExplicitRules.Group group) { super(ignoreCase, group); this.pattern = (this.ignoreCase) ? Pattern.compile(pattern, Pattern.CASE_INSENSITIVE) : Pattern.compile(pattern); this.excludes = Collections.emptyList(); } /** * Get the Pattern. */ public Pattern getPattern() { return pattern; } /** * Get an Iterator over the list of Excludes. */ public List getExcludes() { return excludes; } /** * Add an Exclude. * * @param exclude */ private void add(final Exclude exclude) { if (this.excludes == Collections.EMPTY_LIST) { this.excludes = new ArrayList(); } this.excludes.add(exclude); } /** * Return true if the tableName 1) matches the pattern and 2) is not * matched by any of the Excludes. */ public boolean matches(final String tableName) { if (! pattern.matcher(tableName).matches()) { return false; } else { for (Exclude exclude : getExcludes()) { if (exclude.isExcluded(tableName)) { return false; } } return true; } } /** * Validate excludes and base class. */ public void validate(final MessageRecorder msgRecorder) { msgRecorder.pushContextName("PatternTableDef"); try { checkAttributeString(msgRecorder, pattern.pattern(), "pattern"); for (Exclude exclude : getExcludes()) { exclude.validate(msgRecorder); } super.validate(msgRecorder); } finally { msgRecorder.popContextName(); } } public void print(final PrintWriter pw, final String prefix) { pw.print(prefix); pw.println("ExplicitRules.PatternTableDef:"); super.print(pw, prefix); String subprefix = prefix + " "; String subsubprefix = subprefix + " "; pw.print(subprefix); pw.print("pattern="); pw.print(this.pattern.pattern()); pw.print(":"); pw.println(this.pattern.flags()); pw.print(subprefix); pw.println("Excludes: ["); Iterator it = this.excludes.iterator(); while (it.hasNext()) { Exclude exclude = it.next(); exclude.print(pw, subsubprefix); } pw.print(subprefix); pw.println("]"); } } /** * Helper method used to determine if an attribute with name attrName has a * non-empty value. * * @param msgRecorder * @param attrValue * @param attrName */ private static void checkAttributeString( final MessageRecorder msgRecorder, final String attrValue, final String attrName) { if (attrValue == null) { msgRecorder.reportError(mres.NullAttributeString.str( msgRecorder.getContext(), attrName)); } else if (attrValue.length() == 0) { msgRecorder.reportError(mres.EmptyAttributeString.str( msgRecorder.getContext(), attrName)); } } private ExplicitRules() { } } // End ExplicitRules.java mondrian-3.4.1/src/main/mondrian/rolap/RestrictedMemberReader.java0000644000175000017500000002461011735330606025116 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho // All Rights Reserved. // // jhyde, Feb 26, 2003 */ package mondrian.rolap; import mondrian.olap.*; import mondrian.resource.MondrianResource; import mondrian.rolap.sql.MemberChildrenConstraint; import mondrian.rolap.sql.TupleConstraint; import java.util.*; /** * A RestrictedMemberReader reads only the members of a hierarchy * allowed by a role's access profile. * * @author jhyde * @since Feb 26, 2003 */ class RestrictedMemberReader extends DelegatingMemberReader { private final Role.HierarchyAccess hierarchyAccess; private final boolean ragged; private final SqlConstraintFactory sqlConstraintFactory = SqlConstraintFactory.instance(); /** * Creates a RestrictedMemberReader. * *

There's no filtering to be done unless * either the role has restrictions on this hierarchy, * or the hierarchy is ragged; there's a pre-condition to this effect.

* * @param memberReader Underlying (presumably unrestricted) member reader * @param role Role whose access profile to obey. The role must have * restrictions on this hierarchy * @pre role.getAccessDetails(memberReader.getHierarchy()) != null || * memberReader.getHierarchy().isRagged() */ RestrictedMemberReader(MemberReader memberReader, Role role) { super(memberReader); RolapHierarchy hierarchy = memberReader.getHierarchy(); ragged = hierarchy.isRagged(); if (role.getAccessDetails(hierarchy) == null) { assert ragged; hierarchyAccess = RoleImpl.createAllAccess(hierarchy); } else { hierarchyAccess = role.getAccessDetails(hierarchy); } } public boolean setCache(MemberCache cache) { // Don't support cache-writeback. It would confuse the cache! return false; } public RolapMember getLeadMember(RolapMember member, int n) { int i = 0; int increment = 1; if (n < 0) { increment = -1; n = -n; } while (i < n) { member = memberReader.getLeadMember(member, increment); if (member.isNull()) { return member; } if (canSee(member)) { ++i; } } return member; } public void getMemberChildren( RolapMember parentMember, List children) { MemberChildrenConstraint constraint = sqlConstraintFactory.getMemberChildrenConstraint(null); getMemberChildren(parentMember, children, constraint); } public void getMemberChildren( RolapMember parentMember, List children, MemberChildrenConstraint constraint) { List fullChildren = new ArrayList(); memberReader.getMemberChildren(parentMember, fullChildren, constraint); processMemberChildren(fullChildren, children, constraint); } private void processMemberChildren( List fullChildren, List children, MemberChildrenConstraint constraint) { // todo: optimize if parentMember is beyond last level List grandChildren = null; for (int i = 0; i < fullChildren.size(); i++) { RolapMember member = fullChildren.get(i); // If a child is hidden (due to raggedness) include its children. // This must be done before applying access-control. if (ragged) { if (member.isHidden()) { // Replace this member with all of its children. // They might be hidden too, but we'll get to them in due // course. They also might be access-controlled; that's why // we deal with raggedness before we apply access-control. fullChildren.remove(i); if (grandChildren == null) { grandChildren = new ArrayList(); } else { grandChildren.clear(); } memberReader.getMemberChildren( member, grandChildren, constraint); fullChildren.addAll(i, grandChildren); // Step back to before the first child we just inserted, // and go through the loop again. --i; continue; } } // Filter out children which are invisible because of // access-control. final Access access; if (hierarchyAccess != null) { access = hierarchyAccess.getAccess(member); } else { access = Access.ALL; } switch (access) { case NONE: break; default: children.add(member); break; } } } /** * Writes to members which we can see. * @param members Input list * @param filteredMembers Output list */ private void filterMembers( List members, List filteredMembers) { for (RolapMember member : members) { if (canSee(member)) { filteredMembers.add(member); } } } private boolean canSee(final RolapMember member) { if (ragged && member.isHidden()) { return false; } if (hierarchyAccess != null) { final Access access = hierarchyAccess.getAccess(member); return access != Access.NONE; } return true; } public void getMemberChildren( List parentMembers, List children) { MemberChildrenConstraint constraint = sqlConstraintFactory.getMemberChildrenConstraint(null); getMemberChildren(parentMembers, children, constraint); } public synchronized void getMemberChildren( List parentMembers, List children, MemberChildrenConstraint constraint) { // for (Iterator i = parentMembers.iterator(); i.hasNext();) { // RolapMember parentMember = (RolapMember) i.next(); // getMemberChildren(parentMember, children, constraint); // } List fullChildren = new ArrayList(); super.getMemberChildren(parentMembers, fullChildren, constraint); processMemberChildren(fullChildren, children, constraint); } public List getRootMembers() { int topLevelDepth = hierarchyAccess.getTopLevelDepth(); if (topLevelDepth > 0) { RolapLevel topLevel = (RolapLevel) getHierarchy().getLevels()[topLevelDepth]; final List memberList = getMembersInLevel(topLevel, 0, Integer.MAX_VALUE); if (memberList.isEmpty()) { throw MondrianResource.instance() .HierarchyHasNoAccessibleMembers.ex( getHierarchy().getUniqueName()); } return memberList; } return super.getRootMembers(); } public List getMembersInLevel( RolapLevel level, int startOrdinal, int endOrdinal) { TupleConstraint constraint = sqlConstraintFactory.getLevelMembersConstraint(null); return getMembersInLevel(level, startOrdinal, endOrdinal, constraint); } public List getMembersInLevel( RolapLevel level, int startOrdinal, int endOrdinal, TupleConstraint constraint) { if (hierarchyAccess != null) { final int depth = level.getDepth(); if (depth < hierarchyAccess.getTopLevelDepth()) { return Collections.emptyList(); } if (depth > hierarchyAccess.getBottomLevelDepth()) { return Collections.emptyList(); } } final List membersInLevel = memberReader.getMembersInLevel( level, startOrdinal, endOrdinal, constraint); List filteredMembers = new ArrayList(); filterMembers(membersInLevel, filteredMembers); return filteredMembers; } public RolapMember getDefaultMember() { RolapMember defaultMember = (RolapMember) getHierarchy().getDefaultMember(); if (defaultMember != null) { Access i = hierarchyAccess.getAccess(defaultMember); if (i != Access.NONE) { return defaultMember; } } final List rootMembers = getRootMembers(); if (rootMembers.size() == 1) { return rootMembers.get(0); } else { return new MultiCardinalityDefaultMember(rootMembers.get(0)); } } /** * This is a special subclass of {@link DelegatingRolapMember}. * It is needed because {@link Evaluator} doesn't support multi cardinality * default members. RolapHierarchy.LimitedRollupSubstitutingMemberReader * .substitute() looks for this class and substitutes the *

FIXME: If/when we refactor evaluator to support * multi cardinality default members, we can remove this. */ static class MultiCardinalityDefaultMember extends DelegatingRolapMember { protected MultiCardinalityDefaultMember(RolapMember member) { super(member); } } public RolapMember getMemberParent(RolapMember member) { RolapMember parentMember = member.getParentMember(); // Skip over hidden parents. while (parentMember != null && parentMember.isHidden()) { parentMember = parentMember.getParentMember(); } // Skip over non-accessible parents. if (parentMember != null) { if (hierarchyAccess.getAccess(parentMember) == Access.NONE) { return null; } } return parentMember; } } // End RestrictedMemberReader.java mondrian-3.4.1/src/main/mondrian/rolap/Modulos.java0000644000175000017500000001613211735330606022155 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.Axis; /** * Modulos implementations encapsulate algorithms to map between integral * ordinals and position arrays. There are particular implementations for * the most likely cases where the number of axes is 1, 2 and 3 * as well as a general implementation. *

* Suppose the result is 4 x 3 x 2, then modulo = {1, 4, 12, 24}. * *

* Then the ordinal of cell (3, 2, 1) *

 *  = (modulo[0] * 3) + (modulo[1] * 2) + (modulo[2] * 1)
 *  = (1 * 3) + (4 * 2) + (12 * 1)
 *  = 23
 * 

*

* Reverse calculation: *

 * p[0] = (23 % modulo[1]) / modulo[0] = (23 % 4) / 1 = 3
 * p[1] = (23 % modulo[2]) / modulo[1] = (23 % 12) / 4 = 2
 * p[2] = (23 % modulo[3]) / modulo[2] = (23 % 24) / 12 = 1
 * 

* * @author jhyde */ public interface Modulos { public class Generator { public static Modulos create(Axis[] axes) { switch (axes.length) { case 0: return new Modulos.Zero(axes); case 1: return new Modulos.One(axes); case 2: return new Modulos.Two(axes); case 3: return new Modulos.Three(axes); default: return new Modulos.Many(axes); } } // Used for testing only public static Modulos createMany(Axis[] axes) { return new Modulos.Many(axes); } public static Modulos createMany(int[] lengths) { return new Modulos.Many(lengths); } } public abstract class Base implements Modulos { protected final int[] modulos; protected Base(final Axis[] axes) { this.modulos = new int[axes.length + 1]; this.modulos[0] = 1; } protected Base(final int[] lengths) { this.modulos = new int[lengths.length + 1]; this.modulos[0] = 1; } public abstract int[] getCellPos(int cellOrdinal); public abstract int getCellOrdinal(int[] pos); public String toString() { StringBuilder buf = new StringBuilder(); buf.append('('); for (int i = 0; i < modulos.length; i++) { if (i > 0) { buf.append(','); } buf.append(modulos[i]); } buf.append(')'); return buf.toString(); } } public class Zero extends Base { private static final int[] pos = new int[0]; private Zero(final Axis[] axes) { super(axes); } public final int[] getCellPos(final int cellOrdinal) { return pos; } public final int getCellOrdinal(final int[] pos) { return 0; } } public class One extends Base { private One(final Axis[] axes) { super(axes); this.modulos[1] = axes[0].getPositions().size(); } public final int[] getCellPos(final int cellOrdinal) { return new int[] { (cellOrdinal % this.modulos[1]) }; } public final int getCellOrdinal(final int[] pos) { return (pos[0] * modulos[0]); } } public class Two extends Base { private Two(final Axis[] axes) { super(axes); int modulo = axes[0].getPositions().size(); this.modulos[1] = modulo; modulo *= axes[1].getPositions().size(); this.modulos[2] = modulo; } public final int[] getCellPos(final int cellOrdinal) { final int[] modulos = this.modulos; return new int[] { (cellOrdinal % modulos[1]), (cellOrdinal % modulos[2]) / modulos[1] }; } public final int getCellOrdinal(final int[] pos) { final int[] modulos = this.modulos; return (pos[0] * modulos[0]) + (pos[1] * modulos[1]); } } public class Three extends Base { private Three(final Axis[] axes) { super(axes); int modulo = axes[0].getPositions().size(); this.modulos[1] = modulo; modulo *= axes[1].getPositions().size(); this.modulos[2] = modulo; modulo *= axes[2].getPositions().size(); this.modulos[3] = modulo; } public final int[] getCellPos(final int cellOrdinal) { final int[] modulos = this.modulos; return new int[] { (cellOrdinal % modulos[1]), (cellOrdinal % modulos[2]) / modulos[1], (cellOrdinal % modulos[3]) / modulos[2] }; } public final int getCellOrdinal(final int[] pos) { final int[] modulos = this.modulos; return (pos[0] * modulos[0]) + (pos[1] * modulos[1]) + (pos[2] * modulos[2]); } } public class Many extends Base { private Many(final Axis[] axes) { super(axes); int modulo = 1; for (int i = 0; i < axes.length; i++) { modulo *= axes[i].getPositions().size(); this.modulos[i + 1] = modulo; } } private Many(final int[] lengths) { super(lengths); int modulo = 1; for (int i = 0; i < lengths.length; i++) { modulo *= lengths[i]; this.modulos[i + 1] = modulo; } } public int[] getCellPos(final int cellOrdinal) { final int[] modulos = this.modulos; final int size = modulos.length - 1; final int[] pos = new int[size]; for (int i = 0; i < size; i++) { pos[i] = (cellOrdinal % modulos[i + 1]) / modulos[i]; } return pos; } public int getCellOrdinal(final int[] pos) { final int[] modulos = this.modulos; final int size = modulos.length - 1; int ordinal = 0; for (int i = 0; i < size; i++) { ordinal += pos[i] * modulos[i]; } return ordinal; } } /** * Converts a cell ordinal to a set of cell coordinates. Converse of * {@link #getCellOrdinal}. For example, if this result is 10 x 10 x 10, * then cell ordinal 537 has coordinates (5, 3, 7). * * @param cellOrdinal Cell ordinal * @return cell coordinates */ int[] getCellPos(int cellOrdinal); /** * Converts a set of cell coordinates to a cell ordinal. Converse of * {@link #getCellPos}. * * @param pos Cell coordinates * @return cell ordinal */ int getCellOrdinal(int[] pos); } // End Modulos.java mondrian-3.4.1/src/main/mondrian/rolap/StarPredicate.java0000644000175000017500000000710311735330606023263 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2007 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.rolap.sql.SqlQuery; import java.util.List; /** * Condition which constrains a set of values of a single * {@link mondrian.rolap.RolapStar.Column} or a set of columns. * *

For example, the predicate * Range([Time].[1997].[Q3], [Time].[1998].[Q2]) * constrains the year and quarter columns: * *

*   ((year = 1997 and quarter >= 'Q3')
*     or (year > 1997))
* and ((year = 1998 and quarter <= 'Q2')
*     or (year < 1998))
* * @author jhyde * @since Jan 15, 2007 */ public interface StarPredicate { /** * Returns a list of constrained columns. * * @return List of constrained columns */ public List getConstrainedColumnList(); /** * Returns a bitmap of constrained columns to speed up comparison * @return bitmap representing all constraining columns. */ public BitKey getConstrainedColumnBitKey(); /** * Appends a description of this predicate to a StringBuilder. * For example:
    *
  • =any
  • *
  • =5
  • *
  • in (2, 4, 6)
  • *
* * @param buf Builder to append to */ public abstract void describe(StringBuilder buf); /** * Evaluates a constraint against a list of values. * *

If one of the values is {@link #WILDCARD}, returns true if constraint is * true for all possible values of that column. * * @param valueList List of values, one for each constrained column * @return Whether constraint holds for given set of values */ public boolean evaluate(List valueList); /** * Returns whether this Predicate has the same constraining effect as the * other constraint. This is weaker than {@link Object#equals(Object)}: it * is possible for two different members to constrain the same column in the * same way. * * @param that Other predicate * @return whether the other predicate is equivalent */ boolean equalConstraint(StarPredicate that); /** * Returns the logical inverse of this Predicate. The result is a Predicate * which holds whenever this predicate holds but the other does not. * * @pre predicate != null * @param predicate Predicate * @return Combined predicate */ StarPredicate minus(StarPredicate predicate); /** * Returns this union of this Predicate with another. The result is a * Predicate which holds whenever either predicate holds. * * @pre predicate != null * @param predicate Predicate * @return Combined predicate */ StarPredicate or(StarPredicate predicate); /** * Returns this intersection of this Predicate with another. The result is a * Predicate which holds whenever both predicates hold. * * @pre predicate != null * @param predicate Predicate * @return Combined predicate */ StarPredicate and(StarPredicate predicate); /** * Wildcard value for {@link #evaluate(java.util.List)}. */ Object WILDCARD = new Object(); void toSql(SqlQuery sqlQuery, StringBuilder buf); } // End StarPredicate.java mondrian-3.4.1/src/main/mondrian/rolap/ResultLoader.java0000644000175000017500000001641711735330606023146 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. // // jhyde, Feb 21, 2003 */ package mondrian.rolap; import mondrian.calc.TupleList; import mondrian.olap.Util; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; /** * Loader to be iterated to load all results from database. * * @author luis f. canals */ public class ResultLoader { private final List targets; private final int enumTargetCount; private final boolean execQuery; private final String message; private final TupleList partialResult; private final List> newPartialResult; private final SqlStatement stmt; private final int[] srcMemberIdxes; int currPartialResultIdx = 0; public ResultLoader( final int enumTargetCount, final List targets, final SqlStatement stmt, final boolean execQuery, final TupleList partialResult, final List> newPartialResult) throws SQLException { assert (stmt != null) == execQuery; this.targets = targets; this.enumTargetCount = enumTargetCount; this.stmt = stmt; this.execQuery = execQuery; this.partialResult = partialResult; this.newPartialResult = newPartialResult; this.srcMemberIdxes = enumTargetCount > 0 ? new int[enumTargetCount] : null; this.message = "Populating member cache with members for " + targets; } public boolean loadResult() throws SQLException { /* if (limit > 0 && limit < ++fetchCount) { throw MondrianResource.instance().MemberFetchLimitExceeded .ex((long) limit); } */ if (enumTargetCount == 0) { int column = 0; for (TargetBase target : targets) { target.removeCurrMember(); column = target.addRow(stmt, column); } } else { int firstEnumTarget = 0; for (; firstEnumTarget < targets.size(); firstEnumTarget++) { if (targets.get(firstEnumTarget).getSrcMembers() != null) { break; } } List partialRow; if (execQuery) { partialRow = null; } else { partialRow = Util.cast(partialResult.get(currPartialResultIdx)); } resetCurrMembers(partialRow); addTargets( 0, firstEnumTarget, enumTargetCount, srcMemberIdxes, message); if (newPartialResult != null) { savePartialResult(newPartialResult); } } boolean moreRows; if (execQuery) { moreRows = stmt.getResultSet().next(); if (moreRows) { ++stmt.rowCount; } } else { currPartialResultIdx++; moreRows = currPartialResultIdx < partialResult.size(); } return moreRows; } /** * Closes internal statement. */ public void close() { if (this.stmt != null) { this.stmt.close(); } } /** * Handles an error, and returns an exception that the caller should then * throw. * * @param e Exception * @return Wrapper exception */ public RuntimeException handle(Exception e) { if (stmt != null) { return stmt.handle(e); } else { return Util.newError(e, message); } } // // Private stuff ------------------------------- // /** * Sets the current member for those targets that retrieve their column * values from native sql. * * @param partialRow if set, previously cached result set */ private void resetCurrMembers(List partialRow) { int nativeTarget = 0; for (TargetBase target : targets) { if (target.getSrcMembers() == null) { if (partialRow != null) { target.setCurrMember(partialRow.get(nativeTarget++)); } else { target.removeCurrMember(); } } } } /** * Recursively forms the cross product of a row retrieved through sql * with each of the targets that contains an enumerated set of members. * * @param currEnumTargetIdx current enum target that recursion * is being applied on * @param currTargetIdx index within the list of a targets that * currEnumTargetIdx corresponds to * @param nEnumTargets number of targets that have enumerated members * @param srcMemberIdxes for each enumerated target, the current member * to be retrieved to form the current cross product row * @param message Message to issue on failure */ private void addTargets( int currEnumTargetIdx, int currTargetIdx, int nEnumTargets, int[] srcMemberIdxes, String message) { TargetBase currTarget = targets.get(currTargetIdx); for (int i = 0; i < currTarget.getSrcMembers().size(); i++) { srcMemberIdxes[currEnumTargetIdx] = i; if (currEnumTargetIdx < nEnumTargets - 1) { int nextTargetIdx = currTargetIdx + 1; for (; nextTargetIdx < targets.size(); nextTargetIdx++) { if (targets.get(nextTargetIdx).getSrcMembers() != null) { break; } } addTargets( currEnumTargetIdx + 1, nextTargetIdx, nEnumTargets, srcMemberIdxes, message); } else { int column = 0; int enumTargetIdx = 0; for (TargetBase target : targets) { if (target.getSrcMembers() == null) { try { column = target.addRow(stmt, column); } catch (Throwable e) { throw Util.newError(e, message); } } else { RolapMember member = target.getSrcMembers().get( srcMemberIdxes[enumTargetIdx++]); target.add(member); } } } } } /** * Retrieves the current members fetched from the targets executed * through sql and form tuples, adding them to partialResult * * @param partialResult list containing the columns and rows corresponding * to data fetched through sql */ private void savePartialResult(List> partialResult) { List row = new ArrayList(); for (TargetBase target : targets) { if (target.getSrcMembers() == null) { row.add(target.getCurrMember()); } } partialResult.add(row); } } // End ResultLoader.java mondrian-3.4.1/src/main/mondrian/rolap/SubstitutingMemberReader.java0000644000175000017500000001735511735330606025522 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2010 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.Id; import mondrian.rolap.sql.MemberChildrenConstraint; import mondrian.rolap.sql.TupleConstraint; import java.sql.SQLException; import java.util.*; /** * Implementation of {@link MemberReader} which replaces given members * with a substitute. * *

Derived classes must implement the {@link #substitute(RolapMember)} and * {@link #desubstitute(RolapMember)} methods. * * @author jhyde * @since Oct 5, 2007 */ public abstract class SubstitutingMemberReader extends DelegatingMemberReader { private final TupleReader.MemberBuilder memberBuilder = new SubstitutingMemberBuilder(); /** * Creates a SubstitutingMemberReader. * * @param memberReader Parent member reader */ SubstitutingMemberReader(MemberReader memberReader) { super(memberReader); } // Helper methods private List desubstitute(List members) { List list = new ArrayList(members.size()); for (RolapMember member : members) { list.add(desubstitute(member)); } return list; } private List substitute(List members) { List list = new ArrayList(members.size()); for (RolapMember member : members) { list.add(substitute(member)); } return list; } // ~ -- Implementations of MemberReader methods --------------------------- @Override public RolapMember getLeadMember(RolapMember member, int n) { return substitute( memberReader.getLeadMember(desubstitute(member), n)); } @Override public List getMembersInLevel( RolapLevel level, int startOrdinal, int endOrdinal) { return substitute( memberReader.getMembersInLevel(level, startOrdinal, endOrdinal)); } @Override public void getMemberRange( RolapLevel level, RolapMember startMember, RolapMember endMember, List list) { memberReader.getMemberRange( level, desubstitute(startMember), desubstitute(endMember), new SubstitutingMemberList(list)); } @Override public int compare( RolapMember m1, RolapMember m2, boolean siblingsAreEqual) { return memberReader.compare( desubstitute(m1), desubstitute(m2), siblingsAreEqual); } @Override public RolapHierarchy getHierarchy() { return memberReader.getHierarchy(); } @Override public boolean setCache(MemberCache cache) { // cache semantics don't make sense if members are not comparable throw new UnsupportedOperationException(); } @Override public List getMembers() { // might make sense, but I doubt it throw new UnsupportedOperationException(); } @Override public List getRootMembers() { return substitute(memberReader.getRootMembers()); } @Override public void getMemberChildren( RolapMember parentMember, List children) { memberReader.getMemberChildren( desubstitute(parentMember), new SubstitutingMemberList(children)); } @Override public void getMemberChildren( List parentMembers, List children) { memberReader.getMemberChildren( desubstitute(parentMembers), new SubstitutingMemberList(children)); } @Override public int getMemberCount() { return memberReader.getMemberCount(); } @Override public RolapMember lookupMember( List uniqueNameParts, boolean failIfNotFound) { return substitute( memberReader.lookupMember(uniqueNameParts, failIfNotFound)); } @Override public void getMemberChildren( RolapMember member, List children, MemberChildrenConstraint constraint) { memberReader.getMemberChildren( desubstitute(member), new SubstitutingMemberList(children), constraint); } @Override public void getMemberChildren( List parentMembers, List children, MemberChildrenConstraint constraint) { memberReader.getMemberChildren( desubstitute(parentMembers), new SubstitutingMemberList(children), constraint); } @Override public List getMembersInLevel( RolapLevel level, int startOrdinal, int endOrdinal, TupleConstraint constraint) { return substitute( memberReader.getMembersInLevel( level, startOrdinal, endOrdinal, constraint)); } @Override public RolapMember getDefaultMember() { return substitute(memberReader.getDefaultMember()); } @Override public RolapMember getMemberParent(RolapMember member) { return substitute(memberReader.getMemberParent(desubstitute(member))); } @Override public TupleReader.MemberBuilder getMemberBuilder() { return memberBuilder; } /** * List which writes through to an underlying list, substituting members * as they are written and desubstituting as they are read. */ private class SubstitutingMemberList extends AbstractList { private final List list; SubstitutingMemberList(List list) { this.list = list; } @Override public RolapMember get(int index) { return desubstitute(list.get(index)); } @Override public int size() { return list.size(); } @Override public RolapMember set(int index, RolapMember element) { return desubstitute(list.set(index, substitute(element))); } @Override public void add(int index, RolapMember element) { list.add(index, substitute(element)); } @Override public RolapMember remove(int index) { return list.remove(index); } } private class SubstitutingMemberBuilder implements TupleReader.MemberBuilder { public MemberCache getMemberCache() { return memberReader.getMemberBuilder().getMemberCache(); } public Object getMemberCacheLock() { return memberReader.getMemberBuilder().getMemberCacheLock(); } public RolapMember makeMember( RolapMember parentMember, RolapLevel childLevel, Object value, Object captionValue, boolean parentChild, SqlStatement stmt, Object key, int column) throws SQLException { return substitute( memberReader.getMemberBuilder().makeMember( desubstitute(parentMember), childLevel, value, captionValue, parentChild, stmt, key, column)); } public RolapMember allMember() { return substitute(memberReader.getHierarchy().getAllMember()); } } } // End SubstitutingMemberReader.java mondrian-3.4.1/src/main/mondrian/rolap/sql/0000755000175000017500000000000011735330606020464 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/rolap/sql/MemberListCrossJoinArg.java0000644000175000017500000002166411735330606025667 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.sql; import mondrian.olap.MondrianProperties; import mondrian.rolap.*; import mondrian.rolap.aggmatcher.AggStar; import java.util.ArrayList; import java.util.List; /** * Represents an enumeration {member1, member2, ...}. * All members must to the same level and are non-calculated. */ public class MemberListCrossJoinArg implements CrossJoinArg { private final List members; private final RolapLevel level; private final boolean restrictMemberTypes; private final boolean hasCalcMembers; private final boolean hasNonCalcMembers; private final boolean hasAllMember; private final boolean exclude; private MemberListCrossJoinArg( RolapLevel level, List members, boolean restrictMemberTypes, boolean hasCalcMembers, boolean hasNonCalcMembers, boolean hasAllMember, boolean exclude) { this.level = level; this.members = members; this.restrictMemberTypes = restrictMemberTypes; this.hasCalcMembers = hasCalcMembers; this.hasNonCalcMembers = hasNonCalcMembers; this.hasAllMember = hasAllMember; this.exclude = exclude; } private static boolean isArgSizeSupported( RolapEvaluator evaluator, int argSize) { boolean argSizeNotSupported = false; // Note: arg size 0 is accepted as valid CJ argument // This is used to push down the "1 = 0" predicate // into the emerging CJ so that the entire CJ can // be natively evaluated. // First check that the member list will not result in a predicate // longer than the underlying DB could support. if (argSize > MondrianProperties.instance().MaxConstraints.get()) { argSizeNotSupported = true; } return !argSizeNotSupported; } /** * Creates an instance of {@link CrossJoinArg}, * or returns null if the arguments are invalid. This method also * records properties of the member list such as containing * calc/non calc members, and containing the All member. * *

If restrictMemberTypes is set, then the resulting argument could * contain calculated members. The newly created CrossJoinArg is marked * appropriately for special handling downstream. * *

If restrictMemberTypes is false, then the resulting argument * contains non-calculated members of the same level (after filtering * out any null members). * * @param evaluator the current evaluator * @param args members in the list * @param restrictMemberTypes whether calculated members are allowed * @param exclude Whether to exclude tuples that match the predicate * @return MemberListCrossJoinArg if member list is well formed, * null if not. */ public static CrossJoinArg create( RolapEvaluator evaluator, final List args, final boolean restrictMemberTypes, boolean exclude) { // First check that the member list will not result in a predicate // longer than the underlying DB could support. if (!isArgSizeSupported(evaluator, args.size())) { return null; } RolapLevel level = null; RolapLevel nullLevel = null; boolean hasCalcMembers = false; boolean hasNonCalcMembers = false; // Crossjoin Arg is an empty member list. // This is used to push down the constant "false" condition to the // native evaluator. if (args.size() == 0) { hasNonCalcMembers = true; } boolean hasAllMember = false; for (RolapMember m : args) { if (m.isNull()) { // we're going to filter out null members anyway; // don't choke on the fact that their level // doesn't match that of others nullLevel = m.getLevel(); continue; } // If "All" member, native evaluation is not possible // because "All" member does not have a corresponding // relational representation. // // "All" member is ignored during SQL generation. // The complete MDX query can be evaluated natively only // if there is non all member on at least one level; // otherwise the generated SQL is an empty string. // See SqlTupleReader.addLevelMemberSql() // if (m.isAll()) { hasAllMember = true; } if (m.isCalculated() && !m.isParentChildLeaf()) { if (restrictMemberTypes) { return null; } hasCalcMembers = true; } else { hasNonCalcMembers = true; } if (level == null) { level = m.getLevel(); } else if (!level.equals(m.getLevel())) { // Members should be on the same level. return null; } } if (level == null) { // all members were null; use an arbitrary one of the // null levels since the SQL predicate is going to always // fail anyway level = nullLevel; } // level will be null for an empty CJ input that is pushed down // to the native evaluator. // This case is not treated as a non-native input. if ((level != null) && (!level.isSimple() && !supportedParentChild(level, args))) { return null; } List members = new ArrayList(); for (RolapMember m : args) { if (m.isNull()) { // filter out null members continue; } members.add(m); } return new MemberListCrossJoinArg( level, members, restrictMemberTypes, hasCalcMembers, hasNonCalcMembers, hasAllMember, exclude); } private static boolean supportedParentChild( RolapLevel level, List args) { if (level.isParentChild()) { boolean allArgsLeaf = true; for (RolapMember rolapMember : args) { if (!rolapMember.isParentChildLeaf()) { allArgsLeaf = false; break; } } return allArgsLeaf; } return false; } public RolapLevel getLevel() { return level; } public List getMembers() { return members; } public boolean isPreferInterpreter(boolean joinArg) { if (joinArg) { // If this enumeration only contains calculated members, // prefer non-native evaluation. return hasCalcMembers && !hasNonCalcMembers; } else { // For non-join usage, always prefer non-native // eval, since the members are already known. return true; } } public void addConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar) { SqlConstraintUtils.addMemberConstraint( sqlQuery, baseCube, aggStar, members, restrictMemberTypes, true, exclude); } /** * Returns whether the input CJ arg is empty. * *

This is used to selectively push down empty input arg into the * native evaluator. * * @return whether the input CJ arg is empty */ public boolean isEmptyCrossJoinArg() { return (level == null && members.size() == 0); } public boolean hasCalcMembers() { return hasCalcMembers; } public boolean hasAllMember() { return hasAllMember; } public int hashCode() { int c = 12; for (RolapMember member : members) { c = 31 * c + member.hashCode(); } if (restrictMemberTypes) { c += 1; } if (exclude) { c += 7; } return c; } public boolean equals(Object obj) { if (!(obj instanceof MemberListCrossJoinArg)) { return false; } MemberListCrossJoinArg that = (MemberListCrossJoinArg) obj; if (this.restrictMemberTypes != that.restrictMemberTypes) { return false; } if (this.exclude != that.exclude) { return false; } for (int i = 0; i < members.size(); i++) { if (this.members.get(i) != that.members.get(i)) { return false; } } return true; } } // End MemberListCrossJoinArg.java mondrian-3.4.1/src/main/mondrian/rolap/sql/TupleConstraint.java0000644000175000017500000000545711735330606024500 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.sql; import mondrian.olap.Evaluator; import mondrian.rolap.*; import mondrian.rolap.aggmatcher.AggStar; /** * Restricts the SQL result of {@link mondrian.rolap.TupleReader}. This is also * used by * {@link mondrian.rolap.SqlMemberSource#getMembersInLevel(RolapLevel, int, int, TupleConstraint)}. * * @see mondrian.rolap.TupleReader * @see mondrian.rolap.SqlMemberSource * * @author av */ public interface TupleConstraint extends SqlConstraint { /** * Modifies a Level.Members query. * * @param sqlQuery the query to modify * @param aggStar aggregate star to use * @param baseCube base cube for virtual cube constraints */ public void addConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar); /** * Will be called multiple times for every "group by" level in * Level.Members query, i.e. the level that contains the members and all * parent levels except All. * If the condition requires so, * it may join the levels table to the fact table. * * @param query the query to modify * @param baseCube base cube for virtual cube constraints * @param aggStar Aggregate table, or null if query is against fact table * @param level the level which is accessed in the Level.Members query */ public void addLevelConstraint( SqlQuery query, RolapCube baseCube, AggStar aggStar, RolapLevel level); /** * When the members of a level are fetched, the result is grouped * by into parents and their children. These parent/children are * stored in the parent/children cache, whose key consists of the parent * and the MemberChildrenConstraint#hashKey(). So we need a matching * MemberChildrenConstraint to store the parent with its children into * the parent/children cache. * *

The returned MemberChildrenConstraint must be one that would have * returned the same children for the given parent as the MemberLevel query * has found for that parent. * *

If null is returned, the parent/children will not be cached (but the * level/members still will be). */ MemberChildrenConstraint getMemberChildrenConstraint(RolapMember parent); /** * @return the evaluator currently associated with the constraint; null * if there is no associated evaluator */ public Evaluator getEvaluator(); } // End TupleConstraint.java mondrian-3.4.1/src/main/mondrian/rolap/sql/package.html0000644000175000017500000000012011735330606022736 0ustar drazzibdrazzib Database-independent library for generating SQL. mondrian-3.4.1/src/main/mondrian/rolap/sql/MemberChildrenConstraint.java0000644000175000017500000000475111735330606026263 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.sql; import mondrian.rolap.*; import mondrian.rolap.aggmatcher.AggStar; import java.util.List; /** * Restricts the SQL result of a MembersChildren query in SqlMemberSource. * * @see mondrian.rolap.SqlMemberSource * * @author av * @since Nov 2, 2005 */ public interface MemberChildrenConstraint extends SqlConstraint { /** * Modifies a Member.Children query so that only the children * of parent will be returned in the result set. * * @param sqlQuery the query to modify * @param baseCube base cube for virtual members * @param aggStar Aggregate star, if we are reading from an aggregate table, * @param parent the parent member that restricts the returned children */ public void addMemberConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar, RolapMember parent); /** * Modifies a Member.Children query so that (all or some) * children of all parent members contained in parents * will be returned in the result set. * * @param sqlQuery Query to modify * @param baseCube Base cube for virtual members * @param aggStar Aggregate table, or null if query is against fact table * @param parents List of parent members that restrict the returned * children */ public void addMemberConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar, List parents); /** * Will be called once for the level that contains the * children of a Member.Children query. If the condition requires so, * it may join the levels table to the fact table. * * @param query the query to modify * @param baseCube base cube for virtual members * @param aggStar Aggregate table, or null if query is against fact table * @param level the level that contains the children */ public void addLevelConstraint( SqlQuery query, RolapCube baseCube, AggStar aggStar, RolapLevel level); } // End MemberChildrenConstraint.java mondrian-3.4.1/src/main/mondrian/rolap/sql/DescendantsCrossJoinArg.java0000644000175000017500000000461411735330606026053 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.sql; import mondrian.rolap.*; import mondrian.rolap.aggmatcher.AggStar; import java.util.ArrayList; import java.util.List; /** * Represents one of: *

    *
  • Level.Members: member == null and level != null
  • *
  • Member.Children: member != null and level = * member.getLevel().getChildLevel()
  • *
  • Member.Descendants: member != null and level == some level below * member.getLevel()
  • *
*/ public class DescendantsCrossJoinArg implements CrossJoinArg { RolapMember member; RolapLevel level; public DescendantsCrossJoinArg(RolapLevel level, RolapMember member) { this.level = level; this.member = member; } public RolapLevel getLevel() { return level; } public List getMembers() { if (member == null) { return null; } final List list = new ArrayList(); list.add(member); return list; } public void addConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar) { if (member != null) { SqlConstraintUtils.addMemberConstraint( sqlQuery, baseCube, aggStar, member, true); } } public boolean isPreferInterpreter(boolean joinArg) { return false; } private boolean equals(Object o1, Object o2) { return o1 == null ? o2 == null : o1.equals(o2); } public boolean equals(Object obj) { if (!(obj instanceof DescendantsCrossJoinArg)) { return false; } DescendantsCrossJoinArg that = (DescendantsCrossJoinArg) obj; if (!equals(this.level, that.level)) { return false; } return equals(this.member, that.member); } public int hashCode() { int c = 1; if (level != null) { c = level.hashCode(); } if (member != null) { c = 31 * c + member.hashCode(); } return c; } } // End DescendantsCrossJoinArg.java mondrian-3.4.1/src/main/mondrian/rolap/sql/SqlConstraint.java0000644000175000017500000000165511735330606024142 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.sql; /** * Restricts the members that are fetched by SqlMemberSource. * * @see mondrian.rolap.SqlMemberSource * * @author av * @since Nov 2, 2005 */ public interface SqlConstraint { /** * Returns a key that becomes part of the key for caching the * result of the SQL query. So SqlConstraint instances that * produce the same SQL resultset must return equal keys * in terms of equal() and hashCode(). * @return valid key or null to prevent the result from being cached */ Object getCacheKey(); } // End SqlConstraint.java mondrian-3.4.1/src/main/mondrian/rolap/sql/SqlQuery.java0000644000175000017500000010215211735330606023115 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. // // jhyde, Mar 21, 2002 */ package mondrian.rolap.sql; import mondrian.olap.*; import mondrian.rolap.*; import mondrian.spi.Dialect; import mondrian.spi.DialectManager; import mondrian.util.Pair; import java.util.*; import javax.sql.DataSource; /** * SqlQuery allows us to build a select * statement and generate it in database-specific SQL syntax. * *

Notable differences in database syntax are:

* *
Identifier quoting
*
Oracle (and all JDBC-compliant drivers) uses double-quotes, * for example select * from "emp". Access prefers brackets, * for example select * from [emp]. mySQL allows single- and * double-quotes for string literals, and therefore does not allow * identifiers to be quoted, for example select 'foo', "bar" from * emp.
* *
AS in from clause
*
Oracle doesn't like AS in the from * clause, for example * select from emp as e vs. select * from emp * e.
* *
Column aliases
*
Some databases require that every column in the select list * has a valid alias. If the expression is an expression containing * non-alphanumeric characters, an explicit alias is needed. For example, * Oracle will barfs at select empno + 1 from emp.
* *
Parentheses around table names
*
Oracle doesn't like select * from (emp)
* *
Queries in FROM clause
*
PostgreSQL and hsqldb don't allow, for example, select * from * (select * from emp) as e.
* *
Uniqueness of index names
*
In PostgreSQL and Oracle, index names must be unique within the * database; in Access and hsqldb, they must merely be unique within their * table
* *
Datatypes
*
In Oracle, BIT is CHAR(1), TIMESTAMP is DATE. * In PostgreSQL, DOUBLE is DOUBLE PRECISION, BIT is BOOL.
* * *

* NOTE: Instances of this class are NOT thread safe so the user must make * sure this is accessed by only one thread at a time. * * @author jhyde */ public class SqlQuery { /** Controls the formatting of the sql string. */ private final boolean generateFormattedSql; private boolean distinct; private final ClauseList select; private final FromClauseList from; private final ClauseList where; private final ClauseList groupBy; private final ClauseList having; private final ClauseList orderBy; private final List groupingSets; private final ClauseList groupingFunctions; private final List types = new ArrayList(); /** Controls whether table optimization hints are used */ private boolean allowHints; /** * This list is used to keep track of what aliases have been used in the * FROM clause. One might think that a java.util.Set would be a more * appropriate Collection type, but if you only have a couple of "from * aliases", then iterating over a list is faster than doing a hash lookup * (as is used in java.util.HashSet). */ private final List fromAliases; /** The SQL dialect this query is to be generated in. */ private final Dialect dialect; /** Scratch buffer. Clear it before use. */ private final StringBuilder buf; private final Set relations = new HashSet(); private final Map mapRelationToRoot = new HashMap(); private final Map> mapRootToRelations = new HashMap>(); private final Map columnAliases = new HashMap(); private static final String INDENT = " "; /** * Base constructor used by all other constructors to create an empty * instance. * * @param dialect Dialect * @param formatted Whether to generate SQL formatted on multiple lines */ public SqlQuery(Dialect dialect, boolean formatted) { assert dialect != null; this.generateFormattedSql = formatted; // both select and from allow duplications this.select = new ClauseList(true); this.from = new FromClauseList(true); this.groupingFunctions = new ClauseList(false); this.where = new ClauseList(false); this.groupBy = new ClauseList(false); this.having = new ClauseList(false); this.orderBy = new ClauseList(false); this.fromAliases = new ArrayList(); this.buf = new StringBuilder(128); this.groupingSets = new ArrayList(); this.dialect = dialect; // REVIEW emcdermid 10-Jul-2009: It might be okay to allow // hints in all cases, but for initial implementation this // allows us to them on selectively in specific situations. // Usage will likely expand with experimentation. this.allowHints = false; } /** * Creates a SqlQuery using a given dialect and inheriting the formatting * preferences from {@link MondrianProperties#GenerateFormattedSql} * property. * * @param dialect Dialect */ public SqlQuery(Dialect dialect) { this( dialect, MondrianProperties.instance().GenerateFormattedSql.get()); } /** * Creates an empty SqlQuery with the same environment as this * one. (As per the Gang of Four 'prototype' pattern.) */ public SqlQuery cloneEmpty() { return new SqlQuery(dialect); } public void setDistinct(final boolean distinct) { this.distinct = distinct; } /** * Chooses whether table optimization hints may be used * (assuming the dialect supports it). * * @param t True to allow hints to be used, false otherwise */ public void setAllowHints(boolean t) { this.allowHints = t; } /** * Adds a subquery to the FROM clause of this Query with a given alias. * If the query already exists it either, depending on * failIfExists, throws an exception or does not add the query * and returns false. * * @param query Subquery * @param alias (if not null, must not be zero length). * @param failIfExists if true, throws exception if alias already exists * @return true if query *was* added * * @pre alias != null */ public boolean addFromQuery( final String query, final String alias, final boolean failIfExists) { assert alias != null; assert alias.length() > 0; if (fromAliases.contains(alias)) { if (failIfExists) { throw Util.newInternal( "query already contains alias '" + alias + "'"); } else { return false; } } buf.setLength(0); buf.append('('); buf.append(query); buf.append(')'); if (dialect.allowsAs()) { buf.append(" as "); } else { buf.append(' '); } dialect.quoteIdentifier(alias, buf); fromAliases.add(alias); from.add(buf.toString()); return true; } /** * Adds [schema.]table AS alias to the FROM clause. * * @param schema schema name; may be null * @param name table name * @param alias table alias, may not be null * (if not null, must not be zero length). * @param filter Extra filter condition, or null * @param hints table optimization hints, if any * @param failIfExists Whether to throw a RuntimeException if from clause * already contains this alias * * @pre alias != null * @return true if table was added */ boolean addFromTable( final String schema, final String name, final String alias, final String filter, final Map hints, final boolean failIfExists) { if (fromAliases.contains(alias)) { if (failIfExists) { throw Util.newInternal( "query already contains alias '" + alias + "'"); } else { return false; } } buf.setLength(0); dialect.quoteIdentifier(buf, schema, name); if (alias != null) { Util.assertTrue(alias.length() > 0); if (dialect.allowsAs()) { buf.append(" as "); } else { buf.append(' '); } dialect.quoteIdentifier(alias, buf); fromAliases.add(alias); } if (this.allowHints) { dialect.appendHintsAfterFromClause(buf, hints); } from.add(buf.toString()); if (filter != null) { // append filter condition to where clause addWhere("(", filter, ")"); } return true; } public void addFrom( final SqlQuery sqlQuery, final String alias, final boolean failIfExists) { addFromQuery(sqlQuery.toString(), alias, failIfExists); } /** * Adds a relation to a query, adding appropriate join conditions, unless * it is already present. * *

Returns whether the relation was added to the query. * * @param relation Relation to add * @param alias Alias of relation. If null, uses relation's alias. * @param failIfExists Whether to fail if relation is already present * @return true, if relation *was* added to query */ public boolean addFrom( final MondrianDef.RelationOrJoin relation, final String alias, final boolean failIfExists) { registerRootRelation(relation); if (relation instanceof MondrianDef.Relation) { MondrianDef.Relation relation1 = (MondrianDef.Relation) relation; if (relations.add(relation1) && !MondrianProperties.instance() .FilterChildlessSnowflakeMembers.get()) { // This relation is new to this query. Add a join to any other // relation in the same dimension. // // (If FilterChildlessSnowflakeMembers were false, // this would be unnecessary. Adding a relation automatically // adds all relations between it and the fact table.) MondrianDef.RelationOrJoin root = mapRelationToRoot.get(relation1); for (MondrianDef.Relation relation2 : relations) { if (relation2 != relation1 && mapRelationToRoot.get(relation2) == root) { addJoinBetween(root, relation1, relation2); } } } } if (relation instanceof MondrianDef.View) { final MondrianDef.View view = (MondrianDef.View) relation; final String viewAlias = (alias == null) ? view.getAlias() : alias; final String sqlString = view.getCodeSet().chooseQuery(dialect); return addFromQuery(sqlString, viewAlias, false); } else if (relation instanceof MondrianDef.InlineTable) { final MondrianDef.Relation relation1 = RolapUtil.convertInlineTableToRelation( (MondrianDef.InlineTable) relation, dialect); return addFrom(relation1, alias, failIfExists); } else if (relation instanceof MondrianDef.Table) { final MondrianDef.Table table = (MondrianDef.Table) relation; final String tableAlias = (alias == null) ? table.getAlias() : alias; return addFromTable( table.schema, table.name, tableAlias, table.getFilter(), table.getHintMap(), failIfExists); } else if (relation instanceof MondrianDef.Join) { final MondrianDef.Join join = (MondrianDef.Join) relation; return addJoin( join.left, join.getLeftAlias(), join.leftKey, join.right, join.getRightAlias(), join.rightKey, failIfExists); } else { throw Util.newInternal("bad relation type " + relation); } } private boolean addJoin( MondrianDef.RelationOrJoin left, String leftAlias, String leftKey, MondrianDef.RelationOrJoin right, String rightAlias, String rightKey, boolean failIfExists) { boolean addLeft = addFrom(left, leftAlias, failIfExists); boolean addRight = addFrom(right, rightAlias, failIfExists); boolean added = addLeft || addRight; if (added) { buf.setLength(0); dialect.quoteIdentifier(buf, leftAlias, leftKey); buf.append(" = "); dialect.quoteIdentifier(buf, rightAlias, rightKey); final String condition = buf.toString(); if (dialect.allowsJoinOn()) { from.addOn( leftAlias, leftKey, rightAlias, rightKey, condition); } else { addWhere(condition); } } return added; } private void addJoinBetween( MondrianDef.RelationOrJoin root, MondrianDef.Relation relation1, MondrianDef.Relation relation2) { List relations = mapRootToRelations.get(root); int index1 = find(relations, relation1); int index2 = find(relations, relation2); assert index1 != -1; assert index2 != -1; int min = Math.min(index1, index2); int max = Math.max(index1, index2); for (int i = max - 1; i >= min; i--) { RelInfo relInfo = relations.get(i); addJoin( relInfo.relation, relInfo.leftAlias != null ? relInfo.leftAlias : relInfo.relation.getAlias(), relInfo.leftKey, relations.get(i + 1).relation, relInfo.rightAlias != null ? relInfo.rightAlias : relations.get(i + 1).relation.getAlias(), relInfo.rightKey, false); } } private int find(List relations, MondrianDef.Relation relation) { for (int i = 0, n = relations.size(); i < n; i++) { RelInfo relInfo = relations.get(i); if (relInfo.relation.equals(relation)) { return i; } } return -1; } /** * Adds an expression to the select clause, automatically creating a * column alias. */ public String addSelect(final String expression, SqlStatement.Type type) { // Some DB2 versions (AS/400) throw an error if a column alias is // *not* used in a subsequent order by (Group by). // Derby fails on 'SELECT... HAVING' if column has alias. switch (dialect.getDatabaseProduct()) { case DB2_AS400: case DERBY: return addSelect(expression, type, null); default: return addSelect(expression, type, nextColumnAlias()); } } /** * Adds an expression to the SELECT and GROUP BY clauses. Uses the alias in * the GROUP BY clause, if the dialect requires it. * * @param expression Expression * @return Alias of expression */ public String addSelectGroupBy( final String expression, SqlStatement.Type type) { final String alias = addSelect(expression, type); addGroupBy(expression, alias); return alias; } public int getCurrentSelectListSize() { return select.size(); } public String nextColumnAlias() { return "c" + select.size(); } /** * Adds an expression to the select clause, with a specified type and * column alias. * * @param expression Expression * @param type Java type to be used to hold cursor column * @param alias Column alias (or null for no alias) * @return Column alias */ public String addSelect( final String expression, final SqlStatement.Type type, String alias) { buf.setLength(0); buf.append(expression); if (alias != null) { buf.append(" as "); dialect.quoteIdentifier(alias, buf); } select.add(buf.toString()); addType(type); columnAliases.put(expression, alias); return alias; } public String getAlias(String expression) { return columnAliases.get(expression); } public void addWhere( final String exprLeft, final String exprMid, final String exprRight) { int len = exprLeft.length() + exprMid.length() + exprRight.length(); StringBuilder buf = new StringBuilder(len); buf.append(exprLeft); buf.append(exprMid); buf.append(exprRight); addWhere(buf.toString()); } public void addWhere(RolapStar.Condition joinCondition) { String left = joinCondition.getLeft().getTableAlias(); String right = joinCondition.getRight().getTableAlias(); if (fromAliases.contains(left) && fromAliases.contains(right)) { addWhere( joinCondition.getLeft(this), " = ", joinCondition.getRight(this)); } } public void addWhere(final String expression) { where.add(expression); } public void addGroupBy(final String expression) { groupBy.add(expression); } public void addGroupBy(final String expression, final String alias) { if (dialect.requiresGroupByAlias()) { addGroupBy(dialect.quoteIdentifier(alias)); } else { addGroupBy(expression); } } public void addHaving(final String expression) { having.add(expression); } /** * Adds an item to the ORDER BY clause. * * @param expr the expr to order by * @param ascending sort direction * @param prepend whether to prepend to the current list of items * @param nullable whether the expression might be null */ public void addOrderBy( String expr, boolean ascending, boolean prepend, boolean nullable) { this.addOrderBy(expr, ascending, prepend, nullable, true); } /** * Adds an item to the ORDER BY clause. * * @param expr the expr to order by * @param ascending sort direction * @param prepend whether to prepend to the current list of items * @param nullable whether the expression might be null * @param collateNullsLast whether null values should appear first or last. */ public void addOrderBy( String expr, boolean ascending, boolean prepend, boolean nullable, boolean collateNullsLast) { String orderExpr = dialect.generateOrderItem( expr, nullable, ascending, collateNullsLast); if (prepend) { orderBy.add(0, orderExpr); } else { orderBy.add(orderExpr); } } public String toString() { buf.setLength(0); toBuffer(buf, ""); return buf.toString(); } /** * Writes this SqlQuery to a StringBuilder with each clause on a separate * line, and with the specified indentation prefix. * * @param buf String builder * @param prefix Prefix for each line */ public void toBuffer(StringBuilder buf, String prefix) { final String first = distinct ? "select distinct " : "select "; select.toBuffer(buf, generateFormattedSql, prefix, first, ", ", "", ""); groupingFunctionsToBuffer(buf, prefix); from.toBuffer( buf, generateFormattedSql, prefix, " from ", ", ", "", ""); where.toBuffer( buf, generateFormattedSql, prefix, " where ", " and ", "", ""); if (groupingSets.isEmpty()) { groupBy.toBuffer( buf, generateFormattedSql, prefix, " group by ", ", ", "", ""); } else { ClauseList.listToBuffer( buf, groupingSets, generateFormattedSql, prefix, " group by grouping sets (", ", ", ")"); } having.toBuffer( buf, generateFormattedSql, prefix, " having ", " and ", "", ""); orderBy.toBuffer( buf, generateFormattedSql, prefix, " order by ", ", ", "", ""); } private void groupingFunctionsToBuffer(StringBuilder buf, String prefix) { if (groupingSets.isEmpty()) { return; } int n = 0; for (String groupingFunction : groupingFunctions) { if (generateFormattedSql) { buf.append(",") .append(Util.nl) .append(INDENT) .append(prefix); } else { buf.append(", "); } buf.append("grouping(") .append(groupingFunction) .append(") as "); dialect.quoteIdentifier("g" + n++, buf); } } public Dialect getDialect() { return dialect; } public static SqlQuery newQuery(DataSource dataSource, String err) { final Dialect dialect = DialectManager.createDialect(dataSource, null); return new SqlQuery(dialect); } public void addGroupingSet(List groupingColumnsExpr) { ClauseList groupingList = new ClauseList(false); for (String columnExp : groupingColumnsExpr) { groupingList.add(columnExp); } groupingSets.add(groupingList); } public void addGroupingFunction(String columnExpr) { groupingFunctions.add(columnExpr); // A grouping function will end up in the select clause implicitly. It // needs a corresponding type. types.add(null); } private void addType(SqlStatement.Type type) { types.add(type); } public Pair> toSqlAndTypes() { assert types.size() == select.size() + groupingFunctions.size() : types.size() + " types, " + (select.size() + groupingFunctions.size()) + " select items in query " + this; return Pair.of(toString(), types); } public void registerRootRelation(MondrianDef.RelationOrJoin root) { // REVIEW: In this method, we are building data structures about the // structure of a star schema. These should be built into the schema, // not constructed afresh for each SqlQuery. In mondrian-4.0, // these methods and the data structures 'mapRootToRelations', // 'relations', 'mapRelationToRoot' will disappear. if (mapRelationToRoot.containsKey(root)) { return; } if (mapRootToRelations.containsKey(root)) { return; } List relations = new ArrayList(); flatten(relations, root, null, null, null, null); for (RelInfo relation : relations) { mapRelationToRoot.put(relation.relation, root); } mapRootToRelations.put(root, relations); } private void flatten( List relations, MondrianDef.RelationOrJoin root, String leftKey, String leftAlias, String rightKey, String rightAlias) { if (root instanceof MondrianDef.Join) { MondrianDef.Join join = (MondrianDef.Join) root; flatten( relations, join.left, join.leftKey, join.leftAlias, join.rightKey, join.rightAlias); flatten( relations, join.right, leftKey, leftAlias, rightKey, rightAlias); } else { relations.add( new RelInfo( (MondrianDef.Relation) root, leftKey, leftAlias, rightKey, rightAlias)); } } private static class JoinOnClause { private final String condition; private final String left; private final String right; JoinOnClause(String condition, String left, String right) { this.condition = condition; this.left = left; this.right = right; } } static class FromClauseList extends ClauseList { private final List joinOnClauses = new ArrayList(); FromClauseList(boolean allowsDups) { super(allowsDups); } public void addOn( String leftAlias, String leftKey, String rightAlias, String rightKey, String condition) { if (leftAlias == null && rightAlias == null) { // do nothing } else if (leftAlias == null) { // left is the form of 'Table'.'column' leftAlias = rightAlias; } else if (rightAlias == null) { // Only left contains table name, Table.Column = abc // store the same name for right tables rightAlias = leftAlias; } joinOnClauses.add( new JoinOnClause(condition, leftAlias, rightAlias)); } public void toBuffer(StringBuilder buf, List fromAliases) { int n = 0; for (int i = 0; i < size(); i++) { final String s = get(i); final String alias = fromAliases.get(i); if (n++ == 0) { buf.append(" from "); buf.append(s); } else { // Add "JOIN t ON (a = b ,...)" to the FROM clause. If there // is no JOIN clause matching this alias (or no JOIN clauses // at all), append just ", t". appendJoin(fromAliases.subList(0, i), s, alias, buf); } } } void appendJoin( final List addedTables, final String from, final String alias, final StringBuilder buf) { int n = 0; // first check when the current table is on the left side for (JoinOnClause joinOnClause : joinOnClauses) { // the first table was added before join, it has to be handled // specially: Table.column = expression if ((addedTables.size() == 1 && addedTables.get(0).equals(joinOnClause.left) && joinOnClause.left.equals(joinOnClause.right)) || (alias.equals(joinOnClause.left) && addedTables.contains(joinOnClause.right)) || (alias.equals(joinOnClause.right) && addedTables.contains(joinOnClause.left))) { if (n++ == 0) { buf.append(" join ").append(from).append(" on "); } else { buf.append(" and "); } buf.append(joinOnClause.condition); } } if (n == 0) { // No "JOIN ... ON" clause matching this alias (or maybe no // JOIN ... ON clauses at all, if this is a database that // doesn't support ANSI-join syntax). Append an old-style FROM // item separated by a comma. buf.append(joinOnClauses.isEmpty() ? ", " : " cross join ") .append(from); } } } static class ClauseList extends ArrayList { protected final boolean allowDups; ClauseList(final boolean allowDups) { this.allowDups = allowDups; } /** * Adds an element to this ClauseList if either duplicates are allowed * or if it has not already been added. * * @param element Element to add * @return whether element was added, per * {@link java.util.Collection#add(Object)} */ public boolean add(final String element) { if (allowDups || !contains(element)) { return super.add(element); } return false; } final void toBuffer( StringBuilder buf, boolean generateFormattedSql, String prefix, String first, String sep, String last, String empty) { if (isEmpty()) { buf.append(empty); return; } first = foo(generateFormattedSql, prefix, first); sep = foo(generateFormattedSql, prefix, sep); toBuffer(buf, first, sep, last); } static String foo( boolean generateFormattedSql, String prefix, String s) { if (generateFormattedSql) { if (s.startsWith(" ")) { // E.g. " and " s = Util.nl + prefix + s.substring(1); } if (s.endsWith(" ")) { // E.g. ", " s = s.substring(0, s.length() - 1) + Util.nl + prefix + INDENT; } else if (s.endsWith("(")) { // E.g. "(" s = s + Util.nl + prefix + INDENT; } } return s; } final void toBuffer( final StringBuilder buf, final String first, final String sep, final String last) { int n = 0; buf.append(first); for (String s : this) { if (n++ > 0) { buf.append(sep); } buf.append(s); } buf.append(last); } static void listToBuffer( StringBuilder buf, List clauseListList, boolean generateFormattedSql, String prefix, String first, String sep, String last) { first = foo(generateFormattedSql, prefix, first); sep = foo(generateFormattedSql, prefix, sep); buf.append(first); int n = 0; for (ClauseList clauseList : clauseListList) { if (n++ > 0) { buf.append(sep); } clauseList.toBuffer( buf, false, prefix, "(", ", ", ")", "()"); } buf.append(last); } } /** * Collection of alternative code for alternative dialects. */ public static class CodeSet { private final Map dialectCodes = new HashMap(); public String put(String dialect, String code) { return dialectCodes.put(dialect, code); } /** * Chooses the code variant which best matches the given Dialect. */ public String chooseQuery(Dialect dialect) { String best = getBestName(dialect); String bestCode = dialectCodes.get(best); if (bestCode != null) { return bestCode; } String genericCode = dialectCodes.get("generic"); if (genericCode == null) { throw Util.newError("View has no 'generic' variant"); } return genericCode; } private static String getBestName(Dialect dialect) { return dialect.getDatabaseProduct().getFamily().name() .toLowerCase(); } } private static class RelInfo { final MondrianDef.Relation relation; final String leftKey; final String leftAlias; final String rightKey; final String rightAlias; public RelInfo( MondrianDef.Relation relation, String leftKey, String leftAlias, String rightKey, String rightAlias) { this.relation = relation; this.leftKey = leftKey; this.leftAlias = leftAlias; this.rightKey = rightKey; this.rightAlias = rightAlias; } } } // End SqlQuery.java mondrian-3.4.1/src/main/mondrian/rolap/sql/SqlQueryChecker.java0000644000175000017500000000115611735330606024404 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2005 Pentaho and others // All Rights Reserved. // // jhyde, Mar 21, 2002 */ package mondrian.rolap.sql; /** * Runs a SQL query. * *

Useful for testing purposes. * * @author jhyde * @since 30 August, 2001 */ public interface SqlQueryChecker { void onGenerate(SqlQuery q); } // End SqlQueryChecker.java mondrian-3.4.1/src/main/mondrian/rolap/sql/CrossJoinArgFactory.java0000644000175000017500000010137611735330606025232 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.sql; import mondrian.calc.*; import mondrian.mdx.*; import mondrian.olap.*; import mondrian.olap.fun.*; import mondrian.olap.type.HierarchyType; import mondrian.olap.type.Type; import mondrian.rolap.*; import org.apache.log4j.Logger; import java.util.*; /** * Creates CrossJoinArgs for use in constraining SQL queries. * * @author kwalker * @since Dec 15, 2009 */ public class CrossJoinArgFactory { protected static final Logger LOGGER = Logger.getLogger(CrossJoinArgFactory.class); private boolean restrictMemberTypes; public CrossJoinArgFactory(boolean restrictMemberTypes) { this.restrictMemberTypes = restrictMemberTypes; } public Set buildConstraintFromAllAxes( final RolapEvaluator evaluator) { Set joinArgs = new LinkedHashSet(); for (QueryAxis ax : evaluator.getQuery().getAxes()) { List axesArgs = checkCrossJoinArg(evaluator, ax.getSet(), true); if (axesArgs != null) { for (CrossJoinArg[] axesArg : axesArgs) { joinArgs.addAll(Arrays.asList(axesArg)); } } } return joinArgs; } /** * Scans for memberChildren, levelMembers, memberDescendants, crossJoin. */ public List checkCrossJoinArg( RolapEvaluator evaluator, Exp exp) { return checkCrossJoinArg(evaluator, exp, false); } /** * Checks whether an expression can be natively evaluated. The following * expressions can be natively evaluated: *

*

    *
  • member.Children *
  • level.members *
  • descendents of a member *
  • member list *
  • filter on a dimension *
* * @param evaluator Evaluator * @param exp Expresssion * @return List of CrossJoinArg arrays. The first array represent the * CJ CrossJoinArg and the second array represent the additional * constraints. */ List checkCrossJoinArg( RolapEvaluator evaluator, Exp exp, final boolean returnAny) { if (exp instanceof NamedSetExpr) { NamedSet namedSet = ((NamedSetExpr) exp).getNamedSet(); exp = namedSet.getExp(); } if (!(exp instanceof ResolvedFunCall)) { return null; } final ResolvedFunCall funCall = (ResolvedFunCall) exp; FunDef fun = funCall.getFunDef(); Exp[] args = funCall.getArgs(); final Role role = evaluator.getSchemaReader().getRole(); CrossJoinArg[] cjArgs; cjArgs = checkMemberChildren(role, fun, args); if (cjArgs != null) { return Collections.singletonList(cjArgs); } cjArgs = checkLevelMembers(role, fun, args); if (cjArgs != null) { return Collections.singletonList(cjArgs); } cjArgs = checkDescendants(role, fun, args); if (cjArgs != null) { return Collections.singletonList(cjArgs); } final boolean exclude = false; cjArgs = checkEnumeration(evaluator, fun, args, exclude); if (cjArgs != null) { return Collections.singletonList(cjArgs); } if (returnAny) { cjArgs = checkConstrainedMeasures(evaluator, fun, args); if (cjArgs != null) { return Collections.singletonList(cjArgs); } } List allArgs = checkDimensionFilter(evaluator, fun, args); if (allArgs != null) { return allArgs; } // strip off redundant set braces, for example // { Gender.Gender.members }, or {{{ Gender.M }}} if ("{}".equalsIgnoreCase(fun.getName()) && args.length == 1) { return checkCrossJoinArg(evaluator, args[0], returnAny); } if ("NativizeSet".equalsIgnoreCase(fun.getName()) && args.length == 1) { return checkCrossJoinArg(evaluator, args[0], returnAny); } return checkCrossJoin(evaluator, fun, args, returnAny); } private CrossJoinArg[] checkConstrainedMeasures( RolapEvaluator evaluator, FunDef fun, Exp[] args) { if (isSetOfConstrainedMeasures(fun, args)) { HashMap> memberLists = new LinkedHashMap>(); for (Exp arg : args) { addConstrainingMembersToMap(arg, memberLists); } return memberListCrossJoinArgArray(memberLists, args, evaluator); } return null; } private boolean isSetOfConstrainedMeasures(FunDef fun, Exp[] args) { return fun.getName().equals("{}") && allArgsConstrainedMeasure(args); } private boolean allArgsConstrainedMeasure(Exp[] args) { for (Exp arg : args) { if (!isConstrainedMeasure(arg)) { return false; } } return true; } private boolean isConstrainedMeasure(Exp arg) { if (!(arg instanceof MemberExpr && ((MemberExpr) arg).getMember().isMeasure())) { if (arg instanceof ResolvedFunCall) { ResolvedFunCall call = (ResolvedFunCall) arg; if (call.getFunDef() instanceof SetFunDef || call.getFunDef() instanceof ParenthesesFunDef) { return allArgsConstrainedMeasure(call.getArgs()); } } return false; } Member member = ((MemberExpr) arg).getMember(); if (member instanceof RolapCalculatedMember) { Exp calcExp = ((RolapCalculatedMember) member).getFormula().getExpression(); return ((calcExp instanceof ResolvedFunCall && ((ResolvedFunCall) calcExp).getFunDef() instanceof TupleFunDef)) || calcExp instanceof Literal; } return false; } private void addConstrainingMembersToMap( Exp arg, Map> memberLists) { if (arg instanceof ResolvedFunCall) { ResolvedFunCall call = (ResolvedFunCall) arg; for (Exp callArg : call.getArgs()) { addConstrainingMembersToMap(callArg, memberLists); } } Exp[] tupleArgs = getCalculatedTupleArgs(arg); for (Exp tupleArg : tupleArgs) { Dimension dimension = tupleArg.getType().getDimension(); if (!dimension.isMeasures()) { List members; if (memberLists.containsKey(dimension)) { members = memberLists.get(dimension); } else { members = new ArrayList(); } members.add((RolapMember) ((MemberExpr) tupleArg).getMember()); memberLists.put(dimension, members); } else if (isConstrainedMeasure(tupleArg)) { addConstrainingMembersToMap(tupleArg, memberLists); } } } private Exp[] getCalculatedTupleArgs(Exp arg) { if (arg instanceof MemberExpr) { Member member = ((MemberExpr) arg).getMember(); if (member instanceof RolapCalculatedMember) { Exp formulaExp = ((RolapCalculatedMember) member) .getFormula().getExpression(); if (formulaExp instanceof ResolvedFunCall) { return ((ResolvedFunCall) formulaExp).getArgs(); } } } return new Exp[0]; } private CrossJoinArg[] memberListCrossJoinArgArray( Map> memberLists, Exp[] args, RolapEvaluator evaluator) { List argList = new ArrayList(); for (List memberList : memberLists.values()) { if (memberList.size() == countNonLiteralMeasures(args)) { //when the memberList and args list have the same length //it means there must have been a constraint on each measure //for this dimension. final CrossJoinArg cjArg = MemberListCrossJoinArg.create( evaluator, removeDuplicates(memberList), restrictMemberTypes(), false); if (cjArg != null) { argList.add(cjArg); } } } if (argList.size() > 0) { return argList.toArray(new CrossJoinArg[argList.size()]); } return null; } private List removeDuplicates(List list) { Set set = new HashSet(); List uniqueList = new ArrayList(); for (RolapMember element : list) { if (set.add(element)) { uniqueList.add(element); } } return uniqueList; } private int countNonLiteralMeasures(Exp[] length) { int count = 0; for (Exp exp : length) { if (exp instanceof MemberExpr) { Exp calcExp = ((MemberExpr) exp).getMember().getExpression(); if (!(calcExp instanceof Literal)) { count++; } } else if (exp instanceof ResolvedFunCall) { count += countNonLiteralMeasures(((ResolvedFunCall) exp).getArgs()); } } return count; } /** * Checks for CrossJoin(<set1>, <set2>), where * set1 and set2 are one of * member.children, level.members or * member.descendants. * * @param evaluator Evaluator to use if inputs are to be evaluated * @param fun The function, either "CrossJoin" or "NonEmptyCrossJoin" * @param args Inputs to the CrossJoin * @param returnAny indicates we should return any valid crossjoin args * @return array of CrossJoinArg representing the inputs */ public List checkCrossJoin( RolapEvaluator evaluator, FunDef fun, Exp[] args, final boolean returnAny) { // is this "CrossJoin([A].children, [B].children)" if (!"Crossjoin".equalsIgnoreCase(fun.getName()) && !"NonEmptyCrossJoin".equalsIgnoreCase(fun.getName())) { return null; } if (args.length != 2) { return null; } // Check if the arguments can be natively evaluated. // If not, try evaluating this argument and turning the result into // MemberListCrossJoinArg. List allArgsOneInput; // An array(size 2) of arrays(size arbitary). Each outer array represent // native inputs fro one input. CrossJoinArg[][] cjArgsBothInputs = new CrossJoinArg[2][]; CrossJoinArg[][] predicateArgsBothInputs = new CrossJoinArg[2][]; for (int i = 0; i < 2; i++) { allArgsOneInput = checkCrossJoinArg(evaluator, args[i], returnAny); if (allArgsOneInput == null || allArgsOneInput.isEmpty() || allArgsOneInput.get(0) == null) { cjArgsBothInputs[i] = expandNonNative(evaluator, args[i]); } else { // Collect CJ CrossJoinArg cjArgsBothInputs[i] = allArgsOneInput.get(0); } if (returnAny) { continue; } if (cjArgsBothInputs[i] == null) { return null; } // Collect Predicate CrossJoinArg if it exists. predicateArgsBothInputs[i] = null; if (allArgsOneInput != null && allArgsOneInput.size() == 2) { predicateArgsBothInputs[i] = allArgsOneInput.get(1); } } List allArgsBothInputs = new ArrayList(); // Now combine the cjArgs from both sides CrossJoinArg[] combinedCJArgs = Util.appendArrays( cjArgsBothInputs[0] == null ? CrossJoinArg.EMPTY_ARRAY : cjArgsBothInputs[0], cjArgsBothInputs[1] == null ? CrossJoinArg.EMPTY_ARRAY : cjArgsBothInputs[1]); allArgsBothInputs.add(combinedCJArgs); CrossJoinArg[] combinedPredicateArgs = Util.appendArrays( predicateArgsBothInputs[0] == null ? CrossJoinArg.EMPTY_ARRAY : predicateArgsBothInputs[0], predicateArgsBothInputs[1] == null ? CrossJoinArg.EMPTY_ARRAY : predicateArgsBothInputs[1]); if (combinedPredicateArgs.length > 0) { allArgsBothInputs.add(combinedPredicateArgs); } return allArgsBothInputs; } /** * Checks for a set constructor, {member1, member2, * ...} that does not contain calculated members. * * @return an {@link mondrian.rolap.sql.CrossJoinArg} instance describing the enumeration, * or null if fun represents something else. */ private CrossJoinArg[] checkEnumeration( RolapEvaluator evaluator, FunDef fun, Exp[] args, boolean exclude) { // Return null if not the expected function name or input size. if (fun == null) { if (args.length != 1) { return null; } } else { if (!"{}".equalsIgnoreCase(fun.getName()) || !isArgSizeSupported(evaluator, args.length)) { return null; } } List memberList = new ArrayList(); for (Exp arg : args) { if (!(arg instanceof MemberExpr)) { return null; } final Member member = ((MemberExpr) arg).getMember(); if (member.isCalculated() && !member.isParentChildLeaf()) { // also returns null if any member is calculated return null; } memberList.add((RolapMember) member); } final CrossJoinArg cjArg = MemberListCrossJoinArg.create( evaluator, memberList, restrictMemberTypes(), exclude); if (cjArg == null) { return null; } return new CrossJoinArg[]{cjArg}; } private boolean restrictMemberTypes() { return restrictMemberTypes; } /** * Checks for <Member>.Children. * * @return an {@link mondrian.rolap.sql.CrossJoinArg} instance describing the member.children * function, or null if fun represents something else. */ private CrossJoinArg[] checkMemberChildren( Role role, FunDef fun, Exp[] args) { if (!"Children".equalsIgnoreCase(fun.getName())) { return null; } if (args.length != 1) { return null; } // Note: .Children is not recognized as a native expression. if (!(args[0] instanceof MemberExpr)) { return null; } RolapMember member = (RolapMember) ((MemberExpr) args[0]).getMember(); if (member.isCalculated()) { return null; } RolapLevel level = member.getLevel(); level = (RolapLevel) level.getChildLevel(); if (level == null || !level.isSimple()) { // no child level return null; } // Children of a member in an access-controlled hierarchy cannot be // converted to SQL. (We could be smarter; we don't currently notice // when the member is in a part of the hierarchy that is not // access-controlled.) final Access access = role.getAccess(level.getHierarchy()); switch (access) { case ALL: break; default: return null; } return new CrossJoinArg[]{ new DescendantsCrossJoinArg(level, member) }; } /** * Checks for <Level>.Members. * * @return an {@link mondrian.rolap.sql.CrossJoinArg} instance describing the Level.members * function, or null if fun represents something else. */ private CrossJoinArg[] checkLevelMembers( Role role, FunDef fun, Exp[] args) { if (!"Members".equalsIgnoreCase(fun.getName())) { return null; } if (args.length != 1) { return null; } if (!(args[0] instanceof LevelExpr)) { return null; } RolapLevel level = (RolapLevel) ((LevelExpr) args[0]).getLevel(); if (!level.isSimple()) { return null; } // Members of a level in an access-controlled hierarchy cannot be // converted to SQL. (We could be smarter; we don't currently notice // when the level is in a part of the hierarchy that is not // access-controlled.) final Access access = role.getAccess(level.getHierarchy()); switch (access) { case ALL: break; default: return null; } return new CrossJoinArg[]{ new DescendantsCrossJoinArg(level, null) }; } private static boolean isArgSizeSupported( RolapEvaluator evaluator, int argSize) { boolean argSizeNotSupported = false; // Note: arg size 0 is accepted as valid CJ argument // This is used to push down the "1 = 0" predicate // into the emerging CJ so that the entire CJ can // be natively evaluated. // First check that the member list will not result in a predicate // longer than the underlying DB could support. if (argSize > MondrianProperties.instance().MaxConstraints.get()) { argSizeNotSupported = true; } return !argSizeNotSupported; } /** * Checks for Descendants(<member>, <Level>) * * @return an {@link mondrian.rolap.sql.CrossJoinArg} instance describing the Descendants * function, or null if fun represents something else. */ private CrossJoinArg[] checkDescendants( Role role, FunDef fun, Exp[] args) { if (!"Descendants".equalsIgnoreCase(fun.getName())) { return null; } if (args.length != 2) { return null; } if (!(args[0] instanceof MemberExpr)) { return null; } RolapMember member = (RolapMember) ((MemberExpr) args[0]).getMember(); if (member.isCalculated()) { return null; } RolapLevel level = null; if ((args[1] instanceof LevelExpr)) { level = (RolapLevel) ((LevelExpr) args[1]).getLevel(); } else if (args[1] instanceof Literal) { RolapLevel[] levels = (RolapLevel[]) member.getHierarchy().getLevels(); int currentDepth = member.getDepth(); Literal descendantsDepth = (Literal) args[1]; int newDepth = currentDepth + descendantsDepth.getIntValue(); if (newDepth < levels.length) { level = levels[newDepth]; } } else { return null; } if (!level.isSimple()) { return null; } // Descendants of a member in an access-controlled hierarchy cannot be // converted to SQL. (We could be smarter; we don't currently notice // when the member is in a part of the hierarchy that is not // access-controlled.) final Access access = role.getAccess(level.getHierarchy()); switch (access) { case ALL: break; default: return null; } return new CrossJoinArg[]{ new DescendantsCrossJoinArg(level, member) }; } /** * Check if a dimension filter can be natively evaluated. * Currently, these types of filters can be natively evaluated: * Filter(Set, Qualified Predicate) * where Qualified Predicate is either * CurrentMember reference IN {m1, m2}, * CurrentMember reference Is m1, * negation(NOT) of qualified predicate * conjuction(AND) of qualified predicates * and where * currentmember reference is either a member or * ancester of a member from the context, * * @param evaluator Evaluator * @param fun Filter function * @param filterArgs inputs to the Filter function * @return a list of CrossJoinArg arrays. The first array is the CrossJoin * dimensions. The second array, if any, contains additional * constraints on the dimensions. If either the list or the first * array is null, then native cross join is not feasible. */ private List checkDimensionFilter( RolapEvaluator evaluator, FunDef fun, Exp[] filterArgs) { if (!MondrianProperties.instance().EnableNativeFilter.get()) { return null; } // Return null if not the expected funciton name or input size. if (!"Filter".equalsIgnoreCase(fun.getName()) || filterArgs.length != 2) { return null; } // Now check filterArg[0] can be natively evaluated. // checkCrossJoin returns a list of CrossJoinArg arrays. // The first array is the CrossJoin dimensions // The second array, if any, contains additional constraints on the // dimensions. If either the list or the first array is null, then // native cross join is not feasible. List allArgs = checkCrossJoinArg(evaluator, filterArgs[0]); if (allArgs == null || allArgs.isEmpty() || allArgs.get(0) == null) { return null; } final CrossJoinArg[] cjArgs = allArgs.get(0); if (cjArgs == null) { return null; } final CrossJoinArg[] previousPredicateArgs; if (allArgs.size() == 2) { previousPredicateArgs = allArgs.get(1); } else { previousPredicateArgs = null; } // True if the Filter wants to exclude member(s) final boolean exclude = false; // Check that filterArgs[1] is a qualified predicate // Composites such as AND/OR are not supported at this time CrossJoinArg[] currentPredicateArgs; if (filterArgs[1] instanceof ResolvedFunCall) { ResolvedFunCall predicateCall = (ResolvedFunCall) filterArgs[1]; currentPredicateArgs = checkFilterPredicate(evaluator, predicateCall, exclude); } else { currentPredicateArgs = null; } if (currentPredicateArgs == null) { return null; } // cjArgs remain the same but now there is more predicateArgs // Combine the previous predicate args with the current predicate args. LOGGER.debug("using native dimension filter"); CrossJoinArg[] combinedPredicateArgs = currentPredicateArgs; if (previousPredicateArgs != null) { combinedPredicateArgs = Util.appendArrays(previousPredicateArgs, currentPredicateArgs); } // CJ args do not change. // Predicate args will grow if filter is native. return Arrays.asList(cjArgs, combinedPredicateArgs); } /** * Checks whether the filter predicate can be turned into native SQL. * See comment for checkDimensionFilter for the types of predicates * suported. * * @param evaluator Evaluator * @param predicateCall Call to predicate function (ANd, NOT or parentheses) * @param exclude Whether to exclude tuples that match the predicate * @return if filter predicate can be natively evaluated, the CrossJoinArg * array representing the predicate; otherwise, null. */ private CrossJoinArg[] checkFilterPredicate( RolapEvaluator evaluator, ResolvedFunCall predicateCall, boolean exclude) { CrossJoinArg[] predicateCJArgs = null; if (predicateCall.getFunName().equals("()")) { Exp actualPredicateCall = predicateCall.getArg(0); if (actualPredicateCall instanceof ResolvedFunCall) { return checkFilterPredicate( evaluator, (ResolvedFunCall) actualPredicateCall, exclude); } else { return null; } } if (predicateCall.getFunName().equals("NOT") && predicateCall.getArg(0) instanceof ResolvedFunCall) { predicateCall = (ResolvedFunCall) predicateCall.getArg(0); // Flip the exclude flag exclude = !exclude; return checkFilterPredicate(evaluator, predicateCall, exclude); } if (predicateCall.getFunName().equals("AND")) { Exp andArg0 = predicateCall.getArg(0); Exp andArg1 = predicateCall.getArg(1); if (andArg0 instanceof ResolvedFunCall && andArg1 instanceof ResolvedFunCall) { CrossJoinArg[] andCJArgs0; CrossJoinArg[] andCJArgs1; andCJArgs0 = checkFilterPredicate( evaluator, (ResolvedFunCall) andArg0, exclude); if (andCJArgs0 != null) { andCJArgs1 = checkFilterPredicate( evaluator, (ResolvedFunCall) andArg1, exclude); if (andCJArgs1 != null) { predicateCJArgs = Util.appendArrays(andCJArgs0, andCJArgs1); } } } // predicateCJArgs is either initialized or null return predicateCJArgs; } // Now check the broken down predicate clause. predicateCJArgs = checkFilterPredicateInIs(evaluator, predicateCall, exclude); return predicateCJArgs; } /** * Check whether the predicate is an IN or IS predicate and can be * natively evaluated. * * @param evaluator * @param predicateCall * @param exclude * @return the array of CrossJoinArg containing the predicate. */ private CrossJoinArg[] checkFilterPredicateInIs( RolapEvaluator evaluator, ResolvedFunCall predicateCall, boolean exclude) { final boolean useIs; if (predicateCall.getFunName().equals("IS")) { useIs = true; } else if (predicateCall.getFunName().equals("IN")) { useIs = false; } else { // Neither IN nor IS // This predicate can not be natively evaluated. return null; } Exp[] predArgs = predicateCall.getArgs(); if (predArgs.length != 2) { return null; } // Check that predArgs[0] is a ResolvedFuncCall while FunDef is: // DimensionCurrentMemberFunDef // HierarchyCurrentMemberFunDef // or Ancestor of those functions. if (!(predArgs[0] instanceof ResolvedFunCall)) { return null; } ResolvedFunCall predFirstArgCall = (ResolvedFunCall) predArgs[0]; if (predFirstArgCall.getFunDef().getName().equals("Ancestor")) { Exp[] ancestorArgs = predFirstArgCall.getArgs(); if (!(ancestorArgs[0] instanceof ResolvedFunCall)) { return null; } predFirstArgCall = (ResolvedFunCall) ancestorArgs[0]; } // Now check that predFirstArgCall is a CurrentMember function that // refers to the dimension being filtered FunDef predFirstArgFun = predFirstArgCall.getFunDef(); if (!predFirstArgFun.getName().equals("CurrentMember")) { return null; } Exp currentMemberArg = predFirstArgCall.getArg(0); Type currentMemberArgType = currentMemberArg.getType(); // Input to CurremntMember should be either Dimension or Hierarchy type. if (!(currentMemberArgType instanceof mondrian.olap.type.DimensionType || currentMemberArgType instanceof HierarchyType)) { return null; } // It is not necessary to check currentMemberArg comes from the same // dimension as one of the filterCJArgs, because query parser makes sure // that currentMember always references dimensions in context. // Check that predArgs[1] can be expressed as an MemberListCrossJoinArg. Exp predSecondArg = predArgs[1]; Exp[] predSecondArgList; FunDef predSecondArgFun; CrossJoinArg[] predCJArgs; if (useIs) { // IS operator if (!(predSecondArg instanceof MemberExpr)) { return null; } // IS predicate only contains one member // Make it into a list to be uniform with IN predicate. predSecondArgFun = null; predSecondArgList = new Exp[]{predSecondArg}; } else { // IN operator if (predSecondArg instanceof NamedSetExpr) { NamedSet namedSet = ((NamedSetExpr) predSecondArg).getNamedSet(); predSecondArg = namedSet.getExp(); } if (!(predSecondArg instanceof ResolvedFunCall)) { return null; } ResolvedFunCall predSecondArgCall = (ResolvedFunCall) predSecondArg; predSecondArgFun = predSecondArgCall.getFunDef(); predSecondArgList = predSecondArgCall.getArgs(); } predCJArgs = checkEnumeration( evaluator, predSecondArgFun, predSecondArgList, exclude); return predCJArgs; } private CrossJoinArg[] expandNonNative( RolapEvaluator evaluator, Exp exp) { ExpCompiler compiler = evaluator.getQuery().createCompiler(); CrossJoinArg[] arg0 = null; if (shouldExpandNonEmpty(exp) && evaluator.getActiveNativeExpansions().add(exp)) { ListCalc listCalc0 = compiler.compileList(exp); final TupleList tupleList = listCalc0.evaluateList(evaluator); // Prevent the case when the second argument size is too large Util.checkCJResultLimit(tupleList.size()); if (tupleList.getArity() == 1) { List list0 = Util.cast(tupleList.slice(0)); CrossJoinArg arg = MemberListCrossJoinArg.create( evaluator, list0, restrictMemberTypes(), false); if (arg != null) { arg0 = new CrossJoinArg[]{arg}; } } evaluator.getActiveNativeExpansions().remove(exp); } return arg0; } private boolean shouldExpandNonEmpty(Exp exp) { return MondrianProperties.instance().ExpandNonNative.get() // && !MondrianProperties.instance().EnableNativeCrossJoin.get() || isCheapSet(exp); } private boolean isCheapSet(Exp exp) { return isSet(exp) && allArgsCheapToExpand(exp); } private static final List cheapFuns = Arrays.asList("LastChild", "FirstChild", "Lag"); private boolean allArgsCheapToExpand(Exp exp) { while (exp instanceof NamedSetExpr) { exp = ((NamedSetExpr) exp).getNamedSet().getExp(); } for (Exp arg : ((ResolvedFunCall) exp).getArgs()) { if (arg instanceof ResolvedFunCall) { if (!cheapFuns.contains(((ResolvedFunCall) arg).getFunName())) { return false; } } else if (!(arg instanceof MemberExpr)) { return false; } } return true; } private boolean isSet(Exp exp) { return ((exp instanceof ResolvedFunCall) && ((ResolvedFunCall) exp).getFunName().equals("{}")) || (exp instanceof NamedSetExpr); } } // End CrossJoinArgFactory.java mondrian-3.4.1/src/main/mondrian/rolap/sql/CrossJoinArg.java0000644000175000017500000000172411735330606023676 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap.sql; import mondrian.rolap.*; import mondrian.rolap.aggmatcher.AggStar; import java.util.List; /** * "Light version" of a {@link mondrian.rolap.sql.TupleConstraint}, * represents one of * member.children, level.members, member.descendants, {enumeration}. */ public interface CrossJoinArg { CrossJoinArg[] EMPTY_ARRAY = new CrossJoinArg[0]; RolapLevel getLevel(); List getMembers(); void addConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar); boolean isPreferInterpreter(boolean joinArg); } // End CrossJoinArg.java mondrian-3.4.1/src/main/mondrian/rolap/RolapVirtualCubeMeasure.java0000644000175000017500000000445611735330606025306 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.Annotation; import mondrian.olap.MondrianDef; import java.util.Map; /** * Measure which is defined in a virtual cube, and based on a stored measure * in one of the virtual cube's base cubes. * * @author jhyde * @since Aug 18, 2006 */ public class RolapVirtualCubeMeasure extends RolapMemberBase implements RolapStoredMeasure { /** * The measure in the underlying cube. */ private final RolapStoredMeasure cubeMeasure; private final Map annotationMap; public RolapVirtualCubeMeasure( RolapMember parentMember, RolapLevel level, RolapStoredMeasure cubeMeasure, Map annotationMap) { super(parentMember, level, cubeMeasure.getName()); this.cubeMeasure = cubeMeasure; this.annotationMap = annotationMap; } public Object getPropertyValue(String propertyName, boolean matchCase) { // Look first in this member (against the virtual cube), then // fallback on the base measure. // This allows, for instance, a measure to be invisible in a virtual // cube but visible in its base cube. Object value = super.getPropertyValue(propertyName, matchCase); if (value == null) { value = cubeMeasure.getPropertyValue(propertyName, matchCase); } return value; } public RolapCube getCube() { return cubeMeasure.getCube(); } public Object getStarMeasure() { return cubeMeasure.getStarMeasure(); } public MondrianDef.Expression getMondrianDefExpression() { return cubeMeasure.getMondrianDefExpression(); } public RolapAggregator getAggregator() { return cubeMeasure.getAggregator(); } public RolapResult.ValueFormatter getFormatter() { return cubeMeasure.getFormatter(); } public Map getAnnotationMap() { return annotationMap; } } // End RolapVirtualCubeMeasure.java mondrian-3.4.1/src/main/mondrian/rolap/SmartMemberReader.java0000644000175000017500000004753011735330606024102 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // Copyright (C) 2004-2005 TONBELLER AG // All Rights Reserved. // // jhyde, 21 December, 2001 */ package mondrian.rolap; import mondrian.olap.Id; import mondrian.olap.Util; import mondrian.rolap.TupleReader.MemberBuilder; import mondrian.rolap.sql.MemberChildrenConstraint; import mondrian.rolap.sql.TupleConstraint; import mondrian.util.ConcatenableList; import java.util.*; /** * SmartMemberReader implements {@link MemberReader} by keeping a * cache of members and their children. If a member is 'in cache', there is a * list of its children. It also caches the members of levels. * *

Synchronization: the MemberReader source must be called * from synchronized(this) context - it does not synchronize itself (probably * it should).

* *

Constraints: Member.Children and Level.Members may be constrained by a * SqlConstraint object. In this case a subset of all members is returned. * These subsets are cached too and the SqlConstraint is part of the cache key. * This is used in NON EMPTY context.

* *

Uniqueness. We need to ensure that there is never more than one {@link * RolapMember} object representing the same member.

* * @author jhyde * @since 21 December, 2001 */ public class SmartMemberReader implements MemberReader { private final SqlConstraintFactory sqlConstraintFactory = SqlConstraintFactory.instance(); /** access to source must be synchronized(this) */ protected final MemberReader source; protected final MemberCacheHelper cacheHelper; protected List rootMembers; SmartMemberReader(MemberReader source) { this.source = source; this.cacheHelper = new MemberCacheHelper(source.getHierarchy()); if (!source.setCache(cacheHelper)) { throw Util.newInternal( "MemberSource (" + source + ", " + source.getClass() + ") does not support cache-writeback"); } } // implement MemberReader public RolapHierarchy getHierarchy() { return source.getHierarchy(); } public MemberCache getMemberCache() { return cacheHelper; } // implement MemberSource public boolean setCache(MemberCache cache) { // we do not support cache writeback -- we must be masters of our // own cache return false; } public RolapMember substitute(RolapMember member) { return member; } public RolapMember desubstitute(RolapMember member) { return member; } // implement MemberReader public List getMembers() { List v = new ConcatenableList(); RolapLevel[] levels = (RolapLevel[]) getHierarchy().getLevels(); // todo: optimize by walking to children for members we know about for (RolapLevel level : levels) { List membersInLevel = getMembersInLevel( level, 0, Integer.MAX_VALUE); v.addAll(membersInLevel); } return v; } public List getRootMembers() { if (rootMembers == null) { rootMembers = source.getRootMembers(); } return rootMembers; } public List getMembersInLevel( RolapLevel level, int startOrdinal, int endOrdinal) { TupleConstraint constraint = sqlConstraintFactory.getLevelMembersConstraint(null); return getMembersInLevel(level, startOrdinal, endOrdinal, constraint); } protected void checkCacheStatus() { cacheHelper.checkCacheStatus(); } public List getMembersInLevel( RolapLevel level, int startOrdinal, int endOrdinal, TupleConstraint constraint) { synchronized (cacheHelper) { checkCacheStatus(); List members = cacheHelper.getLevelMembersFromCache(level, constraint); if (members != null) { return members; } members = source.getMembersInLevel( level, startOrdinal, endOrdinal, constraint); cacheHelper.putLevelMembersInCache(level, constraint, members); return members; } } public int getLevelMemberCount(RolapLevel level) { // No need to cache the result: the caller saves the result by calling // RolapLevel.setApproxRowCount return source.getLevelMemberCount(level); } public void getMemberChildren( RolapMember parentMember, List children) { MemberChildrenConstraint constraint = sqlConstraintFactory.getMemberChildrenConstraint(null); getMemberChildren(parentMember, children, constraint); } public void getMemberChildren( RolapMember parentMember, List children, MemberChildrenConstraint constraint) { List parentMembers = Collections.singletonList(parentMember); getMemberChildren(parentMembers, children, constraint); } public void getMemberChildren( List parentMembers, List children) { MemberChildrenConstraint constraint = sqlConstraintFactory.getMemberChildrenConstraint(null); getMemberChildren(parentMembers, children, constraint); } public void getMemberChildren( List parentMembers, List children, MemberChildrenConstraint constraint) { synchronized (cacheHelper) { checkCacheStatus(); List missed = new ArrayList(); for (RolapMember parentMember : parentMembers) { List list = cacheHelper.getChildrenFromCache(parentMember, constraint); if (list == null) { // the null member has no children if (!parentMember.isNull()) { missed.add(parentMember); } } else { children.addAll(list); } } if (missed.size() > 0) { readMemberChildren(missed, children, constraint); } } } public RolapMember lookupMember( List uniqueNameParts, boolean failIfNotFound) { return RolapUtil.lookupMember(this, uniqueNameParts, failIfNotFound); } /** * Reads the children of member into cache, and also into * result. * * @param result Children are written here, in order * @param members Members whose children to read * @param constraint restricts the returned members if possible (optional * optimization) */ protected void readMemberChildren( List members, List result, MemberChildrenConstraint constraint) { if (false) { // Pre-condition disabled. It makes sense to have the pre- // condition, because lists of parent members are typically // sorted by construction, and we should be able to exploit this // when constructing the (significantly larger) set of children. // But currently BasicQueryTest.testBasketAnalysis() fails this // assert, and I haven't had time to figure out why. // -- jhyde, 2004/6/10. Util.assertPrecondition(isSorted(members), "isSorted(members)"); } List children = new ConcatenableList(); source.getMemberChildren(members, children, constraint); // Put them in a temporary hash table first. Register them later, when // we know their size (hence their 'cost' to the cache pool). Map> tempMap = new HashMap>(); for (RolapMember member1 : members) { tempMap.put(member1, Collections.EMPTY_LIST); } for (final RolapMember child : children) { // todo: We could optimize here. If members.length is small, it's // more efficient to drive from members, rather than hashing // children.length times. We could also exploit the fact that the // result is sorted by ordinal and therefore, unless the "members" // contains members from different levels, children of the same // member will be contiguous. assert child != null : "child"; assert tempMap != null : "tempMap"; final RolapMember parentMember = child.getParentMember(); List list = tempMap.get(parentMember); if (list == null) { // The list is null if, due to dropped constraints, we now // have a children list of a member we didn't explicitly // ask for it. Adding it to the cache would be viable, but // let's ignore it. continue; } else if (list == Collections.EMPTY_LIST) { list = new ArrayList(); tempMap.put(parentMember, list); } ((List)list).add(child); ((List)result).add(child); } synchronized (cacheHelper) { for (Map.Entry> entry : tempMap.entrySet()) { final RolapMember member = entry.getKey(); if (cacheHelper.getChildrenFromCache(member, constraint) == null) { final List list = entry.getValue(); cacheHelper.putChildren(member, constraint, list); } } } } /** * Returns true if every element of members is not null and is * strictly less than the following element; false otherwise. */ public boolean isSorted(List members) { final int count = members.size(); if (count == 0) { return true; } RolapMember m1 = members.get(0); if (m1 == null) { // Special case check for 0th element, just in case length == 1. return false; } for (int i = 1; i < count; i++) { RolapMember m0 = m1; m1 = members.get(i); if (m1 == null || compare(m0, m1, false) >= 0) { return false; } } return true; } public RolapMember getLeadMember(RolapMember member, int n) { // uncertain if this method needs to be synchronized synchronized (cacheHelper) { if (n == 0 || member.isNull()) { return member; } else { SiblingIterator iter = new SiblingIterator(this, member); if (n > 0) { RolapMember sibling = null; while (n-- > 0) { if (!iter.hasNext()) { return (RolapMember) member.getHierarchy().getNullMember(); } sibling = iter.nextMember(); } return sibling; } else { n = -n; RolapMember sibling = null; while (n-- > 0) { if (!iter.hasPrevious()) { return (RolapMember) member.getHierarchy().getNullMember(); } sibling = iter.previousMember(); } return sibling; } } } } public void getMemberRange( RolapLevel level, RolapMember startMember, RolapMember endMember, List list) { assert startMember != null; assert endMember != null; assert startMember.getLevel() == endMember.getLevel(); if (compare(startMember, endMember, false) > 0) { return; } list.add(startMember); if (startMember.equals(endMember)) { return; } SiblingIterator siblings = new SiblingIterator(this, startMember); while (siblings.hasNext()) { final RolapMember member = siblings.nextMember(); list.add(member); if (member.equals(endMember)) { return; } } throw Util.newInternal( "sibling iterator did not hit end point, start=" + startMember + ", end=" + endMember); } public int getMemberCount() { return source.getMemberCount(); } public int compare( RolapMember m1, RolapMember m2, boolean siblingsAreEqual) { if (m1.equals(m2)) { return 0; } if (Util.equals(m1.getParentMember(), m2.getParentMember())) { // including case where both parents are null if (siblingsAreEqual) { return 0; } else if (m1.getParentMember() == null) { // at this point we know that both parent members are null. int pos1 = -1, pos2 = -1; List siblingList = getRootMembers(); for (int i = 0, n = siblingList.size(); i < n; i++) { RolapMember child = siblingList.get(i); if (child.equals(m1)) { pos1 = i; } if (child.equals(m2)) { pos2 = i; } } if (pos1 == -1) { throw Util.newInternal(m1 + " not found among siblings"); } if (pos2 == -1) { throw Util.newInternal(m2 + " not found among siblings"); } Util.assertTrue(pos1 != pos2); return pos1 < pos2 ? -1 : 1; } else { List children = new ArrayList(); getMemberChildren(m1.getParentMember(), children); int pos1 = -1, pos2 = -1; for (int i = 0, n = children.size(); i < n; i++) { RolapMember child = children.get(i); if (child.equals(m1)) { pos1 = i; } if (child.equals(m2)) { pos2 = i; } } if (pos1 == -1) { throw Util.newInternal(m1 + " not found among siblings"); } if (pos2 == -1) { throw Util.newInternal(m2 + " not found among siblings"); } Util.assertTrue(pos1 != pos2); return pos1 < pos2 ? -1 : 1; } } int levelDepth1 = m1.getLevel().getDepth(); int levelDepth2 = m2.getLevel().getDepth(); if (levelDepth1 < levelDepth2) { final int c = compare(m1, m2.getParentMember(), false); return (c == 0) ? -1 : c; } else if (levelDepth1 > levelDepth2) { final int c = compare(m1.getParentMember(), m2, false); return (c == 0) ? 1 : c; } else { return compare(m1.getParentMember(), m2.getParentMember(), false); } } /** * SiblingIterator helps traverse a hierarchy of members, by * remembering the position at each level. Each SiblingIterator has a * parent, to which it defers when the last child of the current member is * reached. */ class SiblingIterator { private final MemberReader reader; private final SiblingIterator parentIterator; private List siblings; private int position; SiblingIterator(MemberReader reader, RolapMember member) { this.reader = reader; RolapMember parent = member.getParentMember(); List siblingList; if (parent == null) { siblingList = reader.getRootMembers(); this.parentIterator = null; } else { siblingList = new ArrayList(); reader.getMemberChildren(parent, siblingList); this.parentIterator = new SiblingIterator(reader, parent); } this.siblings = siblingList; this.position = -1; for (int i = 0; i < this.siblings.size(); i++) { if (siblings.get(i).equals(member)) { this.position = i; break; } } if (this.position == -1) { throw Util.newInternal( "member " + member + " not found among its siblings"); } } boolean hasNext() { return (this.position < this.siblings.size() - 1) || (parentIterator != null) && parentIterator.hasNext(); } Object next() { return nextMember(); } RolapMember nextMember() { if (++this.position >= this.siblings.size()) { if (parentIterator == null) { throw Util.newInternal("there is no next member"); } RolapMember parent = parentIterator.nextMember(); List siblingList = new ArrayList(); reader.getMemberChildren(parent, siblingList); this.siblings = siblingList; this.position = 0; } return this.siblings.get(this.position); } boolean hasPrevious() { return (this.position > 0) || (parentIterator != null) && parentIterator.hasPrevious(); } Object previous() { return previousMember(); } RolapMember previousMember() { if (--this.position < 0) { if (parentIterator == null) { throw Util.newInternal("there is no next member"); } RolapMember parent = parentIterator.previousMember(); List siblingList = new ArrayList(); reader.getMemberChildren(parent, siblingList); this.siblings = siblingList; this.position = this.siblings.size() - 1; } return this.siblings.get(this.position); } } public MemberBuilder getMemberBuilder() { return source.getMemberBuilder(); } public RolapMember getDefaultMember() { RolapMember defaultMember = (RolapMember) getHierarchy().getDefaultMember(); if (defaultMember != null) { return defaultMember; } return getRootMembers().get(0); } public RolapMember getMemberParent(RolapMember member) { // This method deals with ragged hierarchies but not access-controlled // hierarchies - assume these have RestrictedMemberReader possibly // wrapped in a SubstitutingMemberReader. RolapMember parentMember = member.getParentMember(); // Skip over hidden parents. while (parentMember != null && parentMember.isHidden()) { parentMember = parentMember.getParentMember(); } return parentMember; } } // End SmartMemberReader.java mondrian-3.4.1/src/main/mondrian/rolap/RolapLevel.java0000644000175000017500000005202311735330606022577 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.resource.MondrianResource; import mondrian.spi.Dialect; import mondrian.spi.PropertyFormatter; import mondrian.spi.impl.Scripts; import org.apache.log4j.Logger; import org.olap4j.impl.UnmodifiableArrayMap; import java.util.*; /** * RolapLevel implements {@link Level} for a ROLAP database. * * @author jhyde * @since 10 August, 2001 */ public class RolapLevel extends LevelBase { private static final Logger LOGGER = Logger.getLogger(RolapLevel.class); /** * The column or expression which yields the level's key. */ protected MondrianDef.Expression keyExp; /** * The column or expression which yields the level's ordinal. */ protected MondrianDef.Expression ordinalExp; /** * The column or expression which yields the level members' caption. */ protected MondrianDef.Expression captionExp; private final Dialect.Datatype datatype; private final int flags; static final int FLAG_ALL = 0x02; /** * For SQL generator. Whether values of "column" are unique globally * unique (as opposed to unique only within the context of the parent * member). */ static final int FLAG_UNIQUE = 0x04; private RolapLevel closedPeerLevel; private final RolapProperty[] properties; private final RolapProperty[] inheritedProperties; /** * Ths expression which gives the name of members of this level. If null, * members are named using the key expression. */ protected MondrianDef.Expression nameExp; /** The expression which joins to the parent member in a parent-child * hierarchy, or null if this is a regular hierarchy. */ protected MondrianDef.Expression parentExp; /** Value which indicates a null parent in a parent-child hierarchy. */ private final String nullParentValue; /** Condition under which members are hidden. */ private final HideMemberCondition hideMemberCondition; protected final MondrianDef.Closure xmlClosure; private final Map annotationMap; private final SqlStatement.Type internalType; // may be null /** * Creates a level. * * @pre parentExp != null || nullParentValue == null * @pre properties != null * @pre levelType != null * @pre hideMemberCondition != null */ RolapLevel( RolapHierarchy hierarchy, String name, String caption, boolean visible, String description, int depth, MondrianDef.Expression keyExp, MondrianDef.Expression nameExp, MondrianDef.Expression captionExp, MondrianDef.Expression ordinalExp, MondrianDef.Expression parentExp, String nullParentValue, MondrianDef.Closure xmlClosure, RolapProperty[] properties, int flags, Dialect.Datatype datatype, SqlStatement.Type internalType, HideMemberCondition hideMemberCondition, LevelType levelType, String approxRowCount, Map annotationMap) { super( hierarchy, name, caption, visible, description, depth, levelType); assert annotationMap != null; Util.assertPrecondition(properties != null, "properties != null"); Util.assertPrecondition( hideMemberCondition != null, "hideMemberCondition != null"); Util.assertPrecondition(levelType != null, "levelType != null"); if (keyExp instanceof MondrianDef.Column) { checkColumn((MondrianDef.Column) keyExp); } this.annotationMap = annotationMap; this.approxRowCount = loadApproxRowCount(approxRowCount); this.flags = flags; this.datatype = datatype; this.keyExp = keyExp; if (nameExp != null) { if (nameExp instanceof MondrianDef.Column) { checkColumn((MondrianDef.Column) nameExp); } } this.nameExp = nameExp; if (captionExp != null) { if (captionExp instanceof MondrianDef.Column) { checkColumn((MondrianDef.Column) captionExp); } } this.captionExp = captionExp; if (ordinalExp != null) { if (ordinalExp instanceof MondrianDef.Column) { checkColumn((MondrianDef.Column) ordinalExp); } this.ordinalExp = ordinalExp; } else { this.ordinalExp = this.keyExp; } if (parentExp instanceof MondrianDef.Column) { checkColumn((MondrianDef.Column) parentExp); } this.parentExp = parentExp; if (parentExp != null) { Util.assertTrue( !isAll(), "'All' level '" + this + "' must not be parent-child"); Util.assertTrue( isUnique(), "Parent-child level '" + this + "' must have uniqueMembers=\"true\""); } this.nullParentValue = nullParentValue; Util.assertPrecondition( parentExp != null || nullParentValue == null, "parentExp != null || nullParentValue == null"); this.xmlClosure = xmlClosure; for (RolapProperty property : properties) { if (property.getExp() instanceof MondrianDef.Column) { checkColumn((MondrianDef.Column) property.getExp()); } } this.properties = properties; List list = new ArrayList(); for (Level level = this; level != null; level = level.getParentLevel()) { final Property[] levelProperties = level.getProperties(); for (final Property levelProperty : levelProperties) { Property existingProperty = lookupProperty( list, levelProperty.getName()); if (existingProperty == null) { list.add(levelProperty); } else if (existingProperty.getType() != levelProperty.getType()) { throw Util.newError( "Property " + this.getName() + "." + levelProperty.getName() + " overrides a " + "property with the same name but different type"); } } } this.inheritedProperties = list.toArray(new RolapProperty[list.size()]); Dimension dim = hierarchy.getDimension(); if (dim.getDimensionType() == DimensionType.TimeDimension) { if (!levelType.isTime() && !isAll()) { throw MondrianResource.instance() .NonTimeLevelInTimeHierarchy.ex(getUniqueName()); } } else if (dim.getDimensionType() == null) { // there was no dimension type assigned to the dimension // - check later } else { if (levelType.isTime()) { throw MondrianResource.instance() .TimeLevelInNonTimeHierarchy.ex(getUniqueName()); } } this.internalType = internalType; this.hideMemberCondition = hideMemberCondition; } public RolapHierarchy getHierarchy() { return (RolapHierarchy) hierarchy; } public Map getAnnotationMap() { return annotationMap; } private int loadApproxRowCount(String approxRowCount) { boolean notNullAndNumeric = approxRowCount != null && approxRowCount.matches("^\\d+$"); if (notNullAndNumeric) { return Integer.parseInt(approxRowCount); } else { // if approxRowCount is not set, return MIN_VALUE to indicate return Integer.MIN_VALUE; } } protected Logger getLogger() { return LOGGER; } String getTableName() { String tableName = null; MondrianDef.Expression expr = getKeyExp(); if (expr instanceof MondrianDef.Column) { MondrianDef.Column mc = (MondrianDef.Column) expr; tableName = mc.getTableAlias(); } return tableName; } public MondrianDef.Expression getKeyExp() { return keyExp; } MondrianDef.Expression getOrdinalExp() { return ordinalExp; } public MondrianDef.Expression getCaptionExp() { return captionExp; } public boolean hasCaptionColumn() { return captionExp != null; } final int getFlags() { return flags; } HideMemberCondition getHideMemberCondition() { return hideMemberCondition; } public final boolean isUnique() { return (flags & FLAG_UNIQUE) != 0; } final Dialect.Datatype getDatatype() { return datatype; } final String getNullParentValue() { return nullParentValue; } /** * Returns whether this level is parent-child. */ public boolean isParentChild() { return parentExp != null; } MondrianDef.Expression getParentExp() { return parentExp; } // RME: this has to be public for two of the DrillThroughTest test. public MondrianDef.Expression getNameExp() { return nameExp; } private Property lookupProperty(List list, String propertyName) { for (Property property : list) { if (property.getName().equals(propertyName)) { return property; } } return null; } RolapLevel( RolapHierarchy hierarchy, int depth, MondrianDef.Level xmlLevel) { this( hierarchy, xmlLevel.name, xmlLevel.caption, xmlLevel.visible, xmlLevel.description, depth, xmlLevel.getKeyExp(), xmlLevel.getNameExp(), xmlLevel.getCaptionExp(), xmlLevel.getOrdinalExp(), xmlLevel.getParentExp(), xmlLevel.nullParentValue, xmlLevel.closure, createProperties(xmlLevel), (xmlLevel.uniqueMembers ? FLAG_UNIQUE : 0), xmlLevel.getDatatype(), toInternalType(xmlLevel.internalType), HideMemberCondition.valueOf(xmlLevel.hideMemberIf), LevelType.valueOf( xmlLevel.levelType.equals("TimeHalfYear") ? "TimeHalfYears" : xmlLevel.levelType), xmlLevel.approxRowCount, RolapHierarchy.createAnnotationMap(xmlLevel.annotations)); if (!Util.isEmpty(xmlLevel.caption)) { setCaption(xmlLevel.caption); } final String memberFormatterClassName; final Scripts.ScriptDefinition scriptDefinition; if (xmlLevel.memberFormatter != null) { memberFormatterClassName = xmlLevel.memberFormatter.className; scriptDefinition = RolapSchema.toScriptDef(xmlLevel.memberFormatter.script); } else { memberFormatterClassName = xmlLevel.formatter; scriptDefinition = null; } if (memberFormatterClassName != null || scriptDefinition != null) { try { memberFormatter = RolapSchema.getMemberFormatter( memberFormatterClassName, scriptDefinition); } catch (Exception e) { throw MondrianResource.instance().MemberFormatterLoadFailed.ex( xmlLevel.formatter, getUniqueName(), e); } } } // helper for constructor private static RolapProperty[] createProperties( MondrianDef.Level xmlLevel) { List list = new ArrayList(); final MondrianDef.Expression nameExp = xmlLevel.getNameExp(); if (nameExp != null) { list.add( new RolapProperty( Property.NAME.name, Property.Datatype.TYPE_STRING, nameExp, null, null, null, true, Property.NAME.description)); } for (int i = 0; i < xmlLevel.properties.length; i++) { MondrianDef.Property xmlProperty = xmlLevel.properties[i]; final PropertyFormatter formatter; final String propertyFormatterClassName; final Scripts.ScriptDefinition scriptDefinition; if (xmlProperty.propertyFormatter != null) { propertyFormatterClassName = xmlProperty.propertyFormatter.className; scriptDefinition = RolapSchema.toScriptDef( xmlProperty.propertyFormatter.script); } else { propertyFormatterClassName = xmlProperty.formatter; scriptDefinition = null; } if (propertyFormatterClassName != null || scriptDefinition != null) { try { formatter = RolapSchema.createPropertyFormatter( propertyFormatterClassName, scriptDefinition); } catch (Exception e) { throw MondrianResource.instance() .PropertyFormatterLoadFailed.ex( propertyFormatterClassName, xmlProperty.name, e); } } else { formatter = null; } list.add( new RolapProperty( xmlProperty.name, convertPropertyTypeNameToCode(xmlProperty.type), xmlLevel.getPropertyExp(i), formatter, xmlProperty.caption, xmlLevel.properties[i].dependsOnLevelValue, false, xmlProperty.description)); } return list.toArray(new RolapProperty[list.size()]); } private static Property.Datatype convertPropertyTypeNameToCode( String type) { if (type.equals("String")) { return Property.Datatype.TYPE_STRING; } else if (type.equals("Numeric")) { return Property.Datatype.TYPE_NUMERIC; } else if (type.equals("Integer")) { return Property.Datatype.TYPE_NUMERIC; } else if (type.equals("Boolean")) { return Property.Datatype.TYPE_BOOLEAN; } else if (type.equals("Timestamp")) { return Property.Datatype.TYPE_TIMESTAMP; } else if (type.equals("Time")) { return Property.Datatype.TYPE_TIME; } else if (type.equals("Date")) { return Property.Datatype.TYPE_DATE; } else { throw Util.newError("Unknown property type '" + type + "'"); } } private void checkColumn(MondrianDef.Column nameColumn) { final RolapHierarchy rolapHierarchy = (RolapHierarchy) hierarchy; if (nameColumn.table == null) { final MondrianDef.Relation table = rolapHierarchy.getUniqueTable(); if (table == null) { throw Util.newError( "must specify a table for level " + getUniqueName() + " because hierarchy has more than one table"); } nameColumn.table = table.getAlias(); } else { if (!rolapHierarchy.tableExists(nameColumn.table)) { throw Util.newError( "Table '" + nameColumn.table + "' not found"); } } } void init(MondrianDef.CubeDimension xmlDimension) { if (xmlClosure != null) { final RolapDimension dimension = ((RolapHierarchy) hierarchy) .createClosedPeerDimension(this, xmlClosure, xmlDimension); closedPeerLevel = (RolapLevel) dimension.getHierarchies()[0].getLevels()[1]; } } public final boolean isAll() { return (flags & FLAG_ALL) != 0; } public boolean areMembersUnique() { return (depth == 0) || (depth == 1) && hierarchy.hasAll(); } public String getTableAlias() { return keyExp.getTableAlias(); } public RolapProperty[] getProperties() { return properties; } public Property[] getInheritedProperties() { return inheritedProperties; } public int getApproxRowCount() { return approxRowCount; } private static final Map VALUES = UnmodifiableArrayMap.of( "int", SqlStatement.Type.INT, "double", SqlStatement.Type.DOUBLE, "Object", SqlStatement.Type.OBJECT, "String", SqlStatement.Type.STRING, "long", SqlStatement.Type.LONG); private static SqlStatement.Type toInternalType(String internalTypeName) { SqlStatement.Type type = VALUES.get(internalTypeName); if (type == null && internalTypeName != null) { throw Util.newError( "Invalid value '" + internalTypeName + "' for attribute 'internalType' of element 'Level'. " + "Valid values are: " + VALUES.keySet()); } return type; } public SqlStatement.Type getInternalType() { return internalType; } /** * Conditions under which a level's members may be hidden (thereby creating * a ragged hierarchy). */ public enum HideMemberCondition { /** A member always appears. */ Never, /** A member doesn't appear if its name is null or empty. */ IfBlankName, /** A member appears unless its name matches its parent's. */ IfParentsName } public OlapElement lookupChild(SchemaReader schemaReader, Id.Segment name) { return lookupChild(schemaReader, name, MatchType.EXACT); } public OlapElement lookupChild( SchemaReader schemaReader, Id.Segment name, MatchType matchType) { List levelMembers = schemaReader.getLevelMembers(this, true); if (levelMembers.size() > 0) { Member parent = levelMembers.get(0).getParentMember(); return RolapUtil.findBestMemberMatch( levelMembers, (RolapMember) parent, this, name, matchType, false); } return null; } /** * Indicates that level is not ragged and not a parent/child level. */ public boolean isSimple() { // most ragged hierarchies are not simple -- see isTooRagged. if (isTooRagged()) { return false; } if (isParentChild()) { return false; } // does not work for measures if (isMeasure()) { return false; } return true; } /** * Determines whether the specified level is too ragged for native * evaluation, which is able to handle one special case of a ragged * hierarchy: when the level specified in the query is the leaf level of * the hierarchy and HideMemberCondition for the level is IfBlankName. * This is true even if higher levels of the hierarchy can be hidden * because even in that case the only column that needs to be read is the * column that holds the leaf. IfParentsName can't be handled even at the * leaf level because in the general case we aren't reading the column * that holds the parent. Also, IfBlankName can't be handled for non-leaf * levels because we would have to read the column for the next level * down for members with blank names. * * @return true if the specified level is too ragged for native * evaluation. */ private boolean isTooRagged() { // Is this the special case of raggedness that native evaluation // is able to handle? if (getDepth() == getHierarchy().getLevels().length - 1) { switch (getHideMemberCondition()) { case Never: case IfBlankName: return false; default: return true; } } // Handle the general case in the traditional way. return getHierarchy().isRagged(); } /** * Returns true when the level is part of a parent/child hierarchy and has * an equivalent closed level. */ boolean hasClosedPeer() { return closedPeerLevel != null; } public RolapLevel getClosedPeer() { return closedPeerLevel; } public static RolapLevel lookupLevel( RolapLevel[] levels, String levelName) { for (RolapLevel level : levels) { if (level.getName().equals(levelName)) { return level; } } return null; } } // End RolapLevel.java mondrian-3.4.1/src/main/mondrian/rolap/RolapNativeRegistry.java0000644000175000017500000000470411735330606024512 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import java.util.HashMap; import java.util.Map; /** * Composite of {@link RolapNative}s. Uses chain of responsibility * to select the appropriate {@link RolapNative} evaluator. */ public class RolapNativeRegistry extends RolapNative { private Map nativeEvaluatorMap = new HashMap(); public RolapNativeRegistry() { super.setEnabled(true); /* * Mondrian functions which might be evaluated natively. */ register("NonEmptyCrossJoin".toUpperCase(), new RolapNativeCrossJoin()); register("CrossJoin".toUpperCase(), new RolapNativeCrossJoin()); register("TopCount".toUpperCase(), new RolapNativeTopCount()); register("Filter".toUpperCase(), new RolapNativeFilter()); } /** * Returns the matching NativeEvaluator or null if fun can not * be executed in SQL for the given context and arguments. */ public NativeEvaluator createEvaluator( RolapEvaluator evaluator, FunDef fun, Exp[] args) { if (!isEnabled()) { return null; } RolapNative rn = nativeEvaluatorMap.get(fun.getName().toUpperCase()); if (rn == null) { return null; } NativeEvaluator ne = rn.createEvaluator(evaluator, fun, args); if (ne != null) { if (listener != null) { NativeEvent e = new NativeEvent(this, ne); listener.foundEvaluator(e); } } return ne; } public void register(String funName, RolapNative rn) { nativeEvaluatorMap.put(funName, rn); } /** for testing */ void setListener(Listener listener) { super.setListener(listener); for (RolapNative rn : nativeEvaluatorMap.values()) { rn.setListener(listener); } } /** for testing */ void useHardCache(boolean hard) { for (RolapNative rn : nativeEvaluatorMap.values()) { rn.useHardCache(hard); } } } // End RolapNativeRegistry.java mondrian-3.4.1/src/main/mondrian/rolap/SqlContextConstraint.java0000644000175000017500000002744311735330606024713 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.mdx.MemberExpr; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.rolap.aggmatcher.AggStar; import mondrian.rolap.sql.*; import java.util.*; /** * limits the result of a Member SQL query to the current evaluation context. * All Members of the current context are joined against the fact table and only * those rows are returned, that have an entry in the fact table. * *

For example, if you have two dimensions, "invoice" and "time", and the * current context (e.g. the slicer) contains a day from the "time" dimension, * then only the invoices of that day are found. Used to optimize NON EMPTY. * *

The {@link TupleConstraint} methods may silently ignore calculated * members (depends on the strict c'tor argument), so these may * return more members than the current context restricts to. The * MemberChildren methods will never accept calculated members as parents, * these will cause an exception. * * @author av * @since Nov 2, 2005 */ public class SqlContextConstraint implements MemberChildrenConstraint, TupleConstraint { private final List cacheKey; private Evaluator evaluator; private boolean strict; /** * @param context evaluation context * @param strict false if more rows than requested may be returned * (i.e. the constraint is incomplete) * * @return false if this contstraint will not work for the current context */ public static boolean isValidContext(Evaluator context, boolean strict) { return isValidContext(context, true, null, strict); } /** * @param context evaluation context * @param disallowVirtualCube if true, check for virtual cubes * @param levels levels being referenced in the current context * @param strict false if more rows than requested may be returned * (i.e. the constraint is incomplete) * * @return false if constraint will not work for current context */ public static boolean isValidContext( Evaluator context, boolean disallowVirtualCube, Level [] levels, boolean strict) { if (context == null) { return false; } RolapCube cube = (RolapCube) context.getCube(); if (disallowVirtualCube) { if (cube.isVirtual()) { return false; } } if (cube.isVirtual()) { Query query = context.getQuery(); Set baseCubes = new HashSet(); List baseCubeList = new ArrayList(); if (!findVirtualCubeBaseCubes(query, baseCubes, baseCubeList)) { return false; } assert levels != null; query.setBaseCubes(baseCubeList); } // may return more rows than requested? if (!strict) { return true; } // Although it is technically possible to build a native SQL predicate // to represent a multi-position compound slicer (see // http://jira.pentaho.com/browse/MONDRIAN-791), this trick // requires that we have access to the slicer axis (so we can iterate // over its positions). Alas, the evaluator does not give us access to // the slicer axis, but only the members on it if (SqlConstraintUtils.hasMultiPositionSlicer(context)) { return false; } // we can not handle calc members in slicer except calc measure Member[] members = context.getMembers(); for (int i = 1; i < members.length; i++) { if (members[i].isCalculated()) { return false; } } return true; } /** * Locates base cubes related to the measures referenced in the query. * * @param query query referencing the virtual cube * @param baseCubes set of base cubes * * @return true if valid measures exist */ private static boolean findVirtualCubeBaseCubes( Query query, Set baseCubes, List baseCubeList) { // Gather the unique set of level-to-column maps corresponding // to the underlying star/cube where the measure column // originates from. Set measureMembers = query.getMeasuresMembers(); // if no measures are explicitly referenced, just use the default // measure if (measureMembers.isEmpty()) { Cube cube = query.getCube(); Dimension dimension = cube.getDimensions()[0]; query.addMeasuresMembers( dimension.getHierarchy().getDefaultMember()); } for (Member member : query.getMeasuresMembers()) { if (member instanceof RolapStoredMeasure) { addMeasure( (RolapStoredMeasure) member, baseCubes, baseCubeList); } else if (member instanceof RolapCalculatedMember) { findMeasures(member.getExpression(), baseCubes, baseCubeList); } } if (baseCubes.isEmpty()) { return false; } return true; } /** * Adds information regarding a stored measure to maps * * @param measure the stored measure * @param baseCubes set of base cubes */ private static void addMeasure( RolapStoredMeasure measure, Set baseCubes, List baseCubeList) { RolapCube baseCube = measure.getCube(); if (baseCubes.add(baseCube)) { baseCubeList.add(baseCube); } } /** * Extracts the stored measures referenced in an expression * * @param exp expression * @param baseCubes set of base cubes */ private static void findMeasures( Exp exp, Set baseCubes, List baseCubeList) { if (exp instanceof MemberExpr) { MemberExpr memberExpr = (MemberExpr) exp; Member member = memberExpr.getMember(); if (member instanceof RolapStoredMeasure) { addMeasure( (RolapStoredMeasure) member, baseCubes, baseCubeList); } else if (member instanceof RolapCalculatedMember) { findMeasures(member.getExpression(), baseCubes, baseCubeList); } } else if (exp instanceof ResolvedFunCall) { ResolvedFunCall funCall = (ResolvedFunCall) exp; Exp [] args = funCall.getArgs(); for (Exp arg : args) { findMeasures(arg, baseCubes, baseCubeList); } } } /** * Creates a SqlContextConstraint. * * @param evaluator Evaluator * @param strict defines the behaviour if the evaluator context * contains calculated members. If true, an exception is thrown, * otherwise calculated members are silently ignored. The * methods {@link mondrian.rolap.sql.MemberChildrenConstraint#addMemberConstraint(mondrian.rolap.sql.SqlQuery, mondrian.rolap.RolapCube, mondrian.rolap.aggmatcher.AggStar, RolapMember)} and * {@link mondrian.rolap.sql.MemberChildrenConstraint#addMemberConstraint(mondrian.rolap.sql.SqlQuery, mondrian.rolap.RolapCube, mondrian.rolap.aggmatcher.AggStar, java.util.List)} will * never accept a calculated member as parent. */ SqlContextConstraint(RolapEvaluator evaluator, boolean strict) { this.evaluator = evaluator.push(); this.strict = strict; cacheKey = new ArrayList(); cacheKey.add(getClass()); cacheKey.add(strict); cacheKey.addAll( Arrays.asList( SqlConstraintUtils.removeMultiPositionSlicerMembers( evaluator.getMembers(), evaluator))); // For virtual cubes, context constraint should be evaluated in the // query's context, because the query might reference different base // cubes. // // Note: we could avoid adding base cubes to the key if the evaluator // contains measure members referenced in the query, rather than // just the default measure for the entire virtual cube. The commented // code in RolapResult() that replaces the default measure seems to // do that. if (evaluator.getCube().isVirtual()) { cacheKey.addAll(evaluator.getQuery().getBaseCubes()); } } /** * Called from MemberChildren: adds parent to the current * context and restricts the SQL resultset to that new context. */ public void addMemberConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar, RolapMember parent) { if (parent.isCalculated()) { throw Util.newInternal("cannot restrict SQL to calculated member"); } final int savepoint = evaluator.savepoint(); evaluator.setContext(parent); SqlConstraintUtils.addContextConstraint( sqlQuery, aggStar, evaluator, strict); evaluator.restore(savepoint); // comment out addMemberConstraint here since constraint // is already added by addContextConstraint // SqlConstraintUtils.addMemberConstraint( // sqlQuery, baseCube, aggStar, parent, true); } /** * Adds parents to the current * context and restricts the SQL resultset to that new context. */ public void addMemberConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar, List parents) { SqlConstraintUtils.addContextConstraint( sqlQuery, aggStar, evaluator, strict); boolean exclude = false; SqlConstraintUtils.addMemberConstraint( sqlQuery, baseCube, aggStar, parents, true, false, exclude); } /** * Called from LevelMembers: restricts the SQL resultset to the current * context. */ public void addConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar) { SqlConstraintUtils.addContextConstraint( sqlQuery, aggStar, evaluator, strict); } /** * Returns whether a join with the fact table is required. A join is * required if the context contains members from dimensions other than * level. If we are interested in the members of a level or a members * children then it does not make sense to join only one dimension (the one * that contains the requested members) with the fact table for NON EMPTY * optimization. */ protected boolean isJoinRequired() { Member[] members = evaluator.getMembers(); // members[0] is the Measure, so loop starts at 1 for (int i = 1; i < members.length; i++) { if (!members[i].isAll()) { return true; } } return false; } public void addLevelConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar, RolapLevel level) { if (!isJoinRequired()) { return; } SqlConstraintUtils.joinLevelTableToFactTable( sqlQuery, baseCube, aggStar, evaluator, (RolapCubeLevel)level); } public MemberChildrenConstraint getMemberChildrenConstraint( RolapMember parent) { return this; } public Object getCacheKey() { return cacheKey; } public Evaluator getEvaluator() { return evaluator; } } // End SqlContextConstraint.java mondrian-3.4.1/src/main/mondrian/rolap/DescendantsConstraint.java0000644000175000017500000000403311735330606025030 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.Evaluator; import mondrian.rolap.aggmatcher.AggStar; import mondrian.rolap.sql.*; import java.util.List; /** * TupleConstaint which restricts the result of a tuple sqlQuery to a * set of parents. All parents must belong to the same level. * * @author av * @since Nov 10, 2005 */ class DescendantsConstraint implements TupleConstraint { List parentMembers; MemberChildrenConstraint mcc; /** * Creates a DescendantsConstraint. * * @param parentMembers list of parents all from the same level * @param mcc the constraint that would return the children for each single * parent */ public DescendantsConstraint( List parentMembers, MemberChildrenConstraint mcc) { this.parentMembers = parentMembers; this.mcc = mcc; } public void addConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar) { mcc.addMemberConstraint(sqlQuery, baseCube, aggStar, parentMembers); } public void addLevelConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar, RolapLevel level) { mcc.addLevelConstraint(sqlQuery, baseCube, aggStar, level); } public MemberChildrenConstraint getMemberChildrenConstraint( RolapMember parent) { return mcc; } /** * {@inheritDoc} * *

This implementation returns null, because descendants is not cached. */ public Object getCacheKey() { return null; } public Evaluator getEvaluator() { return null; } } // End DescendantsConstraint.java mondrian-3.4.1/src/main/mondrian/rolap/CellReader.java0000644000175000017500000000411611735330606022534 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. // // jhyde, 10 August, 2001 */ package mondrian.rolap; import mondrian.olap.Util; /** * A CellReader finds the cell value for the current context * held by evaluator. * *

It returns:

    *
  • null if the source is unable to evaluate the cell (for * example, AggregatingCellReader does not have the cell * in its cache). This value should only be returned if the caller is * expecting it.
  • *
  • {@link Util#nullValue} if the cell evaluates to null
  • *
  • {@link mondrian.olap.Util.ErrorCellValue} if the cell evaluates to an * error
  • *
  • an Object representing a value (often a {@link Double} or a {@link * java.math.BigDecimal}), otherwise
  • *
* * @author jhyde * @since 10 August, 2001 */ interface CellReader { /** * Returns the value of the cell which has the context described by the * evaluator. * A cell could have optional compound member coordinates usually specified * using the Aggregate function. These compound members are contained in the * evaluator. * *

If no aggregation contains the required cell, returns null. * *

If the value is null, returns {@link Util#nullValue}. * * @return Cell value, or null if not found, or {@link Util#nullValue} if * the value is null */ Object get(RolapEvaluator evaluator); /** * Returns the number of times this cell reader has told a lie * (since creation), because the required cell value is not in the * cache. */ int getMissCount(); /** * @return whether thus cell reader has any pending cell requests that are * not loaded yet. */ boolean isDirty(); } // End CellReader.java mondrian-3.4.1/src/main/mondrian/rolap/SmartMemberListCache.java0000644000175000017500000000456511735330606024540 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2010 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.rolap.cache.SmartCache; import mondrian.rolap.cache.SoftSmartCache; import mondrian.rolap.sql.SqlConstraint; import mondrian.util.Pair; /** * Uses a {@link mondrian.rolap.cache.SmartCache} to store lists of members, * where the key depends on a {@link mondrian.rolap.sql.SqlConstraint}. * *

Example 1: * *

 *   select ...
 *   [Customer].[Name].members on rows
 *   ...
 * 
* *

Example 2: *

 *   select ...
 *   NON EMPTY [Customer].[Name].members on rows
 *   ...
 *   WHERE ([Store#14], [Product].[Product#1])
 * 
* *

The first set, all customers are computed, in the second only * those, who have bought Product#1 in Store#14. We want to put both results * into the cache. Then the key for the cache entry is the Level that the * members belong to plus the costraint that restricted the amount of * members fetched. For Level.Members the key consists of the Level and the * cacheKey of the {@link mondrian.rolap.sql.SqlConstraint}. * * @see mondrian.rolap.sql.SqlConstraint#getCacheKey * * @author av * @since Nov 21, 2005 */ public class SmartMemberListCache { SmartCache, V> cache; public SmartMemberListCache() { cache = new SoftSmartCache, V>(); } public Object put(K key, SqlConstraint constraint, V value) { Object cacheKey = constraint.getCacheKey(); if (cacheKey == null) { return null; } Pair key2 = new Pair(key, cacheKey); return cache.put(key2, value); } public V get(K key, SqlConstraint constraint) { Pair key2 = new Pair(key, constraint.getCacheKey()); return cache.get(key2); } public void clear() { cache.clear(); } SmartCache, V> getCache() { return cache; } void setCache(SmartCache, V> cache) { this.cache = cache; } } // End SmartMemberListCache.java mondrian-3.4.1/src/main/mondrian/rolap/RolapCell.java0000644000175000017500000006671011735330606022417 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.mdx.*; import mondrian.olap.*; import mondrian.olap.fun.AggregateFunDef; import mondrian.olap.fun.SetFunDef; import mondrian.resource.MondrianResource; import mondrian.rolap.agg.*; import mondrian.server.*; import mondrian.server.monitor.SqlStatementEvent; import mondrian.spi.Dialect; import org.apache.log4j.Logger; import org.olap4j.AllocationPolicy; import org.olap4j.Scenario; import java.sql.ResultSet; import java.sql.SQLException; import java.util.*; /** * RolapCell implements {@link mondrian.olap.Cell} within a * {@link RolapResult}. */ public class RolapCell implements Cell { /** * @see mondrian.util.Bug#olap4jUpgrade Use * {@link mondrian.xmla.XmlaConstants}.ActionType.DRILLTHROUGH when present */ private static final int MDACTION_TYPE_DRILLTHROUGH = 0x100; private final RolapResult result; protected final int[] pos; protected RolapResult.CellInfo ci; /** * Creates a RolapCell. * * @param result Result cell belongs to * @param pos Coordinates of cell * @param ci Cell information, containing value et cetera */ RolapCell(RolapResult result, int[] pos, RolapResult.CellInfo ci) { this.result = result; this.pos = pos; this.ci = ci; } public List getCoordinateList() { return new AbstractList() { public Integer get(int index) { return pos[index]; } public int size() { return pos.length; } }; } public Object getValue() { if (ci.value == Util.nullValue) { return null; } return ci.value; } public String getCachedFormatString() { return ci.formatString; } public String getFormattedValue() { return ci.getFormatValue(); } public boolean isNull() { return (ci.value == Util.nullValue); } public boolean isError() { return (ci.value instanceof Throwable); } public String getDrillThroughSQL( boolean extendedContext) { return getDrillThroughSQL( new ArrayList(), extendedContext); } public String getDrillThroughSQL( List fields, boolean extendedContext) { if (!MondrianProperties.instance() .EnableDrillThrough.get()) { throw MondrianResource.instance() .DrillthroughDisabled.ex( MondrianProperties.instance() .EnableDrillThrough.getPath()); } final Member[] currentMembers = getMembersForDrillThrough(); // Create a StarPredicate to represent the compound slicer // (if necessary) // NOTE: the method buildDrillthroughSlicerPredicate modifies // the array of members, so it MUST be called before calling // RolapAggregationManager.makeDrillThroughRequest StarPredicate starPredicateSlicer = buildDrillthroughSlicerPredicate( currentMembers, result.getSlicerAxis()); DrillThroughCellRequest cellRequest = RolapAggregationManager.makeDrillThroughRequest( currentMembers, extendedContext, result.getCube(), fields); if (cellRequest == null) { return null; } final RolapConnection connection = result.getExecution().getMondrianStatement() .getMondrianConnection(); final RolapAggregationManager aggMgr = connection.getServer().getAggregationManager(); return aggMgr.getDrillThroughSql( cellRequest, starPredicateSlicer, fields, false); } public int getDrillThroughCount() { final Member[] currentMembers = getMembersForDrillThrough(); // Create a StarPredicate to represent the compound // slicer (if necessary) // NOTE: the method buildDrillthroughSlicerPredicate modifies // the array of members, so it MUST be called before calling // RolapAggregationManager.makeDrillThroughRequest StarPredicate starPredicateSlicer = buildDrillthroughSlicerPredicate( currentMembers, result.getSlicerAxis()); DrillThroughCellRequest cellRequest = RolapAggregationManager.makeDrillThroughRequest( currentMembers, false, result.getCube(), null); if (cellRequest == null) { return -1; } final RolapConnection connection = result.getExecution().getMondrianStatement() .getMondrianConnection(); final RolapAggregationManager aggMgr = connection.getServer().getAggregationManager(); final String sql = aggMgr.getDrillThroughSql( cellRequest, starPredicateSlicer, new ArrayList(), true); final SqlStatement stmt = RolapUtil.executeQuery( connection.getDataSource(), sql, new Locus( new Execution(connection.getInternalStatement(), 0), "RolapCell.getDrillThroughCount", "Error while counting drill-through")); try { ResultSet rs = stmt.getResultSet(); rs.next(); ++stmt.rowCount; return rs.getInt(1); } catch (SQLException e) { throw stmt.handle(e); } finally { stmt.close(); } } /** * This method handles the case of a compound slicer with more than one * {@link Position}. In this case, a simple array of {@link Member}s is not * sufficient to express the set of drill through rows. If the slicer axis * does have multiple positions, this method will do two things: *

    *
  1. Modify the passed-in array if any Member is overly restrictive. * This can happen if the slicer specifies multiple members in the same * hierarchy. In this scenario, the array of Members will contain an * element for only the last selected member in the hierarchy. This method * will replace that Member with the "All" Member from that hierarchy. *
  2. *
  3. Create a {@link StarPredicate} representing the Positions indicated * by the slicer axis. *
  4. *
* * @param membersForDrillthrough the array of Members returned by * {@link #getMembersForDrillThrough()} * @param slicerAxis the slicer {@link Axis} * @return an instance of StarPredicate representing all * of the the positions from the slicer if it has more than one, * or null otherwise. */ private StarPredicate buildDrillthroughSlicerPredicate( Member[] membersForDrillthrough, Axis slicerAxis) { List listOfPositions = slicerAxis.getPositions(); // If the slicer has zero or one position(s), // then there is no need to do // anything; the array of Members is correct as-is if (listOfPositions.size() <= 1) { return null; } // First, iterate through the positions' members, un-constraining the // "membersForDrillthrough" array if any position member is not // in the array for (Position position : listOfPositions) { for (Member member : position) { RolapHierarchy rolapHierarchy = (RolapHierarchy) member.getHierarchy(); // Check if the membersForDrillthrough constraint is identical // to that of the position member if (!membersForDrillthrough[rolapHierarchy.getOrdinalInCube()] .equals(member)) { // There is a discrepancy, so un-constrain the // membersForDrillthrough array membersForDrillthrough[rolapHierarchy.getOrdinalInCube()] = rolapHierarchy.getAllMember(); } } } // This is a list containing an AndPredicate for each position in the // slicer axis List listOfStarPredicatesForSlicerPositions = new ArrayList(); // Now we re-iterate the positions' members, // creating the slicer constraint for (Position position : listOfPositions) { // This is a list of the predicates required to select the // current position (excluding the members of the position // that are already constrained in the membersForDrillthrough array) List listOfStarPredicatesForCurrentPosition = new ArrayList(); // Iterate the members of the current position for (Member member : position) { RolapHierarchy rolapHierarchy = (RolapHierarchy) member.getHierarchy(); // If the membersForDrillthrough is already constraining to // this member, then there is no need to create additional // predicate(s) for this member if (!membersForDrillthrough[rolapHierarchy.getOrdinalInCube()] .equals(member)) { // Walk up the member's hierarchy, adding a // predicate for each level Member memberWalk = member; Level levelLast = null; while (memberWalk != null && ! memberWalk.isAll()) { // Only create a predicate for this member if we // are at a new level. This is for parent-child levels, // however it still suffers from the following bug: // http://jira.pentaho.com/browse/MONDRIAN-318 if (memberWalk.getLevel() != levelLast) { RolapCubeMember rolapCubeMember = (RolapCubeMember) memberWalk; RolapStar.Column column = rolapCubeMember.getLevel() .getBaseStarKeyColumn(result.getCube()); // Add a predicate for the member at this level listOfStarPredicatesForCurrentPosition.add( new MemberColumnPredicate( column, rolapCubeMember)); } levelLast = memberWalk.getLevel(); // Walk up the hierarchy memberWalk = memberWalk.getParentMember(); } } } // AND together all of the predicates that specify // the current position StarPredicate starPredicateForCurrentSlicerPosition = new AndPredicate(listOfStarPredicatesForCurrentPosition); // Add this position's predicate to the list listOfStarPredicatesForSlicerPositions .add(starPredicateForCurrentSlicerPosition); } // OR together the predicates for all of the slicer's // positions and return return new OrPredicate(listOfStarPredicatesForSlicerPositions); } /** * Returns whether it is possible to 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() { if (!MondrianProperties.instance() .EnableDrillThrough.get()) { return false; } // get current members final Member[] currentMembers = getMembersForDrillThrough(); if (containsCalcMembers(currentMembers)) { return false; } Cube x = chooseDrillThroughCube(currentMembers, result.getCube()); return x != null; } private boolean containsCalcMembers(Member[] currentMembers) { // Any calculated members which are not measures, we can't drill // through. Trivial calculated members should have been converted // already. We allow simple calculated measures such as // [Measures].[Unit Sales] / [Measures].[Store Sales] provided that both // are from the same cube. for (int i = 1; i < currentMembers.length; i++) { final Member currentMember = currentMembers[i]; if (currentMember.isCalculated()) { return true; } } return false; } public static RolapCube chooseDrillThroughCube( Member[] currentMembers, RolapCube defaultCube) { if (defaultCube != null && defaultCube.isVirtual()) { List cubes = new ArrayList(); for (RolapMember member : defaultCube.getMeasuresMembers()) { if (member instanceof RolapVirtualCubeMeasure) { RolapVirtualCubeMeasure measure = (RolapVirtualCubeMeasure) member; cubes.add(measure.getCube()); } } defaultCube = cubes.get(0); assert !defaultCube.isVirtual(); } final DrillThroughVisitor visitor = new DrillThroughVisitor(); try { for (Member member : currentMembers) { visitor.handleMember(member); } } catch (RuntimeException e) { if (e == DrillThroughVisitor.bomb) { // No cubes left return null; } else { throw e; } } return visitor.cube == null ? defaultCube : visitor.cube; } private Member[] getMembersForDrillThrough() { final Member[] currentMembers = result.getCellMembers(pos); // replace member if we're dealing with a trivial formula List memberList = Arrays.asList(currentMembers); for (int i = 0; i < currentMembers.length; i++) { replaceTrivialCalcMember(i, memberList); } return currentMembers; } private void replaceTrivialCalcMember(int i, List members) { Member member = members.get(i); if (!member.isCalculated()) { return; } member = RolapUtil.strip((RolapMember) member); // if "cm" is a calc member defined by // "with member cm as m" then // "cm" is equivalent to "m" final Exp expr = member.getExpression(); if (expr instanceof MemberExpr) { members.set( i, ((MemberExpr) expr).getMember()); return; } // "Aggregate({m})" is equivalent to "m" if (expr instanceof ResolvedFunCall) { ResolvedFunCall call = (ResolvedFunCall) expr; if (call.getFunDef() instanceof AggregateFunDef) { final Exp[] args = call.getArgs(); if (args[0] instanceof ResolvedFunCall) { final ResolvedFunCall arg0 = (ResolvedFunCall) args[0]; if (arg0.getFunDef() instanceof SetFunDef) { if (arg0.getArgCount() == 1 && arg0.getArg(0) instanceof MemberExpr) { final MemberExpr memberExpr = (MemberExpr) arg0.getArg(0); members.set(i, memberExpr.getMember()); } } } } } } /** * Generates an executes a SQL statement to drill through this cell. * *

Throws if this cell is not drillable. * *

Enforces limits on the starting and last row. * *

If tabFields is not null, returns the specified columns. (This option * is deprecated.) * * @param maxRowCount Maximum number of rows to retrieve, <= 0 if unlimited * @param firstRowOrdinal Ordinal of row to skip to (1-based), or 0 to * start from beginning * @param fields List of field expressions to return as the * result set columns. * @param extendedContext If true, add non-constraining columns to the * query for levels below each current member. * This additional context makes the drill-through * queries easier for humans to understand. * @param logger Logger. If not null and debug is enabled, log SQL here * @return executed SQL statement */ public SqlStatement drillThroughInternal( int maxRowCount, int firstRowOrdinal, List fields, boolean extendedContext, Logger logger) { if (!canDrillThrough()) { throw Util.newError("Cannot do DrillThrough operation on the cell"); } // Generate SQL. String sql = getDrillThroughSQL(fields, extendedContext); if (logger != null && logger.isDebugEnabled()) { logger.debug("drill through sql: " + sql); } // Choose the appropriate scrollability. If we need to start from an // offset row, it is useful that the cursor is scrollable, but not // essential. final Statement statement = result.getExecution().getMondrianStatement(); final Execution execution = new Execution(statement, 0); final Connection connection = statement.getMondrianConnection(); int resultSetType = ResultSet.TYPE_SCROLL_INSENSITIVE; int resultSetConcurrency = ResultSet.CONCUR_READ_ONLY; final Schema schema = statement.getSchema(); Dialect dialect = ((RolapSchema) schema).getDialect(); if (!dialect.supportsResultSetConcurrency( resultSetType, resultSetConcurrency) || firstRowOrdinal <= 1) { // downgrade to non-scroll cursor, since we can // fake absolute() via forward fetch resultSetType = ResultSet.TYPE_FORWARD_ONLY; } return RolapUtil.executeQuery( connection.getDataSource(), sql, null, maxRowCount, firstRowOrdinal, new SqlStatement.StatementLocus( execution, "RolapCell.drillThrough", "Error in drill through", SqlStatementEvent.Purpose.DRILL_THROUGH, 0), resultSetType, resultSetConcurrency); } public Object getPropertyValue(String propertyName) { final boolean matchCase = MondrianProperties.instance().CaseSensitive.get(); Property property = Property.lookup(propertyName, matchCase); Object defaultValue = null; if (property != null) { switch (property.ordinal) { case Property.CELL_ORDINAL_ORDINAL: return result.getCellOrdinal(pos); case Property.VALUE_ORDINAL: return getValue(); case Property.FORMAT_STRING_ORDINAL: if (ci.formatString == null) { final Evaluator evaluator = result.getRootEvaluator(); final int savepoint = evaluator.savepoint(); try { result.populateEvaluator(evaluator, pos); ci.formatString = evaluator.getFormatString(); } finally { evaluator.restore(savepoint); } } return ci.formatString; case Property.FORMATTED_VALUE_ORDINAL: return getFormattedValue(); case Property.FONT_FLAGS_ORDINAL: defaultValue = 0; break; case Property.SOLVE_ORDER_ORDINAL: defaultValue = 0; break; case Property.ACTION_TYPE_ORDINAL: return canDrillThrough() ? MDACTION_TYPE_DRILLTHROUGH : 0; case Property.DRILLTHROUGH_COUNT_ORDINAL: return canDrillThrough() ? getDrillThroughCount() : -1; default: // fall through } } final Evaluator evaluator = result.getRootEvaluator(); final int savepoint = evaluator.savepoint(); try { result.populateEvaluator(evaluator, pos); return evaluator.getProperty(propertyName, defaultValue); } finally { evaluator.restore(savepoint); } } public Member getContextMember(Hierarchy hierarchy) { return result.getMember(pos, hierarchy); } public void setValue( Scenario scenario, Object newValue, AllocationPolicy allocationPolicy, Object... allocationArgs) { if (allocationPolicy == null) { // user error throw Util.newError( "Allocation policy must not be null"); } final RolapMember[] members = result.getCellMembers(pos); for (int i = 0; i < members.length; i++) { Member member = members[i]; if (ScenarioImpl.isScenario(member.getHierarchy())) { scenario = (Scenario) member.getPropertyValue(Property.SCENARIO.name); members[i] = (RolapMember) member.getHierarchy().getAllMember(); } else if (member.isCalculated()) { throw Util.newError( "Cannot write to cell: one of the coordinates (" + member.getUniqueName() + ") is a calculated member"); } } if (scenario == null) { throw Util.newError("No active scenario"); } if (allocationArgs == null) { allocationArgs = new Object[0]; } final Object currentValue = getValue(); double doubleCurrentValue; if (currentValue == null) { doubleCurrentValue = 0d; } else if (currentValue instanceof Number) { doubleCurrentValue = ((Number) currentValue).doubleValue(); } else { // Cell is not a number. Likely it is a string or a // MondrianEvaluationException. Do not attempt to change the value // in this case. (REVIEW: Is this the correct behavior?) return; } double doubleNewValue = ((Number) newValue).doubleValue(); ((ScenarioImpl) scenario).setCellValue( result.getExecution().getMondrianStatement() .getMondrianConnection(), Arrays.asList(members), doubleNewValue, doubleCurrentValue, allocationPolicy, allocationArgs); } /** * Visitor that walks over a cell's expression and checks whether the * cell should allow drill-through. If not, throws the {@link #bomb} * exception. * *

Examples:

*
    *
  • Literal 1 is drillable
  • *
  • Member [Measures].[Unit Sales] is drillable
  • *
  • Calculated member with expression [Measures].[Unit Sales] + * 1 is drillable
  • *
  • Calculated member with expression * ([Measures].[Unit Sales], [Time].PrevMember) is not drillable
  • *
*/ private static class DrillThroughVisitor extends MdxVisitorImpl { static final RuntimeException bomb = new RuntimeException(); RolapCube cube = null; DrillThroughVisitor() { } public Object visit(MemberExpr memberExpr) { handleMember(memberExpr.getMember()); return null; } public Object visit(ResolvedFunCall call) { final FunDef def = call.getFunDef(); final Exp[] args = call.getArgs(); final String name = def.getName(); if (name.equals("+") || name.equals("-") || name.equals("/") || name.equals("*") || name.equals("CoalesceEmpty") // Allow parentheses but don't allow tuple || name.equals("()") && args.length == 1) { return null; } throw bomb; } public void handleMember(Member member) { if (member instanceof RolapStoredMeasure) { // If this member is in a different cube that previous members // we've seen, we cannot drill through. final RolapCube cube = ((RolapStoredMeasure) member).getCube(); if (this.cube == null) { this.cube = cube; } else if (this.cube != cube) { // this measure lives in a different cube than previous // measures we have seen throw bomb; } } else if (member instanceof RolapCubeMember) { handleMember(((RolapCubeMember) member).member); } else if (member instanceof RolapHierarchy.RolapCalculatedMeasure) { RolapHierarchy.RolapCalculatedMeasure measure = (RolapHierarchy.RolapCalculatedMeasure) member; measure.getFormula().getExpression().accept(this); } else if (member instanceof RolapMember) { // regular RolapMember - fine } else { // don't know what this is! throw bomb; } } public Object visit(NamedSetExpr namedSetExpr) { throw Util.newInternal("not valid here: " + namedSetExpr); } public Object visit(Literal literal) { return null; // literals are drillable } public Object visit(Query query) { throw Util.newInternal("not valid here: " + query); } public Object visit(QueryAxis queryAxis) { throw Util.newInternal("not valid here: " + queryAxis); } public Object visit(Formula formula) { throw Util.newInternal("not valid here: " + formula); } public Object visit(UnresolvedFunCall call) { throw Util.newInternal("expected resolved expression"); } public Object visit(Id id) { throw Util.newInternal("expected resolved expression"); } public Object visit(ParameterExpr parameterExpr) { // Not valid in general; might contain complex expression throw bomb; } public Object visit(DimensionExpr dimensionExpr) { // Not valid in general; might be part of complex expression throw bomb; } public Object visit(HierarchyExpr hierarchyExpr) { // Not valid in general; might be part of complex expression throw bomb; } public Object visit(LevelExpr levelExpr) { // Not valid in general; might be part of complex expression throw bomb; } } } // End RolapCell.java mondrian-3.4.1/src/main/mondrian/rolap/CacheControlImpl.java0000644000175000017500000020255511735330606023727 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.olap.Id.Quoting; import mondrian.resource.MondrianResource; import mondrian.rolap.agg.SegmentCacheManager; import mondrian.rolap.sql.MemberChildrenConstraint; import mondrian.server.Execution; import mondrian.server.Locus; import mondrian.spi.SegmentColumn; import mondrian.util.ArraySortedSet; import org.eigenbase.util.property.BooleanProperty; import java.io.PrintWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.UndeclaredThrowableException; import java.util.*; import java.util.Map.Entry; import java.util.concurrent.Callable; import javax.sql.DataSource; /** * Implementation of {@link CacheControl} API. * * @author jhyde * @since Sep 27, 2006 */ public class CacheControlImpl implements CacheControl { private final RolapConnection connection; /** * Object to lock before making changes to the member cache. * *

The "member cache" is a figure of speech: each RolapHierarchy has its * own MemberCache object. But to provide transparently serialized access * to the "member cache" via the interface CacheControl, provide a common * lock here. * *

NOTE: static member is a little too wide a scope for this lock, * because in theory a JVM can contain multiple independent instances of * mondrian. */ private static final Object MEMBER_CACHE_LOCK = new Object(); /** * Creates a CacheControlImpl. * * @param connection Connection */ public CacheControlImpl(RolapConnection connection) { super(); this.connection = connection; } // cell cache control public CellRegion createMemberRegion(Member member, boolean descendants) { if (member == null) { throw new NullPointerException(); } final ArrayList list = new ArrayList(); list.add(member); return new MemberCellRegion(list, descendants); } public CellRegion createMemberRegion( boolean lowerInclusive, Member lowerMember, boolean upperInclusive, Member upperMember, boolean descendants) { if (lowerMember == null) { lowerInclusive = false; } if (upperMember == null) { upperInclusive = false; } return new MemberRangeCellRegion( (RolapMember) lowerMember, lowerInclusive, (RolapMember) upperMember, upperInclusive, descendants); } public CellRegion createCrossjoinRegion(CellRegion... regions) { assert regions != null; assert regions.length >= 2; final HashSet set = new HashSet(); final List list = new ArrayList(); for (CellRegion region : regions) { int prevSize = set.size(); List dimensionality = region.getDimensionality(); set.addAll(dimensionality); if (set.size() < prevSize + dimensionality.size()) { throw MondrianResource.instance() .CacheFlushCrossjoinDimensionsInCommon.ex( getDimensionalityList(regions)); } flattenCrossjoin((CellRegionImpl) region, list); } return new CrossjoinCellRegion(list); } // Returns e.g. "'[[Product]]', '[[Time], [Product]]'" private String getDimensionalityList(CellRegion[] regions) { StringBuilder buf = new StringBuilder(); int k = 0; for (CellRegion region : regions) { if (k++ > 0) { buf.append(", "); } buf.append("'"); buf.append(region.getDimensionality().toString()); buf.append("'"); } return buf.toString(); } public CellRegion createUnionRegion(CellRegion... regions) { if (regions == null) { throw new NullPointerException(); } if (regions.length < 2) { throw new IllegalArgumentException(); } final List list = new ArrayList(); for (CellRegion region : regions) { if (!region.getDimensionality().equals( regions[0].getDimensionality())) { throw MondrianResource.instance() .CacheFlushUnionDimensionalityMismatch.ex( regions[0].getDimensionality().toString(), region.getDimensionality().toString()); } list.add((CellRegionImpl) region); } return new UnionCellRegion(list); } public CellRegion createMeasuresRegion(Cube cube) { Dimension measuresDimension = null; for (Dimension dim : cube.getDimensions()) { if (dim.isMeasures()) { measuresDimension = dim; break; } } if (measuresDimension == null) { throw new MondrianException( "No measures dimension found for cube " + cube.getName()); } final List measures = cube.getSchemaReader(null).withLocus().getLevelMembers( measuresDimension.getHierarchy().getLevels()[0], false); if (measures.size() == 0) { return new EmptyCellRegion(); } return new MemberCellRegion(measures, false); } public void flush(final CellRegion region) { Locus.execute( connection, "Flush", new Locus.Action() { public Void execute() { flushInternal(region); return null; } }); } private void flushInternal(CellRegion region) { if (region instanceof EmptyCellRegion) { return; } final List dimensionality = region.getDimensionality(); boolean found = false; for (Dimension dimension : dimensionality) { if (dimension.isMeasures()) { found = true; break; } } if (!found) { throw MondrianResource.instance().CacheFlushRegionMustContainMembers .ex(); } final UnionCellRegion union = normalize((CellRegionImpl) region); for (CellRegionImpl cellRegion : union.regions) { // Figure out the bits. flushNonUnion(cellRegion); } } /** * Flushes a list of cell regions. * * @param cellRegionList List of cell regions */ protected void flushRegionList(List cellRegionList) { final CellRegion cellRegion; switch (cellRegionList.size()) { case 0: return; case 1: cellRegion = cellRegionList.get(0); break; default: final CellRegion[] cellRegions = cellRegionList.toArray(new CellRegion[cellRegionList.size()]); cellRegion = createUnionRegion(cellRegions); break; } if (!containsMeasures(cellRegion)) { for (RolapCube cube : connection.getSchema().getCubeList()) { flush( createCrossjoinRegion( createMeasuresRegion(cube), cellRegion)); } } else { flush(cellRegion); } } private boolean containsMeasures(CellRegion cellRegion) { final List dimensionList = cellRegion.getDimensionality(); for (Dimension dimension : dimensionList) { if (dimension.isMeasures()) { return true; } } return false; } public void trace(String message) { // ignore message } public void flushSchemaCache() { RolapSchema.Pool.instance().clear(); } // todo: document public void flushSchema( String catalogUrl, String connectionKey, String jdbcUser, String dataSourceStr) { RolapSchema.Pool.instance().remove( catalogUrl, connectionKey, jdbcUser, dataSourceStr); } // todo: document public void flushSchema( String catalogUrl, DataSource dataSource) { RolapSchema.Pool.instance().remove( catalogUrl, dataSource); } /** * Flushes the given RolapSchema instance from the pool * * @param schema RolapSchema */ public void flushSchema(Schema schema) { if (RolapSchema.class.isInstance(schema)) { RolapSchema.Pool.instance().remove((RolapSchema)schema); } else { throw new UnsupportedOperationException( schema.getClass().getName() + " cannot be flushed"); } } protected void flushNonUnion(CellRegion region) { throw new UnsupportedOperationException(); } /** * Normalizes a CellRegion into a union of crossjoins of member regions. * * @param region Region * @return normalized region */ UnionCellRegion normalize(CellRegionImpl region) { // Search for Union within a Crossjoin. // Crossjoin(a1, a2, Union(r1, r2, r3), a4) // becomes // Union( // Crossjoin(a1, a2, r1, a4), // Crossjoin(a1, a2, r2, a4), // Crossjoin(a1, a2, r3, a4)) // First, decompose into a flat list of non-union regions. List nonUnionList = new LinkedList(); flattenUnion(region, nonUnionList); for (int i = 0; i < nonUnionList.size(); i++) { while (true) { CellRegionImpl nonUnionRegion = nonUnionList.get(i); UnionCellRegion firstUnion = findFirstUnion(nonUnionRegion); if (firstUnion == null) { break; } List list = new ArrayList(); for (CellRegionImpl unionComponent : firstUnion.regions) { // For each unionComponent in (r1, r2, r3), // create Crossjoin(a1, a2, r1, a4). CellRegionImpl cj = copyReplacing( nonUnionRegion, firstUnion, unionComponent); list.add(cj); } // Replace one element which contained a union with several // which contain one fewer union. (Double-linked list helps // here.) nonUnionList.remove(i); nonUnionList.addAll(i, list); } } return new UnionCellRegion(nonUnionList); } private CellRegionImpl copyReplacing( CellRegionImpl region, CellRegionImpl seek, CellRegionImpl replacement) { if (region == seek) { return replacement; } if (region instanceof UnionCellRegion) { final UnionCellRegion union = (UnionCellRegion) region; List list = new ArrayList(); for (CellRegionImpl child : union.regions) { list.add(copyReplacing(child, seek, replacement)); } return new UnionCellRegion(list); } if (region instanceof CrossjoinCellRegion) { final CrossjoinCellRegion crossjoin = (CrossjoinCellRegion) region; List list = new ArrayList(); for (CellRegionImpl child : crossjoin.components) { list.add(copyReplacing(child, seek, replacement)); } return new CrossjoinCellRegion(list); } // This region is atomic, and since regions are immutable we don't need // to clone. return region; } /** * Flatten a region into a list of regions none of which are unions. * * @param region Cell region * @param list Target list */ private void flattenUnion( CellRegionImpl region, List list) { if (region instanceof UnionCellRegion) { UnionCellRegion union = (UnionCellRegion) region; for (CellRegionImpl region1 : union.regions) { flattenUnion(region1, list); } } else { list.add(region); } } /** * Flattens a region into a list of regions none of which are unions. * * @param region Cell region * @param list Target list */ private void flattenCrossjoin( CellRegionImpl region, List list) { if (region instanceof CrossjoinCellRegion) { CrossjoinCellRegion crossjoin = (CrossjoinCellRegion) region; for (CellRegionImpl component : crossjoin.components) { flattenCrossjoin(component, list); } } else { list.add(region); } } private UnionCellRegion findFirstUnion(CellRegion region) { final CellRegionVisitor visitor = new CellRegionVisitorImpl() { public void visit(UnionCellRegion region) { throw new FoundOne(region); } }; try { ((CellRegionImpl) region).accept(visitor); return null; } catch (FoundOne foundOne) { return foundOne.region; } } /** * Returns a list of members of the Measures dimension which are mentioned * somewhere in a region specification. * * @param region Cell region * @return List of members mentioned in cell region specification */ public static List findMeasures(CellRegion region) { final List list = new ArrayList(); final CellRegionVisitor visitor = new CellRegionVisitorImpl() { public void visit(MemberCellRegion region) { if (region.dimension.isMeasures()) { list.addAll(region.memberList); } } public void visit(MemberRangeCellRegion region) { if (region.level.getDimension().isMeasures()) { // FIXME: don't allow range on measures dimension assert false : "ranges on measures dimension"; } } }; ((CellRegionImpl) region).accept(visitor); return list; } public static SegmentColumn[] findAxisValues(CellRegion region) { final List list = new ArrayList(); final CellRegionVisitor visitor = new CellRegionVisitorImpl() { public void visit(MemberCellRegion region) { if (region.dimension.isMeasures()) { return; } final Map> levels = new HashMap>(); for (Member member : region.memberList) { RolapLevel currentLevel = ((RolapLevel)member.getLevel()); while (true) { if (currentLevel == null) { break; } if (currentLevel.isAll()) { currentLevel = (RolapLevel) currentLevel.getChildLevel(); continue; } final String ccName = currentLevel.getKeyExp() .getGenericExpression(); if (!levels.containsKey(ccName)) { levels.put( ccName, new HashSet()); } if (member.getLevel().equals(currentLevel)) { levels.get(ccName).add( (Comparable)((RolapMember)member).getKey()); } else { levels.get(ccName).clear(); levels.get(ccName).add(true); } currentLevel = (RolapLevel) currentLevel.getChildLevel(); } } for (Entry> entry : levels.entrySet()) { // Now sort and convert to an ArraySortedSet. final Comparable[] keys = entry.getValue().toArray( new Comparable[entry.getValue().size()]); if (keys.length == 1 && keys[0].equals(true)) { list.add( new SegmentColumn( entry.getKey(), -1, null)); } else { Arrays.sort( keys, Util.SqlNullSafeComparator.instance); //noinspection unchecked list.add( new SegmentColumn( entry.getKey(), -1, new ArraySortedSet(keys))); } } } public void visit(MemberRangeCellRegion region) { // We translate all ranges into wildcards. // FIXME Optimize this by resolving the list of members // into an actual list of values for ConstrainedColumn list.add( new SegmentColumn( region.level.getKeyExp().getGenericExpression(), -1, null)); } }; ((CellRegionImpl) region).accept(visitor); return list.toArray(new SegmentColumn[list.size()]); } public static List getStarList(CellRegion region) { // Figure out which measure (therefore star) it belongs to. List starList = new ArrayList(); final List measuresList = findMeasures(region); for (Member measure : measuresList) { if (measure instanceof RolapStoredMeasure) { RolapStoredMeasure storedMeasure = (RolapStoredMeasure) measure; final RolapStar.Measure starMeasure = (RolapStar.Measure) storedMeasure.getStarMeasure(); if (!starList.contains(starMeasure.getStar())) { starList.add(starMeasure.getStar()); } } } return starList; } public void printCacheState( final PrintWriter pw, final CellRegion region) { final List starList = getStarList(region); for (RolapStar star : starList) { star.print(pw, "", false); } final SegmentCacheManager manager = MondrianServer.forConnection(connection) .getAggregationManager().cacheMgr; Locus.execute( connection, "CacheControlImpl.printCacheState", new Locus.Action() { public Void execute() { manager.printCacheState(region, pw, Locus.peek()); return null; } }); } public MemberSet createMemberSet(Member member, boolean descendants) { return new SimpleMemberSet( Collections.singletonList((RolapMember) member), descendants); } public MemberSet createMemberSet( boolean lowerInclusive, Member lowerMember, boolean upperInclusive, Member upperMember, boolean descendants) { if (upperMember != null && lowerMember != null) { assert upperMember.getLevel().equals(lowerMember.getLevel()); } if (lowerMember == null) { lowerInclusive = false; } if (upperMember == null) { upperInclusive = false; } return new RangeMemberSet( stripMember((RolapMember) lowerMember), lowerInclusive, stripMember((RolapMember) upperMember), upperInclusive, descendants); } public MemberSet createUnionSet(MemberSet... args) { //noinspection unchecked return new UnionMemberSet((List) Arrays.asList(args)); } public MemberSet filter(Level level, MemberSet baseSet) { if (level instanceof RolapCubeLevel) { // be forgiving level = ((RolapCubeLevel) level).getRolapLevel(); } return ((MemberSetPlus) baseSet).filter((RolapLevel) level); } public void flush(MemberSet memberSet) { // REVIEW How is flush(s) different to executing createDeleteCommand(s)? synchronized (MEMBER_CACHE_LOCK) { final List cellRegionList = new ArrayList(); ((MemberSetPlus) memberSet).accept( new MemberSetVisitorImpl() { public void visit(RolapMember member) { flushMember(member, cellRegionList); } } ); // STUB: flush the set: another visitor // finally, flush cells now invalid flushRegionList(cellRegionList); } } public void printCacheState(PrintWriter pw, MemberSet set) { synchronized (MEMBER_CACHE_LOCK) { pw.println("need to implement printCacheState"); // TODO: } } public MemberEditCommand createCompoundCommand( List commandList) { //noinspection unchecked return new CompoundCommand((List) commandList); } public MemberEditCommand createCompoundCommand( MemberEditCommand... commands) { //noinspection unchecked return new CompoundCommand((List) Arrays.asList(commands)); } public MemberEditCommand createDeleteCommand(Member member) { if (member == null) { throw new IllegalArgumentException("cannot delete null member"); } if (((RolapLevel) member.getLevel()).isParentChild()) { throw new IllegalArgumentException( "delete member not supported for parent-child hierarchy"); } return createDeleteCommand(createMemberSet(member, false)); } public MemberEditCommand createDeleteCommand(MemberSet s) { return new DeleteMemberCommand((MemberSetPlus) s); } public MemberEditCommand createAddCommand( Member member) throws IllegalArgumentException { if (member == null) { throw new IllegalArgumentException("cannot add null member"); } if (((RolapLevel) member.getLevel()).isParentChild()) { throw new IllegalArgumentException( "add member not supported for parent-child hierarchy"); } return new AddMemberCommand((RolapMember) member); } public MemberEditCommand createMoveCommand(Member member, Member loc) throws IllegalArgumentException { if (member == null) { throw new IllegalArgumentException("cannot move null member"); } if (((RolapLevel) member.getLevel()).isParentChild()) { throw new IllegalArgumentException( "move member not supported for parent-child hierarchy"); } if (loc == null) { throw new IllegalArgumentException( "cannot move member to null location"); } // TODO: check that MEMBER and LOC (its new parent) have appropriate // Levels return new MoveMemberCommand((RolapMember) member, (RolapMember) loc); } public MemberEditCommand createSetPropertyCommand( Member member, String name, Object value) throws IllegalArgumentException { if (member == null) { throw new IllegalArgumentException( "cannot set properties on null member"); } if (((RolapLevel) member.getLevel()).isParentChild()) { throw new IllegalArgumentException( "set properties not supported for parent-child hierarchy"); } // TODO: validate that prop NAME exists for Level of MEMBER return new ChangeMemberPropsCommand( new SimpleMemberSet( Collections.singletonList((RolapMember) member), false), Collections.singletonMap(name, value)); } public MemberEditCommand createSetPropertyCommand( MemberSet members, Map propertyValues) throws IllegalArgumentException { // TODO: check that members all at same Level, and validate that props // exist validateSameLevel((MemberSetPlus) members); return new ChangeMemberPropsCommand( (MemberSetPlus) members, propertyValues); } /** * Validates that all members of a member set are the same level. * * @param memberSet Member set * @throws IllegalArgumentException if members are from more than one level */ private void validateSameLevel(MemberSetPlus memberSet) throws IllegalArgumentException { memberSet.accept( new MemberSetVisitor() { final Set levelSet = new HashSet(); private void visitMember( RolapMember member, boolean descendants) { final String message = "all members in set must belong to same level"; if (levelSet.add(member.getLevel()) && levelSet.size() > 1) { throw new IllegalArgumentException(message); } if (descendants && member.getLevel().getChildLevel() != null) { throw new IllegalArgumentException(message); } } public void visit(SimpleMemberSet simpleMemberSet) { for (RolapMember member : simpleMemberSet.members) { visitMember(member, simpleMemberSet.descendants); } } public void visit(UnionMemberSet unionMemberSet) { for (MemberSetPlus item : unionMemberSet.items) { item.accept(this); } } public void visit(RangeMemberSet rangeMemberSet) { visitMember( rangeMemberSet.lowerMember, rangeMemberSet.descendants); visitMember( rangeMemberSet.upperMember, rangeMemberSet.descendants); } } ); } public void execute(MemberEditCommand cmd) { final BooleanProperty prop = MondrianProperties.instance().EnableRolapCubeMemberCache; if (prop.get()) { throw new IllegalArgumentException( "Member cache control operations are not allowed unless " + "property " + prop.getPath() + " is false"); } synchronized (MEMBER_CACHE_LOCK) { // Make sure that a Locus is in the Execution stack, // since some operations might require DB access. Execution execution; try { execution = Locus.peek().execution; } catch (EmptyStackException e) { if (connection == null) { throw new IllegalArgumentException("Connection required"); } execution = new Execution(connection.getInternalStatement(), 0); } final Locus locus = new Locus( execution, "CacheControlImpl.execute", "when modifying the member cache."); Locus.push(locus); try { // Execute the command final List cellRegionList = new ArrayList(); ((MemberEditCommandPlus) cmd).execute(cellRegionList); // Flush the cells touched by the regions for (CellRegion memberRegion : cellRegionList) { // Iterate over the cubes, create a cross region with // its measures, and flush the data cells. // It is possible that some regions don't intersect // with a cube. We will intercept the exceptions and // skip to the next cube if necessary. final List dimensions = memberRegion.getDimensionality(); if (dimensions.size() > 0) { for (Cube cube : dimensions.get(0) .getSchema().getCubes()) { try { final List crossList = new ArrayList(); crossList.add( (CellRegionImpl) createMeasuresRegion(cube)); crossList.add((CellRegionImpl) memberRegion); final CellRegion crossRegion = new CrossjoinCellRegion(crossList); flush(crossRegion); } catch (UndeclaredThrowableException e) { if (e.getCause() instanceof InvocationTargetException) { final InvocationTargetException ite = (InvocationTargetException)e.getCause(); if (ite.getTargetException() instanceof MondrianException) { final MondrianException me = (MondrianException) ite.getTargetException(); if (me.getMessage() .matches( "^Mondrian Error:Member " + "'\\[.*\\]' not found$")) { continue; } } } throw new MondrianException(e); } catch (MondrianException e) { if (e.getMessage() .matches( "^Mondrian Error:Member " + "'\\[.*\\]' not found$")) { continue; } throw e; } } } } // Apply it all. ((MemberEditCommandPlus) cmd).commit(); } finally { Locus.pop(locus); } } } private static MemberCache getMemberCache(RolapMember member) { final MemberReader memberReader = member.getHierarchy().getMemberReader(); SmartMemberReader smartMemberReader = (SmartMemberReader) memberReader; return smartMemberReader.getMemberCache(); } // cell cache control implementation /** * Cell region formed by a list of members. * * @see MemberRangeCellRegion */ static class MemberCellRegion implements CellRegionImpl { private final List memberList; private final Dimension dimension; MemberCellRegion(List memberList, boolean descendants) { assert memberList.size() > 0; this.memberList = memberList; this.dimension = (memberList.get(0)).getDimension(); Util.discard(descendants); } public List getDimensionality() { return Collections.singletonList(dimension); } public String toString() { return Util.commaList("Member", memberList); } public void accept(CellRegionVisitor visitor) { visitor.visit(this); } public List getMemberList() { return memberList; } } /** * An empty cell region. */ static class EmptyCellRegion implements CellRegionImpl { public void accept(CellRegionVisitor visitor) { visitor.visit(this); } public List getDimensionality() { return Collections.emptyList(); } } /** * Cell region formed a range of members between a lower and upper bound. */ static class MemberRangeCellRegion implements CellRegionImpl { private final RolapMember lowerMember; private final boolean lowerInclusive; private final RolapMember upperMember; private final boolean upperInclusive; private final boolean descendants; private final RolapLevel level; MemberRangeCellRegion( RolapMember lowerMember, boolean lowerInclusive, RolapMember upperMember, boolean upperInclusive, boolean descendants) { assert lowerMember != null || upperMember != null; assert lowerMember == null || upperMember == null || lowerMember.getLevel() == upperMember.getLevel(); assert !(lowerMember == null && lowerInclusive); assert !(upperMember == null && upperInclusive); this.lowerMember = lowerMember; this.lowerInclusive = lowerInclusive; this.upperMember = upperMember; this.upperInclusive = upperInclusive; this.descendants = descendants; this.level = lowerMember == null ? upperMember.getLevel() : lowerMember.getLevel(); } public List getDimensionality() { return Collections.singletonList(level.getDimension()); } public RolapLevel getLevel() { return level; } public String toString() { final StringBuilder sb = new StringBuilder("Range("); if (lowerMember == null) { sb.append("null"); } else { sb.append(lowerMember); if (lowerInclusive) { sb.append(" inclusive"); } else { sb.append(" exclusive"); } } sb.append(" to "); if (upperMember == null) { sb.append("null"); } else { sb.append(upperMember); if (upperInclusive) { sb.append(" inclusive"); } else { sb.append(" exclusive"); } } sb.append(")"); return sb.toString(); } public void accept(CellRegionVisitor visitor) { visitor.visit(this); } public boolean getLowerInclusive() { return lowerInclusive; } public RolapMember getLowerBound() { return lowerMember; } public boolean getUpperInclusive() { return upperInclusive; } public RolapMember getUpperBound() { return upperMember; } } /** * Cell region formed by a cartesian product of two or more CellRegions. */ static class CrossjoinCellRegion implements CellRegionImpl { final List dimensions; private List components = new ArrayList(); CrossjoinCellRegion(List regions) { final List dimensionality = new ArrayList(); compute(regions, components, dimensionality); dimensions = Collections.unmodifiableList(dimensionality); } private static void compute( List regions, List components, List dimensionality) { final Set dimensionSet = new HashSet(); for (CellRegionImpl region : regions) { addComponents(region, components); final List regionDimensionality = region.getDimensionality(); dimensionality.addAll(regionDimensionality); dimensionSet.addAll(regionDimensionality); assert dimensionSet.size() == dimensionality.size() : "dimensions in common"; } } public void accept(CellRegionVisitor visitor) { visitor.visit(this); for (CellRegion component : components) { CellRegionImpl cellRegion = (CellRegionImpl) component; cellRegion.accept(visitor); } } private static void addComponents( CellRegionImpl region, List list) { if (region instanceof CrossjoinCellRegion) { CrossjoinCellRegion crossjoinRegion = (CrossjoinCellRegion) region; for (CellRegionImpl component : crossjoinRegion.components) { list.add(component); } } else { list.add(region); } } public List getDimensionality() { return dimensions; } public String toString() { return Util.commaList("Crossjoin", components); } public List getComponents() { return Util.cast(components); } } private static class UnionCellRegion implements CellRegionImpl { private final List regions; UnionCellRegion(List regions) { this.regions = regions; assert regions.size() >= 1; // All regions must have same dimensionality. for (int i = 1; i < regions.size(); i++) { final CellRegion region0 = regions.get(0); final CellRegion region = regions.get(i); assert region0.getDimensionality().equals( region.getDimensionality()); } } public List getDimensionality() { return regions.get(0).getDimensionality(); } public String toString() { return Util.commaList("Union", regions); } public void accept(CellRegionVisitor visitor) { visitor.visit(this); for (CellRegionImpl cellRegion : regions) { cellRegion.accept(visitor); } } } interface CellRegionImpl extends CellRegion { void accept(CellRegionVisitor visitor); } /** * Visitor that visits various sub-types of * {@link mondrian.olap.CacheControl.CellRegion}. */ interface CellRegionVisitor { void visit(MemberCellRegion region); void visit(MemberRangeCellRegion region); void visit(UnionCellRegion region); void visit(CrossjoinCellRegion region); void visit(EmptyCellRegion region); } private static class FoundOne extends RuntimeException { private final transient UnionCellRegion region; public FoundOne(UnionCellRegion region) { this.region = region; } } /** * Default implementation of {@link CellRegionVisitor}. */ private static class CellRegionVisitorImpl implements CellRegionVisitor { public void visit(MemberCellRegion region) { // nothing } public void visit(MemberRangeCellRegion region) { // nothing } public void visit(UnionCellRegion region) { // nothing } public void visit(CrossjoinCellRegion region) { // nothing } public void visit(EmptyCellRegion region) { // nothing } } // ~ member cache control implementation ---------------------------------- /** * Implementation-specific extensions to the * {@link mondrian.olap.CacheControl.MemberEditCommand} interface. */ interface MemberEditCommandPlus extends MemberEditCommand { /** * Executes this command, and gathers a list of cell regions affected * in the {@code cellRegionList} parameter. The caller will flush the * cell regions later. * * @param cellRegionList Populated with a list of cell regions which * are invalidated by this action */ void execute(final List cellRegionList); void commit(); } /** * Implementation-specific extensions to the * {@link mondrian.olap.CacheControl.MemberSet} interface. */ interface MemberSetPlus extends MemberSet { /** * Accepts a visitor. * * @param visitor Visitor */ void accept(MemberSetVisitor visitor); /** * Filters this member set, returning a member set containing all * members at a given Level. When applicable, returns this member set * unchanged. * * @param level Level * @return Member set with members not at the given level removed */ MemberSetPlus filter(RolapLevel level); } /** * Visits the subclasses of {@link MemberSetPlus}. */ interface MemberSetVisitor { void visit(SimpleMemberSet s); void visit(UnionMemberSet s); void visit(RangeMemberSet s); } /** * Default implementation of {@link MemberSetVisitor}. * *

The default implementation may not be efficient. For example, if * flushing a range of members from the cache, you may not wish to fetch * all of the members into the cache in order to flush them. */ public static abstract class MemberSetVisitorImpl implements MemberSetVisitor { public void visit(UnionMemberSet s) { for (MemberSetPlus item : s.items) { item.accept(this); } } public void visit(RangeMemberSet s) { final MemberReader memberReader = s.level.getHierarchy().getMemberReader(); visitRange( memberReader, s.level, s.lowerMember, s.upperMember, s.descendants); } protected void visitRange( MemberReader memberReader, RolapLevel level, RolapMember lowerMember, RolapMember upperMember, boolean recurse) { final List list = new ArrayList(); memberReader.getMemberRange(level, lowerMember, upperMember, list); for (RolapMember member : list) { visit(member); } if (recurse) { list.clear(); memberReader.getMemberChildren(lowerMember, list); if (list.isEmpty()) { return; } RolapMember lowerChild = list.get(0); list.clear(); memberReader.getMemberChildren(upperMember, list); if (list.isEmpty()) { return; } RolapMember upperChild = list.get(list.size() - 1); visitRange( memberReader, level, lowerChild, upperChild, recurse); } } public void visit(SimpleMemberSet s) { for (RolapMember member : s.members) { visit(member); } } /** * Visits a single member. * * @param member Member */ public abstract void visit(RolapMember member); } /** * Member set containing no members. */ static class EmptyMemberSet implements MemberSetPlus { public static final EmptyMemberSet INSTANCE = new EmptyMemberSet(); private EmptyMemberSet() { // prevent instantiation except for singleton } public void accept(MemberSetVisitor visitor) { // nothing } public MemberSetPlus filter(RolapLevel level) { return this; } public String toString() { return "Empty"; } } /** * Member set defined by a list of members from one hierarchy. */ static class SimpleMemberSet implements MemberSetPlus { public final List members; // the set includes the descendants of all members public final boolean descendants; public final RolapHierarchy hierarchy; SimpleMemberSet(List members, boolean descendants) { this.members = new ArrayList(members); stripMemberList(this.members); this.descendants = descendants; this.hierarchy = members.isEmpty() ? null : members.get(0).getHierarchy(); } public String toString() { return Util.commaList("Member", members); } public void accept(MemberSetVisitor visitor) { // Don't descend the subtrees here: may not want to load them into // cache. visitor.visit(this); } public MemberSetPlus filter(RolapLevel level) { List filteredMembers = new ArrayList(); for (RolapMember member : members) { if (member.getLevel().equals(level)) { filteredMembers.add(member); } } if (filteredMembers.isEmpty()) { return EmptyMemberSet.INSTANCE; } else if (filteredMembers.equals(members)) { return this; } else { return new SimpleMemberSet(filteredMembers, false); } } } /** * Member set defined by the union of other member sets. */ static class UnionMemberSet implements MemberSetPlus { private final List items; UnionMemberSet(List items) { this.items = items; } public String toString() { final StringBuilder sb = new StringBuilder("Union("); for (int i = 0; i < items.size(); i++) { if (i > 0) { sb.append(", "); } MemberSetPlus item = items.get(i); sb.append(item.toString()); } sb.append(")"); return sb.toString(); } public void accept(MemberSetVisitor visitor) { visitor.visit(this); } public MemberSetPlus filter(RolapLevel level) { final List filteredItems = new ArrayList(); for (MemberSetPlus item : items) { final MemberSetPlus filteredItem = item.filter(level); if (filteredItem == EmptyMemberSet.INSTANCE) { // skip it } else { assert !(filteredItem instanceof EmptyMemberSet); filteredItems.add(filteredItem); } } if (filteredItems.isEmpty()) { return EmptyMemberSet.INSTANCE; } else if (filteredItems.equals(items)) { return this; } else { return new UnionMemberSet(filteredItems); } } } /** * Member set defined by a range of members between a lower and upper * bound. */ static class RangeMemberSet implements MemberSetPlus { private final RolapMember lowerMember; private final boolean lowerInclusive; private final RolapMember upperMember; private final boolean upperInclusive; private final boolean descendants; private final RolapLevel level; RangeMemberSet( RolapMember lowerMember, boolean lowerInclusive, RolapMember upperMember, boolean upperInclusive, boolean descendants) { assert lowerMember != null || upperMember != null; assert lowerMember == null || upperMember == null || lowerMember.getLevel() == upperMember.getLevel(); assert !(lowerMember == null && lowerInclusive); assert !(upperMember == null && upperInclusive); assert !(lowerMember instanceof RolapCubeMember); assert !(upperMember instanceof RolapCubeMember); this.lowerMember = lowerMember; this.lowerInclusive = lowerInclusive; this.upperMember = upperMember; this.upperInclusive = upperInclusive; this.descendants = descendants; this.level = lowerMember == null ? upperMember.getLevel() : lowerMember.getLevel(); } public String toString() { final StringBuilder sb = new StringBuilder("Range("); if (lowerMember == null) { sb.append("null"); } else { sb.append(lowerMember); if (lowerInclusive) { sb.append(" inclusive"); } else { sb.append(" exclusive"); } } sb.append(" to "); if (upperMember == null) { sb.append("null"); } else { sb.append(upperMember); if (upperInclusive) { sb.append(" inclusive"); } else { sb.append(" exclusive"); } } sb.append(")"); return sb.toString(); } public void accept(MemberSetVisitor visitor) { // Don't traverse the range here: may not want to load it into cache visitor.visit(this); } public MemberSetPlus filter(RolapLevel level) { if (level == this.level) { return this; } else { return filter2(level, this.level, lowerMember, upperMember); } } public MemberSetPlus filter2( RolapLevel seekLevel, RolapLevel level, RolapMember lower, RolapMember upper) { if (level == seekLevel) { return new RangeMemberSet( lower, lowerInclusive, upper, upperInclusive, false); } else if (descendants && level.getHierarchy() == seekLevel.getHierarchy() && level.getDepth() < seekLevel.getDepth()) { final MemberReader memberReader = level.getHierarchy().getMemberReader(); final List list = new ArrayList(); memberReader.getMemberChildren(lower, list); if (list.isEmpty()) { return EmptyMemberSet.INSTANCE; } RolapMember lowerChild = list.get(0); list.clear(); memberReader.getMemberChildren(upper, list); if (list.isEmpty()) { return EmptyMemberSet.INSTANCE; } RolapMember upperChild = list.get(list.size() - 1); return filter2( seekLevel, (RolapLevel) level.getChildLevel(), lowerChild, upperChild); } else { return EmptyMemberSet.INSTANCE; } } } /** * Command consisting of a set of commands executed in sequence. */ private static class CompoundCommand implements MemberEditCommandPlus { private final List commandList; CompoundCommand(List commandList) { this.commandList = commandList; } public String toString() { return Util.commaList("Compound", commandList); } public void execute(final List cellRegionList) { for (MemberEditCommandPlus command : commandList) { command.execute(cellRegionList); } } public void commit() { for (MemberEditCommandPlus command : commandList) { command.commit(); } } } /** * Command that deletes a member and its descendants from the cache. */ private class DeleteMemberCommand extends MemberSetVisitorImpl implements MemberEditCommandPlus { private final MemberSetPlus set; private List cellRegionList; private Callable callable; DeleteMemberCommand(MemberSetPlus set) { this.set = set; } public String toString() { return "DeleteMemberCommand(" + set + ")"; } public void execute(final List cellRegionList) { // NOTE: use of cellRegionList makes this class non-reentrant this.cellRegionList = cellRegionList; set.accept(this); this.cellRegionList = null; } public void visit(RolapMember member) { this.callable = deleteMember(member, member.getParentMember(), cellRegionList); } public void commit() { try { callable.call(); } catch (Exception e) { throw new MondrianException(e); } } } /** * Command that adds a new member to the cache. */ private class AddMemberCommand implements MemberEditCommandPlus { private final RolapMember member; private Callable callable; public AddMemberCommand(RolapMember member) { assert member != null; this.member = stripMember(member); } public String toString() { return "AddMemberCommand(" + member + ")"; } public void execute(List cellRegionList) { this.callable = addMember(member, member.getParentMember(), cellRegionList); } public void commit() { try { callable.call(); } catch (Exception e) { throw new MondrianException(e); } } } /** * Command that moves a member to a new parent. */ private class MoveMemberCommand implements MemberEditCommandPlus { private final RolapMember member; private final RolapMember newParent; private Callable callable1; private Callable callable2; MoveMemberCommand(RolapMember member, RolapMember newParent) { this.member = member; this.newParent = newParent; } public String toString() { return "MoveMemberCommand(" + member + ", " + newParent + ")"; } public void execute(final List cellRegionList) { this.callable1 = deleteMember(member, member.getParentMember(), cellRegionList); this.callable2 = addMember(member, newParent, cellRegionList); } public void commit() { try { ((RolapMemberBase) member).setParentMember(newParent); callable1.call(); ((RolapMemberBase) member).setUniqueName(member.getKey()); callable2.call(); } catch (Exception e) { throw new MondrianException(e); } } } /** * Command that changes one or more properties of a member. */ private class ChangeMemberPropsCommand extends MemberSetVisitorImpl implements MemberEditCommandPlus { final MemberSetPlus memberSet; final Map propertyValues; final List members = new ArrayList(); ChangeMemberPropsCommand( MemberSetPlus memberSet, Map propertyValues) { this.memberSet = memberSet; this.propertyValues = propertyValues; } public String toString() { return "CreateMemberPropsCommand(" + memberSet + ", " + propertyValues + ")"; } public void execute(List cellRegionList) { // ignore cellRegionList - no changes to cell cache memberSet.accept(this); } public void visit(RolapMember member) { members.add(member); } public void commit() { for (RolapMember member : members) { // Change member's properties. member = stripMember(member); final MemberCache memberCache = getMemberCache(member); final Object cacheKey = memberCache.makeKey( member.getParentMember(), member.getKey()); final RolapMember cacheMember = memberCache.getMember(cacheKey); if (cacheMember == null) { return; } for (Map.Entry entry : propertyValues.entrySet()) { cacheMember.setProperty(entry.getKey(), entry.getValue()); } } } } private static RolapMember stripMember(RolapMember member) { if (member instanceof RolapCubeMember) { member = ((RolapCubeMember) member).member; } return member; } private static void stripMemberList(List members) { for (int i = 0; i < members.size(); i++) { RolapMember member = members.get(i); if (member instanceof RolapCubeMember) { members.set(i, ((RolapCubeMember) member).member); } } } private Callable deleteMember( final RolapMember member, final RolapMember previousParent, List cellRegionList) { // Cells for member and its ancestors are now invalid. // It's sufficient to flush the member. cellRegionList.add(createMemberRegion(member, true)); return new Callable() { public Boolean call() throws Exception { final MemberCache memberCache = getMemberCache(member); final MemberChildrenConstraint memberConstraint = new ChildByNameConstraint( new Id.Segment(member.getName(), Quoting.QUOTED)); // Remove the member from its parent's lists. First try the // unconstrained cache. final List childrenList = memberCache.getChildrenFromCache( previousParent, DefaultMemberChildrenConstraint.instance()); if (childrenList != null) { // A list existed before. Let's splice it. childrenList.remove(member); memberCache.putChildren( previousParent, DefaultMemberChildrenConstraint.instance(), childrenList); } // Now make sure there is no constrained cache entry // for this member's parent. memberCache.putChildren( previousParent, memberConstraint, null); // Let's update the level members cache. final List levelMembers = memberCache .getLevelMembersFromCache( member.getLevel(), DefaultTupleConstraint.instance()); if (levelMembers != null) { levelMembers.remove(member); memberCache.putChildren( member.getLevel(), DefaultTupleConstraint.instance(), childrenList); } // Remove the member itself. The MemberCacheHelper takes care of // removing the member's children as well. final Object key = memberCache.makeKey(previousParent, member.getKey()); memberCache.removeMember(key); return true; } }; } /** * Adds a member to cache. * * @param member Member * @param parent Member's parent (generally equals member.getParentMember) * @param cellRegionList List of cell regions to be flushed * * @return Callable that yields true when the member has been added to the * cache */ private Callable addMember( final RolapMember member, final RolapMember parent, List cellRegionList) { // Cells for all of member's ancestors are now invalid. It's sufficient // to flush its parent. cellRegionList.add(createMemberRegion(parent, false)); return new Callable() { public Boolean call() throws Exception { final MemberCache memberCache = getMemberCache(member); final MemberChildrenConstraint memberConstraint = new ChildByNameConstraint( new Id.Segment(member.getName(), Quoting.QUOTED)); // Check if there is already a list in cache // constrained by a wildcard. List childrenList = memberCache.getChildrenFromCache( parent, DefaultMemberChildrenConstraint.instance()); if (childrenList == null) { // There was no cached list. We can ignore. } else { // A list existed before. We can save a SQL query. // Might be immutable. Let's append to it. if (childrenList.isEmpty()) { childrenList = new ArrayList(); } childrenList.add(member); memberCache.putChildren( parent, memberConstraint, childrenList); } final List levelMembers = memberCache .getLevelMembersFromCache( member.getLevel(), DefaultTupleConstraint.instance()); if (levelMembers != null) { // There was already a cached list. // Let's append to it. levelMembers.add(member); memberCache.putChildren( member.getLevel(), DefaultTupleConstraint.instance(), levelMembers); } // Now add the member itself into cache final Object memberKey = memberCache.makeKey( member.getParentMember(), member.getKey()); memberCache.putMember(memberKey, member); return true; } }; } /** * Removes a member from cache. * * @param member Member * @param cellRegionList Populated with cell region to be flushed */ private void flushMember( RolapMember member, List cellRegionList) { final MemberCache memberCache = getMemberCache(member); final Object key = memberCache.makeKey(member.getParentMember(), member.getKey()); memberCache.removeMember(key); cellRegionList.add(createMemberRegion(member, false)); } } // End CacheControlImpl.java mondrian-3.4.1/src/main/mondrian/rolap/ArrayMemberSource.java0000644000175000017500000000443011735330606024120 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. // // jhyde, 22 December, 2001 */ package mondrian.rolap; import mondrian.olap.Id; import mondrian.olap.Util; import mondrian.resource.MondrianResource; import java.util.Collections; import java.util.List; /** * ArrayMemberSource implements a flat, static hierarchy. There is * no root member, and all members are siblings. * * @author jhyde * @since 22 December, 2001 */ abstract class ArrayMemberSource implements MemberSource { protected final RolapHierarchy hierarchy; protected final List members; ArrayMemberSource(RolapHierarchy hierarchy, List members) { this.hierarchy = hierarchy; this.members = members; } public RolapHierarchy getHierarchy() { return hierarchy; } public boolean setCache(MemberCache cache) { return false; // we do not support cache writeback } public List getMembers() { return members; } public int getMemberCount() { return members.size(); } public List getRootMembers() { return Collections.emptyList(); } public void getMemberChildren( RolapMember parentMember, List children) { // there are no children } public void getMemberChildren( List parentMembers, List children) { // there are no children } public RolapMember lookupMember( List uniqueNameParts, boolean failIfNotFound) { String uniqueName = Util.implode(uniqueNameParts); for (RolapMember member : members) { if (member.getUniqueName().equals(uniqueName)) { return member; } } if (failIfNotFound) { throw MondrianResource.instance().MdxCantFindMember.ex(uniqueName); } else { return null; } } } // End ArrayMemberSource.java mondrian-3.4.1/src/main/mondrian/rolap/NoCacheMemberReader.java0000644000175000017500000004330111735330606024304 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2002 Julian Hyde // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2007-2008 StrateBI // Copyright (C) 2008-2010 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.rolap.TupleReader.MemberBuilder; import mondrian.rolap.sql.MemberChildrenConstraint; import mondrian.rolap.sql.TupleConstraint; import mondrian.util.ConcatenableList; import org.apache.log4j.Logger; import java.util.*; /** * NoCacheMemberReader implements {@link MemberReader} but * without doing any kind of caching and avoiding to read all members. * * @author jlopez, lcanals * @since 06 October, 2007 */ public class NoCacheMemberReader implements MemberReader, MemberCache { private static final Logger LOGGER = Logger.getLogger(NoCacheMemberReader.class); private final SqlConstraintFactory sqlConstraintFactory = SqlConstraintFactory.instance(); private final MemberReader source; NoCacheMemberReader(MemberReader source) { this.source = source; if (!source.setCache(this)) { throw Util.newInternal( "MemberSource (" + source + ", " + source.getClass() + ") does not support cache-writeback"); } } // implementes MemberCache public boolean isMutable() { return false; } // implementes MemberCache public RolapMember removeMember(Object key) { return null; } // implementes MemberCache public RolapMember removeMemberAndDescendants(Object key) { return null; } // implement MemberReader public RolapHierarchy getHierarchy() { return source.getHierarchy(); } // implement MemberCache public boolean setCache(MemberCache cache) { return false; } // implement MemberCache public Object makeKey(final RolapMember parent, final Object key) { LOGGER.debug("Entering makeKey"); return new MemberKey(parent, key); } public synchronized RolapMember getMember(final Object key) { return getMember(key, true); } public RolapMember getMember( final Object key, final boolean mustCheckCacheStatus) { LOGGER.debug("Returning null member: no cache"); return null; } // implement MemberCache public Object putMember(final Object key, final RolapMember value) { LOGGER.debug("putMember void for no caching"); return value; } // implement MemberReader public List getMembers() { System.out.println("NoCache getMembers"); List v = new ArrayList(); RolapLevel[] levels = (RolapLevel[]) getHierarchy().getLevels(); // todo: optimize by walking to children for members we know about for (RolapLevel level : levels) { List membersInLevel = getMembersInLevel(level, 0, Integer.MAX_VALUE); v.addAll(membersInLevel); } return v; } public List getRootMembers() { LOGGER.debug("Getting root members"); return source.getRootMembers(); } public List getMembersInLevel( final RolapLevel level, final int startOrdinal, final int endOrdinal) { TupleConstraint constraint = sqlConstraintFactory.getLevelMembersConstraint(null); return getMembersInLevel(level, startOrdinal, endOrdinal, constraint); } public List getMembersInLevel( final RolapLevel level, final int startOrdinal, final int endOrdinal, final TupleConstraint constraint) { LOGGER.debug("Entering getMembersInLevel"); return source.getMembersInLevel( level, startOrdinal, endOrdinal, constraint); } public void getMemberChildren( final RolapMember parentMember, final List children) { MemberChildrenConstraint constraint = sqlConstraintFactory.getMemberChildrenConstraint(null); getMemberChildren(parentMember, children, constraint); } public void getMemberChildren( final RolapMember parentMember, final List children, final MemberChildrenConstraint constraint) { List parentMembers = new ArrayList(); parentMembers.add(parentMember); getMemberChildren(parentMembers, children, constraint); } public void getMemberChildren( final List parentMembers, final List children) { MemberChildrenConstraint constraint = sqlConstraintFactory.getMemberChildrenConstraint(null); getMemberChildren(parentMembers, children, constraint); } public void getMemberChildren( final List parentMembers, final List children, final MemberChildrenConstraint constraint) { assert (constraint != null); LOGGER.debug("Entering getMemberChildren"); source.getMemberChildren(parentMembers, children, constraint); } public RolapMember lookupMember( final List uniqueNameParts, final boolean failIfNotFound) { return RolapUtil.lookupMember(this, uniqueNameParts, failIfNotFound); } public List getChildrenFromCache( final RolapMember member, final MemberChildrenConstraint constraint) { return null; } public List getLevelMembersFromCache( final RolapLevel level, final TupleConstraint constraint) { return null; } public void putChildren( final RolapMember member, final MemberChildrenConstraint constraint, final List children) { } public void putChildren( final RolapLevel level, final TupleConstraint constraint, final List children) { } public RolapMember getLeadMember(RolapMember member, int n) { if (n == 0 || member.isNull()) { return member; } else { SiblingIterator iter = new SiblingIterator(this, member); if (n > 0) { RolapMember sibling = null; while (n-- > 0) { if (!iter.hasNext()) { return (RolapMember) member.getHierarchy() .getNullMember(); } sibling = iter.nextMember(); } return sibling; } else { n = -n; RolapMember sibling = null; while (n-- > 0) { if (!iter.hasPrevious()) { return (RolapMember) member.getHierarchy() .getNullMember(); } sibling = iter.previousMember(); } return sibling; } } } public void getMemberRange( final RolapLevel level, final RolapMember startMember, final RolapMember endMember, final List list) { assert startMember != null : "pre"; assert endMember != null : "pre"; assert startMember.getLevel() == endMember.getLevel() : "pre: startMember.getLevel() == endMember.getLevel()"; if (compare(startMember, endMember, false) > 0) { return; } list.add(startMember); if (startMember.equals(endMember)) { return; } SiblingIterator siblings = new SiblingIterator(this, startMember); while (siblings.hasNext()) { final RolapMember member = siblings.nextMember(); list.add(member); if (member.equals(endMember)) { return; } } throw Util.newInternal( "sibling iterator did not hit end point, start=" + startMember + ", end=" + endMember); } public int getMemberCount() { return source.getMemberCount(); } public int compare( final RolapMember m1, final RolapMember m2, final boolean siblingsAreEqual) { if (Util.equals(m1, m2)) { return 0; } if (Util.equals(m1.getParentMember(), m2.getParentMember())) { // including case where both parents are null if (siblingsAreEqual) { return 0; } else if (m1.getParentMember() == null) { // at this point we know that both parent members are null. int pos1 = -1, pos2 = -1; List siblingList = getRootMembers(); for (int i = 0, n = siblingList.size(); i < n; i++) { RolapMember child = siblingList.get(i); if (child.equals(m1)) { pos1 = i; } if (child.equals(m2)) { pos2 = i; } } if (pos1 == -1) { throw Util.newInternal(m1 + " not found among siblings"); } if (pos2 == -1) { throw Util.newInternal(m2 + " not found among siblings"); } Util.assertTrue(pos1 != pos2); return pos1 < pos2 ? -1 : 1; } else { List children = new ArrayList(); getMemberChildren(m1.getParentMember(), children); int pos1 = -1, pos2 = -1; for (int i = 0, n = children.size(); i < n; i++) { RolapMember child = children.get(i); if (child.equals(m1)) { pos1 = i; } if (child.equals(m2)) { pos2 = i; } } if (pos1 == -1) { throw Util.newInternal(m1 + " not found among siblings"); } if (pos2 == -1) { throw Util.newInternal(m2 + " not found among siblings"); } assert pos1 != pos2; return pos1 < pos2 ? -1 : 1; } } int levelDepth1 = m1.getLevel().getDepth(); int levelDepth2 = m2.getLevel().getDepth(); if (levelDepth1 < levelDepth2) { final int c = compare(m1, m2.getParentMember(), false); return (c == 0) ? -1 : c; } else if (levelDepth1 > levelDepth2) { final int c = compare(m1.getParentMember(), m2, false); return (c == 0) ? 1 : c; } else { return compare(m1.getParentMember(), m2.getParentMember(), false); } } /** * SiblingIterator helps traverse a hierarchy of members, by * remembering the position at each level. Each SiblingIterator has a * parent, to which it defers when the last child of the current member is * reached. */ class SiblingIterator { private final MemberReader reader; private final SiblingIterator parentIterator; private List siblings; private int position; SiblingIterator(MemberReader reader, RolapMember member) { this.reader = reader; RolapMember parent = member.getParentMember(); List siblingList; if (parent == null) { siblingList = reader.getRootMembers(); this.parentIterator = null; } else { siblingList = new ArrayList(); reader.getMemberChildren(parent, siblingList); this.parentIterator = new SiblingIterator(reader, parent); } this.siblings = siblingList; this.position = -1; for (int i = 0; i < this.siblings.size(); i++) { if (siblings.get(i).equals(member)) { this.position = i; break; } } if (this.position == -1) { throw Util.newInternal( "member " + member + " not found among its siblings"); } } boolean hasNext() { return (this.position < this.siblings.size() - 1) || (parentIterator != null) && parentIterator.hasNext(); } Object next() { return nextMember(); } RolapMember nextMember() { if (++this.position >= this.siblings.size()) { if (parentIterator == null) { throw Util.newInternal("there is no next member"); } RolapMember parent = parentIterator.nextMember(); List siblingList = new ArrayList(); reader.getMemberChildren(parent, siblingList); this.siblings = siblingList; this.position = 0; } return (RolapMember) this.siblings.get(this.position); } boolean hasPrevious() { return (this.position > 0) || (parentIterator != null) && parentIterator.hasPrevious(); } Object previous() { return previousMember(); } RolapMember previousMember() { if (--this.position < 0) { if (parentIterator == null) { throw Util.newInternal("there is no next member"); } RolapMember parent = parentIterator.previousMember(); List siblingList = new ArrayList(); reader.getMemberChildren(parent, siblingList); this.siblings = siblingList; this.position = this.siblings.size() - 1; } return (RolapMember) this.siblings.get(this.position); } } public MemberBuilder getMemberBuilder() { return source.getMemberBuilder(); } public RolapMember getDefaultMember() { RolapMember defaultMember = (RolapMember) getHierarchy().getDefaultMember(); if (defaultMember != null) { return defaultMember; } return getRootMembers().get(0); } public int getLevelMemberCount(RolapLevel level) { // No need to cache the result: the caller saves the result by calling // RolapLevel.setApproxRowCount return source.getLevelMemberCount(level); } public RolapMember desubstitute(RolapMember member) { return member; } public RolapMember substitute(RolapMember member) { return member; } public RolapMember getMemberParent(RolapMember member) { // This method deals with ragged hierarchies but not access-controlled // hierarchies - assume these have RestrictedMemberReader possibly // wrapped in a SubstitutingMemberReader. RolapMember parentMember = member.getParentMember(); // Skip over hidden parents. while (parentMember != null && parentMember.isHidden()) { parentMember = parentMember.getParentMember(); } return parentMember; } /** * Reads the children of member into result. * * @param result Children are written here, in order * @param members Members whose children to read * @param constraint restricts the returned members if possible (optional * optimization) */ protected void readMemberChildren( List members, List result, MemberChildrenConstraint constraint) { List children = new ConcatenableList(); source.getMemberChildren(members, children, constraint); // Put them in a temporary hash table first. Register them later, when // we know their size (hence their 'cost' to the cache pool). Map> tempMap = new HashMap>(); for (RolapMember member1 : members) { //noinspection unchecked tempMap.put(member1, Collections.EMPTY_LIST); } for (final RolapMember child : children) { // todo: We could optimize here. If members.length is small, it's // more efficient to drive from members, rather than hashing // children.length times. We could also exploit the fact that the // result is sorted by ordinal and therefore, unless the "members" // contains members from different levels, children of the same // member will be contiguous. assert child != null : "child"; final RolapMember parentMember = child.getParentMember(); List list = tempMap.get(parentMember); if (list == null) { // The list is null if, due to dropped constraints, we now // have a children list of a member we didn't explicitly // ask for it. Adding it to the cache would be viable, but // let's ignore it. continue; } else if (list == Collections.EMPTY_LIST) { list = new ArrayList(); tempMap.put(parentMember, list); } list.add(child); result.add(child); } } } // End NoCacheMemberReader.java mondrian-3.4.1/src/main/mondrian/rolap/HierarchyUsage.java0000644000175000017500000003316511735330606023443 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. // // jhyde, 21 March, 2002 */ package mondrian.rolap; import mondrian.olap.*; import mondrian.resource.MondrianResource; import org.apache.log4j.Logger; /** * A HierarchyUsage is the usage of a hierarchy in the context * of a cube. Private hierarchies can only be used in their own * cube. Public hierarchies can be used in several cubes. The problem comes * when several cubes which the same public hierarchy are brought together * in one virtual cube. There are now several usages of the same public * hierarchy. Which one to use? It depends upon what measure we are * currently using. We should use the hierarchy usage for the fact table * which underlies the measure. That is what determines the foreign key to * join on. * * A HierarchyUsage is identified by * (hierarchy.sharedHierarchy, factTable) if the hierarchy is * shared, or (hierarchy, factTable) if it is private. * * @author jhyde * @since 21 March, 2002 */ public class HierarchyUsage { private static final Logger LOGGER = Logger.getLogger(HierarchyUsage.class); enum Kind { UNKNOWN, SHARED, VIRTUAL, PRIVATE } /** * Fact table (or relation) which this usage is joining to. This * identifies the usage, and determines which join conditions need to be * used. */ protected final MondrianDef.Relation fact; /** * This matches the hierarchy - may not be unique. * NOT NULL. */ private final String hierarchyName; /** * not NULL for DimensionUsage * not NULL for Dimension */ private final String name; /** * This is the name used to look up the hierachy usage. When the dimension * has only a single hierachy, then the fullName is simply the * CubeDimension name; there is no need to use the default dimension name. * But, when the dimension has more than one hierachy, then the fullName * is the CubeDimension dotted with the dimension hierachy name. * *

NOTE: jhyde, 2009/2/2: The only use of this field today is for * {@link RolapCube#getUsageByName}, which is used only for tracing. */ private final String fullName; /** * The foreign key by which this {@link Hierarchy} is joined to * the {@link #fact} table. */ private final String foreignKey; /** * not NULL for DimensionUsage * NULL for Dimension */ private final String source; /** * May be null, this is the field that is used to disambiguate column * names in aggregate tables */ private final String usagePrefix; // NOT USED private final String level; //final String type; //final String caption; /** * Dimension table which contains the primary key for the hierarchy. * (Usually the table of the lowest level of the hierarchy.) */ private MondrianDef.Relation joinTable; /** * The expression (usually a {@link mondrian.olap.MondrianDef.Column}) by * which the hierarchy which is joined to the fact table. */ private MondrianDef.Expression joinExp; private final Kind kind; /** * Creates a HierarchyUsage. * * @param cube Cube * @param hierarchy Hierarchy * @param cubeDim XML definition of a dimension which belongs to a cube */ HierarchyUsage( RolapCube cube, RolapHierarchy hierarchy, MondrianDef.CubeDimension cubeDim) { assert cubeDim != null : "precondition: cubeDim != null"; this.fact = cube.fact; // Attributes common to all Hierarchy kinds // name // foreignKey this.name = cubeDim.name; this.foreignKey = cubeDim.foreignKey; if (cubeDim instanceof MondrianDef.DimensionUsage) { this.kind = Kind.SHARED; // Shared Hierarchy attributes // source // level MondrianDef.DimensionUsage du = (MondrianDef.DimensionUsage) cubeDim; this.hierarchyName = deriveHierarchyName(hierarchy); int index = this.hierarchyName.indexOf('.'); if (index == -1) { this.fullName = this.name; this.source = du.source; } else { String hname = this.hierarchyName.substring( index + 1, this.hierarchyName.length()); StringBuilder buf = new StringBuilder(32); buf.append(this.name); buf.append('.'); buf.append(hname); this.fullName = buf.toString(); buf.setLength(0); buf.append(du.source); buf.append('.'); buf.append(hname); this.source = buf.toString(); } this.level = du.level; this.usagePrefix = du.usagePrefix; init(cube, hierarchy, du); } else if (cubeDim instanceof MondrianDef.Dimension) { this.kind = Kind.PRIVATE; // Private Hierarchy attributes // type // caption MondrianDef.Dimension d = (MondrianDef.Dimension) cubeDim; this.hierarchyName = deriveHierarchyName(hierarchy); this.fullName = this.name; this.source = null; this.usagePrefix = d.usagePrefix; this.level = null; init(cube, hierarchy, null); } else if (cubeDim instanceof MondrianDef.VirtualCubeDimension) { this.kind = Kind.VIRTUAL; // Virtual Hierarchy attributes MondrianDef.VirtualCubeDimension vd = (MondrianDef.VirtualCubeDimension) cubeDim; this.hierarchyName = cubeDim.name; this.fullName = this.name; this.source = null; this.usagePrefix = null; this.level = null; init(cube, hierarchy, null); } else { getLogger().warn( "HierarchyUsage: Unknown cubeDim=" + cubeDim.getClass().getName()); this.kind = Kind.UNKNOWN; this.hierarchyName = cubeDim.name; this.fullName = this.name; this.source = null; this.usagePrefix = null; this.level = null; init(cube, hierarchy, null); } if (getLogger().isDebugEnabled()) { getLogger().debug( toString() + ", cubeDim=" + cubeDim.getClass().getName()); } } private String deriveHierarchyName(RolapHierarchy hierarchy) { final String name = hierarchy.getName(); if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { return name; } else { final String dimensionName = hierarchy.getDimension().getName(); if (name == null || name.equals("") || name.equals(dimensionName)) { return name; } else { return dimensionName + '.' + name; } } } protected Logger getLogger() { return LOGGER; } public String getHierarchyName() { return this.hierarchyName; } public String getFullName() { return this.fullName; } public String getName() { return this.name; } public String getForeignKey() { return this.foreignKey; } public String getSource() { return this.source; } public String getLevelName() { return this.level; } public String getUsagePrefix() { return this.usagePrefix; } public MondrianDef.Relation getJoinTable() { return this.joinTable; } public MondrianDef.Expression getJoinExp() { return this.joinExp; } public Kind getKind() { return this.kind; } public boolean isShared() { return this.kind == Kind.SHARED; } public boolean isVirtual() { return this.kind == Kind.VIRTUAL; } public boolean isPrivate() { return this.kind == Kind.PRIVATE; } public boolean equals(Object o) { if (o instanceof HierarchyUsage) { HierarchyUsage other = (HierarchyUsage) o; return (this.kind == other.kind) && Util.equals(this.fact, other.fact) && this.hierarchyName.equals(other.hierarchyName) && Util.equalName(this.name, other.name) && Util.equalName(this.source, other.source) && Util.equalName(this.foreignKey, other.foreignKey); } else { return false; } } public int hashCode() { int h = fact.hashCode(); h = Util.hash(h, hierarchyName); h = Util.hash(h, name); h = Util.hash(h, source); h = Util.hash(h, foreignKey); return h; } public String toString() { StringBuilder buf = new StringBuilder(100); buf.append("HierarchyUsage: "); buf.append("kind="); buf.append(this.kind.name()); buf.append(", hierarchyName="); buf.append(this.hierarchyName); buf.append(", fullName="); buf.append(this.fullName); buf.append(", foreignKey="); buf.append(this.foreignKey); buf.append(", source="); buf.append(this.source); buf.append(", level="); buf.append(this.level); buf.append(", name="); buf.append(this.name); return buf.toString(); } void init( RolapCube cube, RolapHierarchy hierarchy, MondrianDef.DimensionUsage cubeDim) { // Three ways that a hierarchy can be joined to the fact table. if (cubeDim != null && cubeDim.level != null) { // 1. Specify an explicit 'level' attribute in a . RolapLevel joinLevel = (RolapLevel) Util.lookupHierarchyLevel(hierarchy, cubeDim.level); if (joinLevel == null) { throw MondrianResource.instance() .DimensionUsageHasUnknownLevel.ex( hierarchy.getUniqueName(), cube.getName(), cubeDim.level); } this.joinTable = findJoinTable(hierarchy, joinLevel.getKeyExp().getTableAlias()); this.joinExp = joinLevel.getKeyExp(); } else if (hierarchy.getXmlHierarchy() != null && hierarchy.getXmlHierarchy().primaryKey != null) { // 2. Specify a "primaryKey" attribute of in . You must // also specify the "primaryKeyTable" attribute if the hierarchy // is a join (hence has more than one table). this.joinTable = findJoinTable( hierarchy, hierarchy.getXmlHierarchy().primaryKeyTable); this.joinExp = new MondrianDef.Column( this.joinTable.getAlias(), hierarchy.getXmlHierarchy().primaryKey); } else { // 3. If neither of the above, the join is assumed to be to key of // the last level. final Level[] levels = hierarchy.getLevels(); RolapLevel joinLevel = (RolapLevel) levels[levels.length - 1]; this.joinTable = findJoinTable( hierarchy, joinLevel.getKeyExp().getTableAlias()); this.joinExp = joinLevel.getKeyExp(); } // Unless this hierarchy is drawing from the fact table, we need // a join expresion and a foreign key. final boolean inFactTable = this.joinTable.equals(cube.getFact()); if (!inFactTable) { if (this.joinExp == null) { throw MondrianResource.instance() .MustSpecifyPrimaryKeyForHierarchy.ex( hierarchy.getUniqueName(), cube.getName()); } if (foreignKey == null) { throw MondrianResource.instance() .MustSpecifyForeignKeyForHierarchy.ex( hierarchy.getUniqueName(), cube.getName()); } } } /** * Chooses the table with which to join a hierarchy to the fact table. * * @param hierarchy Hierarchy to be joined * @param tableName Alias of the table; may be omitted if the hierarchy * has only one table * @return A table, never null */ private MondrianDef.Relation findJoinTable( RolapHierarchy hierarchy, String tableName) { final MondrianDef.Relation table; if (tableName == null) { table = hierarchy.getUniqueTable(); if (table == null) { throw MondrianResource.instance() .MustSpecifyPrimaryKeyTableForHierarchy.ex( hierarchy.getUniqueName()); } } else { table = hierarchy.getRelation().find(tableName); if (table == null) { // todo: i18n msg throw Util.newError( "no table '" + tableName + "' found in hierarchy " + hierarchy.getUniqueName()); } } return table; } } // End HierarchyUsage.java mondrian-3.4.1/src/main/mondrian/rolap/aggtab/0000755000175000017500000000000011735330606021112 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/rolap/aggtab/package.html0000644000175000017500000000032111735330606023367 0ustar drazzibdrazzib Provides support for aggregate tables.

Tools:

  • AggAdvisor recommends aggregate tables to build.
  • AggGenerator generates SQL for the list of aggregate tables.
mondrian-3.4.1/src/main/mondrian/rolap/RolapSchema.java0000644000175000017500000020110511735330606022725 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. // // jhyde, 26 July, 2001 */ package mondrian.rolap; import mondrian.olap.*; import mondrian.olap.fun.*; import mondrian.olap.type.*; import mondrian.resource.MondrianResource; import mondrian.rolap.aggmatcher.AggTableManager; import mondrian.rolap.aggmatcher.JdbcSchema; import mondrian.spi.CellFormatter; import mondrian.spi.*; import mondrian.spi.MemberFormatter; import mondrian.spi.PropertyFormatter; import mondrian.spi.impl.Scripts; import mondrian.util.ByteString; import org.apache.commons.vfs.FileSystemException; import org.apache.log4j.Logger; import org.eigenbase.xom.*; import org.eigenbase.xom.Parser; import org.olap4j.impl.Olap4jUtil; import org.olap4j.mdx.IdentifierSegment; import java.io.*; import java.lang.ref.SoftReference; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.*; import javax.sql.DataSource; /** * A RolapSchema is a collection of {@link RolapCube}s and * shared {@link RolapDimension}s. It is shared betweeen {@link * RolapConnection}s. It caches {@link MemberReader}s, etc. * * @see RolapConnection * @author jhyde * @since 26 July, 2001 */ public class RolapSchema implements Schema { private static final Logger LOGGER = Logger.getLogger(RolapSchema.class); private static final Set schemaAllowed = Olap4jUtil.enumSetOf( Access.NONE, Access.ALL, Access.ALL_DIMENSIONS, Access.CUSTOM); private static final Set cubeAllowed = Olap4jUtil.enumSetOf(Access.NONE, Access.ALL, Access.CUSTOM); private static final Set dimensionAllowed = Olap4jUtil.enumSetOf(Access.NONE, Access.ALL, Access.CUSTOM); private static final Set hierarchyAllowed = Olap4jUtil.enumSetOf(Access.NONE, Access.ALL, Access.CUSTOM); private static final Set memberAllowed = Olap4jUtil.enumSetOf(Access.NONE, Access.ALL); private String name; /** * Internal use only. */ private RolapConnection internalConnection; /** * Holds cubes in this schema. */ private final Map mapNameToCube = new HashMap(); /** * Maps {@link String shared hierarchy name} to {@link MemberReader}. * Shared between all statements which use this connection. */ private final Map mapSharedHierarchyToReader = new HashMap(); /** * Maps {@link String names of shared hierarchies} to {@link * RolapHierarchy the canonical instance of those hierarchies}. */ private final Map mapSharedHierarchyNameToHierarchy = new HashMap(); /** * The default role for connections to this schema. */ private Role defaultRole; private ByteString md5Bytes; private final boolean useContentChecksum; /** * A schema's aggregation information */ private AggTableManager aggTableManager; /** * This is basically a unique identifier for this RolapSchema instance * used it its equals and hashCode methods. */ private String key; /** * Maps {@link String names of roles} to {@link Role roles with those names}. */ private final Map mapNameToRole = new HashMap(); /** * Maps {@link String names of sets} to {@link NamedSet named sets}. */ private final Map mapNameToSet = new HashMap(); /** * Table containing all standard MDX functions, plus user-defined functions * for this schema. */ private FunTable funTable; private MondrianDef.Schema xmlSchema; final List parameterList = new ArrayList(); private Date schemaLoadDate; private DataSourceChangeListener dataSourceChangeListener; /** * Map containing column cardinality. The combination of * Mondrianef.Relation and MondrianDef.Expression uniquely * identifies a relational expression(e.g. a column) specified * in the xml schema. */ private final Map< MondrianDef.Relation, Map> relationExprCardinalityMap = new HashMap< MondrianDef.Relation, Map>(); /** * List of warnings. Populated when a schema is created by a connection * that has * {@link mondrian.rolap.RolapConnectionProperties#Ignore Ignore}=true. */ private final List warningList = new ArrayList(); private Map annotationMap; /** * Unique schema instance id that will be used * to inform clients when the schema has changed. * *

Expect a different ID for each Mondrian instance node. */ private final String id; /** * This is ONLY called by other constructors (and MUST be called * by them) and NEVER by the Pool. * * @param key Key * @param connectInfo Connect properties * @param dataSource Data source * @param md5Bytes MD5 hash * @param useContentChecksum Whether to use content checksum */ private RolapSchema( final String key, final Util.PropertyList connectInfo, final DataSource dataSource, final ByteString md5Bytes, boolean useContentChecksum) { this.id = Util.generateUuidString(); this.key = key; this.md5Bytes = md5Bytes; this.useContentChecksum = useContentChecksum; assert !(useContentChecksum && md5Bytes == null); // the order of the next two lines is important this.defaultRole = Util.createRootRole(this); final MondrianServer internalServer = MondrianServer.forId(null); this.internalConnection = new RolapConnection(internalServer, connectInfo, this, dataSource); internalServer.removeConnection(internalConnection); internalServer.removeStatement( internalConnection.getInternalStatement()); this.aggTableManager = new AggTableManager(this); this.dataSourceChangeListener = createDataSourceChangeListener(connectInfo); } /** * Create RolapSchema given the MD5 hash, catalog name and string (content) * and the connectInfo object. * * @param md5Bytes may be null * @param catalogUrl URL of catalog * @param catalogStr may be null * @param connectInfo Connection properties */ private RolapSchema( String key, ByteString md5Bytes, String catalogUrl, String catalogStr, Util.PropertyList connectInfo, DataSource dataSource) { this(key, connectInfo, dataSource, md5Bytes, md5Bytes != null); load(catalogUrl, catalogStr); assert this.md5Bytes != null; } /** * Given the name of a cell formatter class and/or a cell formatter script, * returns a cell formatter. * * @param className Name of cell formatter class * @param script Script * @return Cell formatter * @throws Exception if class cannot be instantiated */ static CellFormatter getCellFormatter( String className, Scripts.ScriptDefinition script) throws Exception { if (className == null && script == null) { throw Util.newError( "Must specify either className attribute or Script element"); } if (className != null && script != null) { throw Util.newError( "Must not specify both className attribute and Script element"); } if (className != null) { @SuppressWarnings({"unchecked"}) Class clazz = (Class) Class.forName(className); Constructor ctor = clazz.getConstructor(); return ctor.newInstance(); } else { return Scripts.cellFormatter(script); } } /** * Given the name of a member formatter class, returns a member formatter. * * @param className Name of cell formatter class * @param script Script * @return Member formatter * @throws Exception if class cannot be instantiated */ static MemberFormatter getMemberFormatter( String className, Scripts.ScriptDefinition script) throws Exception { if (className == null && script == null) { throw Util.newError( "Must specify either className attribute or Script element"); } if (className != null && script != null) { throw Util.newError( "Must not specify both className attribute and Script element"); } if (className != null) { @SuppressWarnings({"unchecked"}) Class clazz = (Class) Class.forName(className); Constructor ctor = clazz.getConstructor(); return ctor.newInstance(); } else { return Scripts.memberFormatter(script); } } /** * Given the name of a property formatter class, returns a propert * formatter. * * @param className Name of property formatter class * @param script Script * @return Property formatter * @throws Exception if class cannot be instantiated */ static PropertyFormatter createPropertyFormatter( String className, Scripts.ScriptDefinition script) throws Exception { if (className == null && script == null) { throw Util.newError( "Must specify either className attribute or Script element"); } if (className != null && script != null) { throw Util.newError( "Must not specify both className attribute and Script element"); } if (className != null) { @SuppressWarnings({"unchecked"}) Class clazz = (Class) Class.forName(className); Constructor ctor = clazz.getConstructor(); return ctor.newInstance(); } else { return Scripts.propertyFormatter(script); } } protected void finalCleanUp() { if (aggTableManager != null) { aggTableManager.finalCleanUp(); aggTableManager = null; } } protected void finalize() throws Throwable { try { super.finalize(); finalCleanUp(); } catch (Throwable t) { LOGGER.info( MondrianResource.instance() .FinalizerErrorRolapSchema.baseMessage, t); } } public boolean equals(Object o) { if (!(o instanceof RolapSchema)) { return false; } RolapSchema other = (RolapSchema) o; return other.key.equals(key); } public int hashCode() { return key.hashCode(); } protected Logger getLogger() { return LOGGER; } /** * Method called by all constructors to load the catalog into DOM and build * application mdx and sql objects. * * @param catalogUrl URL of catalog * @param catalogStr Text of catalog, or null */ protected void load(String catalogUrl, String catalogStr) { try { final Parser xmlParser = XOMUtil.createDefaultParser(); final DOMWrapper def; if (catalogStr == null) { InputStream in = null; try { in = Util.readVirtualFile(catalogUrl); def = xmlParser.parse(in); } finally { if (in != null) { in.close(); } } // Compute catalog string, if needed for debug or for computing // Md5 hash. if (getLogger().isDebugEnabled() || md5Bytes == null) { try { catalogStr = Util.readVirtualFileAsString(catalogUrl); } catch (java.io.IOException ex) { getLogger().debug("RolapSchema.load: ex=" + ex); catalogStr = "?"; } } if (getLogger().isDebugEnabled()) { getLogger().debug( "RolapSchema.load: content: \n" + catalogStr); } } else { if (getLogger().isDebugEnabled()) { getLogger().debug( "RolapSchema.load: catalogStr: \n" + catalogStr); } def = xmlParser.parse(catalogStr); } if (md5Bytes == null) { // If a null catalogStr was passed in, we should have // computed it above by re-reading the catalog URL. assert catalogStr != null; md5Bytes = new ByteString(Util.digestMd5(catalogStr)); } xmlSchema = new MondrianDef.Schema(def); if (getLogger().isDebugEnabled()) { StringWriter sw = new StringWriter(4096); PrintWriter pw = new PrintWriter(sw); pw.println("RolapSchema.load: dump xmlschema"); xmlSchema.display(pw, 2); pw.flush(); getLogger().debug(sw.toString()); } load(xmlSchema); } catch (XOMException e) { throw Util.newError(e, "while parsing catalog " + catalogUrl); } catch (FileSystemException e) { throw Util.newError(e, "while parsing catalog " + catalogUrl); } catch (IOException e) { throw Util.newError(e, "while parsing catalog " + catalogUrl); } aggTableManager.initialize(); setSchemaLoadDate(); } private void setSchemaLoadDate() { schemaLoadDate = new Date(); } public Date getSchemaLoadDate() { return schemaLoadDate; } public List getWarnings() { return Collections.unmodifiableList(warningList); } public Role getDefaultRole() { return defaultRole; } public MondrianDef.Schema getXMLSchema() { return xmlSchema; } public String getName() { Util.assertPostcondition(name != null, "return != null"); Util.assertPostcondition(name.length() > 0, "return.length() > 0"); return name; } /** * Returns this schema instance unique ID. * @return A string representing the schema ID. */ public String getId() { return this.id; } public Map getAnnotationMap() { return annotationMap; } /** * Returns this schema's SQL dialect. * *

NOTE: This method is not cheap. The implementation gets a connection * from the connection pool. * * @return dialect */ public Dialect getDialect() { DataSource dataSource = getInternalConnection().getDataSource(); return DialectManager.createDialect(dataSource, null); } private void load(MondrianDef.Schema xmlSchema) { this.name = xmlSchema.name; if (name == null || name.equals("")) { throw Util.newError(" name must be set"); } this.annotationMap = RolapHierarchy.createAnnotationMap(xmlSchema.annotations); // Validate user-defined functions. Must be done before we validate // calculated members, because calculated members will need to use the // function table. final Map mapNameToUdf = new HashMap(); for (MondrianDef.UserDefinedFunction udf : xmlSchema.userDefinedFunctions) { final Scripts.ScriptDefinition scriptDef = toScriptDef(udf.script); defineFunction(mapNameToUdf, udf.name, udf.className, scriptDef); } final RolapSchemaFunctionTable funTable = new RolapSchemaFunctionTable(mapNameToUdf.values()); funTable.init(); this.funTable = funTable; // Validate public dimensions. for (MondrianDef.Dimension xmlDimension : xmlSchema.dimensions) { if (xmlDimension.foreignKey != null) { throw MondrianResource.instance() .PublicDimensionMustNotHaveForeignKey.ex( xmlDimension.name); } } // Create parameters. Set parameterNames = new HashSet(); for (MondrianDef.Parameter xmlParameter : xmlSchema.parameters) { String name = xmlParameter.name; if (!parameterNames.add(name)) { throw MondrianResource.instance().DuplicateSchemaParameter.ex( name); } Type type; if (xmlParameter.type.equals("String")) { type = new StringType(); } else if (xmlParameter.type.equals("Numeric")) { type = new NumericType(); } else { type = new MemberType(null, null, null, null); } final String description = xmlParameter.description; final boolean modifiable = xmlParameter.modifiable; String defaultValue = xmlParameter.defaultValue; RolapSchemaParameter param = new RolapSchemaParameter( this, name, defaultValue, description, type, modifiable); Util.discard(param); } // Create cubes. for (MondrianDef.Cube xmlCube : xmlSchema.cubes) { if (xmlCube.isEnabled()) { RolapCube cube = new RolapCube(this, xmlSchema, xmlCube, true); Util.discard(cube); } } // Create virtual cubes. for (MondrianDef.VirtualCube xmlVirtualCube : xmlSchema.virtualCubes) { if (xmlVirtualCube.isEnabled()) { RolapCube cube = new RolapCube(this, xmlSchema, xmlVirtualCube, true); Util.discard(cube); } } // Create named sets. for (MondrianDef.NamedSet xmlNamedSet : xmlSchema.namedSets) { mapNameToSet.put(xmlNamedSet.name, createNamedSet(xmlNamedSet)); } // Create roles. for (MondrianDef.Role xmlRole : xmlSchema.roles) { Role role = createRole(xmlRole); mapNameToRole.put(xmlRole.name, role); } // Set default role. if (xmlSchema.defaultRole != null) { Role role = lookupRole(xmlSchema.defaultRole); if (role == null) { error( "Role '" + xmlSchema.defaultRole + "' not found", locate(xmlSchema, "defaultRole")); } else { // At this stage, the only roles in mapNameToRole are // RoleImpl roles so it is safe to case. defaultRole = (RoleImpl) role; } } } static Scripts.ScriptDefinition toScriptDef(MondrianDef.Script script) { if (script == null) { return null; } final Scripts.ScriptLanguage language = Scripts.ScriptLanguage.lookup(script.language); if (language == null) { throw Util.newError( "Invalid script language '" + script.language + "'"); } return new Scripts.ScriptDefinition(script.cdata, language); } /** * Returns the location of an element or attribute in an XML document. * *

TODO: modify eigenbase-xom parser to return position info * * @param node Node * @param attributeName Attribute name, or null * @return Location of node or attribute in an XML document */ XmlLocation locate(ElementDef node, String attributeName) { return null; } /** * Reports an error. If we are tolerant of errors * (see {@link mondrian.rolap.RolapConnectionProperties#Ignore}), adds * it to the stack, overwise throws. A thrown exception will typically * abort the attempt to create the exception. * * @param message Message * @param xmlLocation Location of XML element or attribute that caused * the error, or null */ void error( String message, XmlLocation xmlLocation) { final RuntimeException ex = new RuntimeException(message); if (internalConnection != null && "true".equals( internalConnection.getProperty( RolapConnectionProperties.Ignore.name()))) { warningList.add(ex); } else { throw ex; } } private NamedSet createNamedSet(MondrianDef.NamedSet xmlNamedSet) { final String formulaString = xmlNamedSet.getFormula(); final Exp exp; try { exp = getInternalConnection().parseExpression(formulaString); } catch (Exception e) { throw MondrianResource.instance().NamedSetHasBadFormula.ex( xmlNamedSet.name, e); } final Formula formula = new Formula( new Id( new Id.Segment( xmlNamedSet.name, Id.Quoting.UNQUOTED)), exp); return formula.getNamedSet(); } private Role createRole(MondrianDef.Role xmlRole) { if (xmlRole.union != null) { if (xmlRole.schemaGrants != null && xmlRole.schemaGrants.length > 0) { throw MondrianResource.instance().RoleUnionGrants.ex(); } List roleList = new ArrayList(); for (MondrianDef.RoleUsage roleUsage : xmlRole.union.roleUsages) { final Role role = mapNameToRole.get(roleUsage.roleName); if (role == null) { throw MondrianResource.instance().UnknownRole.ex( roleUsage.roleName); } roleList.add(role); } return RoleImpl.union(roleList); } RoleImpl role = new RoleImpl(); for (MondrianDef.SchemaGrant schemaGrant : xmlRole.schemaGrants) { role.grant(this, getAccess(schemaGrant.access, schemaAllowed)); for (MondrianDef.CubeGrant cubeGrant : schemaGrant.cubeGrants) { RolapCube cube = lookupCube(cubeGrant.cube); if (cube == null) { throw Util.newError( "Unknown cube '" + cubeGrant.cube + "'"); } role.grant(cube, getAccess(cubeGrant.access, cubeAllowed)); final SchemaReader schemaReader = cube.getSchemaReader(null); for (MondrianDef.DimensionGrant dimensionGrant : cubeGrant.dimensionGrants) { Dimension dimension = (Dimension) schemaReader.lookupCompound( cube, Util.parseIdentifier(dimensionGrant.dimension), true, Category.Dimension); role.grant( dimension, getAccess(dimensionGrant.access, dimensionAllowed)); } for (MondrianDef.HierarchyGrant hierarchyGrant : cubeGrant.hierarchyGrants) { Hierarchy hierarchy = (Hierarchy) schemaReader.lookupCompound( cube, Util.parseIdentifier(hierarchyGrant.hierarchy), true, Category.Hierarchy); final Access hierarchyAccess = getAccess(hierarchyGrant.access, hierarchyAllowed); Level topLevel = null; if (hierarchyGrant.topLevel != null) { if (hierarchyAccess != Access.CUSTOM) { throw Util.newError( "You may only specify 'topLevel' if " + "access='custom'"); } topLevel = (Level) schemaReader.lookupCompound( cube, Util.parseIdentifier(hierarchyGrant.topLevel), true, Category.Level); } Level bottomLevel = null; if (hierarchyGrant.bottomLevel != null) { if (hierarchyAccess != Access.CUSTOM) { throw Util.newError( "You may only specify 'bottomLevel' if " + "access='custom'"); } bottomLevel = (Level) schemaReader.lookupCompound( cube, Util.parseIdentifier(hierarchyGrant.bottomLevel), true, Category.Level); } Role.RollupPolicy rollupPolicy; if (hierarchyGrant.rollupPolicy != null) { try { rollupPolicy = Role.RollupPolicy.valueOf( hierarchyGrant.rollupPolicy.toUpperCase()); } catch (IllegalArgumentException e) { throw Util.newError( "Illegal rollupPolicy value '" + hierarchyGrant.rollupPolicy + "'"); } } else { rollupPolicy = Role.RollupPolicy.FULL; } role.grant( hierarchy, hierarchyAccess, topLevel, bottomLevel, rollupPolicy); for (MondrianDef.MemberGrant memberGrant : hierarchyGrant.memberGrants) { if (hierarchyAccess != Access.CUSTOM) { throw Util.newError( "You may only specify if " + " has access='custom'"); } final boolean ignoreInvalidMembers = MondrianProperties.instance().IgnoreInvalidMembers .get(); Member member = schemaReader.withLocus() .getMemberByUniqueName( Util.parseIdentifier(memberGrant.member), !ignoreInvalidMembers); if (member == null) { // They asked to ignore members that don't exist // (e.g. [Store].[USA].[Foo]), so ignore this grant // too. assert ignoreInvalidMembers; continue; } if (member.getHierarchy() != hierarchy) { throw Util.newError( "Member '" + member + "' is not in hierarchy '" + hierarchy + "'"); } role.grant( member, getAccess(memberGrant.access, memberAllowed)); } } } } role.makeImmutable(); return role; } private Access getAccess(String accessString, Set allowed) { final Access access = Access.valueOf(accessString.toUpperCase()); if (allowed.contains(access)) { return access; // value is ok } throw Util.newError("Bad value access='" + accessString + "'"); } public Dimension createDimension(Cube cube, String xml) { MondrianDef.CubeDimension xmlDimension; try { final Parser xmlParser = XOMUtil.createDefaultParser(); final DOMWrapper def = xmlParser.parse(xml); final String tagName = def.getTagName(); if (tagName.equals("Dimension")) { xmlDimension = new MondrianDef.Dimension(def); } else if (tagName.equals("DimensionUsage")) { xmlDimension = new MondrianDef.DimensionUsage(def); } else { throw new XOMException( "Got <" + tagName + "> when expecting or "); } } catch (XOMException e) { throw Util.newError( e, "Error while adding dimension to cube '" + cube + "' from XML [" + xml + "]"); } return ((RolapCube) cube).createDimension(xmlDimension, xmlSchema); } public Cube createCube(String xml) { RolapCube cube; try { final Parser xmlParser = XOMUtil.createDefaultParser(); final DOMWrapper def = xmlParser.parse(xml); final String tagName = def.getTagName(); if (tagName.equals("Cube")) { // Create empty XML schema, to keep the method happy. This is // okay, because there are no forward-references to resolve. final MondrianDef.Schema xmlSchema = new MondrianDef.Schema(); MondrianDef.Cube xmlDimension = new MondrianDef.Cube(def); cube = new RolapCube(this, xmlSchema, xmlDimension, false); } else if (tagName.equals("VirtualCube")) { // Need the real schema here. MondrianDef.Schema xmlSchema = getXMLSchema(); MondrianDef.VirtualCube xmlDimension = new MondrianDef.VirtualCube(def); cube = new RolapCube(this, xmlSchema, xmlDimension, false); } else { throw new XOMException( "Got <" + tagName + "> when expecting "); } } catch (XOMException e) { throw Util.newError( e, "Error while creating cube from XML [" + xml + "]"); } return cube; } /** * A collection of schemas, identified by their connection properties * (catalog name, JDBC URL, and so forth). * *

To lookup a schema, call Pool.instance().{@link #get}. */ static class Pool { private static Pool pool = new Pool(); private final Map> mapUrlToSchema = new HashMap>(); private final Map> mapMd5ToSchema = new HashMap>(); private Pool() { } static Pool instance() { return pool; } synchronized RolapSchema get( final String catalogUrl, final String connectionKey, final String jdbcUser, final String dataSourceStr, final Util.PropertyList connectInfo) { return get( catalogUrl, connectionKey, jdbcUser, dataSourceStr, null, connectInfo); } synchronized RolapSchema get( final String catalogUrl, final DataSource dataSource, final Util.PropertyList connectInfo) { return get( catalogUrl, null, null, null, dataSource, connectInfo); } private RolapSchema get( final String catalogUrl, final String connectionKey, final String jdbcUser, final String dataSourceStr, final DataSource dataSource, final Util.PropertyList connectInfo) { String key = (dataSource == null) ? makeKey(catalogUrl, connectionKey, jdbcUser, dataSourceStr) : makeKey(catalogUrl, dataSource); RolapSchema schema = null; String dynProcName = connectInfo.get( RolapConnectionProperties.DynamicSchemaProcessor.name()); String catalogStr = connectInfo.get( RolapConnectionProperties.CatalogContent.name()); if (catalogUrl == null && catalogStr == null) { throw MondrianResource.instance() .ConnectStringMandatoryProperties.ex( RolapConnectionProperties.Catalog.name(), RolapConnectionProperties.CatalogContent.name()); } // If CatalogContent is specified in the connect string, ignore // everything else. In particular, ignore the dynamic schema // processor. if (catalogStr != null) { dynProcName = null; // REVIEW: Are we including enough in the key to make it // unique? key = catalogStr; } final boolean useContentChecksum = Boolean.parseBoolean( connectInfo.get( RolapConnectionProperties.UseContentChecksum.name())); // Use the schema pool unless "UseSchemaPool" is explicitly false. final boolean useSchemaPool = Boolean.parseBoolean( connectInfo.get( RolapConnectionProperties.UseSchemaPool.name(), "true")); // If there is a dynamic processor registered, use it. This // implies there is not MD5 based caching, but, as with the previous // implementation, if the catalog string is in the connectInfo // object as catalog content then it is used. if (! Util.isEmpty(dynProcName)) { assert catalogStr == null; try { @SuppressWarnings("unchecked") final Class clazz = (Class) Class.forName(dynProcName); final Constructor ctor = clazz.getConstructor(); final DynamicSchemaProcessor dynProc = ctor.newInstance(); catalogStr = dynProc.processSchema(catalogUrl, connectInfo); // Use the content of the catalog to find the schema. // Previously we'd use the key, but we didn't include // DynamicSchemaProcessor, and that would give false hits. key = catalogStr; } catch (Exception e) { throw Util.newError( e, "loading DynamicSchemaProcessor " + dynProcName); } if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Pool.get: create schema \"" + catalogUrl + "\" using dynamic processor"); } } if (!useSchemaPool) { schema = new RolapSchema( key, null, catalogUrl, catalogStr, connectInfo, dataSource); } else if (useContentChecksum) { // Different catalogUrls can actually yield the same // catalogStr! So, we use the MD5 as the key as well as // the key made above - its has two entries in the // mapUrlToSchema Map. We must then also during the // remove operation make sure we remove both. ByteString md5Bytes = null; try { if (catalogStr == null) { // Use VFS to get the content catalogStr = Util.readVirtualFileAsString(catalogUrl); } md5Bytes = new ByteString(Util.digestMd5(catalogStr)); } catch (Exception ex) { // Note, can not throw an Exception from this method // but just to show that all is not well in Mudville // we print stack trace (for now - better to change // method signature and throw). ex.printStackTrace(); } if (md5Bytes != null) { SoftReference ref = mapMd5ToSchema.get(md5Bytes); if (ref != null) { schema = ref.get(); if (schema == null) { // clear out the reference since schema is null mapUrlToSchema.remove(key); mapMd5ToSchema.remove(md5Bytes); } } } if (schema == null || md5Bytes == null || !schema.useContentChecksum || !schema.md5Bytes.equals(md5Bytes)) { schema = new RolapSchema( key, md5Bytes, catalogUrl, catalogStr, connectInfo, dataSource); SoftReference ref = new SoftReference(schema); if (md5Bytes != null) { mapMd5ToSchema.put(md5Bytes, ref); } mapUrlToSchema.put(key, ref); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Pool.get: create schema \"" + catalogUrl + "\" with MD5"); } } else if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Pool.get: schema \"" + catalogUrl + "\" exists already with MD5"); } } else { SoftReference ref = mapUrlToSchema.get(key); if (ref != null) { schema = ref.get(); if (schema == null) { // clear out the reference since schema is null mapUrlToSchema.remove(key); } } if (schema == null) { schema = new RolapSchema( key, null, catalogUrl, catalogStr, connectInfo, dataSource); mapUrlToSchema.put( key, new SoftReference(schema)); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Pool.get: create schema \"" + catalogUrl + "\""); } } else if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Pool.get: schema \"" + catalogUrl + "\" exists already "); } } return schema; } synchronized void remove( final String catalogUrl, final String connectionKey, final String jdbcUser, final String dataSourceStr) { final String key = makeKey( catalogUrl, connectionKey, jdbcUser, dataSourceStr); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Pool.remove: schema \"" + catalogUrl + "\" and datasource string \"" + dataSourceStr + "\""); } remove(key); } synchronized void remove( final String catalogUrl, final DataSource dataSource) { final String key = makeKey(catalogUrl, dataSource); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Pool.remove: schema \"" + catalogUrl + "\" and datasource object"); } remove(key); } synchronized void remove(RolapSchema schema) { if (schema != null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Pool.remove: schema \"" + schema.name + "\" and datasource object"); } remove(schema.key); } } private void remove(String key) { SoftReference ref = mapUrlToSchema.get(key); if (ref != null) { RolapSchema schema = ref.get(); if (schema != null) { mapMd5ToSchema.remove(schema.md5Bytes); schema.finalCleanUp(); } } mapUrlToSchema.remove(key); } synchronized void clear() { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Pool.clear: clearing all RolapSchemas"); } for (SoftReference ref : mapUrlToSchema.values()) { if (ref != null) { RolapSchema schema = ref.get(); if (schema != null) { schema.finalCleanUp(); } } } mapUrlToSchema.clear(); mapMd5ToSchema.clear(); JdbcSchema.clearAllDBs(); } /** * Returns a list of schemas in this pool. * * @return List of schemas in this pool */ synchronized List getRolapSchemas() { List list = new ArrayList(); for (RolapSchema schema : Util.GcIterator.over(mapUrlToSchema.values())) { list.add(schema); } return list; } synchronized boolean contains(RolapSchema rolapSchema) { return mapUrlToSchema.containsKey(rolapSchema.key); } /** * Creates a key with which to identify a schema in the cache. */ private static String makeKey( final String catalogUrl, final String connectionKey, final String jdbcUser, final String dataSourceStr) { final StringBuilder buf = new StringBuilder(100); appendIfNotNull(buf, catalogUrl); appendIfNotNull(buf, connectionKey); appendIfNotNull(buf, jdbcUser); appendIfNotNull(buf, dataSourceStr); return buf.toString(); } /** * Creates a key with which to identify a schema in the cache. */ private static String makeKey( final String catalogUrl, final DataSource dataSource) { final StringBuilder buf = new StringBuilder(100); appendIfNotNull(buf, catalogUrl); buf.append('.'); buf.append("external#"); buf.append(System.identityHashCode(dataSource)); return buf.toString(); } private static void appendIfNotNull(StringBuilder buf, String s) { if (s != null) { if (buf.length() > 0) { buf.append('.'); } buf.append(s); } } } public static List getRolapSchemas() { return Pool.instance().getRolapSchemas(); } public static boolean cacheContains(RolapSchema rolapSchema) { return Pool.instance().contains(rolapSchema); } public Cube lookupCube(final String cube, final boolean failIfNotFound) { RolapCube mdxCube = lookupCube(cube); if (mdxCube == null && failIfNotFound) { throw MondrianResource.instance().MdxCubeNotFound.ex(cube); } return mdxCube; } /** * Finds a cube called 'cube' in the current catalog, or return null if no * cube exists. */ protected RolapCube lookupCube(final String cubeName) { return mapNameToCube.get(Util.normalizeName(cubeName)); } /** * Returns an xmlCalculatedMember called 'calcMemberName' in the * cube called 'cubeName' or return null if no calculatedMember or * xmlCube by those name exists. */ protected MondrianDef.CalculatedMember lookupXmlCalculatedMember( final String calcMemberName, final String cubeName) { for (final MondrianDef.Cube cube : xmlSchema.cubes) { if (!Util.equalName(cube.name, cubeName)) { continue; } for (MondrianDef.CalculatedMember xmlCalcMember : cube.calculatedMembers) { // FIXME: Since fully-qualified names are not unique, we // should compare unique names. Also, the logic assumes that // CalculatedMember.dimension is not quoted (e.g. "Time") // and CalculatedMember.hierarchy is quoted // (e.g. "[Time].[Weekly]"). if (Util.equalName( calcMemberFqName(xmlCalcMember), calcMemberName)) { return xmlCalcMember; } } } return null; } private String calcMemberFqName(MondrianDef.CalculatedMember xmlCalcMember) { if (xmlCalcMember.dimension != null) { return Util.makeFqName( Util.quoteMdxIdentifier(xmlCalcMember.dimension), xmlCalcMember.name); } else { return Util.makeFqName( xmlCalcMember.hierarchy, xmlCalcMember.name); } } public List getCubesWithStar(RolapStar star) { List list = new ArrayList(); for (RolapCube cube : mapNameToCube.values()) { if (star == cube.getStar()) { list.add(cube); } } return list; } /** * Adds a cube to the cube name map. * @see #lookupCube(String) */ protected void addCube(final RolapCube cube) { mapNameToCube.put( Util.normalizeName(cube.getName()), cube); } public boolean removeCube(final String cubeName) { final RolapCube cube = mapNameToCube.remove(Util.normalizeName(cubeName)); return cube != null; } public Cube[] getCubes() { Collection cubes = mapNameToCube.values(); return cubes.toArray(new RolapCube[cubes.size()]); } public List getCubeList() { return new ArrayList(mapNameToCube.values()); } public Hierarchy[] getSharedHierarchies() { Collection hierarchies = mapSharedHierarchyNameToHierarchy.values(); return hierarchies.toArray(new RolapHierarchy[hierarchies.size()]); } RolapHierarchy getSharedHierarchy(final String name) { return mapSharedHierarchyNameToHierarchy.get(name); } public NamedSet getNamedSet(String name) { return mapNameToSet.get(name); } public NamedSet getNamedSet(IdentifierSegment segment) { // FIXME: write a map that efficiently maps segment->value, taking // into account case-sensitivity etc. for (Map.Entry entry : mapNameToSet.entrySet()) { if (Util.matches(segment, entry.getKey())) { return entry.getValue(); } } return null; } public Role lookupRole(final String role) { return mapNameToRole.get(role); } public Set roleNames() { return mapNameToRole.keySet(); } public FunTable getFunTable() { return funTable; } public Parameter[] getParameters() { return parameterList.toArray( new Parameter[parameterList.size()]); } /** * Defines a user-defined function in this table. * *

If the function is not valid, throws an error. * * @param name Name of the function. * @param className Name of the class which implements the function. * The class must implement {@link mondrian.spi.UserDefinedFunction} * (otherwise it is a user-error). */ private void defineFunction( Map mapNameToUdf, final String name, String className, final Scripts.ScriptDefinition script) { if (className == null && script == null) { throw Util.newError( "Must specify either className attribute or Script element"); } if (className != null && script != null) { throw Util.newError( "Must not specify both className attribute and Script element"); } final UdfResolver.UdfFactory udfFactory; if (className != null) { // Lookup class. final Class klass; try { //noinspection unchecked klass = (Class) Class.forName(className); } catch (ClassNotFoundException e) { throw MondrianResource.instance().UdfClassNotFound.ex( name, className); } // Instantiate UDF by calling correct constructor. udfFactory = new UdfResolver.ClassUdfFactory(klass, name); } else { udfFactory = new UdfResolver.UdfFactory() { public UserDefinedFunction create() { return Scripts.userDefinedFunction(script, name); } }; } // Validate function. validateFunction(udfFactory); // Check for duplicate. UdfResolver.UdfFactory existingUdf = mapNameToUdf.get(name); if (existingUdf != null) { throw MondrianResource.instance().UdfDuplicateName.ex(name); } mapNameToUdf.put(name, udfFactory); } /** * Throws an error if a user-defined function does not adhere to the * API. */ private void validateFunction(UdfResolver.UdfFactory udfFactory) { final UserDefinedFunction udf = udfFactory.create(); // Check that the name is not null or empty. final String udfName = udf.getName(); if (udfName == null || udfName.equals("")) { throw Util.newInternal( "User-defined function defined by class '" + udf.getClass() + "' has empty name"); } // It's OK for the description to be null. final String description = udf.getDescription(); Util.discard(description); final Type[] parameterTypes = udf.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { Type parameterType = parameterTypes[i]; if (parameterType == null) { throw Util.newInternal( "Invalid user-defined function '" + udfName + "': parameter type #" + i + " is null"); } } // It's OK for the reserved words to be null or empty. final String[] reservedWords = udf.getReservedWords(); Util.discard(reservedWords); // Test that the function returns a sensible type when given the FORMAL // types. It may still fail when we give it the ACTUAL types, but it's // impossible to check that now. final Type returnType = udf.getReturnType(parameterTypes); if (returnType == null) { throw Util.newInternal( "Invalid user-defined function '" + udfName + "': return type is null"); } final Syntax syntax = udf.getSyntax(); if (syntax == null) { throw Util.newInternal( "Invalid user-defined function '" + udfName + "': syntax is null"); } } /** * Gets a {@link MemberReader} with which to read a hierarchy. If the * hierarchy is shared (sharedName is not null), looks up * a reader from a cache, or creates one if necessary. * *

Synchronization: thread safe */ synchronized MemberReader createMemberReader( final String sharedName, final RolapHierarchy hierarchy, final String memberReaderClass) { MemberReader reader; if (sharedName != null) { reader = mapSharedHierarchyToReader.get(sharedName); if (reader == null) { reader = createMemberReader(hierarchy, memberReaderClass); // share, for other uses of the same shared hierarchy if (false) { mapSharedHierarchyToReader.put(sharedName, reader); } /* System.out.println("RolapSchema.createMemberReader: "+ "add to sharedHierName->Hier map"+ " sharedName=" + sharedName + ", hierarchy=" + hierarchy.getName() + ", hierarchy.dim=" + hierarchy.getDimension().getName() ); if (mapSharedHierarchyNameToHierarchy.containsKey(sharedName)) { System.out.println("RolapSchema.createMemberReader: CONTAINS NAME"); } else { mapSharedHierarchyNameToHierarchy.put(sharedName, hierarchy); } */ if (! mapSharedHierarchyNameToHierarchy.containsKey( sharedName)) { mapSharedHierarchyNameToHierarchy.put( sharedName, hierarchy); } //mapSharedHierarchyNameToHierarchy.put(sharedName, hierarchy); } else { // final RolapHierarchy sharedHierarchy = (RolapHierarchy) // mapSharedHierarchyNameToHierarchy.get(sharedName); // final RolapDimension sharedDimension = (RolapDimension) // sharedHierarchy.getDimension(); // final RolapDimension dimension = // (RolapDimension) hierarchy.getDimension(); // Util.assertTrue( // dimension.getGlobalOrdinal() == // sharedDimension.getGlobalOrdinal()); } } else { reader = createMemberReader(hierarchy, memberReaderClass); } return reader; } /** * Creates a {@link MemberReader} with which to Read a hierarchy. */ private MemberReader createMemberReader( final RolapHierarchy hierarchy, final String memberReaderClass) { if (memberReaderClass != null) { Exception e2; try { Properties properties = null; Class clazz = Class.forName(memberReaderClass); Constructor constructor = clazz.getConstructor( RolapHierarchy.class, Properties.class); Object o = constructor.newInstance(hierarchy, properties); if (o instanceof MemberReader) { return (MemberReader) o; } else if (o instanceof MemberSource) { return new CacheMemberReader((MemberSource) o); } else { throw Util.newInternal( "member reader class " + clazz + " does not implement " + MemberSource.class); } } catch (ClassNotFoundException e) { e2 = e; } catch (NoSuchMethodException e) { e2 = e; } catch (InstantiationException e) { e2 = e; } catch (IllegalAccessException e) { e2 = e; } catch (InvocationTargetException e) { e2 = e; } throw Util.newInternal( e2, "while instantiating member reader '" + memberReaderClass); } else { SqlMemberSource source = new SqlMemberSource(hierarchy); if (hierarchy.getDimension().isHighCardinality()) { LOGGER.debug( "High cardinality for " + hierarchy.getDimension()); return new NoCacheMemberReader(source); } else { LOGGER.debug( "Normal cardinality for " + hierarchy.getDimension()); return new SmartMemberReader(source); } } } public SchemaReader getSchemaReader() { return new RolapSchemaReader(defaultRole, this).withLocus(); } /** * Creates a {@link DataSourceChangeListener} with which to detect changes to datasources. */ private DataSourceChangeListener createDataSourceChangeListener( Util.PropertyList connectInfo) { DataSourceChangeListener changeListener = null; // If CatalogContent is specified in the connect string, ignore // everything else. In particular, ignore the dynamic schema // processor. String dataSourceChangeListenerStr = connectInfo.get( RolapConnectionProperties.DataSourceChangeListener.name()); if (! Util.isEmpty(dataSourceChangeListenerStr)) { try { Class clazz = Class.forName(dataSourceChangeListenerStr); Constructor constructor = clazz.getConstructor(); changeListener = (DataSourceChangeListener) constructor.newInstance(); /* final Class clazz = (Class) Class.forName(dataSourceChangeListenerStr); final Constructor ctor = clazz.getConstructor(); changeListener = ctor.newInstance(); */ changeListener = (DataSourceChangeListener) constructor.newInstance(); } catch (Exception e) { throw Util.newError( e, "loading DataSourceChangeListener " + dataSourceChangeListenerStr); } if (LOGGER.isDebugEnabled()) { LOGGER.debug( "RolapSchema.createDataSourceChangeListener: " + "create datasource change listener \"" + dataSourceChangeListenerStr); } } return changeListener; } /** * Returns the checksum of this schema. Returns * null if {@link RolapConnectionProperties#UseContentChecksum} * is set to false. * * @return MD5 checksum of this schema */ public ByteString getChecksum() { return md5Bytes; } /** * Connection for purposes of parsing and validation. Careful! It won't * have the correct locale or access-control profile. */ public RolapConnection getInternalConnection() { return internalConnection; } /** * Returns the cached cardinality for the column. * The cache is stored in the schema so that queries on different * cubes can share them. * @return the cardinality map */ Integer getCachedRelationExprCardinality( MondrianDef.Relation relation, MondrianDef.Expression columnExpr) { Integer card = null; synchronized (relationExprCardinalityMap) { Map exprCardinalityMap = relationExprCardinalityMap.get(relation); if (exprCardinalityMap != null) { card = exprCardinalityMap.get(columnExpr); } } return card; } /** * Sets the cardinality for a given column in cache. * * @param relation the relation associated with the column expression * @param columnExpr the column expression to cache the cardinality for * @param cardinality the cardinality for the column expression */ void putCachedRelationExprCardinality( MondrianDef.Relation relation, MondrianDef.Expression columnExpr, Integer cardinality) { synchronized (relationExprCardinalityMap) { Map exprCardinalityMap = relationExprCardinalityMap.get(relation); if (exprCardinalityMap == null) { exprCardinalityMap = new HashMap(); relationExprCardinalityMap.put(relation, exprCardinalityMap); } exprCardinalityMap.put(columnExpr, cardinality); } } private RolapStar makeRolapStar(final MondrianDef.Relation fact) { DataSource dataSource = getInternalConnection().getDataSource(); return new RolapStar(this, dataSource, fact); } /** * RolapStarRegistry is a registry for {@link RolapStar}s. */ public class RolapStarRegistry { private final Map stars = new HashMap(); RolapStarRegistry() { } /** * Looks up a {@link RolapStar}, creating it if it does not exist. * *

{@link RolapStar.Table#addJoin} works in a similar way. */ synchronized RolapStar getOrCreateStar( final MondrianDef.Relation fact) { String factTableName = fact.toString(); RolapStar star = stars.get(factTableName); if (star == null) { star = makeRolapStar(fact); stars.put(factTableName, star); } return star; } synchronized RolapStar getStar(final String factTableName) { return stars.get(factTableName); } synchronized Collection getStars() { return stars.values(); } } private RolapStarRegistry rolapStarRegistry = new RolapStarRegistry(); public RolapStarRegistry getRolapStarRegistry() { return rolapStarRegistry; } /** * Function table which contains all of the user-defined functions in this * schema, plus all of the standard functions. */ static class RolapSchemaFunctionTable extends FunTableImpl { private final List udfFactoryList; RolapSchemaFunctionTable(Collection udfs) { udfFactoryList = new ArrayList(udfs); } public void defineFunctions(Builder builder) { final FunTable globalFunTable = GlobalFunTable.instance(); for (String reservedWord : globalFunTable.getReservedWords()) { builder.defineReserved(reservedWord); } for (Resolver resolver : globalFunTable.getResolvers()) { builder.define(resolver); } for (UdfResolver.UdfFactory udfFactory : udfFactoryList) { builder.define(new UdfResolver(udfFactory)); } } } public RolapStar getStar(final String factTableName) { return getRolapStarRegistry().getStar(factTableName); } public Collection getStars() { return getRolapStarRegistry().getStars(); } final RolapNativeRegistry nativeRegistry = new RolapNativeRegistry(); RolapNativeRegistry getNativeRegistry() { return nativeRegistry; } /** * @return Returns the dataSourceChangeListener. */ public DataSourceChangeListener getDataSourceChangeListener() { return dataSourceChangeListener; } /** * @param dataSourceChangeListener The dataSourceChangeListener to set. */ public void setDataSourceChangeListener( DataSourceChangeListener dataSourceChangeListener) { this.dataSourceChangeListener = dataSourceChangeListener; } /** * Location of a node in an XML document. */ private interface XmlLocation { } } // End RolapSchema.java mondrian-3.4.1/src/main/mondrian/rolap/ChildByNameConstraint.java0000644000175000017500000000400711735330606024715 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2010 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.Id; import mondrian.rolap.aggmatcher.AggStar; import mondrian.rolap.sql.SqlQuery; import java.util.Arrays; /** * Constraint which optimizes the search for a child by name. This is used * whenever the string representation of a member is parsed, e.g. * [Customers].[USA].[CA]. Restricts the result to * the member we are searching for. * * @author avix */ class ChildByNameConstraint extends DefaultMemberChildrenConstraint { private final String childName; private final Object cacheKey; /** * Creates a ChildByNameConstraint. * * @param childName Name of child */ public ChildByNameConstraint(Id.Segment childName) { this.childName = childName.name; this.cacheKey = Arrays.asList(ChildByNameConstraint.class, childName); } @Override public int hashCode() { return getCacheKey().hashCode(); } @Override public boolean equals(Object obj) { return obj instanceof ChildByNameConstraint && getCacheKey().equals( ((ChildByNameConstraint) obj).getCacheKey()); } public void addLevelConstraint( SqlQuery query, RolapCube baseCube, AggStar aggStar, RolapLevel level) { super.addLevelConstraint(query, baseCube, aggStar, level); query.addWhere( SqlConstraintUtils.constrainLevel( level, query, baseCube, aggStar, childName, true)); } public String toString() { return "ChildByNameConstraint(" + childName + ")"; } public Object getCacheKey() { return cacheKey; } } // End ChildByNameConstraint.java mondrian-3.4.1/src/main/mondrian/rolap/MemberSource.java0000644000175000017500000001030411735330606023116 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. // // jhyde, 21 December, 2001 */ package mondrian.rolap; import mondrian.olap.Id; import java.util.List; /** * A MemberSource has the basic operations to read the members of a * {@link RolapHierarchy hierarchy}. * *

A MemberSource may optionally support writeback to a * {@link MemberCache}. During the initialization of a * MemberSource, the consumer calls {@link #setCache}; the return * value indicates whether the MemberSource supports * cache-writeback. * *

A custom member reader is a user-defined class which implements * the operations to retrieve members. It either implements the * MemberSource interface, or the derived interface * {@link MemberReader}, which has more operations. In addition to the interface * methods, the class must have a constructor which takes parameters * ({@link RolapHierarchy}, {@link java.util.Properties}) and * throws no exceptions. To declare a hierarchy based upon the class, use the * memberReaderClass attribute of the * <Hierarchy> element in your XML schema file; the * properties constructor parameter is populated from any * <Param name="..." value="..."> child elements. * * @see MemberReader * @see MemberCache * * @author jhyde * @since 21 December, 2001 */ public interface MemberSource { /** * Returns the hierarchy that this source is reading for. */ RolapHierarchy getHierarchy(); /** * Sets the cache which this MemberSource will write to. * *

Cache-writeback is optional (for example, {@link SqlMemberSource} * supports it, and {@link ArrayMemberSource} does not), and the return * value from this method indicates whether this object supports it. * *

If this method returns true, the {@link #getMembers}, * {@link #getRootMembers} and {@link #getMemberChildren} methods must * write to the cache, in addition to returning members as a return value. * * @param cache The MemberCache which the caller would like * this MemberSource to write to. * @return Whether this MemberSource supports cache-writeback. */ boolean setCache(MemberCache cache); /** * Returns all members of this hierarchy, sorted by ordinal. * *

If this object {@link #setCache supports cache-writeaback}, also * writes these members to the cache. */ List getMembers(); /** * Returns all members of this hierarchy which do not have a parent, * sorted by ordinal. * *

If this object {@link #setCache supports cache-writeback}, also * writes these members to the cache. * * @return {@link List} of {@link RolapMember}s */ List getRootMembers(); /** * Writes all children parentMember to children. * *

If this object {@link #setCache supports cache-writeback}, also * writes these members to the cache. */ void getMemberChildren( RolapMember parentMember, List children); /** * Returns all members which are a child of one of the members in * parentMembers, sorted by ordinal. * *

If this object {@link #setCache supports cache-writeaback}, also * writes these members to the cache. */ void getMemberChildren( List parentMembers, List children); /** * Returns an estimate of number of members in this hierarchy. */ int getMemberCount(); /** * Finds a member based upon its unique name. */ RolapMember lookupMember( List uniqueNameParts, boolean failIfNotFound); } // End MemberSource.java mondrian-3.4.1/src/main/mondrian/rolap/RolapResultShepherd.java0000644000175000017500000001540211735330606024471 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.server.Execution; import mondrian.util.Pair; import java.util.List; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.*; /** * A utility class for {@link RolapConnection}. It specializes in * shepherding the creation of RolapResult by running the actual execution * on a separate thread from the user thread so we can: *

    *
  • Monitor all executions for timeouts and resource limits as they run * in the background
  • *
  • Bubble exceptions to the user thread as fast as they happen.
  • *
  • Gracefully cancel all SQL statements and cleanup in the background.
  • *
* * @author LBoudreau */ public class RolapResultShepherd { /** * An executor service used for both the shepherd thread and the * Execution objects. */ private final ExecutorService executor = Util.getExecutorService( MondrianProperties.instance() .RolapConnectionShepherdNbThreads.get(), "mondrian.rolap.RolapResultShepherd$executor"); /** * List of tasks that should be monitored by the shepherd thread. */ private static final List, Execution>> tasks = new CopyOnWriteArrayList,Execution>>(); private final Timer timer = Util.newTimer("mondrian.rolap.RolapResultShepherd#timer", true); public RolapResultShepherd() { timer.scheduleAtFixedRate( new TimerTask() { public void run() { for (final Pair, Execution> task : tasks) { if (task.left.isDone()) { tasks.remove(task); continue; } if (task.right.isCancelOrTimeout()) { // Remove it from the list so that we know // it was cleaned once. tasks.remove(task); // Cancel the FutureTask for which // the user thread awaits. The user // thread will call // Execution.checkCancelOrTimeout // later and take care of sending // an exception on the user thread. task.left.cancel(true); // The cleanup operation can be done async. // Let's not interrupt this task. executor.submit( new Runnable() { public void run() { // Now cleanup the statement on // this thread. task.right.cancelSqlStatements(); } }); } } } }, MondrianProperties.instance() .RolapConnectionShepherdThreadPollingInterval.get(), MondrianProperties.instance() .RolapConnectionShepherdThreadPollingInterval.get()); } /** * Executes and shepherds the execution of an Execution instance. * The shepherd will wrap the Execution instance into a Future object * which can be monitored for exceptions. If any are encountered, * two things will happen. First, the user thread will be returned and * the resulting exception will bubble up. Second, the execution thread * will attempt to do a graceful stop of all running SQL statements and * release all other resources gracefully in the background. * @param execution An Execution instance. * @param callable A callable to monitor returning a Result instance. * @throws ResourceLimitExceededException if some resource limit specified * in the property file was exceeded * @throws QueryCanceledException if query was canceled during execution * @throws QueryTimeoutException if query exceeded timeout specified in * the property file * @return A Result object, as supplied by the Callable passed as a * parameter. */ public Result shepherdExecution( Execution execution, Callable callable) { // We must wrap this execution into a task that so that we are able // to monitor, cancel and detach from it. FutureTask task = new FutureTask(callable); // Register this task with the shepherd thread final Pair, Execution> pair = new Pair, Execution>( task, execution); tasks.add(pair); try { // Now run it. executor.execute(task); return task.get(); } catch (Exception e) { if (e instanceof InterruptedException) { Thread.currentThread().interrupt(); } Throwable node = e; if (e instanceof ExecutionException) { ExecutionException executionException = (ExecutionException) e; node = executionException.getCause(); } // Let the Execution throw whatever it wants to, this way the // API contract is respected. The program should in most cases // stop here as most exceptions will originate from the Execution // instance. execution.checkCancelOrTimeout(); // We must also check for ResourceLimitExceededExceptions, // which might be wrapped by an ExecutionException. In order to // respect the API contract, we must throw the cause, not the // wrapper. final ResourceLimitExceededException t = Util.getMatchingCause( node, ResourceLimitExceededException.class); if (t != null) { throw t; } // Since we got here, this means that the exception was // something else. Just wrap/throw. if (node instanceof RuntimeException) { throw (RuntimeException) node; } else if (node instanceof Error) { throw (Error) node; } else { throw new MondrianException(node); } } } public void shutdown() { this.timer.cancel(); this.executor.shutdown(); } } // End RolapResultShepherd.java mondrian-3.4.1/src/main/mondrian/rolap/RolapCubeDimension.java0000644000175000017500000000753211735330606024261 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import java.util.List; /** * RolapCubeDimension wraps a RolapDimension for a specific Cube. * * @author Will Gorman, 19 October 2007 */ public class RolapCubeDimension extends RolapDimension { RolapCube cube; RolapDimension rolapDimension; int cubeOrdinal; MondrianDef.CubeDimension xmlDimension; /** * Creates a RolapCubeDimension. * * @param cube Cube * @param rolapDim Dimension wrapped by this dimension * @param cubeDim XML element definition * @param name Name of dimension * @param cubeOrdinal Ordinal of dimension within cube * @param hierarchyList List of hierarchies in cube * @param highCardinality Whether high cardinality dimension */ public RolapCubeDimension( RolapCube cube, RolapDimension rolapDim, MondrianDef.CubeDimension cubeDim, String name, int cubeOrdinal, List hierarchyList, final boolean highCardinality) { super( null, name, cubeDim.caption != null ? cubeDim.caption : rolapDim.getCaption(), cubeDim.visible, cubeDim.caption != null ? cubeDim.description : rolapDim.getDescription(), null, highCardinality, RolapHierarchy.createAnnotationMap(cubeDim.annotations)); this.xmlDimension = cubeDim; this.rolapDimension = rolapDim; this.cubeOrdinal = cubeOrdinal; this.cube = cube; this.caption = cubeDim.caption; // create new hierarchies hierarchies = new RolapCubeHierarchy[rolapDim.getHierarchies().length]; for (int i = 0; i < rolapDim.getHierarchies().length; i++) { final RolapCubeHierarchy cubeHierarchy = new RolapCubeHierarchy( this, cubeDim, (RolapHierarchy) rolapDim.getHierarchies()[i], ((HierarchyBase) rolapDim.getHierarchies()[i]).getSubName(), hierarchyList.size()); hierarchies[i] = cubeHierarchy; hierarchyList.add(cubeHierarchy); } } public RolapCube getCube() { return cube; } public Schema getSchema() { return rolapDimension.getSchema(); } // this method should eventually replace the call below public int getOrdinal() { return cubeOrdinal; } public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof RolapCubeDimension)) { return false; } RolapCubeDimension that = (RolapCubeDimension)o; if (!cube.equals(that.cube)) { return false; } return getUniqueName().equals(that.getUniqueName()); } RolapCubeHierarchy newHierarchy( String subName, boolean hasAll, RolapHierarchy closureFor) { throw new UnsupportedOperationException(); } public String getCaption() { if (caption != null) { return caption; } return rolapDimension.getCaption(); } public void setCaption(String caption) { if (true) { throw new UnsupportedOperationException(); } rolapDimension.setCaption(caption); } public DimensionType getDimensionType() { return rolapDimension.getDimensionType(); } } // End RolapCubeDimension.java mondrian-3.4.1/src/main/mondrian/rolap/RolapMember.java0000644000175000017500000000162311735330606022737 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.Member; /** * A RolapMember is a member of a {@link RolapHierarchy}. There are * sub-classes for {@link RolapStoredMeasure}, {@link RolapCalculatedMember}. * * @author jhyde * @since 10 August, 2001 */ public interface RolapMember extends Member, RolapCalculation { Object getKey(); RolapMember getParentMember(); RolapHierarchy getHierarchy(); RolapLevel getLevel(); /** @deprecated will be removed in mondrian-4.0 */ boolean isAllMember(); } // End RolapMember.java mondrian-3.4.1/src/main/mondrian/rolap/RolapMeasure.java0000644000175000017500000000145111735330606023130 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.Member; /** * Interface implemented by all measures (both stored and calculated). * * @author jhyde * @since 10 August, 2001 */ public interface RolapMeasure extends Member { /** * Returns the object that formats cells of this measure, or null to use * default formatting. * * @return formatter */ RolapResult.ValueFormatter getFormatter(); } // End RolapMeasure.java mondrian-3.4.1/src/main/mondrian/rolap/RolapBaseCubeMeasure.java0000644000175000017500000001250311735330606024522 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2012 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.resource.MondrianResource; import mondrian.spi.CellFormatter; import mondrian.spi.Dialect; import java.util.*; /** * Measure which is computed from a SQL column (or expression) and which is * defined in a non-virtual cube. * * @see RolapVirtualCubeMeasure * * @author jhyde * @since 24 August, 2006 */ public class RolapBaseCubeMeasure extends RolapMemberBase implements RolapStoredMeasure { private static final List datatypeList = Arrays.asList("Integer", "Numeric", "String"); /** * For SQL generator. Column which holds the value of the measure. */ private final MondrianDef.Expression expression; /** * For SQL generator. Has values "SUM", "COUNT", etc. */ private final RolapAggregator aggregator; private final RolapCube cube; private final Map annotationMap; /** * Holds the {@link mondrian.rolap.RolapStar.Measure} from which this * member is computed. Untyped, because another implementation might store * it somewhere else. */ private Object starMeasure; private RolapResult.ValueFormatter formatter; /** * Creates a RolapBaseCubeMeasure. * * @param cube Cube * @param parentMember Parent member * @param level Level this member belongs to * @param name Name of this member * @param caption Caption * @param description Description * @param formatString Format string * @param expression Expression * @param aggregatorName Aggregator * @param datatype Data type * @param annotationMap Annotations */ RolapBaseCubeMeasure( RolapCube cube, RolapMember parentMember, RolapLevel level, String name, String caption, String description, String formatString, MondrianDef.Expression expression, String aggregatorName, String datatype, Map annotationMap) { super(parentMember, level, name, null, MemberType.MEASURE); assert annotationMap != null; this.cube = cube; this.annotationMap = annotationMap; this.caption = caption; this.expression = expression; if (description != null) { setProperty( Property.DESCRIPTION.name, description); } if (formatString == null) { formatString = ""; } setProperty( Property.FORMAT_EXP_PARSED.name, Literal.createString(formatString)); setProperty( Property.FORMAT_EXP.name, formatString); // Validate aggregator. this.aggregator = RolapAggregator.enumeration.getValue(aggregatorName, false); if (this.aggregator == null) { StringBuilder buf = new StringBuilder(); for (String aggName : RolapAggregator.enumeration.getNames()) { if (buf.length() > 0) { buf.append(", "); } buf.append('\''); buf.append(aggName); buf.append('\''); } throw MondrianResource.instance().UnknownAggregator.ex( aggregatorName, buf.toString()); } setProperty(Property.AGGREGATION_TYPE.name, aggregator); if (datatype == null) { if (aggregator == RolapAggregator.Count || aggregator == RolapAggregator.DistinctCount) { datatype = "Integer"; } else { datatype = "Numeric"; } } // todo: End-user error. Util.assertTrue( RolapBaseCubeMeasure.datatypeList.contains(datatype), "invalid datatype " + datatype); setProperty(Property.DATATYPE.name, datatype); } public MondrianDef.Expression getMondrianDefExpression() { return expression; } public RolapAggregator getAggregator() { return aggregator; } public RolapCube getCube() { return cube; } public RolapResult.ValueFormatter getFormatter() { return formatter; } public void setFormatter(CellFormatter cellFormatter) { this.formatter = new RolapResult.CellFormatterValueFormatter(cellFormatter); } public Object getStarMeasure() { return starMeasure; } void setStarMeasure(Object starMeasure) { this.starMeasure = starMeasure; } @Override public Map getAnnotationMap() { return annotationMap; } public Dialect.Datatype getDatatype() { Object datatype = getPropertyValue(Property.DATATYPE.name); try { return Dialect.Datatype.valueOf((String) datatype); } catch (ClassCastException e) { return Dialect.Datatype.String; } catch (IllegalArgumentException e) { return Dialect.Datatype.String; } } } // End RolapBaseCubeMeasure.java mondrian-3.4.1/src/main/mondrian/rolap/RolapSchemaParameter.java0000644000175000017500000000721311735330606024572 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2010 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.*; import mondrian.calc.impl.GenericCalc; import mondrian.olap.*; import mondrian.olap.type.Type; import mondrian.resource.MondrianResource; /** * Parameter that is defined in a schema. * * @author jhyde * @since Jul 20, 2006 */ public class RolapSchemaParameter implements Parameter, ParameterCompilable { private final RolapSchema schema; private final String name; private String description; private String defaultExpString; private Type type; private final boolean modifiable; private Object value; private boolean assigned; private Object cachedDefaultValue; RolapSchemaParameter( RolapSchema schema, String name, String defaultExpString, String description, Type type, boolean modifiable) { assert defaultExpString != null; assert name != null; assert schema != null; assert type != null; this.schema = schema; this.name = name; this.defaultExpString = defaultExpString; this.description = description; this.type = type; this.modifiable = modifiable; schema.parameterList.add(this); } RolapSchema getSchema() { return schema; } public boolean isModifiable() { return modifiable; } public Scope getScope() { return Scope.Schema; } public Type getType() { return type; } public Exp getDefaultExp() { throw new UnsupportedOperationException(); } public String getName() { return name; } public String getDescription() { return description; } public Object getValue() { return value; } public void setValue(Object value) { if (!modifiable) { throw MondrianResource.instance().ParameterIsNotModifiable.ex( getName(), getScope().name()); } this.assigned = true; this.value = value; } public boolean isSet() { return assigned; } public void unsetValue() { if (!modifiable) { throw MondrianResource.instance().ParameterIsNotModifiable.ex( getName(), getScope().name()); } assigned = false; value = null; } public Calc compile(ExpCompiler compiler) { // Parse and compile the expression for the default value. Exp defaultExp = compiler.getValidator() .getQuery() .getConnection() .parseExpression(defaultExpString); defaultExp = compiler.getValidator().validate(defaultExp, true); final Calc defaultCalc = defaultExp.accept(compiler); // Generate a program which looks at the assigned value first, // and if it is not set, returns the default expression. return new GenericCalc(defaultExp) { public Calc[] getCalcs() { return new Calc[] {defaultCalc}; } public Object evaluate(Evaluator evaluator) { if (value != null) { return value; } if (cachedDefaultValue == null) { cachedDefaultValue = defaultCalc.evaluate(evaluator); } return cachedDefaultValue; } }; } } // End RolapSchemaParameter.java mondrian-3.4.1/src/main/mondrian/rolap/Target.java0000644000175000017500000002057411735330606021766 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.ResultStyle; import mondrian.olap.*; import mondrian.rolap.sql.TupleConstraint; import java.sql.SQLException; import java.util.*; /** * Helper class for {@link mondrian.rolap.HighCardSqlTupleReader} that * keeps track of target levels and constraints for adding to SQL query. * * @author luis f. canals, Kurtis Walker * @since July 23, 2009 */ public class Target extends TargetBase { private final HighCardSqlTupleReader sqlTupleReader; private final MemberCache cache; private final TupleConstraint constraint; boolean parentChild; private RolapLevel[] levels; private int levelDepth; public Target( final RolapLevel level, final TupleReader.MemberBuilder memberBuilder, final List srcMembers, final TupleConstraint constraint, final HighCardSqlTupleReader sqlTupleReader) { super(srcMembers, level, memberBuilder); this.sqlTupleReader = sqlTupleReader; this.constraint = constraint; this.cache = memberBuilder.getMemberCache(); } public void open() { levels = (RolapLevel[]) level.getHierarchy().getLevels(); // REVIEW: ArrayDeque is preferable to LinkedList (JDK1.6 and up) but it // doesn't implement List, so we can't easily interoperate the two. setList(new LinkedList()); levelDepth = level.getDepth(); parentChild = level.isParentChild(); } int internalAddRow(SqlStatement stmt, int column) throws SQLException { RolapMember member = null; if (getCurrMember() != null) { member = getCurrMember(); } else { boolean checkCacheStatus = true; final List accessors = stmt.getAccessors(); for (int i = 0; i <= levelDepth; i++) { RolapLevel childLevel = levels[i]; if (childLevel.isAll()) { member = memberBuilder.allMember(); continue; } if (childLevel.isParentChild()) { column++; } Object value = accessors.get(column++).get(); if (value == null) { value = RolapUtil.sqlNullValue; } Object captionValue; if (childLevel.hasCaptionColumn()) { captionValue = accessors.get(column++).get(); } else { captionValue = null; } RolapMember parentMember = member; Object key = cache.makeKey(parentMember, value); member = cache.getMember(key, checkCacheStatus); checkCacheStatus = false; /* Only check the first time */ if (member == null) { if (constraint instanceof RolapNativeCrossJoin.NonEmptyCrossJoinConstraint && childLevel.isParentChild()) { member = castToNonEmptyCJConstraint(constraint) .findMember(value); } if (member == null) { member = memberBuilder.makeMember( parentMember, childLevel, value, captionValue, parentChild, stmt, key, column); } } // Skip over the columns consumed by makeMember if (!childLevel.getOrdinalExp().equals( childLevel.getKeyExp())) { ++column; } column += childLevel.getProperties().length; } setCurrMember(member); } getList().add(member); return column; } public List close() { final boolean asList = this.constraint.getEvaluator() != null && this.constraint.getEvaluator().getQuery().getResultStyle() == ResultStyle.LIST; final int limit = MondrianProperties.instance().ResultLimit.get(); final List l = new AbstractList() { private boolean moreRows = true; private int offset = 0; private RolapMember first = null; private boolean firstMemberAssigned = false; /** * Performs a load of the whole result set. */ public int size() { while (this.moreRows) { this.moreRows = sqlTupleReader.readNextTuple(); if (limit > 0 && !asList && getList().size() > limit) { System.out.println( "Target: 199, Ouch! Toooo big array..." + hashCode()); new Throwable().printStackTrace(); } } return getList().size(); } public RolapMember get(final int idx) { if (asList) { return getList().get(idx); } if (idx == 0 && this.firstMemberAssigned) { return this.first; } int index = idx - offset; if (0 < limit && index < 0) { // Cannot send NoSuchElementException since its intercepted // by AbstractSequentialList to identify out of bounds. throw new RuntimeException( "Element " + idx + " has been forgotten"); } while (index >= getList().size() && this.moreRows) { this.moreRows = sqlTupleReader.readNextTuple(); if (limit > 0 && getList().size() > limit) { while (getList().size() > limit) { index--; offset++; ((LinkedList) getList()).removeFirst(); } } } if (idx == 0) { this.first = getList().get(index); // Above might run into exception which is caught in // isEmpty(). So can change the state of the object after // that. this.firstMemberAssigned = true; return this.first; } else { return getList().get(index); } } public RolapMember set(final int i, final RolapMember e) { if (asList) { return getList().set(i, e); } else { throw new UnsupportedOperationException(); } } public boolean isEmpty() { try { get(0); return false; } catch (IndexOutOfBoundsException e) { return true; } } public int hashCode() { return Target.this.hashCode(); } public Iterator iterator() { return new Iterator() { private int cursor = 0; public boolean hasNext() { try { get(cursor); return true; } catch (IndexOutOfBoundsException ioobe) { return false; } } public RolapMember next() { return get(cursor++); } public void remove() { throw new UnsupportedOperationException(); } }; } }; if (asList) { l.size(); } return Util.cast(l); } } // End Target.java mondrian-3.4.1/src/main/mondrian/rolap/RolapCube.java0000644000175000017500000034026111735330606022412 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.Calc; import mondrian.calc.ExpCompiler; import mondrian.mdx.*; import mondrian.olap.*; import mondrian.olap.fun.FunDefBase; import mondrian.resource.MondrianResource; import mondrian.rolap.aggmatcher.ExplicitRules; import mondrian.rolap.cache.SoftSmartCache; import mondrian.server.Locus; import mondrian.server.Statement; import mondrian.spi.CellFormatter; import mondrian.spi.impl.Scripts; import org.apache.log4j.Logger; import org.eigenbase.xom.*; import org.eigenbase.xom.Parser; import org.olap4j.mdx.IdentifierNode; import org.olap4j.mdx.IdentifierSegment; import java.util.*; /** * RolapCube implements {@link Cube} for a ROLAP database. * * @author jhyde * @since 10 August, 2001 */ public class RolapCube extends CubeBase { private static final Logger LOGGER = Logger.getLogger(RolapCube.class); private final RolapSchema schema; private final Map annotationMap; private final RolapHierarchy measuresHierarchy; /** For SQL generator. Fact table. */ final MondrianDef.Relation fact; /** Schema reader which can see this cube and nothing else. */ private SchemaReader schemaReader; /** * List of calculated members. */ private final List calculatedMemberList = new ArrayList(); /** * Role-based cache of calculated members */ private final SoftSmartCache> roleToAccessibleCalculatedMembers = new SoftSmartCache>(); /** * List of named sets. */ private final List namedSetList = new ArrayList(); /** Contains {@link HierarchyUsage}s for this cube */ private final List hierarchyUsages; private RolapStar star; private ExplicitRules.Group aggGroup; private final Map firstUsageMap = new HashMap(); /** * Refers {@link RolapCubeUsages} if this is a virtual cube */ private RolapCubeUsages cubeUsages; RolapBaseCubeMeasure factCountMeasure; final List hierarchyList = new ArrayList(); /** * Set to true when a cube is being modified after creation. * * @see #isLoadInProgress() */ private boolean loadInProgress = false; private Map virtualToBaseMap = new HashMap(); final BitKey closureColumnBitKey; /** * Private constructor used by both normal cubes and virtual cubes. * * @param schema Schema cube belongs to * @param name Name of cube * @param caption Caption * @param description Description * @param fact Definition of fact table * @param load Whether cube is being created while loading the schema * @param annotationMap Annotations */ private RolapCube( RolapSchema schema, MondrianDef.Schema xmlSchema, String name, boolean visible, String caption, String description, boolean isCache, MondrianDef.Relation fact, MondrianDef.CubeDimension[] dimensions, boolean load, Map annotationMap) { super( name, caption, visible, description, new RolapDimension[dimensions.length + 1]); assert annotationMap != null; this.schema = schema; this.annotationMap = annotationMap; this.caption = caption; this.fact = fact; this.hierarchyUsages = new ArrayList(); if (! isVirtual()) { this.star = schema.getRolapStarRegistry().getOrCreateStar(fact); // only set if different from default (so that if two cubes share // the same fact table, either can turn off caching and both are // effected). if (! isCache) { star.setCacheAggregations(isCache); } } if (getLogger().isDebugEnabled()) { if (isVirtual()) { getLogger().debug( "RolapCube: virtual cube=" + this.name); } else { getLogger().debug("RolapCube: cube=" + this.name); } } RolapDimension measuresDimension = new RolapDimension( schema, Dimension.MEASURES_NAME, null, true, null, DimensionType.MeasuresDimension, false, Collections.emptyMap()); this.dimensions[0] = measuresDimension; this.measuresHierarchy = measuresDimension.newHierarchy(null, false, null); hierarchyList.add(measuresHierarchy); if (!Util.isEmpty(xmlSchema.measuresCaption)) { measuresDimension.setCaption(xmlSchema.measuresCaption); this.measuresHierarchy.setCaption(xmlSchema.measuresCaption); } for (int i = 0; i < dimensions.length; i++) { MondrianDef.CubeDimension xmlCubeDimension = dimensions[i]; // Look up usages of shared dimensions in the schema before // consulting the XML schema (which may be null). RolapCubeDimension dimension = getOrCreateDimension( xmlCubeDimension, schema, xmlSchema, i + 1, hierarchyList); if (getLogger().isDebugEnabled()) { getLogger().debug( "RolapCube: dimension=" + dimension.getName()); } this.dimensions[i + 1] = dimension; if (! isVirtual()) { createUsages(dimension, xmlCubeDimension); } // the register Dimension call was moved here // to keep the RolapStar in sync with the realiasing // within the RolapCubeHierarchy objects. registerDimension(dimension); } // Initialize closure bit key only when we know how many columns are in // the star. if (! isVirtual()) { closureColumnBitKey = BitKey.Factory.makeBitKey(star.getColumnCount()); } else { closureColumnBitKey = null; } schema.addCube(this); } /** * Creates a RolapCube from a regular cube. */ RolapCube( RolapSchema schema, MondrianDef.Schema xmlSchema, MondrianDef.Cube xmlCube, boolean load) { this( schema, xmlSchema, xmlCube.name, xmlCube.visible, xmlCube.caption, xmlCube.description, xmlCube.cache, xmlCube.fact, xmlCube.dimensions, load, RolapHierarchy.createAnnotationMap(xmlCube.annotations)); if (fact == null) { throw Util.newError( "Must specify fact table of cube '" + getName() + "'"); } if (fact.getAlias() == null) { throw Util.newError( "Must specify alias for fact table of cube '" + getName() + "'"); } // since MondrianDef.Measure and MondrianDef.VirtualCubeMeasure // can not be treated as the same, measure creation can not be // done in a common constructor. RolapLevel measuresLevel = this.measuresHierarchy.newMeasuresLevel(); List measureList = new ArrayList(xmlCube.measures.length); Member defaultMeasure = null; for (int i = 0; i < xmlCube.measures.length; i++) { RolapBaseCubeMeasure measure = createMeasure(xmlCube, measuresLevel, i, xmlCube.measures[i]); measureList.add(measure); // Is this the default measure? if (Util.equalName(measure.getName(), xmlCube.defaultMeasure)) { defaultMeasure = measure; } if (measure.getAggregator() == RolapAggregator.Count) { factCountMeasure = measure; } } boolean writebackEnabled = false; for (RolapHierarchy hierarchy : hierarchyList) { if (ScenarioImpl.isScenario(hierarchy)) { writebackEnabled = true; } } // Ensure that cube has an atomic cell count // measure even if the schema does not contain one. if (factCountMeasure == null) { final MondrianDef.Measure xmlMeasure = new MondrianDef.Measure(); xmlMeasure.aggregator = "count"; xmlMeasure.name = "Fact Count"; xmlMeasure.visible = false; factCountMeasure = createMeasure( xmlCube, measuresLevel, measureList.size(), xmlMeasure); measureList.add(factCountMeasure); } setMeasuresHierarchyMemberReader( new CacheMemberReader( new MeasureMemberSource(this.measuresHierarchy, measureList))); this.measuresHierarchy.setDefaultMember(defaultMeasure); init(xmlCube.dimensions); init(xmlCube, measureList); setMeasuresHierarchyMemberReader( new CacheMemberReader( new MeasureMemberSource(this.measuresHierarchy, measureList))); checkOrdinals(xmlCube.name, measureList); loadAggGroup(xmlCube); } /** * Creates a measure. * * @param xmlCube XML cube * @param measuresLevel Member that all measures belong to * @param ordinal Ordinal of measure * @param xmlMeasure XML measure * @return Measure */ private RolapBaseCubeMeasure createMeasure( MondrianDef.Cube xmlCube, RolapLevel measuresLevel, int ordinal, final MondrianDef.Measure xmlMeasure) { 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 if (xmlMeasure.aggregator.equals("count")) { // it's ok if count has no expression; it means 'count(*)' measureExp = null; } else { throw MondrianResource.instance().BadMeasureSource.ex( xmlCube.name, xmlMeasure.name); } // Validate aggregator name. Substitute deprecated "distinct count" // with modern "distinct-count". String aggregator = xmlMeasure.aggregator; if (aggregator.equals("distinct count")) { aggregator = RolapAggregator.DistinctCount.getName(); } final RolapBaseCubeMeasure measure = new RolapBaseCubeMeasure( this, null, measuresLevel, xmlMeasure.name, xmlMeasure.caption, xmlMeasure.description, xmlMeasure.formatString, measureExp, aggregator, xmlMeasure.datatype, RolapHierarchy.createAnnotationMap(xmlMeasure.annotations)); final String cellFormatterClassName; final Scripts.ScriptDefinition scriptDefinition; if (xmlMeasure.cellFormatter != null) { cellFormatterClassName = xmlMeasure.cellFormatter.className; scriptDefinition = RolapSchema.toScriptDef(xmlMeasure.cellFormatter.script); } else { cellFormatterClassName = xmlMeasure.formatter; scriptDefinition = null; } if (cellFormatterClassName != null || scriptDefinition != null) { try { CellFormatter cellFormatter = RolapSchema.getCellFormatter( cellFormatterClassName, scriptDefinition); measure.setFormatter(cellFormatter); } catch (Exception e) { throw MondrianResource.instance().CellFormatterLoadFailed.ex( cellFormatterClassName, measure.getUniqueName(), e); } } // Set member's caption, if present. if (!Util.isEmpty(xmlMeasure.caption)) { // there is a special caption string measure.setProperty( Property.CAPTION.name, xmlMeasure.caption); } // Set member's visibility, default true. Boolean visible = xmlMeasure.visible; if (visible == null) { visible = Boolean.TRUE; } measure.setProperty(Property.VISIBLE.name, visible); List propNames = new ArrayList(); List propExprs = new ArrayList(); validateMemberProps( xmlMeasure.memberProperties, propNames, propExprs, xmlMeasure.name); for (int j = 0; j < propNames.size(); j++) { String propName = propNames.get(j); final Object propExpr = propExprs.get(j); measure.setProperty(propName, propExpr); if (propName.equals(Property.MEMBER_ORDINAL.name) && propExpr instanceof String) { final String expr = (String) propExpr; if (expr.startsWith("\"") && expr.endsWith("\"")) { try { ordinal = Integer.valueOf( expr.substring(1, expr.length() - 1)); } catch (NumberFormatException e) { Util.discard(e); } } } } measure.setOrdinal(ordinal); return measure; } /** * Makes sure that the schemaReader cache is invalidated. * Problems can occur if the measure hierarchy member reader is out * of sync with the cache. * * @param memberReader new member reader for measures hierarchy */ private void setMeasuresHierarchyMemberReader(MemberReader memberReader) { this.measuresHierarchy.setMemberReader(memberReader); // this invalidates any cached schema reader this.schemaReader = null; } /** * Creates a RolapCube from a virtual cube. */ RolapCube( RolapSchema schema, MondrianDef.Schema xmlSchema, MondrianDef.VirtualCube xmlVirtualCube, boolean load) { this( schema, xmlSchema, xmlVirtualCube.name, xmlVirtualCube.visible, xmlVirtualCube.caption, xmlVirtualCube.description, true, null, xmlVirtualCube.dimensions, load, RolapHierarchy.createAnnotationMap(xmlVirtualCube.annotations)); // Since MondrianDef.Measure and MondrianDef.VirtualCubeMeasure cannot // be treated as the same, measure creation cannot be done in a common // constructor. RolapLevel measuresLevel = this.measuresHierarchy.newMeasuresLevel(); // Recreate CalculatedMembers, as the original members point to // incorrect dimensional ordinals for the virtual cube. List origMeasureList = new ArrayList(); List origCalcMeasureList = new ArrayList(); CubeComparator cubeComparator = new CubeComparator(); Map> calculatedMembersMap = new TreeMap>( cubeComparator); Member defaultMeasure = null; this.cubeUsages = new RolapCubeUsages(xmlVirtualCube.cubeUsage); for (MondrianDef.VirtualCubeMeasure xmlMeasure : xmlVirtualCube.measures) { // Lookup a measure in an existing cube. RolapCube cube = schema.lookupCube(xmlMeasure.cubeName); if (cube == null) { throw Util.newError( "Cube '" + xmlMeasure.cubeName + "' not found"); } List cubeMeasures = cube.getMeasures(); boolean found = false; for (Member cubeMeasure : cubeMeasures) { if (cubeMeasure.getUniqueName().equals(xmlMeasure.name)) { if (cubeMeasure.getName().equalsIgnoreCase( xmlVirtualCube.defaultMeasure)) { defaultMeasure = cubeMeasure; } found = true; if (cubeMeasure instanceof RolapCalculatedMember) { // We have a calulated member! Keep track of which // base cube each calculated member is associated // with, so we can resolve the calculated member // relative to its base cube. We're using a treeMap // to store the mapping to ensure a deterministic // order for the members. MondrianDef.CalculatedMember calcMember = schema.lookupXmlCalculatedMember( xmlMeasure.name, xmlMeasure.cubeName); if (calcMember == null) { throw Util.newInternal( "Could not find XML Calculated Member '" + xmlMeasure.name + "' in XML cube '" + xmlMeasure.cubeName + "'"); } List memberList = calculatedMembersMap.get(cube); if (memberList == null) { memberList = new ArrayList(); } memberList.add(calcMember); origCalcMeasureList.add(calcMember); calculatedMembersMap.put(cube, memberList); } else { // This is the a standard measure. (Don't know // whether it will confuse things that this // measure still points to its 'real' cube.) RolapVirtualCubeMeasure virtualCubeMeasure = new RolapVirtualCubeMeasure( null, measuresLevel, (RolapStoredMeasure) cubeMeasure, RolapHierarchy.createAnnotationMap( xmlMeasure.annotations)); // Set member's visibility, default true. Boolean visible = xmlMeasure.visible; if (visible == null) { visible = Boolean.TRUE; } virtualCubeMeasure.setProperty( Property.VISIBLE.name, visible); // Inherit caption from the "real" measure virtualCubeMeasure.setProperty( Property.CAPTION.name, cubeMeasure.getCaption()); origMeasureList.add(virtualCubeMeasure); } break; } } if (!found) { throw Util.newInternal( "could not find measure '" + xmlMeasure.name + "' in cube '" + xmlMeasure.cubeName + "'"); } } // Must init the dimensions before dealing with calculated members init(xmlVirtualCube.dimensions); // Loop through the base cubes containing calculated members // referenced by this virtual cube. Resolve those members relative // to their base cubes first, then resolve them relative to this // cube so the correct dimension ordinals are used List modifiedMeasureList = new ArrayList(origMeasureList); for (Object o : calculatedMembersMap.keySet()) { RolapCube baseCube = (RolapCube) o; List xmlCalculatedMemberList = calculatedMembersMap.get(baseCube); Query queryExp = resolveCalcMembers( xmlCalculatedMemberList, Collections.emptyList(), baseCube, false); MeasureFinder measureFinder = new MeasureFinder(this, baseCube, measuresLevel); queryExp.accept(measureFinder); modifiedMeasureList.addAll(measureFinder.getMeasuresFound()); } // Add the original calculated members from the base cubes to our // list of calculated members List xmlCalculatedMemberList = new ArrayList(); for (Object o : calculatedMembersMap.keySet()) { RolapCube baseCube = (RolapCube) o; xmlCalculatedMemberList.addAll( calculatedMembersMap.get(baseCube)); } xmlCalculatedMemberList.addAll( Arrays.asList(xmlVirtualCube.calculatedMembers)); // Resolve all calculated members relative to this virtual cube, // whose measureHierarchy member reader now contains all base // measures referenced in those calculated members setMeasuresHierarchyMemberReader( new CacheMemberReader( new MeasureMemberSource( this.measuresHierarchy, Util.cast(modifiedMeasureList)))); createCalcMembersAndNamedSets( xmlCalculatedMemberList, Arrays.asList(xmlVirtualCube.namedSets), new ArrayList(), new ArrayList(), this, false); // reset the measureHierarchy member reader back to the list of // measures that are only defined on this virtual cube setMeasuresHierarchyMemberReader( new CacheMemberReader( new MeasureMemberSource( this.measuresHierarchy, Util.cast(origMeasureList)))); this.measuresHierarchy.setDefaultMember(defaultMeasure); List xmlVirtualCubeCalculatedMemberList = Arrays.asList(xmlVirtualCube.calculatedMembers); if (!vcHasAllCalcMembers( origCalcMeasureList, xmlVirtualCubeCalculatedMemberList)) { // Remove from the calculated members array // those members that weren't originally defined // on this virtual cube. List calculatedMemberListCopy = new ArrayList(calculatedMemberList); calculatedMemberList.clear(); for (Formula calculatedMember : calculatedMemberListCopy) { if (findOriginalMembers( calculatedMember, origCalcMeasureList, calculatedMemberList)) { continue; } findOriginalMembers( calculatedMember, xmlVirtualCubeCalculatedMemberList, calculatedMemberList); } } for (Formula calcMember : calculatedMemberList) { if (calcMember.getName().equalsIgnoreCase( xmlVirtualCube.defaultMeasure)) { this.measuresHierarchy.setDefaultMember( calcMember.getMdxMember()); break; } } // We modify the measures schema reader one last time with a version // which includes all calculated members as well. final List finalMeasureMembers = new ArrayList(); for (RolapVirtualCubeMeasure measure : origMeasureList) { finalMeasureMembers.add((RolapMember)measure); } for (Formula formula : calculatedMemberList) { finalMeasureMembers.add( (RolapMember)formula.getMdxMember()); } setMeasuresHierarchyMemberReader( new CacheMemberReader( new MeasureMemberSource( this.measuresHierarchy, Util.cast(finalMeasureMembers)))); // Note: virtual cubes do not get aggregate } private boolean vcHasAllCalcMembers( List origCalcMeasureList, List xmlVirtualCubeCalculatedMemberList) { return calculatedMemberList.size() == (origCalcMeasureList.size() + xmlVirtualCubeCalculatedMemberList.size()); } private boolean findOriginalMembers( Formula formula, List xmlCalcMemberList, List calcMemberList) { for (MondrianDef.CalculatedMember xmlCalcMember : xmlCalcMemberList) { Hierarchy hierarchy = null; if (xmlCalcMember.dimension != null) { Dimension dimension = lookupDimension( new Id.Segment( xmlCalcMember.dimension, Id.Quoting.UNQUOTED)); if (dimension != null && dimension.getHierarchy() != null) { hierarchy = dimension.getHierarchy(); } } else if (xmlCalcMember.hierarchy != null) { hierarchy = lookupHierarchy( new Id.Segment( xmlCalcMember.hierarchy, Id.Quoting.UNQUOTED), true); } if (formula.getName().equals(xmlCalcMember.name) && formula.getMdxMember().getHierarchy().equals( hierarchy)) { calcMemberList.add(formula); return true; } } return false; } protected Logger getLogger() { return LOGGER; } public Map getAnnotationMap() { return annotationMap; } public boolean hasAggGroup() { return aggGroup != null; } public ExplicitRules.Group getAggGroup() { return aggGroup; } void loadAggGroup(MondrianDef.Cube xmlCube) { aggGroup = ExplicitRules.Group.make(this, xmlCube); } /** * Creates a dimension from its XML definition. If the XML definition is * a <DimensionUsage>, and the shared dimension is cached in the * schema, returns that. * * @param xmlCubeDimension XML Dimension or DimensionUsage * @param schema Schema * @param xmlSchema XML Schema * @param dimensionOrdinal Ordinal of dimension * @param cubeHierarchyList List of hierarchies in cube * @return A dimension */ private RolapCubeDimension getOrCreateDimension( MondrianDef.CubeDimension xmlCubeDimension, RolapSchema schema, MondrianDef.Schema xmlSchema, int dimensionOrdinal, List cubeHierarchyList) { RolapDimension dimension = null; if (xmlCubeDimension instanceof MondrianDef.DimensionUsage) { MondrianDef.DimensionUsage usage = (MondrianDef.DimensionUsage) xmlCubeDimension; final RolapHierarchy sharedHierarchy = schema.getSharedHierarchy(usage.source); if (sharedHierarchy != null) { dimension = (RolapDimension) sharedHierarchy.getDimension(); } } if (dimension == null) { MondrianDef.Dimension xmlDimension = xmlCubeDimension.getDimension(xmlSchema); dimension = new RolapDimension( schema, this, xmlDimension, xmlCubeDimension); } // wrap the shared or regular dimension with a // rolap cube dimension object return new RolapCubeDimension( this, dimension, xmlCubeDimension, xmlCubeDimension.name, dimensionOrdinal, cubeHierarchyList, xmlCubeDimension.highCardinality); } /** * Post-initialization, doing things which cannot be done in the * constructor. */ private void init( MondrianDef.Cube xmlCube, final List memberList) { // Load calculated members and named sets. // (We cannot do this in the constructor, because // cannot parse the generated query, because the schema has not been // set in the cube at this point.) List formulaList = new ArrayList(); createCalcMembersAndNamedSets( Arrays.asList(xmlCube.calculatedMembers), Arrays.asList(xmlCube.namedSets), memberList, formulaList, this, true); } /** * Checks that the ordinals of measures (including calculated measures) * are unique. * * @param cubeName name of the cube (required for error messages) * @param measures measure list */ private void checkOrdinals( String cubeName, List measures) { Map ordinals = new HashMap(); for (RolapMember measure : measures) { Integer ordinal = measure.getOrdinal(); if (!ordinals.containsKey(ordinal)) { ordinals.put(ordinal, measure.getUniqueName()); } else { throw MondrianResource.instance().MeasureOrdinalsNotUnique.ex( cubeName, ordinal.toString(), ordinals.get(ordinal), measure.getUniqueName()); } } } /** * Adds a collection of calculated members and named sets to this cube. * The members and sets can refer to each other. * * @param xmlCalcMembers XML objects representing members * @param xmlNamedSets Array of XML definition of named set * @param memberList Output list of {@link mondrian.olap.Member} objects * @param formulaList Output list of {@link mondrian.olap.Formula} objects * @param cube the cube that the calculated members originate from * @param errOnDups throws an error if a duplicate member is found */ private void createCalcMembersAndNamedSets( List xmlCalcMembers, List xmlNamedSets, List memberList, List formulaList, RolapCube cube, boolean errOnDups) { final Query queryExp = resolveCalcMembers( xmlCalcMembers, xmlNamedSets, cube, errOnDups); if (queryExp == null) { return; } // Now pick through the formulas. Util.assertTrue( queryExp.getFormulas().length == xmlCalcMembers.size() + xmlNamedSets.size()); for (int i = 0; i < xmlCalcMembers.size(); i++) { postCalcMember(xmlCalcMembers, i, queryExp, memberList); } for (int i = 0; i < xmlNamedSets.size(); i++) { postNamedSet( xmlNamedSets, xmlCalcMembers.size(), i, queryExp, formulaList); } } private Query resolveCalcMembers( List xmlCalcMembers, List xmlNamedSets, RolapCube cube, boolean errOnDups) { // If there are no objects to create, our generated SQL will be so // silly, the parser will laugh. if (xmlCalcMembers.size() == 0 && xmlNamedSets.size() == 0) { return null; } StringBuilder buf = new StringBuilder(256); buf.append("WITH").append(Util.nl); // Check the members individually, and generate SQL. final Set fqNames = new LinkedHashSet(); for (int i = 0; i < xmlCalcMembers.size(); i++) { preCalcMember(xmlCalcMembers, i, buf, cube, errOnDups, fqNames); } // Check the named sets individually (for uniqueness) and generate SQL. Set nameSet = new HashSet(); for (Formula namedSet : namedSetList) { nameSet.add(namedSet.getName()); } for (MondrianDef.NamedSet xmlNamedSet : xmlNamedSets) { preNamedSet(xmlNamedSet, nameSet, buf); } buf.append("SELECT FROM ").append(cube.getUniqueName()); // Parse and validate this huge MDX query we've created. final String queryString = buf.toString(); try { final RolapConnection conn = schema.getInternalConnection(); return Locus.execute( conn, "RolapCube.resolveCalcMembers", new Locus.Action() { public Query execute() { final Query queryExp = conn.parseQuery(queryString); queryExp.resolve(); return queryExp; } }); } catch (Exception e) { throw MondrianResource.instance().UnknownNamedSetHasBadFormula.ex( getName(), e); } } private void postNamedSet( List xmlNamedSets, final int offset, int i, final Query queryExp, List formulaList) { MondrianDef.NamedSet xmlNamedSet = xmlNamedSets.get(i); Util.discard(xmlNamedSet); Formula formula = queryExp.getFormulas()[offset + i]; final SetBase namedSet = (SetBase) formula.getNamedSet(); if (xmlNamedSet.caption != null && xmlNamedSet.caption.length() > 0) { namedSet.setCaption(xmlNamedSet.caption); } if (xmlNamedSet.description != null && xmlNamedSet.description.length() > 0) { namedSet.setDescription(xmlNamedSet.description); } namedSet.setAnnotationMap( RolapHierarchy.createAnnotationMap(xmlNamedSet.annotations)); namedSetList.add(formula); formulaList.add(formula); } private void preNamedSet( MondrianDef.NamedSet xmlNamedSet, Set nameSet, StringBuilder buf) { if (!nameSet.add(xmlNamedSet.name)) { throw MondrianResource.instance().NamedSetNotUnique.ex( xmlNamedSet.name, getName()); } buf.append("SET ") .append(Util.makeFqName(xmlNamedSet.name)) .append(Util.nl) .append(" AS "); Util.singleQuoteString(xmlNamedSet.getFormula(), buf); buf.append(Util.nl); } private void postCalcMember( List xmlCalcMembers, int i, final Query queryExp, List memberList) { MondrianDef.CalculatedMember xmlCalcMember = xmlCalcMembers.get(i); final Formula formula = queryExp.getFormulas()[i]; calculatedMemberList.add(formula); final RolapMember member = (RolapMember) formula.getMdxMember(); Boolean visible = xmlCalcMember.visible; if (visible == null) { visible = Boolean.TRUE; } member.setProperty(Property.VISIBLE.name, visible); if (xmlCalcMember.caption != null && xmlCalcMember.caption.length() > 0) { member.setProperty( Property.CAPTION.name, xmlCalcMember.caption); } if (xmlCalcMember.description != null && xmlCalcMember.description.length() > 0) { member.setProperty( Property.DESCRIPTION.name, xmlCalcMember.description); } final RolapMember member1 = RolapUtil.strip(member); ((RolapCalculatedMember) member1).setAnnotationMap( RolapHierarchy.createAnnotationMap(xmlCalcMember.annotations)); memberList.add(member); } private void preCalcMember( List xmlCalcMembers, int j, StringBuilder buf, RolapCube cube, boolean errOnDup, Set fqNames) { MondrianDef.CalculatedMember xmlCalcMember = xmlCalcMembers.get(j); if (xmlCalcMember.hierarchy != null && xmlCalcMember.dimension != null) { throw MondrianResource.instance() .CalcMemberHasBothDimensionAndHierarchy.ex( xmlCalcMember.name, getName()); } // Lookup dimension Hierarchy hierarchy = null; String dimName = null; if (xmlCalcMember.dimension != null) { dimName = xmlCalcMember.dimension; final Dimension dimension = lookupDimension( new Id.Segment( xmlCalcMember.dimension, Id.Quoting.UNQUOTED)); if (dimension != null) { hierarchy = dimension.getHierarchy(); } } else if (xmlCalcMember.hierarchy != null) { dimName = xmlCalcMember.hierarchy; hierarchy = (Hierarchy) getSchemaReader().withLocus().lookupCompound( this, Util.parseIdentifier(dimName), false, Category.Hierarchy); } if (hierarchy == null) { throw MondrianResource.instance().CalcMemberHasBadDimension.ex( dimName, xmlCalcMember.name, getName()); } // Root of fully-qualified name. String parentFqName; if (xmlCalcMember.parent != null) { parentFqName = xmlCalcMember.parent; } else { parentFqName = hierarchy.getUniqueNameSsas(); } if (!hierarchy.getDimension().isMeasures()) { // Check if the parent exists. final OlapElement parent = Util.lookupCompound( getSchemaReader().withLocus(), this, Util.parseIdentifier(parentFqName), false, Category.Unknown); if (parent == null) { throw MondrianResource.instance() .CalcMemberHasUnknownParent.ex( parentFqName, xmlCalcMember.name, getName()); } if (parent.getHierarchy() != hierarchy) { throw MondrianResource.instance() .CalcMemberHasDifferentParentAndHierarchy.ex( xmlCalcMember.name, getName(), hierarchy.getUniqueName()); } } // If we're processing a virtual cube, it's possible that we've // already processed this calculated member because it's // referenced in another measure; in that case, remove it from the // list, since we'll add it back in later; otherwise, in the // non-virtual cube case, throw an exception final String fqName = Util.makeFqName(parentFqName, xmlCalcMember.name); for (int i = 0; i < calculatedMemberList.size(); i++) { Formula formula = calculatedMemberList.get(i); if (formula.getName().equals(xmlCalcMember.name) && formula.getMdxMember().getHierarchy().equals( hierarchy)) { if (errOnDup) { throw MondrianResource.instance().CalcMemberNotUnique.ex( fqName, getName()); } else { calculatedMemberList.remove(i); --i; } } } // Check this calc member doesn't clash with one earlier in this // batch. if (!fqNames.add(fqName)) { throw MondrianResource.instance().CalcMemberNotUnique.ex( fqName, getName()); } final MondrianDef.CalculatedMemberProperty[] xmlProperties = xmlCalcMember.memberProperties; List propNames = new ArrayList(); List propExprs = new ArrayList(); validateMemberProps( xmlProperties, propNames, propExprs, xmlCalcMember.name); final int measureCount = cube.measuresHierarchy.getMemberReader().getMemberCount(); // Generate SQL. assert fqName.startsWith("["); buf.append("MEMBER ") .append(fqName) .append(Util.nl) .append(" AS "); Util.singleQuoteString(xmlCalcMember.getFormula(), buf); if (xmlCalcMember.cellFormatter != null) { if (xmlCalcMember.cellFormatter.className != null) { propNames.add(Property.CELL_FORMATTER.name); propExprs.add( Util.quoteForMdx(xmlCalcMember.cellFormatter.className)); } if (xmlCalcMember.cellFormatter.script != null) { if (xmlCalcMember.cellFormatter.script.language != null) { propNames.add(Property.CELL_FORMATTER_SCRIPT_LANGUAGE.name); propExprs.add( Util.quoteForMdx( xmlCalcMember.cellFormatter.script.language)); } propNames.add(Property.CELL_FORMATTER_SCRIPT.name); propExprs.add( Util.quoteForMdx(xmlCalcMember.cellFormatter.script.cdata)); } } assert propNames.size() == propExprs.size(); processFormatStringAttribute(xmlCalcMember, buf); for (int i = 0; i < propNames.size(); i++) { String name = propNames.get(i); String expr = propExprs.get(i); buf.append(",").append(Util.nl); expr = removeSurroundingQuotesIfNumericProperty(name, expr); buf.append(name).append(" = ").append(expr); } // Flag that the calc members are defined against a cube; will // determine the value of Member.isCalculatedInQuery buf.append(",") .append(Util.nl); Util.quoteMdxIdentifier(Property.MEMBER_SCOPE.name, buf); buf.append(" = 'CUBE'"); // Assign the member an ordinal higher than all of the stored measures. if (!propNames.contains(Property.MEMBER_ORDINAL.getName())) { buf.append(",") .append(Util.nl) .append(Property.MEMBER_ORDINAL) .append(" = ") .append(measureCount + j); } buf.append(Util.nl); } private String removeSurroundingQuotesIfNumericProperty( String name, String expr) { Property prop = Property.lookup(name, false); if (prop != null && prop.getType() == Property.Datatype.TYPE_NUMERIC && isSurroundedWithQuotes(expr) && expr.length() > 2) { return expr.substring(1, expr.length() - 1); } return expr; } private boolean isSurroundedWithQuotes(String expr) { return expr.startsWith("\"") && expr.endsWith("\""); } void processFormatStringAttribute( MondrianDef.CalculatedMember xmlCalcMember, StringBuilder buf) { if (xmlCalcMember.formatString != null) { buf.append(",") .append(Util.nl) .append(Property.FORMAT_STRING.name) .append(" = ") .append(Util.quoteForMdx(xmlCalcMember.formatString)); } } /** * Validates an array of member properties, and populates a list of names * and expressions, one for each property. * * @param xmlProperties Array of property definitions. * @param propNames Output array of property names. * @param propExprs Output array of property expressions. * @param memberName Name of member which the properties belong to. */ private void validateMemberProps( final MondrianDef.CalculatedMemberProperty[] xmlProperties, List propNames, List propExprs, String memberName) { if (xmlProperties == null) { return; } for (MondrianDef.CalculatedMemberProperty xmlProperty : xmlProperties) { if (xmlProperty.expression == null && xmlProperty.value == null) { throw MondrianResource.instance() .NeitherExprNorValueForCalcMemberProperty.ex( xmlProperty.name, memberName, getName()); } if (xmlProperty.expression != null && xmlProperty.value != null) { throw MondrianResource.instance().ExprAndValueForMemberProperty .ex( xmlProperty.name, memberName, getName()); } propNames.add(xmlProperty.name); if (xmlProperty.expression != null) { propExprs.add(xmlProperty.expression); } else { propExprs.add(Util.quoteForMdx(xmlProperty.value)); } } } public RolapSchema getSchema() { return schema; } /** * Returns the named sets of this cube. */ public NamedSet[] getNamedSets() { NamedSet[] namedSetsArray = new NamedSet[namedSetList.size()]; for (int i = 0; i < namedSetList.size(); i++) { namedSetsArray[i] = namedSetList.get(i).getNamedSet(); } return namedSetsArray; } /** * Returns the schema reader which enforces the appropriate access-control * context. schemaReader is cached, and needs to stay in sync with * any changes to the cube. * * @post return != null * @see #getSchemaReader(Role) */ public synchronized SchemaReader getSchemaReader() { if (schemaReader == null) { schemaReader = new RolapCubeSchemaReader(Util.createRootRole(schema)); } return schemaReader; } public SchemaReader getSchemaReader(Role role) { if (role == null) { return getSchemaReader(); } else { return new RolapCubeSchemaReader(role); } } MondrianDef.CubeDimension lookup( MondrianDef.CubeDimension[] xmlDimensions, String name) { for (MondrianDef.CubeDimension cd : xmlDimensions) { if (name.equals(cd.name)) { return cd; } } // TODO: this ought to be a fatal error. return null; } private void init(MondrianDef.CubeDimension[] xmlDimensions) { for (Dimension dimension1 : dimensions) { final RolapDimension dimension = (RolapDimension) dimension1; dimension.init(lookup(xmlDimensions, dimension.getName())); } register(); } private void register() { if (isVirtual()) { return; } List storedMeasures = new ArrayList(); for (Member measure : getMeasures()) { if (measure instanceof RolapBaseCubeMeasure) { storedMeasures.add((RolapBaseCubeMeasure) measure); } } RolapStar star = getStar(); RolapStar.Table table = star.getFactTable(); // create measures (and stars for them, if necessary) for (RolapBaseCubeMeasure storedMeasure : storedMeasures) { table.makeMeasure(storedMeasure); } } /** * Returns true if this Cube is either virtual or if the Cube's * RolapStar is caching aggregates. * * @return Whether this Cube's RolapStar should cache aggregations */ public boolean isCacheAggregations() { return isVirtual() || star.isCacheAggregations(); } /** * Set if this (non-virtual) Cube's RolapStar should cache * aggregations. * * @param cache Whether this Cube's RolapStar should cache aggregations */ public void setCacheAggregations(boolean cache) { if (! isVirtual()) { star.setCacheAggregations(cache); } } /** * Clear the in memory aggregate cache associated with this Cube, but * only if Disabling Caching has been enabled. */ public void clearCachedAggregations() { clearCachedAggregations(false); } /** * Clear the in memory aggregate cache associated with this Cube. */ public void clearCachedAggregations(boolean forced) { if (isVirtual()) { // TODO: // Currently a virtual cube does not keep a list of all of its // base cubes, so we need to iterate through each and flush // the ones that should be flushed. Could use a CacheControl // method here. for (RolapStar star1 : schema.getStars()) { // this will only flush the star's aggregate cache if // 1) DisableCaching is true or 2) the star's cube has // cacheAggregations set to false in the schema. star1.clearCachedAggregations(forced); } } else { star.clearCachedAggregations(forced); } } /** * Returns this cube's underlying star schema. */ public RolapStar getStar() { return star; } private void createUsages( RolapCubeDimension dimension, MondrianDef.CubeDimension xmlCubeDimension) { // RME level may not be in all hierarchies // If one uses the DimensionUsage attribute "level", which level // in a hierarchy to join on, and there is more than one hierarchy, // then a HierarchyUsage can not be created for the hierarchies // that do not have the level defined. RolapCubeHierarchy[] hierarchies = (RolapCubeHierarchy[]) dimension.getHierarchies(); if (hierarchies.length == 1) { // Only one, so let lower level error checking handle problems createUsage(hierarchies[0], xmlCubeDimension); } else if ((xmlCubeDimension instanceof MondrianDef.DimensionUsage) && (((MondrianDef.DimensionUsage) xmlCubeDimension).level != null)) { // More than one, make sure if we are joining by level, that // at least one hierarchy can and those that can not are // not registered MondrianDef.DimensionUsage du = (MondrianDef.DimensionUsage) xmlCubeDimension; int cnt = 0; for (RolapCubeHierarchy hierarchy : hierarchies) { if (getLogger().isDebugEnabled()) { getLogger().debug( "RolapCube: hierarchy=" + hierarchy.getName()); } RolapLevel joinLevel = (RolapLevel) Util.lookupHierarchyLevel(hierarchy, du.level); if (joinLevel == null) { continue; } createUsage(hierarchy, xmlCubeDimension); cnt++; } if (cnt == 0) { // None of the hierarchies had the level, let lower level // detect and throw error createUsage(hierarchies[0], xmlCubeDimension); } } else { // just do it for (RolapCubeHierarchy hierarchy : hierarchies) { if (getLogger().isDebugEnabled()) { getLogger().debug( "RolapCube: hierarchy=" + hierarchy.getName()); } createUsage(hierarchy, xmlCubeDimension); } } } synchronized void createUsage( RolapCubeHierarchy hierarchy, MondrianDef.CubeDimension cubeDim) { HierarchyUsage usage = new HierarchyUsage(this, hierarchy, cubeDim); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "RolapCube.createUsage: " + "cube=" + getName() + ", hierarchy=" + hierarchy.getName() + ", usage=" + usage); } for (HierarchyUsage hierUsage : hierarchyUsages) { if (hierUsage.equals(usage)) { getLogger().warn( "RolapCube.createUsage: duplicate " + hierUsage); return; } } if (getLogger().isDebugEnabled()) { getLogger().debug("RolapCube.createUsage: register " + usage); } this.hierarchyUsages.add(usage); } private synchronized HierarchyUsage getUsageByName(String name) { for (HierarchyUsage hierUsage : hierarchyUsages) { if (hierUsage.getFullName().equals(name)) { return hierUsage; } } return null; } /** * A Hierarchy may have one or more HierarchyUsages. This method returns * an array holding the one or more usages associated with a Hierarchy. * The HierarchyUsages hierarchyName attribute always equals the name * attribute of the Hierarchy. * * @param hierarchy Hierarchy * @return an HierarchyUsages array with 0 or more members. */ public synchronized HierarchyUsage[] getUsages(Hierarchy hierarchy) { String name = hierarchy.getName(); if (!name.equals(hierarchy.getDimension().getName()) && MondrianProperties.instance().SsasCompatibleNaming.get()) { name = hierarchy.getDimension().getName() + "." + name; } if (getLogger().isDebugEnabled()) { getLogger().debug("RolapCube.getUsages: name=" + name); } HierarchyUsage hierUsage = null; List list = null; for (HierarchyUsage hu : hierarchyUsages) { if (hu.getHierarchyName().equals(name)) { if (list != null) { if (getLogger().isDebugEnabled()) { getLogger().debug( "RolapCube.getUsages: " + "add list HierarchyUsage.name=" + hu.getName()); } list.add(hu); } else if (hierUsage == null) { hierUsage = hu; } else { list = new ArrayList(); if (getLogger().isDebugEnabled()) { getLogger().debug( "RolapCube.getUsages: " + "add list hierUsage.name=" + hierUsage.getName() + ", hu.name=" + hu.getName()); } list.add(hierUsage); list.add(hu); hierUsage = null; } } } if (hierUsage != null) { return new HierarchyUsage[] { hierUsage }; } else if (list != null) { if (getLogger().isDebugEnabled()) { getLogger().debug("RolapCube.getUsages: return list"); } return list.toArray(new HierarchyUsage[list.size()]); } else { return new HierarchyUsage[0]; } } synchronized HierarchyUsage getFirstUsage(Hierarchy hier) { HierarchyUsage hierarchyUsage = firstUsageMap.get(hier); if (hierarchyUsage == null) { HierarchyUsage[] hierarchyUsages = getUsages(hier); if (hierarchyUsages.length != 0) { hierarchyUsage = hierarchyUsages[0]; firstUsageMap.put(hier, hierarchyUsage); } } return hierarchyUsage; } /** * Looks up all of the HierarchyUsages with the same "source" returning * an array of HierarchyUsage of length 0 or more. * * This method is currently only called if an error occurs in lookupChild(), * so that more information can be displayed in the error log. * * @param source Name of shared dimension * @return array of HierarchyUsage (HierarchyUsage[]) - never null. */ private synchronized HierarchyUsage[] getUsagesBySource(String source) { if (getLogger().isDebugEnabled()) { getLogger().debug("RolapCube.getUsagesBySource: source=" + source); } HierarchyUsage hierUsage = null; List list = null; for (HierarchyUsage hu : hierarchyUsages) { String s = hu.getSource(); if ((s != null) && s.equals(source)) { if (list != null) { if (getLogger().isDebugEnabled()) { getLogger().debug( "RolapCube.getUsagesBySource: " + "add list HierarchyUsage.name=" + hu.getName()); } list.add(hu); } else if (hierUsage == null) { hierUsage = hu; } else { list = new ArrayList(); if (getLogger().isDebugEnabled()) { getLogger().debug( "RolapCube.getUsagesBySource: " + "add list hierUsage.name=" + hierUsage.getName() + ", hu.name=" + hu.getName()); } list.add(hierUsage); list.add(hu); hierUsage = null; } } } if (hierUsage != null) { return new HierarchyUsage[] { hierUsage }; } else if (list != null) { if (getLogger().isDebugEnabled()) { getLogger().debug("RolapCube.getUsagesBySource: return list"); } return list.toArray(new HierarchyUsage[list.size()]); } else { return new HierarchyUsage[0]; } } /** * Understand this and you are no longer a novice. * * @param dimension Dimension */ void registerDimension(RolapCubeDimension dimension) { RolapStar star = getStar(); Hierarchy[] hierarchies = dimension.getHierarchies(); for (Hierarchy hierarchy1 : hierarchies) { RolapHierarchy hierarchy = (RolapHierarchy) hierarchy1; MondrianDef.RelationOrJoin relation = hierarchy.getRelation(); if (relation == null) { continue; // e.g. [Measures] hierarchy } RolapCubeLevel[] levels = (RolapCubeLevel[]) hierarchy.getLevels(); HierarchyUsage[] hierarchyUsages = getUsages(hierarchy); if (hierarchyUsages.length == 0) { if (getLogger().isDebugEnabled()) { StringBuilder buf = new StringBuilder(64); buf.append("RolapCube.registerDimension: "); buf.append("hierarchyUsages == null for cube=\""); buf.append(this.name); buf.append("\", hierarchy=\""); buf.append(hierarchy.getName()); buf.append("\""); getLogger().debug(buf.toString()); } continue; } for (HierarchyUsage hierarchyUsage : hierarchyUsages) { String usagePrefix = hierarchyUsage.getUsagePrefix(); RolapStar.Table table = star.getFactTable(); String levelName = hierarchyUsage.getLevelName(); // RME // If a DimensionUsage has its level attribute set, then // one wants joins to occur at that level and not below (not // at a finer level), i.e., if you have levels: Year, Quarter, // Month, and Day, and the level attribute is set to Month, the // you do not want aggregate joins to include the Day level. // By default, it is the lowest level that the fact table // joins to, the Day level. // To accomplish this, we reorganize the relation and then // copy it (so that elsewhere the original relation can // still be used), and finally, clip off those levels below // the DimensionUsage level attribute. // Note also, if the relation (MondrianDef.Relation) is not // a MondrianDef.Join, i.e., the dimension is not a snowflake, // there is a single dimension table, then this is currently // an unsupported configuation and all bets are off. if (relation instanceof MondrianDef.Join) { // RME // take out after things seem to be working MondrianDef.RelationOrJoin relationTmp1 = relation; relation = reorder(relation, levels); if (relation == null && getLogger().isDebugEnabled()) { getLogger().debug( "RolapCube.registerDimension: after reorder relation==null"); getLogger().debug( "RolapCube.registerDimension: reorder relationTmp1=" + format(relationTmp1)); } } MondrianDef.RelationOrJoin relationTmp2 = relation; if (levelName != null) { // When relation is a table, this does nothing. Otherwise // it tries to arrange the joins so that the fact table // in the RolapStar will be joining at the lowest level. // // Make sure the level exists RolapLevel level = RolapLevel.lookupLevel(levels, levelName); if (level == null) { StringBuilder buf = new StringBuilder(64); buf.append("For cube \""); buf.append(getName()); buf.append("\" and HierarchyUsage ["); buf.append(hierarchyUsage); buf.append("], there is no level with given"); buf.append(" level name \""); buf.append(levelName); buf.append("\""); throw Util.newInternal(buf.toString()); } // If level has child, not the lowest level, then snip // relation between level and its child so that // joins do not include the lower levels. // If the child level is null, then the DimensionUsage // level attribute was simply set to the default, lowest // level and we do nothing. if (relation instanceof MondrianDef.Join) { RolapLevel childLevel = (RolapLevel) level.getChildLevel(); if (childLevel != null) { String tableName = childLevel.getTableName(); if (tableName != null) { relation = snip(relation, tableName); if (relation == null && getLogger().isDebugEnabled()) { getLogger().debug( "RolapCube.registerDimension: after snip relation==null"); getLogger().debug( "RolapCube.registerDimension: snip relationTmp2=" + format(relationTmp2)); } } } } } // cube and dimension usage are in different tables if (!relation.equals(table.getRelation())) { // HierarchyUsage should have checked this. if (hierarchyUsage.getForeignKey() == null) { throw MondrianResource.instance() .HierarchyMustHaveForeignKey.ex( hierarchy.getName(), getName()); } // jhyde: check is disabled until we handle correctly if (false && !star.getFactTable().containsColumn( hierarchyUsage.getForeignKey())) { throw MondrianResource.instance() .HierarchyInvalidForeignKey.ex( hierarchyUsage.getForeignKey(), hierarchy.getName(), getName()); } // parameters: // fact table, // fact table foreign key, MondrianDef.Column column = new MondrianDef.Column( table.getAlias(), hierarchyUsage.getForeignKey()); // parameters: // left column // right column RolapStar.Condition joinCondition = new RolapStar.Condition( column, hierarchyUsage.getJoinExp()); // (rchen) potential bug?: // FACT table joins with tables in a hierarchy in the // order they appear in the schema definition, even though // the primary key for this hierarchy can be on a table // which is not the leftmost. // e.g. // // // // //

//
// // // // // When this hierarchy is referenced in a cube, the fact // table is joined with the dimension tables using this // incorrect join condition which assumes the leftmost // table produces the primaryKey: // "fact"."foreignKey" = "product_class"."product_id" table = table.addJoin(this, relation, joinCondition); } // The parent Column is used so that non-shared dimensions // which use the fact table (not a separate dimension table) // can keep a record of what other columns are in the // same set of levels. RolapStar.Column parentColumn = null; //RME // If the level name is not null, then we need only register // those columns for that level and above. if (levelName != null) { for (RolapCubeLevel level : levels) { if (level.getKeyExp() != null) { parentColumn = makeColumns( table, level, parentColumn, usagePrefix); } if (levelName.equals(level.getName())) { break; } } } else { // This is the normal case, no level attribute so register // all columns. for (RolapCubeLevel level : levels) { if (level.getKeyExp() != null) { parentColumn = makeColumns( table, level, parentColumn, usagePrefix); } } } } } } /** * Adds a column to the appropriate table in the {@link RolapStar}. * Note that if the RolapLevel has a table attribute, then the associated * column needs to be associated with that table. */ protected RolapStar.Column makeColumns( RolapStar.Table table, RolapCubeLevel level, RolapStar.Column parentColumn, String usagePrefix) { // If there is a table name, then first see if the table name is the // table parameter's name or alias and, if so, simply add the column // to that table. On the other hand, find the ancestor of the table // parameter and if found, then associate the new column with // that table. // // Lastly, if the ancestor can not be found, i.e., there is no table // with the level's table name, what to do. Here we simply punt and // associated the new column with the table parameter which might // be an error. We do issue a warning in any case. String tableName = level.getTableName(); if (tableName != null) { if (table.getAlias().equals(tableName)) { parentColumn = table.makeColumns( this, level, parentColumn, usagePrefix); } else if (table.equalsTableName(tableName)) { parentColumn = table.makeColumns( this, level, parentColumn, usagePrefix); } else { RolapStar.Table t = table.findAncestor(tableName); if (t != null) { parentColumn = t.makeColumns( this, level, parentColumn, usagePrefix); } else { // Issue warning and keep going. getLogger().warn( "RolapCube.makeColumns: for cube \"" + getName() + "\" the Level \"" + level.getName() + "\" has a table name attribute \"" + tableName + "\" but the associated RolapStar does not" + " have a table with that name."); parentColumn = table.makeColumns( this, level, parentColumn, usagePrefix); } } } else { // level's expr is not a MondrianDef.Column (this is used by tests) // or there is no table name defined parentColumn = table.makeColumns( this, level, parentColumn, usagePrefix); } return parentColumn; } // The following code deals with handling the DimensionUsage level attribute // and snowflake dimensions only. /** * Formats a {@link mondrian.olap.MondrianDef.RelationOrJoin}, indenting * joins for readability. * * @param relation */ private static String format(MondrianDef.RelationOrJoin relation) { StringBuilder buf = new StringBuilder(); format(relation, buf, ""); return buf.toString(); } private static void format( MondrianDef.RelationOrJoin relation, StringBuilder buf, String indent) { if (relation instanceof MondrianDef.Table) { MondrianDef.Table table = (MondrianDef.Table) relation; buf.append(indent); buf.append(table.name); if (table.alias != null) { buf.append('('); buf.append(table.alias); buf.append(')'); } buf.append(Util.nl); } else { MondrianDef.Join join = (MondrianDef.Join) relation; String subindent = indent + " "; buf.append(indent); //buf.append(join.leftAlias); buf.append(join.getLeftAlias()); buf.append('.'); buf.append(join.leftKey); buf.append('='); buf.append(join.getRightAlias()); //buf.append(join.rightAlias); buf.append('.'); buf.append(join.rightKey); buf.append(Util.nl); format(join.left, buf, subindent); format(join.right, buf, indent); } } /** * This method tells us if unrelated dimensions to measures from * the input base cube should be pushed to default member or not * during aggregation. * @param baseCubeName name of the base cube for which we want * to check this property * @return boolean */ public boolean shouldIgnoreUnrelatedDimensions(String baseCubeName) { return cubeUsages != null && cubeUsages.shouldIgnoreUnrelatedDimensions(baseCubeName); } /** * Returns a list of all hierarchies in this cube, in order of dimension. * *

TODO: Make this method return RolapCubeHierarchy, when the measures * hierarchy is a RolapCubeHierarchy. * * @return List of hierarchies */ public List getHierarchies() { return hierarchyList; } public boolean isLoadInProgress() { return loadInProgress || getSchema().getSchemaLoadDate() == null; } /** * Association between a MondrianDef.Table with its associated * level's depth. This is used to rank tables in a snowflake so that * the table with the lowest rank, level depth, is furthest from * the base fact table in the RolapStar. */ private static class RelNode { /** * Finds a RelNode by table name or, if that fails, by table alias * from a map of RelNodes. * * @param table * @param map */ private static RelNode lookup( MondrianDef.Relation table, Map map) { RelNode relNode; if (table instanceof MondrianDef.Table) { relNode = map.get(((MondrianDef.Table) table).name); if (relNode != null) { return relNode; } } return map.get(table.getAlias()); } private int depth; private String alias; private MondrianDef.Relation table; RelNode(String alias, int depth) { this.alias = alias; this.depth = depth; } } /** * Attempts to transform a {@link mondrian.olap.MondrianDef.RelationOrJoin} * into the "canonical" form. * *

What is the canonical form? It is only relevant * when the relation is a snowflake (nested joins), not simply a table. * The canonical form has lower levels to the left of higher levels (Day * before Month before Quarter before Year) and the nested joins are always * on the right side of the parent join. * *

The canonical form is (using a Time dimension example): *

     *            |
     *    ----------------
     *    |             |
     *   Day      --------------
     *            |            |
     *          Month      ---------
     *                     |       |
     *                   Quarter  Year
     * 
*

* When the relation looks like the above, then the fact table joins to the * lowest level table (the Day table) which joins to the next level (the * Month table) which joins to the next (the Quarter table) which joins to * the top level table (the Year table). *

* This method supports the transformation of a subset of all possible * join/table relation trees (and anyone who whats to generalize it is * welcome to). It will take any of the following and convert them to * the canonical. *

     *            |
     *    ----------------
     *    |             |
     *   Year     --------------
     *            |            |
     *         Quarter     ---------
     *                     |       |
     *                   Month    Day
     *
     *                  |
     *           ----------------
     *           |              |
     *        --------------   Year
     *        |            |
     *    ---------     Quarter
     *    |       |
     *   Day     Month
     *
     *                  |
     *           ----------------
     *           |              |
     *        --------------   Day
     *        |            |
     *    ---------      Month
     *    |       |
     *   Year   Quarter
     *
     *            |
     *    ----------------
     *    |             |
     *   Day      --------------
     *            |            |
     *          Month      ---------
     *                     |       |
     *                   Quarter  Year
     *
     * 
*

* In addition, at any join node, it can exchange the left and right * child relations so that the lower level depth is to the left. * For example, it can also transform the following: *

     *                |
     *         ----------------
     *         |              |
     *      --------------   Day
     *      |            |
     *    Month     ---------
     *              |       |
     *             Year   Quarter
     * 
*

* What it can not handle are cases where on both the left and right side of * a join there are child joins: *

     *                |
     *         ----------------
     *         |              |
     *      ---------     ----------
     *      |       |     |        |
     *    Month    Day   Year    Quarter
     *
     *                |
     *         ----------------
     *         |              |
     *      ---------     ----------
     *      |       |     |        |
     *    Year     Day   Month   Quarter
     * 
*

* When does this method do nothing? 1) when there are less than 2 levels, * 2) when any level does not have a table name, and 3) when for every table * in the relation there is not a level. In these cases, this method simply * return the original relation. * * @param relation * @param levels */ private static MondrianDef.RelationOrJoin reorder( MondrianDef.RelationOrJoin relation, RolapLevel[] levels) { // Need at least two levels, with only one level theres nothing to do. if (levels.length < 2) { return relation; } Map nodeMap = new HashMap(); // Create RelNode in top down order (year -> day) for (int i = 0; i < levels.length; i++) { RolapLevel level = levels[i]; if (level.isAll()) { continue; } // this is the table alias String tableName = level.getTableName(); if (tableName == null) { // punt, no table name return relation; } RelNode rnode = new RelNode(tableName, i); nodeMap.put(tableName, rnode); } if (! validateNodes(relation, nodeMap)) { return relation; } relation = copy(relation); // Put lower levels to the left of upper levels leftToRight(relation, nodeMap); // Move joins to the right side topToBottom(relation); return relation; } /** * The map has to be validated against the relation because there are * certain cases where we do not want to (read: can not) do reordering, for * instance, when closures are involved. * * @param relation * @param map */ private static boolean validateNodes( MondrianDef.RelationOrJoin relation, Map map) { if (relation instanceof MondrianDef.Relation) { MondrianDef.Relation table = (MondrianDef.Relation) relation; RelNode relNode = RelNode.lookup(table, map); return (relNode != null); } else if (relation instanceof MondrianDef.Join) { MondrianDef.Join join = (MondrianDef.Join) relation; return validateNodes(join.left, map) && validateNodes(join.right, map); } else { throw Util.newInternal("bad relation type " + relation); } } /** * Transforms the Relation moving the tables associated with * lower levels (greater level depth, i.e., Day is lower than Month) to the * left of tables with high levels. * * @param relation * @param map */ private static int leftToRight( MondrianDef.RelationOrJoin relation, Map map) { if (relation instanceof MondrianDef.Relation) { MondrianDef.Relation table = (MondrianDef.Relation) relation; RelNode relNode = RelNode.lookup(table, map); // Associate the table with its RelNode!!!! This is where this // happens. relNode.table = table; return relNode.depth; } else if (relation instanceof MondrianDef.Join) { MondrianDef.Join join = (MondrianDef.Join) relation; int leftDepth = leftToRight(join.left, map); int rightDepth = leftToRight(join.right, map); // we want the right side to be less than the left if (rightDepth > leftDepth) { // switch String leftAlias = join.leftAlias; String leftKey = join.leftKey; MondrianDef.RelationOrJoin left = join.left; join.leftAlias = join.rightAlias; join.leftKey = join.rightKey; join.left = join.right; join.rightAlias = leftAlias; join.rightKey = leftKey; join.right = left; } // Does not currently matter which is returned because currently we // only support structures where the left and right depth values // form an inclusive subset of depth values, that is, any // node with a depth value between the left or right values is // a child of this current join. return leftDepth; } else { throw Util.newInternal("bad relation type " + relation); } } /** * Transforms so that all joins have a table as their left child and either * a table of child join on the right. * * @param relation */ private static void topToBottom(MondrianDef.RelationOrJoin relation) { if (relation instanceof MondrianDef.Table) { // nothing } else if (relation instanceof MondrianDef.Join) { MondrianDef.Join join = (MondrianDef.Join) relation; while (join.left instanceof MondrianDef.Join) { MondrianDef.Join jleft = (MondrianDef.Join) join.left; join.right = new MondrianDef.Join( join.leftAlias, join.leftKey, jleft.right, join.rightAlias, join.rightKey, join.right); join.left = jleft.left; join.rightAlias = jleft.rightAlias; join.rightKey = jleft.rightKey; join.leftAlias = jleft.leftAlias; join.leftKey = jleft.leftKey; } } } /** * Copies a {@link mondrian.olap.MondrianDef.RelationOrJoin}. * * @param relation */ private static MondrianDef.RelationOrJoin copy( MondrianDef.RelationOrJoin relation) { if (relation instanceof MondrianDef.Table) { MondrianDef.Table table = (MondrianDef.Table) relation; return new MondrianDef.Table(table); } else if (relation instanceof MondrianDef.InlineTable) { MondrianDef.InlineTable table = (MondrianDef.InlineTable) relation; return new MondrianDef.InlineTable(table); } else if (relation instanceof MondrianDef.Join) { MondrianDef.Join join = (MondrianDef.Join) relation; MondrianDef.RelationOrJoin left = copy(join.left); MondrianDef.RelationOrJoin right = copy(join.right); return new MondrianDef.Join( join.leftAlias, join.leftKey, left, join.rightAlias, join.rightKey, right); } else { throw Util.newInternal("bad relation type " + relation); } } /** * Takes a relation in canonical form and snips off the * the tables with the given tableName (or table alias). The matching table * only appears once in the relation. * * @param relation * @param tableName */ private static MondrianDef.RelationOrJoin snip( MondrianDef.RelationOrJoin relation, String tableName) { if (relation instanceof MondrianDef.Table) { MondrianDef.Table table = (MondrianDef.Table) relation; // Return null if the table's name or alias matches tableName return ((table.alias != null) && table.alias.equals(tableName)) ? null : (table.name.equals(tableName) ? null : table); } else if (relation instanceof MondrianDef.Join) { MondrianDef.Join join = (MondrianDef.Join) relation; // snip left MondrianDef.RelationOrJoin left = snip(join.left, tableName); if (left == null) { // left got snipped so return the right // (the join is no longer a join). return join.right; } else { // whatever happened on the left, save it join.left = left; // snip right MondrianDef.RelationOrJoin right = snip(join.right, tableName); if (right == null) { // right got snipped so return the left. return join.left; } else { // save the right, join still has right and left children // so return it. join.right = right; return join; } } } else { throw Util.newInternal("bad relation type " + relation); } } public Member[] getMembersForQuery(String query, List calcMembers) { throw new UnsupportedOperationException(); } /** * Returns the time hierarchy for this cube. If there is no time hierarchy, * throws. */ public RolapHierarchy getTimeHierarchy(String funName) { for (RolapHierarchy hierarchy : hierarchyList) { if (hierarchy.getDimension().getDimensionType() == DimensionType.TimeDimension) { return hierarchy; } } throw MondrianResource.instance().NoTimeDimensionInCube.ex(funName); } /** * Finds out non joining dimensions for this cube. * Useful for finding out non joining dimensions for a stored measure from * a base cube. * * @param tuple array of members * @return Set of dimensions that do not exist (non joining) in this cube */ public Set nonJoiningDimensions(Member[] tuple) { Set otherDims = new HashSet(); for (Member member : tuple) { if (!member.isCalculated()) { otherDims.add(member.getDimension()); } } return nonJoiningDimensions(otherDims); } /** * Finds out non joining dimensions for this cube. Equality test for * dimensions is done based on the unique name. Object equality can't be * used. * * @param otherDims Set of dimensions to be tested for existence in this * cube * @return Set of dimensions that do not exist (non joining) in this cube */ public Set nonJoiningDimensions(Set otherDims) { Dimension[] baseCubeDimensions = getDimensions(); Set baseCubeDimNames = new HashSet(); for (Dimension baseCubeDimension : baseCubeDimensions) { baseCubeDimNames.add(baseCubeDimension.getUniqueName()); } Set nonJoiningDimensions = new HashSet(); for (Dimension otherDim : otherDims) { if (!baseCubeDimNames.contains(otherDim.getUniqueName())) { nonJoiningDimensions.add(otherDim); } } return nonJoiningDimensions; } List getMeasures() { Level measuresLevel = dimensions[0].getHierarchies()[0].getLevels()[0]; return getSchemaReader().getLevelMembers(measuresLevel, true); } /** * Returns this cube's fact table, null if the cube is virtual. */ MondrianDef.RelationOrJoin getFact() { return fact; } /** * Returns whether this cube is virtual. We use the fact that virtual cubes * do not have fact tables. */ public boolean isVirtual() { return fact == null; } /** * Returns the system measure that counts the number of fact table rows in * a given cell. * *

Never null, because if there is no count measure explicitly defined, * the system creates one. */ RolapMeasure getFactCountMeasure() { return factCountMeasure; } /** * Returns the system measure that counts the number of atomic cells in * a given cell. * *

A cell is atomic if all dimensions are at their lowest level. * If the fact table has a primary key, this measure is equivalent to the * {@link #getFactCountMeasure() fact count measure}. */ RolapMeasure getAtomicCellCountMeasure() { // TODO: separate measure return factCountMeasure; } /** * Locates the base cube hierarchy for a particular virtual hierarchy. * If not found, return null. This may be converted to a map lookup * or cached in some way in the future to increase performance * with cubes that have large numbers of hierarchies * * @param hierarchy virtual hierarchy * @return base cube hierarchy if found */ RolapHierarchy findBaseCubeHierarchy(RolapHierarchy hierarchy) { for (int i = 0; i < getDimensions().length; i++) { Dimension dimension = getDimensions()[i]; if (dimension.getName().equals( hierarchy.getDimension().getName())) { for (int j = 0; j < dimension.getHierarchies().length; j++) { Hierarchy hier = dimension.getHierarchies()[j]; if (hier.getName().equals(hierarchy.getName())) { return (RolapHierarchy)hier; } } } } return null; } /** * Locates the base cube level for a particular virtual level. * If not found, return null. This may be converted to a map lookup * or cached in some way in the future to increase performance * with cubes that have large numbers of hierarchies and levels * * @param level virtual level * @return base cube level if found */ public RolapCubeLevel findBaseCubeLevel(RolapLevel level) { if (virtualToBaseMap.containsKey(level)) { return virtualToBaseMap.get(level); } String levelDimName = level.getDimension().getName(); String levelHierName = level.getHierarchy().getName(); // Closures are not in the dimension list so we need special logic for // locating the level. // // REVIEW: jhyde, 2009/7/21: This may no longer be the case, and we may // be able to improve performance. RolapCube.hierarchyList now contains // all hierarchies, including closure hierarchies; and // RolapHierarchy.closureFor indicates the base hierarchy for a closure // hierarchy. boolean isClosure = false; String closDimName = null; String closHierName = null; if (levelDimName.endsWith("$Closure")) { isClosure = true; closDimName = levelDimName.substring(0, levelDimName.length() - 8); closHierName = levelHierName.substring(0, levelHierName.length() - 8); } for (Dimension dimension : getDimensions()) { final String dimensionName = dimension.getName(); if (dimensionName.equals(levelDimName) || (isClosure && dimensionName.equals(closDimName))) { for (Hierarchy hier : dimension.getHierarchies()) { final String hierarchyName = hier.getName(); if (hierarchyName.equals(levelHierName) || (isClosure && hierarchyName.equals(closHierName))) { if (isClosure) { final RolapCubeLevel baseLevel = ((RolapCubeLevel) hier.getLevels()[1]).getClosedPeer(); virtualToBaseMap.put(level, baseLevel); return baseLevel; } for (Level lvl : hier.getLevels()) { if (lvl.getName().equals(level.getName())) { final RolapCubeLevel baseLevel = (RolapCubeLevel) lvl; virtualToBaseMap.put(level, baseLevel); return baseLevel; } } } } } } return null; } RolapCubeDimension createDimension( MondrianDef.CubeDimension xmlCubeDimension, MondrianDef.Schema xmlSchema) { RolapCubeDimension dimension = getOrCreateDimension( xmlCubeDimension, schema, xmlSchema, dimensions.length, hierarchyList); if (! isVirtual()) { createUsages(dimension, xmlCubeDimension); } registerDimension(dimension); dimension.init(xmlCubeDimension); // add to dimensions array this.dimensions = Util.append(dimensions, dimension); return dimension; } public OlapElement lookupChild(SchemaReader schemaReader, Id.Segment s) { return lookupChild(schemaReader, s, MatchType.EXACT); } public OlapElement lookupChild( SchemaReader schemaReader, Id.Segment s, MatchType matchType) { // Note that non-exact matches aren't supported at this level, // so the matchType is ignored String status = null; OlapElement oe = null; if (matchType == MatchType.EXACT_SCHEMA) { oe = super.lookupChild(schemaReader, s, MatchType.EXACT_SCHEMA); } else { oe = super.lookupChild(schemaReader, s, MatchType.EXACT); } if (oe == null) { HierarchyUsage[] usages = getUsagesBySource(s.name); if (usages.length > 0) { StringBuilder buf = new StringBuilder(64); buf.append("RolapCube.lookupChild: "); buf.append("In cube \""); buf.append(getName()); buf.append("\" use of unaliased Dimension name \""); buf.append(s); if (usages.length == 1) { // ERROR: this will work but is bad coding buf.append("\" rather than the alias name "); buf.append("\""); buf.append(usages[0].getName()); buf.append("\" "); getLogger().error(buf.toString()); throw new MondrianException(buf.toString()); } else { // ERROR: this is not allowed buf.append("\" rather than one of the alias names "); for (HierarchyUsage usage : usages) { buf.append("\""); buf.append(usage.getName()); buf.append("\" "); } getLogger().error(buf.toString()); throw new MondrianException(buf.toString()); } } } if (getLogger().isDebugEnabled()) { if (!s.matches("Measures")) { HierarchyUsage hierUsage = getUsageByName(s.name); if (hierUsage == null) { status = "hierUsage == null"; } else { status = "hierUsage == " + (hierUsage.isShared() ? "shared" : "not shared"); } } StringBuilder buf = new StringBuilder(64); buf.append("RolapCube.lookupChild: "); buf.append("name="); buf.append(getName()); buf.append(", childname="); buf.append(s); if (status != null) { buf.append(", status="); buf.append(status); } if (oe == null) { buf.append(" returning null"); } else { buf.append(" returning elementname=").append(oe.getName()); } getLogger().debug(buf.toString()); } return oe; } /** * Returns the the measures hierarchy. */ public Hierarchy getMeasuresHierarchy() { return measuresHierarchy; } public List getMeasuresMembers() { return measuresHierarchy.getMemberReader().getMembers(); } public Member createCalculatedMember(String xml) { MondrianDef.CalculatedMember xmlCalcMember; try { final Parser xmlParser = XOMUtil.createDefaultParser(); final DOMWrapper def = xmlParser.parse(xml); final String tagName = def.getTagName(); if (tagName.equals("CalculatedMember")) { xmlCalcMember = new MondrianDef.CalculatedMember(def); } else { throw new XOMException( "Got <" + tagName + "> when expecting "); } } catch (XOMException e) { throw Util.newError( e, "Error while creating calculated member from XML [" + xml + "]"); } try { loadInProgress = true; final List memberList = new ArrayList(); createCalcMembersAndNamedSets( Collections.singletonList(xmlCalcMember), Collections.emptyList(), memberList, new ArrayList(), this, true); assert memberList.size() == 1; return memberList.get(0); } finally { loadInProgress = false; } } /** * Creates a calculated member. * *

The member will be called [{dimension name}].[{name}]. * *

Not for public use. * * @param hierarchy Hierarchy the calculated member belongs to * @param name Name of member * @param calc Compiled expression */ RolapMember createCalculatedMember( RolapHierarchy hierarchy, String name, Calc calc) { final List segmentList = new ArrayList(); segmentList.addAll( Util.parseIdentifier(hierarchy.getUniqueName())); segmentList.add(new Id.Segment(name, Id.Quoting.QUOTED)); final Formula formula = new Formula( new Id(segmentList), createDummyExp(calc), new MemberProperty[0]); final Statement statement = schema.getInternalConnection().getInternalStatement(); try { final Query query = new Query( statement, this, new Formula[] {formula}, new QueryAxis[0], null, new QueryPart[0], new Parameter[0], false); query.createValidator().validate(formula); calculatedMemberList.add(formula); return (RolapMember) formula.getMdxMember(); } finally { statement.close(); } } /** * Schema reader which works from the perspective of a particular cube * (and hence includes calculated members defined in that cube) and also * applies the access-rights of a given role. */ private class RolapCubeSchemaReader extends RolapSchemaReader implements NameResolver.Namespace { public RolapCubeSchemaReader(Role role) { super(role, RolapCube.this.schema); assert role != null : "precondition: role != null"; } public List getLevelMembers( Level level, boolean includeCalculated) { List members = super.getLevelMembers(level, false); if (includeCalculated) { members = Util.addLevelCalculatedMembers(this, level, members); } return members; } public Member getCalculatedMember(List nameParts) { final String uniqueName = Util.implode(nameParts); for (Formula formula : calculatedMemberList) { final String formulaUniqueName = formula.getMdxMember().getUniqueName(); if (formulaUniqueName.equals(uniqueName) && getRole().canAccess(formula.getMdxMember())) { return formula.getMdxMember(); } } return null; } public NamedSet getNamedSet(List segments) { if (segments.size() == 1) { Id.Segment segment = segments.get(0); for (Formula namedSet : namedSetList) { if (segment.matches(namedSet.getName())) { return namedSet.getNamedSet(); } } } return super.getNamedSet(segments); } public List getCalculatedMembers(Hierarchy hierarchy) { ArrayList list = new ArrayList(); if (getRole().getAccess(hierarchy) == Access.NONE) { return list; } for (Member member : getCalculatedMembers()) { if (member.getHierarchy().equals(hierarchy)) { list.add(member); } } return list; } public List getCalculatedMembers(Level level) { List list = new ArrayList(); if (getRole().getAccess(level) == Access.NONE) { return list; } for (Member member : getCalculatedMembers()) { if (member.getLevel().equals(level)) { list.add(member); } } return list; } public List getCalculatedMembers() { List list = roleToAccessibleCalculatedMembers.get(getRole()); if (list == null) { list = new ArrayList(); for (Formula formula : calculatedMemberList) { Member member = formula.getMdxMember(); if (getRole().canAccess(member)) { list.add(member); } } // calculatedMembers array may not have been initialized if (list.size() > 0) { roleToAccessibleCalculatedMembers.put(getRole(), list); } } return list; } public SchemaReader withoutAccessControl() { assert getClass() == RolapCubeSchemaReader.class : "Derived class " + getClass() + " must override method"; return RolapCube.this.getSchemaReader(); } public Member getMemberByUniqueName( List uniqueNameParts, boolean failIfNotFound, MatchType matchType) { Member member = (Member) lookupCompound( RolapCube.this, uniqueNameParts, failIfNotFound, Category.Member, matchType); if (member == null) { assert !failIfNotFound; return null; } if (getRole().canAccess(member)) { return member; } else { if (failIfNotFound) { throw Util.newElementNotFoundException( Category.Member, new IdentifierNode( Util.toOlap4j(uniqueNameParts))); } return null; } } public Cube getCube() { return RolapCube.this; } public List getNamespaces() { final List list = new ArrayList(); list.add(this); list.addAll(schema.getSchemaReader().getNamespaces()); return list; } public OlapElement lookupChild( OlapElement parent, IdentifierSegment segment, MatchType matchType) { // ignore matchType return lookupChild(parent, segment); } public OlapElement lookupChild( OlapElement parent, IdentifierSegment segment) { // Don't look for stored members, or look for dimensions, // hierarchies, levels at all. Only look for calculated members // and named sets defined against this cube. // Look up calc member. for (Formula formula : calculatedMemberList) { if (NameResolver.matches(formula, parent, segment)) { return formula.getMdxMember(); } } // Look up named set. if (parent == RolapCube.this) { for (Formula formula : namedSetList) { if (Util.matches(segment, formula.getName())) { return formula.getNamedSet(); } } } return null; } } /** * Visitor that walks an MDX parse tree containing formulas * associated with calculated members defined in a base cube but * referenced from a virtual cube. When walking the tree, look * for other calculated members as well as stored measures. Keep * track of all stored measures found, and for the calculated members, * once the formula of that calculated member has been visited, resolve * the calculated member relative to the virtual cube. */ private class MeasureFinder extends MdxVisitorImpl { /** * The virtual cube where the original calculated member was * referenced from */ private RolapCube virtualCube; /** * The base cube where the original calculated member is defined */ private RolapCube baseCube; /** * The measures level corresponding to the virtual cube */ private RolapLevel measuresLevel; /** * List of measures found */ private List measuresFound; /** * List of calculated members found */ private List calcMembersSeen; public MeasureFinder( RolapCube virtualCube, RolapCube baseCube, RolapLevel measuresLevel) { this.virtualCube = virtualCube; this.baseCube = baseCube; this.measuresLevel = measuresLevel; this.measuresFound = new ArrayList(); this.calcMembersSeen = new ArrayList(); } public Object visit(MemberExpr memberExpr) { Member member = memberExpr.getMember(); if (member instanceof RolapCalculatedMember) { // ignore the calculated member if we've already processed // it in another reference if (calcMembersSeen.contains(member)) { return null; } RolapCalculatedMember calcMember = (RolapCalculatedMember) member; Formula formula = calcMember.getFormula(); formula.accept(this); calcMembersSeen.add(calcMember); // now that we've located all measures referenced in the // calculated member's formula, resolve the calculated // member relative to the virtual cube virtualCube.setMeasuresHierarchyMemberReader( new CacheMemberReader( new MeasureMemberSource( virtualCube.measuresHierarchy, Util.cast(measuresFound)))); MondrianDef.CalculatedMember xmlCalcMember = schema.lookupXmlCalculatedMember( calcMember.getUniqueName(), baseCube.name); createCalcMembersAndNamedSets( Collections.singletonList(xmlCalcMember), Collections.emptyList(), new ArrayList(), new ArrayList(), virtualCube, false); return null; } else if (member instanceof RolapBaseCubeMeasure) { RolapBaseCubeMeasure baseMeasure = (RolapBaseCubeMeasure) member; RolapVirtualCubeMeasure virtualCubeMeasure = new RolapVirtualCubeMeasure( null, measuresLevel, baseMeasure, Collections.emptyMap()); if (!measuresFound.contains(virtualCubeMeasure)) { measuresFound.add(virtualCubeMeasure); } } return null; } public List getMeasuresFound() { return measuresFound; } } public static class CubeComparator implements Comparator { public int compare(RolapCube c1, RolapCube c2) { return c1.getName().compareTo(c2.getName()); } } /** * Creates an expression that compiles to a given compiled expression. * *

Use this for synthetic expressions that do not correspond to anything * in an MDX parse tree, and just need to compile to a particular compiled * expression. The expression has minimal amounts of metadata, for example * type information, but the function has no name or description. * * @see mondrian.calc.DummyExp */ static Exp createDummyExp(final Calc calc) { return new ResolvedFunCall( new FunDefBase("dummy", null, "fn") { public Calc compileCall( ResolvedFunCall call, ExpCompiler compiler) { return calc; } }, new Exp[0], calc.getType()); } } // End RolapCube.java mondrian-3.4.1/src/main/mondrian/rolap/FastBatchingCellReader.java0000644000175000017500000017327511735330606025027 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.rolap.agg.*; import mondrian.rolap.aggmatcher.AggGen; import mondrian.rolap.aggmatcher.AggStar; import mondrian.rolap.cache.SegmentCacheIndex; import mondrian.rolap.cache.SegmentCacheIndexImpl; import mondrian.server.Execution; import mondrian.server.Locus; import mondrian.spi.*; import mondrian.util.*; import org.apache.log4j.Logger; import org.apache.log4j.MDC; import java.util.*; import java.util.concurrent.Future; /** * A FastBatchingCellReader doesn't really Read cells: when asked * to look up the values of stored measures, it lies, and records the fact * that the value was asked for. Later, we can look over the values which * are required, fetch them in an efficient way, and re-run the evaluation * with a real evaluator. * *

NOTE: When it doesn't know the answer, it lies by returning an error * object. The calling code must be able to deal with that.

* *

This class tries to minimize the amount of storage needed to record the * fact that a cell was requested.

*/ public class FastBatchingCellReader implements CellReader { private static final Logger LOGGER = Logger.getLogger(FastBatchingCellReader.class); private final RolapCube cube; /** * Records the number of requests. The field is used for correctness: if * the request count stays the same during an operation, you know that the * FastBatchingCellReader has not told any lies during that operation, and * therefore the result is true. The field is also useful for debugging. */ private int missCount; /** * Number of occasions that a requested cell was already in cache. */ private int hitCount; /** * Number of occasions that requested cell was in the process of being * loaded into cache but not ready. */ private int pendingCount; private final AggregationManager aggMgr; private final SegmentCacheManager cacheMgr; private final RolapAggregationManager.PinSet pinnedSegments; /** * Indicates that the reader has given incorrect results. */ private boolean dirty; private final List cellRequests = new ArrayList(); /** * Creates a FastBatchingCellReader. * * @param execution Execution that calling statement belongs to. Allows us * to check for cancel * @param cube Cube that requests belong to * @param aggMgr Aggregation manager */ public FastBatchingCellReader( Execution execution, RolapCube cube, AggregationManager aggMgr) { assert cube != null; assert execution != null; this.cube = cube; this.aggMgr = aggMgr; cacheMgr = aggMgr.cacheMgr; pinnedSegments = this.aggMgr.createPinSet(); } public Object get(RolapEvaluator evaluator) { final CellRequest request = RolapAggregationManager.makeRequest(evaluator); if (request == null || request.isUnsatisfiable()) { return Util.nullValue; // request not satisfiable. } // Try to retrieve a cell and simultaneously pin the segment which // contains it. final Object o = aggMgr.getCellFromCache(request, pinnedSegments); assert o != Boolean.TRUE : "getCellFromCache no longer returns TRUE"; if (o != null) { ++hitCount; return o; } // If this query has not had any cache misses, it's worth doing a // synchronous request for the cell segment. If it is in the cache, it // will be worth the wait, because we can avoid the effort of batching // up requests that could have been satisfied by the same segment. if (missCount == 0) { SegmentWithData segmentWithData = cacheMgr.peek(request); if (segmentWithData != null) { segmentWithData.getStar().register(segmentWithData); final Object o2 = aggMgr.getCellFromCache(request, pinnedSegments); if (o2 != null) { ++hitCount; return o2; } } } // if there is no such cell, record that we need to fetch it, and // return 'error' recordCellRequest(request); return RolapUtil.valueNotReadyException; } public int getMissCount() { return missCount; } public int getHitCount() { return hitCount; } public int getPendingCount() { return pendingCount; } public final void recordCellRequest(CellRequest request) { assert !request.isUnsatisfiable(); ++missCount; cellRequests.add(request); if (cellRequests.size() % 5000 == 0) { // Signal that it's time to ask the cache manager if it has cells // we need in the cache. Not really an exception. throw CellRequestQuantumExceededException.INSTANCE; } } /** * Returns whether this reader has told a lie. This is the case if there * are pending batches to load or if {@link #setDirty(boolean)} has been * called. */ public boolean isDirty() { return dirty || !cellRequests.isEmpty(); } /** * Resolves any pending cell reads using the cache. After calling this * method, all cells requested in a given batch are loaded into this * statement's local cache. * *

The method is implemented by making an asynchronous call to the cache * manager. The result is a list of segments that satisfies every cell * request.

* *

The client should put the resulting segments into its "query local" * cache, to ensure that future cells in that segment can be answered * without a call to the cache manager. (That is probably 1000x faster.)

* *

The cache manager does not inform where client where each segment * came from. There are several possibilities:

* *
    *
  • Segment was already in cache (header and body)
  • *
  • Segment is in the process of being loaded by executing a SQL * statement (probably due to a request from another client)
  • *
  • Segment is in an external cache (that is, header is in the cache, * body is not yet)
  • *
  • Segment can be created by rolling up one or more cache segments. * (And of course each of these segments might be "paged out".)
  • *
  • By executing a SQL {@code GROUP BY} statement
  • *
* *

Furthermore, segments in external cache may take some time to retrieve * (a LAN round trip, say 1 millisecond, is a reasonable guess); and the * request may fail. (It depends on the cache, but caches are at liberty * to 'forget' segments.) So, any strategy that relies on cache segments * should be able to fall back. Even if there are fall backs, only one call * needs to be made to the cache manager.

* * @return Whether any aggregations were loaded. */ boolean loadAggregations() { if (!isDirty()) { return false; } // List of futures yielding segments populated by SQL statements. If // loading requires several iterations, we just append to the list. We // don't mind if it takes a while for SQL statements to return. final List>> sqlSegmentMapFutures = new ArrayList>>(); final List cellRequests1 = new ArrayList(cellRequests); for (int iteration = 0;; ++iteration) { final BatchLoader.LoadBatchResponse response = cacheMgr.execute( new BatchLoader.LoadBatchCommand( Locus.peek(), cacheMgr, getDialect(), cube, Collections.unmodifiableList(cellRequests1))); int failureCount = 0; // Segments that have been retrieved from cache this cycle. Allows // us to reduce calls to the external cache. Map headerBodies = new HashMap(); // Segments that could not be loaded. Will need to tell cache mgr to // remove them from the index. final Set failedSegments = new HashSet(); // Load each suggested segment from cache, and place it in // thread-local cache. Note that this step can't be done by the // cacheMgr -- it's our cache. for (SegmentHeader header : response.cacheSegments) { final SegmentBody body = cacheMgr.compositeCache.get(header); if (body == null) { cacheMgr.remove(cube.getStar(), header); failedSegments.add(header); ++failureCount; continue; } headerBodies.put(header, body); final SegmentWithData segmentWithData = response.convert(header, body); segmentWithData.getStar().register(segmentWithData); } // Perform each suggested rollup. // Rollups that failed. If there are any, we may need to re-process // some cell requests. final List failedRollups = new ArrayList(); // Rollups that succeeded. Will tell cache mgr to put the headers // into the index and the header/bodies in cache. final Map succeededRollups = new HashMap(); for (BatchLoader.RollupInfo rollup : response.rollups) { // Gather the required segments. Map map = findResidentRollupCandidate(headerBodies, rollup); if (map == null) { // None of the candidate segment-sets for this rollup was // all present in the cache. failedRollups.add(rollup); ++failureCount; continue; } final Set keepColumns = new HashSet(); for (RolapStar.Column column : rollup.constrainedColumns) { keepColumns.add( column.getExpression().getGenericExpression()); } Pair rollupHeaderBody = SegmentBuilder.rollup( map, keepColumns, rollup.constrainedColumnsBitKey, rollup.measure.getAggregator().getRollup()); final SegmentHeader header = rollupHeaderBody.left; final SegmentBody body = rollupHeaderBody.right; if (headerBodies.containsKey(header)) { // We had already created this segment, somehow. continue; } headerBodies.put(header, body); succeededRollups.put(header, body); final SegmentWithData segmentWithData = response.convert(header, body); segmentWithData.getStar().register(segmentWithData); } // Wait for SQL statements to end -- but only if there are no // failures. // // If there are failures, and its the first iteration, it's more // urgent that we create and execute a follow-up request. We will // wait for the pending SQL statements at the end of that. // // If there are failures on later iterations, wait for SQL // statements to end. The cache might be porous. SQL might be the // only way to make progress. sqlSegmentMapFutures.addAll(response.sqlSegmentMapFutures); if (failureCount == 0 || iteration > 0) { // Wait on segments being loaded by someone else. for (Map.Entry> entry : response.futures.entrySet()) { final SegmentHeader header = entry.getKey(); final Future bodyFuture = entry.getValue(); final SegmentBody body = Util.safeGet( bodyFuture, "Waiting for someone else's segment to load via SQL"); final SegmentWithData segmentWithData = response.convert(header, body); segmentWithData.getStar().register(segmentWithData); } // Wait on segments being loaded by SQL statements we asked for. for (Future> sqlSegmentMapFuture : sqlSegmentMapFutures) { final Map segmentMap = Util.safeGet( sqlSegmentMapFuture, "Waiting for segment to load via SQL"); for (SegmentWithData segmentWithData : segmentMap.values()) { segmentWithData.getStar().register(segmentWithData); } // TODO: also pass back SegmentHeader and SegmentBody, // and add these to headerBodies. Might help? } } if (failureCount == 0) { break; } // Figure out which cell requests are not satisfied by any of the // segments retrieved. @SuppressWarnings("unchecked") List old = new ArrayList(cellRequests1); cellRequests1.clear(); for (CellRequest cellRequest : old) { if (cellRequest.getMeasure().getStar() .getCellFromCache(cellRequest, null) == null) { cellRequests1.add(cellRequest); } } if (cellRequests1.isEmpty()) { break; } if (cellRequests1.size() >= old.size() && iteration > 10) { throw Util.newError( "Cache round-trip did not resolve any cell requests. " + "Iteration #" + iteration + "; request count " + cellRequests1.size() + "; requested headers: " + response.cacheSegments.size() + "; requested rollups: " + response.rollups.size() + "; requested SQL: " + response.sqlSegmentMapFutures.size()); } // Continue loop; form and execute a new request with the smaller // set of cell requests. } dirty = false; cellRequests.clear(); return true; } /** * Finds a segment-list among a list of candidate segment-lists * for which the bodies of all segments are in cache. Returns a map * from segment-to-body if found, or null if not found. * * @param headerBodies Cache of bodies previously retrieved from external * cache * * @param rollup Specifies what segments to roll up, and the * target dimensionality * * @return Collection of segment headers and bodies suitable for rollup, * or null */ private Map findResidentRollupCandidate( Map headerBodies, BatchLoader.RollupInfo rollup) { candidateLoop: for (List headers : rollup.candidateLists) { final Map map = new HashMap(); for (SegmentHeader header : headers) { SegmentBody body = loadSegmentFromCache(headerBodies, header); if (body == null) { // To proceed with a candidate, require all headers to // be in cache. continue candidateLoop; } map.put(header, body); } return map; } return null; } private SegmentBody loadSegmentFromCache( Map headerBodies, SegmentHeader header) { SegmentBody body = headerBodies.get(header); if (body != null) { return body; } body = cacheMgr.compositeCache.get(header); if (body == null) { cacheMgr.remove(cube.getStar(), header); return null; } headerBodies.put(header, body); return body; } /** * Returns the SQL dialect. Overridden in some unit tests. * * @return Dialect */ Dialect getDialect() { final RolapStar star = cube.getStar(); if (star != null) { return star.getSqlQueryDialect(); } else { return cube.getSchema().getDialect(); } } /** * Sets the flag indicating that the reader has told a lie. */ void setDirty(boolean dirty) { this.dirty = dirty; } } /** * Context for processing a request to the cache manager for segments matching a * collection of cell requests. All methods except the constructor are executed * by the cache manager's dedicated thread. */ class BatchLoader { private static final Logger LOGGER = Logger.getLogger(FastBatchingCellReader.class); private final Locus locus; private final SegmentCacheManager cacheMgr; private final Dialect dialect; private final RolapCube cube; private final Map batches = new HashMap(); private final Set cacheHeaders = new LinkedHashSet(); private final Map> futures = new HashMap>(); private final List rollups = new ArrayList(); private final Set rollupBitmaps = new HashSet(); private final Map converterMap = new HashMap(); public BatchLoader( Locus locus, SegmentCacheManager cacheMgr, Dialect dialect, RolapCube cube) { this.locus = locus; this.cacheMgr = cacheMgr; this.dialect = dialect; this.cube = cube; } final boolean shouldUseGroupingFunction() { return MondrianProperties.instance().EnableGroupingSets.get() && dialect.supportsGroupingSets(); } private void recordCellRequest2(final CellRequest request) { // If there is a segment matching these criteria, write it to the list // of found segments, and remove the cell request from the list. final AggregationKey key = new AggregationKey(request); // Is request matched by one of the headers we intend to load? final Map mappedCellValues = request.getMappedCellValues(); final List compoundPredicates = AggregationKey.getCompoundPredicateStringList( key.getStar(), key.getCompoundPredicateList()); for (SegmentHeader header : cacheHeaders) { if (SegmentCacheIndexImpl.matches( header, mappedCellValues, compoundPredicates)) { // It's likely that the header will be in the cache, so this // request will be satisfied. If not, the header will be removed // from the segment index, and we'll be back. return; } } final RolapStar.Measure measure = request.getMeasure(); final RolapStar star = measure.getStar(); final RolapSchema schema = star.getSchema(); final SegmentCacheIndex index = cacheMgr.getIndexRegistry().getIndex(star); final List headersInCache = index.locate( schema.getName(), schema.getChecksum(), measure.getCubeName(), measure.getName(), star.getFactTable().getAlias(), request.getConstrainedColumnsBitKey(), mappedCellValues, compoundPredicates); // Ask for the first segment to be loaded from cache. (If it's no longer // in cache, we'll be back, and presumably we'll try the second // segment.) final SegmentBuilder.SegmentConverterImpl converter = new SegmentBuilder.SegmentConverterImpl(key, request); if (!headersInCache.isEmpty()) { final SegmentHeader headerInCache = headersInCache.get(0); final Future future = index.getFuture(headerInCache); if (future != null) { // Segment header is in cache, body is being loaded. Worker will // need to wait for load to complete. futures.put(headerInCache, future); } else { // Segment is in cache. cacheHeaders.add(headerInCache); } index.setConverter( headerInCache.schemaName, headerInCache.schemaChecksum, headerInCache.cubeName, headerInCache.rolapStarFactTableName, headerInCache.measureName, headerInCache.compoundPredicates, converter); converterMap.put( SegmentCacheIndexImpl.makeConverterKey(request, key), converter); return; } // Try to roll up if the measure's rollup aggregator supports // "fast" aggregation from raw objects. // // Do not try to roll up if this request has already chosen a rollup // with the same target dimensionality. It is quite likely that the // other rollup will satisfy this request, and it's complicated to be // 100% sure. If we're wrong, we'll be back. // Also make sure that we don't try to rollup a measure which // doesn't support rollup from raw data, like a distinct count // for example. Both the measure's aggregator and its rollup // aggregator must support raw data aggregation. We call // Aggregator.supportsFastAggregates() to verify. if (measure.getAggregator().supportsFastAggregates( measure.getDatatype()) && measure.getAggregator().getRollup().supportsFastAggregates( measure.getDatatype()) && !rollupBitmaps.contains(request.getConstrainedColumnsBitKey())) { // Don't even bother doing a segment lookup if we can't // rollup that measure. final List> rollup = index.findRollupCandidates( schema.getName(), schema.getChecksum(), measure.getCubeName(), measure.getName(), star.getFactTable().getAlias(), request.getConstrainedColumnsBitKey(), mappedCellValues, AggregationKey.getCompoundPredicateStringList( star, key.getCompoundPredicateList())); if (!rollup.isEmpty()) { rollups.add( new RollupInfo( request, rollup)); rollupBitmaps.add(request.getConstrainedColumnsBitKey()); converterMap.put( SegmentCacheIndexImpl.makeConverterKey(request, key), new SegmentBuilder.StarSegmentConverter( measure, key.getCompoundPredicateList())); return; } } // Finally, add to a batch. It will turn in to a SQL request. Batch batch = batches.get(key); if (batch == null) { batch = new Batch(request); batches.put(key, batch); converterMap.put( SegmentCacheIndexImpl.makeConverterKey(request, key), converter); if (LOGGER.isDebugEnabled()) { StringBuilder buf = new StringBuilder(100); buf.append("FastBatchingCellReader: bitkey="); buf.append(request.getConstrainedColumnsBitKey()); buf.append(Util.nl); for (RolapStar.Column column : request.getConstrainedColumns()) { buf.append(" "); buf.append(column); buf.append(Util.nl); } LOGGER.debug(buf.toString()); } } batch.add(request); } /** * Determines which segments need to be loaded from external cache, * created using roll up, or created using SQL to satisfy a given list * of cell requests. * * @return List of segment futures. Each segment future may or may not be * already present (it depends on the current location of the segment * body). Each future will return a not-null segment (or throw). */ LoadBatchResponse load(List cellRequests) { // Check for cancel/timeout. The request might have been on the queue // for a while. if (locus.execution != null) { locus.execution.checkCancelOrTimeout(); } final long t1 = System.currentTimeMillis(); // Now we're inside the cache manager, we can see which of our cell // requests can be answered from cache. Those that can will be added // to the segments list; those that can not will be converted into // batches and rolled up or loaded using SQL. for (CellRequest cellRequest : cellRequests) { recordCellRequest2(cellRequest); } // Sort the batches into deterministic order. List batchList = new ArrayList(batches.values()); Collections.sort(batchList, BatchComparator.instance); final List>> segmentMapFutures = new ArrayList>>(); if (shouldUseGroupingFunction()) { LOGGER.debug("Using grouping sets"); List groupedBatches = groupBatches(batchList); for (CompositeBatch batch : groupedBatches) { batch.load(segmentMapFutures); } } else { // Load batches in turn. for (Batch batch : batchList) { batch.loadAggregation(segmentMapFutures); } } if (LOGGER.isDebugEnabled()) { final long t2 = System.currentTimeMillis(); LOGGER.debug("load (millis): " + (t2 - t1)); } // Create a response and return it to the client. The response is a // bunch of work to be done (waiting for segments to load from SQL, to // come from cache, and so forth) on the client's time. Some of the bets // may not come off, in which case, the client will send us another // request. return new LoadBatchResponse( cellRequests, new ArrayList(cacheHeaders), rollups, converterMap, segmentMapFutures, futures); } static List groupBatches(List batchList) { Map batchGroups = new HashMap(); for (int i = 0; i < batchList.size(); i++) { for (int j = i + 1; j < batchList.size();) { final Batch iBatch = batchList.get(i); final Batch jBatch = batchList.get(j); if (iBatch.canBatch(jBatch)) { batchList.remove(j); addToCompositeBatch(batchGroups, iBatch, jBatch); } else if (jBatch.canBatch(iBatch)) { batchList.set(i, jBatch); batchList.remove(j); addToCompositeBatch(batchGroups, jBatch, iBatch); j = i + 1; } else { j++; } } } wrapNonBatchedBatchesWithCompositeBatches(batchList, batchGroups); final CompositeBatch[] compositeBatches = batchGroups.values().toArray( new CompositeBatch[batchGroups.size()]); Arrays.sort(compositeBatches, CompositeBatchComparator.instance); return Arrays.asList(compositeBatches); } private static void wrapNonBatchedBatchesWithCompositeBatches( List batchList, Map batchGroups) { for (Batch batch : batchList) { if (batchGroups.get(batch.batchKey) == null) { batchGroups.put(batch.batchKey, new CompositeBatch(batch)); } } } static void addToCompositeBatch( Map batchGroups, Batch detailedBatch, Batch summaryBatch) { CompositeBatch compositeBatch = batchGroups.get(detailedBatch.batchKey); if (compositeBatch == null) { compositeBatch = new CompositeBatch(detailedBatch); batchGroups.put(detailedBatch.batchKey, compositeBatch); } CompositeBatch compositeBatchOfSummaryBatch = batchGroups.remove(summaryBatch.batchKey); if (compositeBatchOfSummaryBatch != null) { compositeBatch.merge(compositeBatchOfSummaryBatch); } else { compositeBatch.add(summaryBatch); } } /** * Command that loads the segments required for a collection of cell * requests. Returns the collection of segments. */ public static class LoadBatchCommand implements SegmentCacheManager.Command { private final Locus locus; private final SegmentCacheManager cacheMgr; private final Dialect dialect; private final RolapCube cube; private final List cellRequests; private final Map mdc = new HashMap(); public LoadBatchCommand( Locus locus, SegmentCacheManager cacheMgr, Dialect dialect, RolapCube cube, List cellRequests) { this.locus = locus; this.cacheMgr = cacheMgr; this.dialect = dialect; this.cube = cube; this.cellRequests = cellRequests; if (MDC.getContext() != null) { this.mdc.putAll(MDC.getContext()); } } public LoadBatchResponse call() { if (MDC.getContext() != null) { final Map old = MDC.getContext(); old.clear(); old.putAll(mdc); } return new BatchLoader(locus, cacheMgr, dialect, cube) .load(cellRequests); } public Locus getLocus() { return locus; } } /** * Set of Batches which can grouped together. */ static class CompositeBatch { /** Batch with most number of constraint columns */ final Batch detailedBatch; /** Batches whose data can be fetched using rollup on detailed batch */ final List summaryBatches = new ArrayList(); CompositeBatch(Batch detailedBatch) { this.detailedBatch = detailedBatch; } void add(Batch summaryBatch) { summaryBatches.add(summaryBatch); } void merge(CompositeBatch summaryBatch) { summaryBatches.add(summaryBatch.detailedBatch); summaryBatches.addAll(summaryBatch.summaryBatches); } public void load( List>> segmentFutures) { GroupingSetsCollector batchCollector = new GroupingSetsCollector(true); this.detailedBatch.loadAggregation(batchCollector, segmentFutures); int cellRequestCount = 0; for (Batch batch : summaryBatches) { batch.loadAggregation(batchCollector, segmentFutures); cellRequestCount += batch.cellRequestCount; } getSegmentLoader().load( cellRequestCount, batchCollector.getGroupingSets(), detailedBatch.batchKey.getCompoundPredicateList(), segmentFutures); } SegmentLoader getSegmentLoader() { return new SegmentLoader(detailedBatch.getCacheMgr()); } } private static final Logger BATCH_LOGGER = Logger.getLogger(Batch.class); public static class RollupInfo { final RolapStar.Column[] constrainedColumns; final BitKey constrainedColumnsBitKey; final RolapStar.Measure measure; final List> candidateLists; RollupInfo( CellRequest request, List> candidateLists) { this.candidateLists = candidateLists; constrainedColumns = request.getConstrainedColumns(); constrainedColumnsBitKey = request.getConstrainedColumnsBitKey(); measure = request.getMeasure(); } } /** * Request sent from cache manager to a worker to load segments into * the cache, create segments by rolling up, and to wait for segments * being loaded via SQL. */ static class LoadBatchResponse { /** * List of segments that are being loaded using SQL. * *

Other workers are executing the SQL. When done, they will write a * segment body or an error into the respective futures. The thread * processing this request will wait on those futures, once all segments * have successfully arrived from cache.

*/ final List>> sqlSegmentMapFutures; /** * List of segments we are trying to load from the cache. */ final List cacheSegments; /** * List of cell requests that will be satisfied by segments we are * trying to load from the cache (or create by rolling up). */ final List cellRequests; /** * List of segments to be created from segments in the cache, provided * that the cache segments come through. * *

If they do not, we will need to tell the cache manager to remove * the pending segments.

*/ final List rollups; final Map converterMap; final Map> futures; LoadBatchResponse( List cellRequests, List cacheSegments, List rollups, Map converterMap, List>> sqlSegmentMapFutures, Map> futures) { this.cellRequests = cellRequests; this.sqlSegmentMapFutures = sqlSegmentMapFutures; this.cacheSegments = cacheSegments; this.rollups = rollups; this.converterMap = converterMap; this.futures = futures; } public SegmentWithData convert( SegmentHeader header, SegmentBody body) { final SegmentBuilder.SegmentConverter converter = converterMap.get( SegmentCacheIndexImpl.makeConverterKey(header)); return converter.convert(header, body); } } public class Batch { // the CellRequest's constrained columns final RolapStar.Column[] columns; final List measuresList = new ArrayList(); final Set[] valueSets; final AggregationKey batchKey; // string representation; for debug; set lazily in toString private String string; private int cellRequestCount; private List tuples = new ArrayList(); public Batch(CellRequest request) { columns = request.getConstrainedColumns(); valueSets = new HashSet[columns.length]; for (int i = 0; i < valueSets.length; i++) { valueSets[i] = new HashSet(); } batchKey = new AggregationKey(request); } public String toString() { if (string == null) { final StringBuilder buf = new StringBuilder(); buf.append("Batch {\n") .append(" columns={").append(Arrays.toString(columns)) .append("}\n") .append(" measures={").append(measuresList).append("}\n") .append(" valueSets={").append(Arrays.toString(valueSets)) .append("}\n") .append(" batchKey=").append(batchKey).append("}\n") .append("}"); string = buf.toString(); } return string; } public final void add(CellRequest request) { ++cellRequestCount; final int valueCount = request.getNumValues(); final StarColumnPredicate[] tuple = new StarColumnPredicate[valueCount]; for (int j = 0; j < valueCount; j++) { final StarColumnPredicate value = request.getValueAt(j); valueSets[j].add(value); tuple[j] = value; } tuples.add(tuple); final RolapStar.Measure measure = request.getMeasure(); if (!measuresList.contains(measure)) { assert (measuresList.size() == 0) || (measure.getStar() == (measuresList.get(0)).getStar()) : "Measure must belong to same star as other measures"; measuresList.add(measure); } } /** * Returns the RolapStar associated with the Batch's first Measure. * *

This method can only be called after the {@link #add} method has * been called. * * @return the RolapStar associated with the Batch's first Measure */ private RolapStar getStar() { RolapStar.Measure measure = measuresList.get(0); return measure.getStar(); } public BitKey getConstrainedColumnsBitKey() { return batchKey.getConstrainedColumnsBitKey(); } public SegmentCacheManager getCacheMgr() { return cacheMgr; } public final void loadAggregation( List>> segmentFutures) { GroupingSetsCollector collectorWithGroupingSetsTurnedOff = new GroupingSetsCollector(false); loadAggregation(collectorWithGroupingSetsTurnedOff, segmentFutures); } final void loadAggregation( GroupingSetsCollector groupingSetsCollector, List>> segmentFutures) { if (MondrianProperties.instance().GenerateAggregateSql.get()) { generateAggregateSql(); } final StarColumnPredicate[] predicates = initPredicates(); final long t1 = System.currentTimeMillis(); // TODO: optimize key sets; drop a constraint if more than x% of // the members are requested; whether we should get just the cells // requested or expand to a n-cube // If the database cannot execute "count(distinct ...)", split the // distinct aggregations out. int distinctMeasureCount = getDistinctMeasureCount(measuresList); boolean tooManyDistinctMeasures = distinctMeasureCount > 0 && !dialect.allowsCountDistinct() || distinctMeasureCount > 1 && !dialect.allowsMultipleCountDistinct(); if (tooManyDistinctMeasures) { doSpecialHandlingOfDistinctCountMeasures( predicates, groupingSetsCollector, segmentFutures); } // Load agg(distinct ) measures individually // for DBs that does allow multiple distinct SQL measures. if (!dialect.allowsMultipleDistinctSqlMeasures()) { // Note that the intention was originally to capture the // subquery SQL measures and separate them out; However, // without parsing the SQL string, Mondrian cannot distinguish // between "col1" + "col2" and subquery. Here the measure list // contains both types. // See the test case testLoadDistinctSqlMeasure() in // mondrian.rolap.FastBatchingCellReaderTest List distinctSqlMeasureList = getDistinctSqlMeasures(measuresList); for (RolapStar.Measure measure : distinctSqlMeasureList) { AggregationManager.loadAggregation( cacheMgr, cellRequestCount, Collections.singletonList(measure), columns, batchKey, predicates, groupingSetsCollector, segmentFutures); measuresList.remove(measure); } } final int measureCount = measuresList.size(); if (measureCount > 0) { AggregationManager.loadAggregation( cacheMgr, cellRequestCount, measuresList, columns, batchKey, predicates, groupingSetsCollector, segmentFutures); } if (BATCH_LOGGER.isDebugEnabled()) { final long t2 = System.currentTimeMillis(); BATCH_LOGGER.debug( "Batch.load (millis) " + (t2 - t1)); } } private void doSpecialHandlingOfDistinctCountMeasures( StarColumnPredicate[] predicates, GroupingSetsCollector groupingSetsCollector, List>> segmentFutures) { while (true) { // Scan for a measure based upon a distinct aggregation. final RolapStar.Measure distinctMeasure = getFirstDistinctMeasure(measuresList); if (distinctMeasure == null) { break; } final String expr = distinctMeasure.getExpression().getGenericExpression(); final List distinctMeasuresList = new ArrayList(); for (int i = 0; i < measuresList.size();) { final RolapStar.Measure measure = measuresList.get(i); if (measure.getAggregator().isDistinct() && measure.getExpression().getGenericExpression() .equals(expr)) { measuresList.remove(i); distinctMeasuresList.add(distinctMeasure); } else { i++; } } // Load all the distinct measures based on the same expression // together AggregationManager.loadAggregation( cacheMgr, cellRequestCount, distinctMeasuresList, columns, batchKey, predicates, groupingSetsCollector, segmentFutures); } } private StarColumnPredicate[] initPredicates() { StarColumnPredicate[] predicates = new StarColumnPredicate[columns.length]; for (int j = 0; j < columns.length; j++) { Set valueSet = valueSets[j]; StarColumnPredicate predicate; if (valueSet == null) { predicate = LiteralStarPredicate.FALSE; } else { ValueColumnPredicate[] values = valueSet.toArray( new ValueColumnPredicate[valueSet.size()]); // Sort array to achieve determinism in generated SQL. Arrays.sort( values, ValueColumnConstraintComparator.instance); predicate = new ListColumnPredicate( columns[j], Arrays.asList((StarColumnPredicate[]) values)); } predicates[j] = predicate; } return predicates; } private void generateAggregateSql() { if (cube == null || cube.isVirtual()) { final StringBuilder buf = new StringBuilder(64); buf.append( "AggGen: Sorry, can not create SQL for virtual Cube \"") .append(cube == null ? null : cube.getName()) .append("\", operation not currently supported"); BATCH_LOGGER.error(buf.toString()); } else { final AggGen aggGen = new AggGen(cube.getName(), cube.getStar(), columns); if (aggGen.isReady()) { // PRINT TO STDOUT - DO NOT USE BATCH_LOGGER System.out.println( "createLost:" + Util.nl + aggGen.createLost()); System.out.println( "insertIntoLost:" + Util.nl + aggGen.insertIntoLost()); System.out.println( "createCollapsed:" + Util.nl + aggGen.createCollapsed()); System.out.println( "insertIntoCollapsed:" + Util.nl + aggGen.insertIntoCollapsed()); } else { BATCH_LOGGER.error("AggGen failed"); } } } /** * Returns the first measure based upon a distinct aggregation, or null * if there is none. */ final RolapStar.Measure getFirstDistinctMeasure( List measuresList) { for (RolapStar.Measure measure : measuresList) { if (measure.getAggregator().isDistinct()) { return measure; } } return null; } /** * Returns the number of the measures based upon a distinct * aggregation. */ private int getDistinctMeasureCount( List measuresList) { int count = 0; for (RolapStar.Measure measure : measuresList) { if (measure.getAggregator().isDistinct()) { ++count; } } return count; } /** * Returns the list of measures based upon a distinct aggregation * containing SQL measure expressions(as opposed to column expressions). * * This method was initially intended for only those measures that are * defined using subqueries(for DBs that support them). However, since * Mondrian does not parse the SQL string, the method will count both * queries as well as some non query SQL expressions. */ private List getDistinctSqlMeasures( List measuresList) { List distinctSqlMeasureList = new ArrayList(); for (RolapStar.Measure measure : measuresList) { if (measure.getAggregator().isDistinct() && measure.getExpression() instanceof MondrianDef.MeasureExpression) { MondrianDef.MeasureExpression measureExpr = (MondrianDef.MeasureExpression) measure.getExpression(); MondrianDef.SQL measureSql = measureExpr.expressions[0]; // Checks if the SQL contains "SELECT" to detect the case a // subquery is used to define the measure. This is not a // perfect check, because a SQL expression on column names // containing "SELECT" will also be detected. e,g, // count("select beef" + "regular beef"). if (measureSql.cdata.toUpperCase().contains("SELECT")) { distinctSqlMeasureList.add(measure); } } } return distinctSqlMeasureList; } /** * Returns whether another Batch can be batched to this Batch. * *

This is possible if: *

  • columns list is super set of other batch's constraint columns; * and *
  • both have same Fact Table; and *
  • matching columns of this and other batch has the same value; and *
  • non matching columns of this batch have ALL VALUES * */ boolean canBatch(Batch other) { return hasOverlappingBitKeys(other) && constraintsMatch(other) && hasSameMeasureList(other) && !hasDistinctCountMeasure() && !other.hasDistinctCountMeasure() && haveSameStarAndAggregation(other) && haveSameClosureColumns(other); } /** * Returns whether the constraints on this Batch subsume the constraints * on another Batch and therefore the other Batch can be subsumed into * this one for GROUPING SETS purposes. Not symmetric. * * @param other Other batch * @return Whether other batch can be subsumed into this one */ private boolean constraintsMatch(Batch other) { if (areBothDistinctCountBatches(other)) { if (getConstrainedColumnsBitKey().equals( other.getConstrainedColumnsBitKey())) { return hasSameCompoundPredicate(other) && haveSameValues(other); } else { return hasSameCompoundPredicate(other) || (other.batchKey.getCompoundPredicateList().isEmpty() || equalConstraint( batchKey.getCompoundPredicateList(), other.batchKey.getCompoundPredicateList())) && haveSameValues(other); } } else { return haveSameValues(other); } } private boolean equalConstraint( List predList1, List predList2) { if (predList1.size() != predList2.size()) { return false; } for (int i = 0; i < predList1.size(); i++) { StarPredicate pred1 = predList1.get(i); StarPredicate pred2 = predList2.get(i); if (!pred1.equalConstraint(pred2)) { return false; } } return true; } private boolean areBothDistinctCountBatches(Batch other) { return this.hasDistinctCountMeasure() && !this.hasNormalMeasures() && other.hasDistinctCountMeasure() && !other.hasNormalMeasures(); } private boolean hasNormalMeasures() { return getDistinctMeasureCount(measuresList) != measuresList.size(); } private boolean hasSameMeasureList(Batch other) { return this.measuresList.size() == other.measuresList.size() && this.measuresList.containsAll(other.measuresList); } boolean hasOverlappingBitKeys(Batch other) { return getConstrainedColumnsBitKey() .isSuperSetOf(other.getConstrainedColumnsBitKey()); } boolean hasDistinctCountMeasure() { return getDistinctMeasureCount(measuresList) > 0; } boolean hasSameCompoundPredicate(Batch other) { final StarPredicate starPredicate = compoundPredicate(); final StarPredicate otherStarPredicate = other.compoundPredicate(); if (starPredicate == null && otherStarPredicate == null) { return true; } else if (starPredicate != null && otherStarPredicate != null) { return starPredicate.equalConstraint(otherStarPredicate); } return false; } private StarPredicate compoundPredicate() { StarPredicate predicate = null; for (Set valueSet : valueSets) { StarPredicate orPredicate = null; for (StarColumnPredicate starColumnPredicate : valueSet) { if (orPredicate == null) { orPredicate = starColumnPredicate; } else { orPredicate = orPredicate.or(starColumnPredicate); } } if (predicate == null) { predicate = orPredicate; } else { predicate = predicate.and(orPredicate); } } for (StarPredicate starPredicate : batchKey.getCompoundPredicateList()) { if (predicate == null) { predicate = starPredicate; } else { predicate = predicate.and(starPredicate); } } return predicate; } boolean haveSameStarAndAggregation(Batch other) { boolean rollup[] = {false}; boolean otherRollup[] = {false}; boolean hasSameAggregation = getAgg(rollup) == other.getAgg(otherRollup); boolean hasSameRollupOption = rollup[0] == otherRollup[0]; boolean hasSameStar = getStar().equals(other.getStar()); return hasSameStar && hasSameAggregation && hasSameRollupOption; } /** * Returns whether this batch has the same closure columns as another. * *

    Ensures that we do not group together a batch that includes a * level of a parent-child closure dimension with a batch that does not. * It is not safe to roll up from a parent-child closure level; due to * multiple accounting, the 'all' level is less than the sum of the * members of the closure level. * * @param other Other batch * @return Whether batches have the same closure columns */ boolean haveSameClosureColumns(Batch other) { final BitKey cubeClosureColumnBitKey = cube.closureColumnBitKey; if (cubeClosureColumnBitKey == null) { // Virtual cubes have a null bitkey. For now, punt; should do // better. return true; } final BitKey closureColumns = this.batchKey.getConstrainedColumnsBitKey() .and(cubeClosureColumnBitKey); final BitKey otherClosureColumns = other.batchKey.getConstrainedColumnsBitKey() .and(cubeClosureColumnBitKey); return closureColumns.equals(otherClosureColumns); } /** * @param rollup Out parameter * @return AggStar */ private AggStar getAgg(boolean[] rollup) { return AggregationManager.findAgg( getStar(), getConstrainedColumnsBitKey(), makeMeasureBitKey(), rollup); } private BitKey makeMeasureBitKey() { BitKey bitKey = getConstrainedColumnsBitKey().emptyCopy(); for (RolapStar.Measure measure : measuresList) { bitKey.set(measure.getBitPosition()); } return bitKey; } /** * Return whether have same values for overlapping columns or * has all children for others. */ boolean haveSameValues( Batch other) { for (int j = 0; j < columns.length; j++) { boolean isCommonColumn = false; for (int i = 0; i < other.columns.length; i++) { if (areSameColumns(other.columns[i], columns[j])) { if (hasSameValues(other.valueSets[i], valueSets[j])) { isCommonColumn = true; break; } else { return false; } } } if (!isCommonColumn && !hasAllValues(columns[j], valueSets[j])) { return false; } } return true; } private boolean hasAllValues( RolapStar.Column column, Set valueSet) { return column.getCardinality() == valueSet.size(); } private boolean areSameColumns( RolapStar.Column otherColumn, RolapStar.Column thisColumn) { return otherColumn.equals(thisColumn); } private boolean hasSameValues( Set otherValueSet, Set thisValueSet) { return otherValueSet.equals(thisValueSet); } } private static class CompositeBatchComparator implements Comparator { static final CompositeBatchComparator instance = new CompositeBatchComparator(); public int compare(CompositeBatch o1, CompositeBatch o2) { return BatchComparator.instance.compare( o1.detailedBatch, o2.detailedBatch); } } private static class BatchComparator implements Comparator { static final BatchComparator instance = new BatchComparator(); private BatchComparator() { } public int compare( Batch o1, Batch o2) { if (o1.columns.length != o2.columns.length) { return o1.columns.length - o2.columns.length; } for (int i = 0; i < o1.columns.length; i++) { int c = o1.columns[i].getName().compareTo( o2.columns[i].getName()); if (c != 0) { return c; } } for (int i = 0; i < o1.columns.length; i++) { int c = compare(o1.valueSets[i], o2.valueSets[i]); if (c != 0) { return c; } } return 0; } int compare(Set set1, Set set2) { if (set1.size() != set2.size()) { return set1.size() - set2.size(); } Iterator iter1 = set1.iterator(); Iterator iter2 = set2.iterator(); while (iter1.hasNext()) { T v1 = iter1.next(); T v2 = iter2.next(); int c = Util.compareKey(v1, v2); if (c != 0) { return c; } } return 0; } } private static class ValueColumnConstraintComparator implements Comparator { static final ValueColumnConstraintComparator instance = new ValueColumnConstraintComparator(); private ValueColumnConstraintComparator() { } public int compare( ValueColumnPredicate o1, ValueColumnPredicate o2) { Object v1 = o1.getValue(); Object v2 = o2.getValue(); if (v1.getClass() == v2.getClass() && v1 instanceof Comparable) { return ((Comparable) v1).compareTo(v2); } else { return v1.toString().compareTo(v2.toString()); } } } } // End FastBatchingCellReader.java mondrian-3.4.1/src/main/mondrian/rolap/RolapAllCubeMember.java0000644000175000017500000000450111735330606024165 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.Util; /** * The 'All' member of a {@link mondrian.rolap.RolapCubeHierarchy}. * *

    A minor extension to {@link mondrian.rolap.RolapCubeMember} because the * naming rules are different. * * @author Will Gorman, 19 October 2007 */ class RolapAllCubeMember extends RolapCubeMember { protected final String name; private final String uniqueName; /** * Creates a RolapAllCubeMember. * * @param member Member of underlying (non-cube) hierarchy * @param cubeLevel Level */ public RolapAllCubeMember(RolapMember member, RolapCubeLevel cubeLevel) { super(null, member, cubeLevel); assert member.isAll(); // replace hierarchy name portion of all member with new name if (member.getHierarchy().getName().equals(getHierarchy().getName())) { name = member.getName(); } else { // special case if we're dealing with a closure String replacement = getHierarchy().getName().replaceAll("\\$", "\\\\\\$"); // convert string to regular expression String memberLevelName = member.getHierarchy().getName().replaceAll("\\.", "\\\\."); name = member.getName().replaceAll(memberLevelName, replacement); } // Assign unique name. We use a kludge to ensure that calc members are // called [Measures].[Foo] not [Measures].[Measures].[Foo]. We can // remove this code when we revisit the scheme to generate member unique // names. if (getHierarchy().getName().equals(getDimension().getName())) { this.uniqueName = Util.makeFqName(getDimension(), name); } else { this.uniqueName = Util.makeFqName(getHierarchy(), name); } } public String getName() { return name; } @Override public String getUniqueName() { return uniqueName; } } // End RolapAllCubeMember.java mondrian-3.4.1/src/main/mondrian/rolap/DefaultMemberChildrenConstraint.java0000644000175000017500000000374611735330606026774 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2010 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.rolap.aggmatcher.AggStar; import mondrian.rolap.sql.MemberChildrenConstraint; import mondrian.rolap.sql.SqlQuery; import java.util.List; /** * Restricts the SQL result set to the parent member of a * MemberChildren query. If called with a calculated member an * exception will be thrown. */ public class DefaultMemberChildrenConstraint implements MemberChildrenConstraint { private static final MemberChildrenConstraint instance = new DefaultMemberChildrenConstraint(); protected DefaultMemberChildrenConstraint() { } public void addMemberConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar, RolapMember parent) { SqlConstraintUtils.addMemberConstraint( sqlQuery, baseCube, aggStar, parent, true); } public void addMemberConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar, List parents) { boolean exclude = false; SqlConstraintUtils.addMemberConstraint( sqlQuery, baseCube, aggStar, parents, true, false, exclude); } public void addLevelConstraint( SqlQuery query, RolapCube baseCube, AggStar aggStar, RolapLevel level) { } public String toString() { return "DefaultMemberChildrenConstraint"; } public Object getCacheKey() { // we have no state, so all instances are equal return this; } public static MemberChildrenConstraint instance() { return instance; } } // End DefaultMemberChildrenConstraint.java mondrian-3.4.1/src/main/mondrian/rolap/MemberCache.java0000644000175000017500000001245211735330606022667 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. // // jhyde, 22 December, 2001 */ package mondrian.rolap; import mondrian.rolap.sql.MemberChildrenConstraint; import mondrian.rolap.sql.TupleConstraint; import java.util.List; /** * A MemberCache can retrieve members based upon their parent and * name. * * @author jhyde * @since 22 December, 2001 */ interface MemberCache { /** * Creates a key with which to {@link #getMember(Object)} or * {@link #putMember(Object, RolapMember)} the * {@link RolapMember} with a given parent and key. * * @param parent Parent member * @param key Key of member within parent * @return key with which to address this member in the cache */ Object makeKey(RolapMember parent, Object key); /** * Retrieves the {@link RolapMember} with a given key. * * @param key cache key, created by {@link #makeKey} * @return member with a given cache key */ RolapMember getMember(Object key); /** * Retrieves the {@link RolapMember} with a given key. * * @param key cache key, created by {@link #makeKey} * @param mustCheckCacheStatus If {@code true}, do not check cache status * @return member with a given cache key */ RolapMember getMember(Object key, boolean mustCheckCacheStatus); /** * Replaces the {@link RolapMember} with a given key and returns the * previous member if any. * * @param key cache key, created by {@link #makeKey} * @param member new member * @return Previous member with that key, or null. */ Object putMember(Object key, RolapMember member); /** * Returns whether the cache supports removing selected items. If it does, * it is valid to call the {@link #removeMember(Object)} and * {@link #removeMemberAndDescendants(Object)} methods. * *

    REVIEW: remove isMutable and move removeMember and * removeMemberAndDescendants to new interface MutableMemberCache * * @return true if the cache supports removing selected items. */ boolean isMutable(); /** * Removes the {@link RolapMember} with a given key from the cache. * Returns the previous member with that key, or null. * Optional operation: see {@link #isMutable}. * * @param key cache key, created by {@link #makeKey} * @return previous member with that key, or null */ RolapMember removeMember(Object key); /** * Removes the designated {@link RolapMember} and all its descendants. * Returns the previous member with that key, or null. * Optional operation: see {@link #isMutable}. * * @param key cache key, created by {@link #makeKey} * @return previous member with that key, or null */ RolapMember removeMemberAndDescendants(Object key); /** * Returns the children of member if they are currently in the * cache, otherwise returns null. * *

    The children may be garbage collected as * soon as the returned list may be garbage collected.

    * * @param parent the parent member * @param constraint the condition that was used when the members were * fetched. May be null for all members (no constraint) * @return list of children, or null if not in cache */ List getChildrenFromCache( RolapMember parent, MemberChildrenConstraint constraint); /** * Returns the members of level if they are currently in the * cache, otherwise returns null. * *

    The members may be garbage collected as soon as the * returned list may be garbage collected.

    * * @param level the level whose members should be fetched * @param constraint the condition that was used when the members were * fetched. May be null for all members (no constraint) * @return members of level, or null if not in cache */ List getLevelMembersFromCache( RolapLevel level, TupleConstraint constraint); /** * Registers that the children of member are * children (a list of {@link RolapMember}s). * * @param member the parent member * @param constraint the condition that was used when the members were * fetched. May be null for all members (no constraint) * @param children list of children */ void putChildren( RolapMember member, MemberChildrenConstraint constraint, List children); /** * Registers that the children of level are * children (a list of {@link RolapMember}s). * * @param level the parent level * @param constraint the condition that was used when the members were * fetched. May be null for all members (no constraint) * @param children list of children */ void putChildren( RolapLevel level, TupleConstraint constraint, List children); } // End MemberCache.java mondrian-3.4.1/src/main/mondrian/rolap/RolapCubeUsages.java0000644000175000017500000000241711735330606023560 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.MondrianDef; /** * Provides the base cubes that a virtual cube uses and * specifies if unrelated dimensions to measures from these cubes should be * ignored. * * @author ajoglekar * @since Nov 22 2007 */ public class RolapCubeUsages { private MondrianDef.CubeUsages cubeUsages; public RolapCubeUsages(MondrianDef.CubeUsages cubeUsage) { this.cubeUsages = cubeUsage; } public boolean shouldIgnoreUnrelatedDimensions(String baseCubeName) { if (cubeUsages == null || cubeUsages.cubeUsages == null) { return false; } for (MondrianDef.CubeUsage usage : cubeUsages.cubeUsages) { if (usage.cubeName.equals(baseCubeName) && Boolean.TRUE.equals(usage.ignoreUnrelatedDimensions)) { return true; } } return false; } } // End RolapCubeUsages.java mondrian-3.4.1/src/main/mondrian/rolap/SqlConstraintUtils.java0000644000175000017500000014030111735330606024354 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.rolap.agg.*; import mondrian.rolap.aggmatcher.AggStar; import mondrian.rolap.sql.SqlQuery; import mondrian.spi.Dialect; import mondrian.util.FilteredIterableList; import java.util.*; /** * Utility class used by implementations of {@link mondrian.rolap.sql.SqlConstraint}, * used to generate constraints into {@link mondrian.rolap.sql.SqlQuery}. * * @author av * @since Nov 21, 2005 */ public class SqlConstraintUtils { /** Utility class */ private SqlConstraintUtils() { } /** * For every restricting member in the current context, generates * a WHERE condition and a join to the fact table. * * @param sqlQuery the query to modify * @param aggStar Aggregate table, or null if query is against fact table * @param restrictMemberTypes defines the behavior if the current context * contains calculated members. If true, thows an exception. * @param evaluator Evaluator */ public static void addContextConstraint( SqlQuery sqlQuery, AggStar aggStar, Evaluator evaluator, boolean restrictMemberTypes) { // Add constraint using the current evaluator context Member[] members = evaluator.getNonAllMembers(); if (restrictMemberTypes) { if (containsCalculatedMember(members)) { throw Util.newInternal( "can not restrict SQL to calculated Members"); } if (hasMultiPositionSlicer(evaluator)) { throw Util.newInternal( "can not restrict SQL to context with multi-position slicer"); } } else { members = removeCalculatedAndDefaultMembers(members); members = removeMultiPositionSlicerMembers(members, evaluator); } final CellRequest request = RolapAggregationManager.makeRequest(members); if (request == null) { if (restrictMemberTypes) { throw Util.newInternal("CellRequest is null - why?"); } // One or more of the members was null or calculated, so the // request is impossible to satisfy. return; } RolapStar.Column[] columns = request.getConstrainedColumns(); Object[] values = request.getSingleValues(); int arity = columns.length; // following code is similar to // AbstractQuerySpec#nonDistinctGenerateSQL() for (int i = 0; i < arity; i++) { RolapStar.Column column = columns[i]; String expr; if (aggStar != null) { int bitPos = column.getBitPosition(); AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); AggStar.Table table = aggColumn.getTable(); table.addToFrom(sqlQuery, false, true); expr = aggColumn.generateExprString(sqlQuery); } else { RolapStar.Table table = column.getTable(); table.addToFrom(sqlQuery, false, true); expr = column.generateExprString(sqlQuery); } final String value = String.valueOf(values[i]); if ((RolapUtil.mdxNullLiteral().equalsIgnoreCase(value)) || (value.equalsIgnoreCase(RolapUtil.sqlNullValue.toString()))) { sqlQuery.addWhere( expr, " is ", RolapUtil.sqlNullLiteral); } else { if (column.getDatatype().isNumeric()) { // make sure it can be parsed Double.valueOf(value); } final StringBuilder buf = new StringBuilder(); sqlQuery.getDialect().quote(buf, value, column.getDatatype()); sqlQuery.addWhere( expr, " = ", buf.toString()); } } } /** * Looks at the given evaluator to determine if it has more * than one slicer member from any particular hierarchy. * @param evaluator the evaluator to look at * @return true if the evaluator's slicer has more than one * member from any particular hierarchy */ public static boolean hasMultiPositionSlicer(Evaluator evaluator) { if (evaluator instanceof RolapEvaluator) { Map mapOfSlicerMembers = new HashMap(); for ( Member slicerMember : ((RolapEvaluator)evaluator).getSlicerMembers()) { Hierarchy hierarchy = slicerMember.getHierarchy(); if (mapOfSlicerMembers.containsKey(hierarchy)) { // We have found a second member in this hierarchy return true; } mapOfSlicerMembers.put(hierarchy, slicerMember); } } return false; } protected static Member[] removeMultiPositionSlicerMembers( Member[] members, Evaluator evaluator) { List slicerMembers = null; if (evaluator instanceof RolapEvaluator) { // get the slicer members from the evaluator slicerMembers = ((RolapEvaluator)evaluator).getSlicerMembers(); } if (slicerMembers != null) { // Iterate the list of slicer members, grouping them by hierarchy Map> mapOfSlicerMembers = new HashMap>(); for (Member slicerMember : slicerMembers) { Hierarchy hierarchy = slicerMember.getHierarchy(); if (!mapOfSlicerMembers.containsKey(hierarchy)) { mapOfSlicerMembers.put(hierarchy, new HashSet()); } mapOfSlicerMembers.get(hierarchy).add(slicerMember); } List listOfMembers = new ArrayList(); // Iterate the given list of members, removing any whose hierarchy // has multiple members on the slicer axis for (Member member : members) { Hierarchy hierarchy = member.getHierarchy(); if (!mapOfSlicerMembers.containsKey(hierarchy) || mapOfSlicerMembers.get(hierarchy).size() < 2) { listOfMembers.add(member); } } members = listOfMembers.toArray(new Member[listOfMembers.size()]); } return members; } /** * Removes calculated and default members from an array. * *

    This is required only if the default member is * not the ALL member. The time dimension for example, has 1997 as default * member. When we evaluate the query *

         *   select NON EMPTY crossjoin(
         *     {[Time].[1998]}, [Customer].[All].children
         *  ) on columns
         *   from [sales]
         * 
    * the [Customer].[All].children is evaluated with the default * member [Time].[1997] in the evaluator context. This is wrong * because the NON EMPTY must filter out Customers with no rows in the fact * table for 1998 not 1997. So we do not restrict the time dimension and * fetch all children. * *

    For calculated members, effect is the same as * {@link #removeCalculatedMembers(java.util.List)}. * * @param members Array of members * @return Members with calculated members removed (except those that are * leaves in a parent-child hierarchy) and with members that are the * default member of their hierarchy */ private static Member[] removeCalculatedAndDefaultMembers( Member[] members) { List memberList = new ArrayList(members.length); for (int i = 0; i < members.length; ++i) { Member member = members[i]; // Skip calculated members (except if leaf of parent-child hier) if (member.isCalculated() && !member.isParentChildLeaf()) { continue; } // Remove members that are the default for their hierarchy, // except for the measures hierarchy. if (i > 0 && member.getHierarchy().getDefaultMember().equals(member)) { continue; } memberList.add(member); } return memberList.toArray(new Member[memberList.size()]); } static List removeCalculatedMembers(List members) { return new FilteredIterableList( members, new FilteredIterableList.Filter() { public boolean accept(final Member m) { return !m.isCalculated() || m.isParentChildLeaf(); } }); } public static boolean containsCalculatedMember(Member[] members) { for (Member member : members) { if (member.isCalculated()) { return true; } } return false; } /** * Ensures that the table of level is joined to the fact * table * * @param sqlQuery sql query under construction * @param aggStar * @param e evaluator corresponding to query * @param level level to be added to query */ public static void joinLevelTableToFactTable( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar, Evaluator e, RolapCubeLevel level) { RolapStar.Column starColumn = level.getBaseStarKeyColumn(baseCube); if (aggStar != null) { int bitPos = starColumn.getBitPosition(); AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); AggStar.Table table = aggColumn.getTable(); table.addToFrom(sqlQuery, false, true); } else { RolapStar.Table table = starColumn.getTable(); assert table != null; table.addToFrom(sqlQuery, false, true); } } /** * Creates a "WHERE parent = value" constraint. * * @param sqlQuery the query to modify * @param baseCube base cube if virtual * @param aggStar Definition of the aggregate table, or null * @param parent the list of parent members * @param restrictMemberTypes defines the behavior if parent * is a calculated member. If true, an exception is thrown */ public static void addMemberConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar, RolapMember parent, boolean restrictMemberTypes) { List list = Collections.singletonList(parent); boolean exclude = false; addMemberConstraint( sqlQuery, baseCube, aggStar, list, restrictMemberTypes, false, exclude); } /** * Creates a "WHERE exp IN (...)" condition containing the values * of all parents. All parents must belong to the same level. * *

    If this constraint is part of a native cross join, there are * multiple constraining members, and the members comprise the cross * product of all unique member keys referenced at each level, then * generating IN expressions would result in incorrect results. In that * case, "WHERE ((level1 = val1a AND level2 = val2a AND ...) * OR (level1 = val1b AND level2 = val2b AND ...) OR ..." is generated * instead. * * @param sqlQuery the query to modify * @param baseCube base cube if virtual * @param aggStar (not used) * @param members the list of members for this constraint * @param restrictMemberTypes defines the behavior if parents * contains calculated members. * If true, and one of the members is calculated, an exception is thrown. * @param crossJoin true if constraint is being generated as part of * a native crossjoin * @param exclude whether to exclude the members in the SQL predicate. * e.g. not in { member list}. */ public static void addMemberConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar, List members, boolean restrictMemberTypes, boolean crossJoin, boolean exclude) { if (members.size() == 0) { // Generate a predicate which is always false in order to produce // the empty set. It would be smarter to avoid executing SQL at // all in this case, but doing it this way avoid special-case // evaluation code. String predicate = "(1 = 0)"; if (exclude) { predicate = "(1 = 1)"; } sqlQuery.addWhere(predicate); return; } // Find out the first(lowest) unique parent level. // Only need to compare members up to that level. RolapMember member = members.get(0); RolapLevel memberLevel = member.getLevel(); RolapMember firstUniqueParent = member; RolapLevel firstUniqueParentLevel = null; for (; firstUniqueParent != null && !firstUniqueParent.getLevel().isUnique(); firstUniqueParent = firstUniqueParent.getParentMember()) { } if (firstUniqueParent != null) { // There's a unique parent along the hierarchy firstUniqueParentLevel = firstUniqueParent.getLevel(); } String condition = "("; // If this constraint is part of a native cross join and there // are multiple values for the parent members, then we can't // use single value IN clauses if (crossJoin && !memberLevel.isUnique() && !membersAreCrossProduct(members)) { assert (member.getParentMember() != null); condition += constrainMultiLevelMembers( sqlQuery, baseCube, aggStar, members, firstUniqueParentLevel, restrictMemberTypes, exclude); } else { condition += generateSingleValueInExpr( sqlQuery, baseCube, aggStar, members, firstUniqueParentLevel, restrictMemberTypes, exclude); } if (condition.length() > 1) { // condition is not empty condition += ")"; sqlQuery.addWhere(condition); } } private static StarColumnPredicate getColumnPredicates( RolapStar.Column column, Collection members) { switch (members.size()) { case 0: return new LiteralStarPredicate(column, false); case 1: return new MemberColumnPredicate(column, members.iterator().next()); default: List predicateList = new ArrayList(); for (RolapMember member : members) { predicateList.add(new MemberColumnPredicate(column, member)); } return new ListColumnPredicate(column, predicateList); } } private static LinkedHashSet getUniqueParentMembers( Collection members) { LinkedHashSet set = new LinkedHashSet(); for (RolapMember m : members) { m = m.getParentMember(); if (m != null) { set.add(m); } } return set; } /** * Adds to the where clause of a query expression matching a specified * list of members * * @param sqlQuery query containing the where clause * @param baseCube base cube if virtual * @param aggStar aggregate star if available * @param members list of constraining members * @param fromLevel lowest parent level that is unique * @param restrictMemberTypes defines the behavior when calculated members * are present * @param exclude whether to exclude the members. Default is false. * * @return a non-empty String if SQL is generated for the multi-level * member list. */ private static String constrainMultiLevelMembers( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar, List members, RolapLevel fromLevel, boolean restrictMemberTypes, boolean exclude) { // Use LinkedHashMap so that keySet() is deterministic. Map> parentChildrenMap = new LinkedHashMap>(); StringBuilder condition = new StringBuilder(); StringBuilder condition1 = new StringBuilder(); if (exclude) { condition.append("not ("); } // First try to generate IN list for all members if (sqlQuery.getDialect().supportsMultiValueInExpr()) { condition1.append( generateMultiValueInExpr( sqlQuery, baseCube, aggStar, members, fromLevel, restrictMemberTypes, parentChildrenMap)); // The members list might contain NULL values in the member levels. // // e.g. // [USA].[CA].[San Jose] // [null].[null].[San Francisco] // [null].[null].[Los Angeles] // [null].[CA].[San Diego] // [null].[CA].[Sacramento] // // Pick out such members to generate SQL later. // These members are organized in a map that maps the parant levels // containing NULL to all its children members in the list. e.g. // the member list above becomes the following map, after SQL is // generated for [USA].[CA].[San Jose] in the call above. // // [null].[null]->([San Francisco], [Los Angeles]) // [null]->([CA].[San Diego], [CA].[Sacramento]) // if (parentChildrenMap.isEmpty()) { condition.append(condition1.toString()); if (exclude) { // If there are no NULL values in the member levels, then // we're done except we need to also explicitly include // members containing nulls across all levels. condition.append(")"); condition.append(" or "); condition.append( generateMultiValueIsNullExprs( sqlQuery, baseCube, members.get(0), fromLevel)); } return condition.toString(); } } else { // Multi-value IN list not supported // Classify members into List that share the same parent. // // Using the same example as above, the resulting map will be // [USA].[CA]->[San Jose] // [null].[null]->([San Francisco], [Los Angesles]) // [null].[CA]->([San Diego],[Sacramento]) // // The idea is to be able to "compress" the original member list // into groups that can use single value IN list for part of the // comparison that does not involve NULLs // for (RolapMember m : members) { if (m.isCalculated()) { if (restrictMemberTypes) { throw Util.newInternal( "addMemberConstraint: cannot " + "restrict SQL to calculated member :" + m); } continue; } RolapMember p = m.getParentMember(); List childrenList = parentChildrenMap.get(p); if (childrenList == null) { childrenList = new ArrayList(); parentChildrenMap.put(p, childrenList); } childrenList.add(m); } } // Now we try to generate predicates for the remaining // parent-children group. // Note that NULLs are not used to enforce uniqueness // so we ignore the fromLevel here. boolean firstParent = true; StringBuilder condition2 = new StringBuilder(); if (condition1.toString().length() > 0) { // Some members have already been translated into IN list. firstParent = false; condition.append(condition1.toString()); condition.append(" or "); } RolapLevel memberLevel = members.get(0).getLevel(); // The children for each parent are turned into IN list so they // should not contain null. for (RolapMember p : parentChildrenMap.keySet()) { assert p != null; if (condition2.toString().length() > 0) { condition2.append(" or "); } condition2.append("("); // First generate ANDs for all members in the parent lineage of // this parent-children group int levelCount = 0; for (RolapMember gp = p; gp != null; gp = gp.getParentMember()) { if (gp.isAll()) { // Ignore All member // Get the next parent continue; } RolapLevel level = gp.getLevel(); // add the level to the FROM clause if this is the // first parent-children group we're generating sql for if (firstParent) { RolapHierarchy hierarchy = level.getHierarchy(); // this method can be called within the context of shared // members, outside of the normal rolap star, therefore // we need to check the level to see if it is a shared or // cube level. RolapStar.Column column = null; if (level instanceof RolapCubeLevel) { column = ((RolapCubeLevel)level).getBaseStarKeyColumn( baseCube); } if (column != null) { if (aggStar != null) { int bitPos = column.getBitPosition(); AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); AggStar.Table table = aggColumn.getTable(); table.addToFrom(sqlQuery, false, true); } else { RolapStar.Table targetTable = column.getTable(); hierarchy.addToFrom(sqlQuery, targetTable); } } else { assert (aggStar == null); hierarchy.addToFrom(sqlQuery, level.getKeyExp()); } } if (levelCount > 0) { condition2.append(" and "); } ++levelCount; condition2.append( constrainLevel( level, sqlQuery, baseCube, aggStar, getColumnValue( level.nameExp != null ? gp.getName() : gp.getKey(), sqlQuery.getDialect(), level.getDatatype()), false)); if (gp.getLevel() == fromLevel) { // SQL is completely generated for this parent break; } } firstParent = false; // Next, generate children for this parent-children group List children = parentChildrenMap.get(p); // If no children to be generated for this parent then we are done if (!children.isEmpty()) { Map> tmpParentChildrenMap = new HashMap>(); if (levelCount > 0) { condition2.append(" and "); } RolapLevel childrenLevel = (RolapLevel)(p.getLevel().getChildLevel()); if (sqlQuery.getDialect().supportsMultiValueInExpr() && childrenLevel != memberLevel) { // Multi-level children and multi-value IN list supported condition2.append( generateMultiValueInExpr( sqlQuery, baseCube, aggStar, children, childrenLevel, restrictMemberTypes, tmpParentChildrenMap)); assert tmpParentChildrenMap.isEmpty(); } else { // Can only be single level children // If multi-value IN list not supported, children will be on // the same level as members list. Only single value IN list // needs to be generated for this case. assert childrenLevel == memberLevel; condition2.append( generateSingleValueInExpr( sqlQuery, baseCube, aggStar, children, childrenLevel, restrictMemberTypes, false)); } } // SQL is complete for this parent-children group. condition2.append(")"); } // In the case where multi-value IN expressions are not generated, // condition2 contains the entire filter condition. In the // case of excludes, we also need to explicitly include null values, // minus the ones that are referenced in condition2. Therefore, // we OR on a condition that corresponds to an OR'ing of IS NULL // filters on each level PLUS an exclusion of condition2. // // Note that the expression generated is non-optimal in the case where // multi-value IN's cannot be used because we end up excluding // non-null values as well as the null ones. Ideally, we only need to // exclude the expressions corresponding to nulls, which is possible // in the multi-value IN case, since we have a map of the null values. condition.append(condition2.toString()); if (exclude) { condition.append(") or ("); condition.append( generateMultiValueIsNullExprs( sqlQuery, baseCube, members.get(0), fromLevel)); condition.append(" and not("); condition.append(condition2.toString()); condition.append("))"); } return condition.toString(); } /** * @param members list of members * * @return true if the members comprise the cross product of all unique * member keys referenced at each level */ private static boolean membersAreCrossProduct(List members) { int crossProdSize = getNumUniqueMemberKeys(members); for (Collection parents = getUniqueParentMembers(members); !parents.isEmpty(); parents = getUniqueParentMembers(parents)) { crossProdSize *= parents.size(); } return (crossProdSize == members.size()); } /** * @param members list of members * * @return number of unique member keys in a list of members */ private static int getNumUniqueMemberKeys(List members) { final HashSet set = new HashSet(); for (RolapMember m : members) { set.add(m.getKey()); } return set.size(); } /** * @param key key corresponding to a member * @param dialect sql dialect being used * @param datatype data type of the member * * @return string value corresponding to the member */ private static String getColumnValue( Object key, Dialect dialect, Dialect.Datatype datatype) { if (key != RolapUtil.sqlNullValue) { return key.toString(); } else { return RolapUtil.mdxNullLiteral(); } } /** * Generates a sql expression constraining a level by some value * * @param level the level * @param query the query that the sql expression will be added to * @param baseCube base cube for virtual levels * @param aggStar aggregate star if available * @param columnValue value constraining the level * @param caseSensitive if true, need to handle case sensitivity of the * member value * * @return generated string corresponding to the expression */ public static String constrainLevel( RolapLevel level, SqlQuery query, RolapCube baseCube, AggStar aggStar, String columnValue, boolean caseSensitive) { // this method can be called within the context of shared members, // outside of the normal rolap star, therefore we need to // check the level to see if it is a shared or cube level. RolapStar.Column column = null; if (level instanceof RolapCubeLevel) { column = ((RolapCubeLevel)level).getBaseStarKeyColumn(baseCube); } String columnString; Dialect.Datatype datatype; if (column != null) { if (column.getNameColumn() == null) { datatype = level.getDatatype(); } else { column = column.getNameColumn(); // The schema doesn't specify the datatype of the name column, // but we presume that it is a string. datatype = Dialect.Datatype.String; } if (aggStar != null) { // this makes the assumption that the name column is the same // as the key column int bitPos = column.getBitPosition(); AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); columnString = aggColumn.generateExprString(query); } else { columnString = column.generateExprString(query); } } else { assert (aggStar == null); MondrianDef.Expression exp = level.getNameExp(); if (exp == null) { exp = level.getKeyExp(); datatype = level.getDatatype(); } else { // The schema doesn't specify the datatype of the name column, // but we presume that it is a string. datatype = Dialect.Datatype.String; } columnString = exp.getExpression(query); } String constraint; if (RolapUtil.mdxNullLiteral().equalsIgnoreCase(columnValue)) { constraint = columnString + " is " + RolapUtil.sqlNullLiteral; } else { if (datatype.isNumeric()) { // make sure it can be parsed Double.valueOf(columnValue); } final StringBuilder buf = new StringBuilder(); query.getDialect().quote(buf, columnValue, datatype); String value = buf.toString(); if (caseSensitive && datatype == Dialect.Datatype.String) { // Some databases (like DB2) compare case-sensitive. We convert // the value to upper-case in the DBMS (e.g. UPPER('Foo')) // rather than in Java (e.g. 'FOO') in case the DBMS is running // a different locale. if (!MondrianProperties.instance().CaseSensitive.get()) { columnString = query.getDialect().toUpper(columnString); value = query.getDialect().toUpper(value); } } constraint = columnString + " = " + value; } return constraint; } /** * Generates a multi-value IN expression corresponding to a list of * member expressions, and adds the expression to the WHERE clause * of a query, provided the member values are all non-null * * @param sqlQuery query containing the where clause * @param baseCube base cube if virtual * @param aggStar aggregate star if available * @param members list of constraining members * @param fromLevel lowest parent level that is unique * @param restrictMemberTypes defines the behavior when calculated members * are present * @param parentWithNullToChildrenMap upon return this map contains members * that have Null values in its (parent) levels * @return a non-empty String if multi-value IN list was generated for some * members */ private static String generateMultiValueInExpr( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar, List members, RolapLevel fromLevel, boolean restrictMemberTypes, Map> parentWithNullToChildrenMap) { final StringBuilder columnBuf = new StringBuilder(); final StringBuilder valueBuf = new StringBuilder(); final StringBuilder memberBuf = new StringBuilder(); columnBuf.append("("); // generate the left-hand side of the IN expression int ordinalInMultiple = 0; for (RolapMember m = members.get(0); m != null; m = m.getParentMember()) { if (m.isAll()) { continue; } RolapLevel level = m.getLevel(); RolapHierarchy hierarchy = level.getHierarchy(); // this method can be called within the context of shared members, // outside of the normal rolap star, therefore we need to // check the level to see if it is a shared or cube level. RolapStar.Column column = null; if (level instanceof RolapCubeLevel) { column = ((RolapCubeLevel)level).getBaseStarKeyColumn(baseCube); } // REVIEW: The following code mostly uses the name column (or name // expression) of the level. Shouldn't it use the key column (or key // expression)? String columnString; if (column != null) { if (aggStar != null) { // this assumes that the name column is identical to the // id column int bitPos = column.getBitPosition(); AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); AggStar.Table table = aggColumn.getTable(); table.addToFrom(sqlQuery, false, true); columnString = aggColumn.generateExprString(sqlQuery); } else { RolapStar.Table targetTable = column.getTable(); hierarchy.addToFrom(sqlQuery, targetTable); columnString = column.generateExprString(sqlQuery); } } else { assert (aggStar == null); hierarchy.addToFrom(sqlQuery, level.getKeyExp()); MondrianDef.Expression nameExp = level.getNameExp(); if (nameExp == null) { nameExp = level.getKeyExp(); } columnString = nameExp.getExpression(sqlQuery); } if (ordinalInMultiple++ > 0) { columnBuf.append(", "); } columnBuf.append(columnString); // Only needs to compare up to the first(lowest) unique level. if (m.getLevel() == fromLevel) { break; } } columnBuf.append(")"); // generate the RHS of the IN predicate valueBuf.append("("); int memberOrdinal = 0; for (RolapMember m : members) { if (m.isCalculated()) { if (restrictMemberTypes) { throw Util.newInternal( "addMemberConstraint: cannot " + "restrict SQL to calculated member :" + m); } continue; } ordinalInMultiple = 0; memberBuf.setLength(0); memberBuf.append("("); boolean containsNull = false; for (RolapMember p = m; p != null; p = p.getParentMember()) { if (p.isAll()) { // Ignore the ALL level. // Generate SQL condition for the next level continue; } RolapLevel level = p.getLevel(); String value = getColumnValue( p.getKey(), sqlQuery.getDialect(), level.getDatatype()); // If parent at a level is NULL, record this parent and all // its children(if there's any) if (RolapUtil.mdxNullLiteral().equalsIgnoreCase(value)) { // Add to the nullParent map List childrenList = parentWithNullToChildrenMap.get(p); if (childrenList == null) { childrenList = new ArrayList(); parentWithNullToChildrenMap.put(p, childrenList); } // If p has children if (m != p) { childrenList.add(m); } // Skip generating condition for this parent containsNull = true; break; } if (ordinalInMultiple++ > 0) { memberBuf.append(", "); } sqlQuery.getDialect().quote( memberBuf, value, level.getDatatype()); // Only needs to compare up to the first(lowest) unique level. if (p.getLevel() == fromLevel) { break; } } // Now check if sql string is sucessfully generated for this member. // If parent levels do not contain NULL then SQL must have been // generated successfully. if (!containsNull) { memberBuf.append(")"); if (memberOrdinal++ > 0) { valueBuf.append(", "); } valueBuf.append(memberBuf); } } StringBuilder condition = new StringBuilder(); if (memberOrdinal > 0) { // SQLs are generated for some members. condition.append(columnBuf); condition.append(" in "); condition.append(valueBuf); condition.append(")"); } return condition.toString(); } /** * Generates an expression that is an OR of IS NULL expressions, one * per level in a RolapMember. * * @param sqlQuery query corresponding to the expression * @param baseCube base cube if virtual * @param member the RolapMember * @param fromLevel lowest parent level that is unique * * @return the text of the expression */ private static String generateMultiValueIsNullExprs( SqlQuery sqlQuery, RolapCube baseCube, RolapMember member, RolapLevel fromLevel) { final StringBuilder conditionBuf = new StringBuilder(); conditionBuf.append("("); // generate the left-hand side of the IN expression boolean isFirstLevelInMultiple = true; for (RolapMember m = member; m != null; m = m.getParentMember()) { if (m.isAll()) { continue; } RolapLevel level = m.getLevel(); // this method can be called within the context of shared members, // outside of the normal rolap star, therefore we need to // check the level to see if it is a shared or cube level. RolapStar.Column column = null; if (level instanceof RolapCubeLevel) { column = ((RolapCubeLevel)level).getBaseStarKeyColumn(baseCube); } String columnString; if (column != null) { RolapStar.Column nameColumn = column.getNameColumn(); if (nameColumn == null) { nameColumn = column; } columnString = nameColumn.generateExprString(sqlQuery); } else { MondrianDef.Expression nameExp = level.getNameExp(); if (nameExp == null) { nameExp = level.getKeyExp(); } columnString = nameExp.getExpression(sqlQuery); } if (!isFirstLevelInMultiple) { conditionBuf.append(" or "); } else { isFirstLevelInMultiple = false; } conditionBuf.append(columnString); conditionBuf.append(" is null"); // Only needs to compare up to the first(lowest) unique level. if (m.getLevel() == fromLevel) { break; } } conditionBuf.append(")"); return conditionBuf.toString(); } /** * Generates a multi-value IN expression corresponding to a list of * member expressions, and adds the expression to the WHERE clause * of a query, provided the member values are all non-null * * @param sqlQuery query containing the where clause * @param baseCube base cube if virtual * @param aggStar aggregate star if available * @param members list of constraining members * @param fromLevel lowest parent level that is unique * @param restrictMemberTypes defines the behavior when calculated members * are present * @param exclude whether to exclude the members. Default is false. * @return a non-empty String if IN list was generated for the members. */ private static String generateSingleValueInExpr( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar, List members, RolapLevel fromLevel, boolean restrictMemberTypes, boolean exclude) { int maxConstraints = MondrianProperties.instance().MaxConstraints.get(); Dialect dialect = sqlQuery.getDialect(); String condition = ""; boolean firstLevel = true; for (Collection c = members; !c.isEmpty(); c = getUniqueParentMembers(c)) { RolapMember m = c.iterator().next(); if (m.isAll()) { continue; } if (m.isNull()) { return "1 = 0"; } if (m.isCalculated() && !m.isParentChildLeaf()) { if (restrictMemberTypes) { throw Util.newInternal( "addMemberConstraint: cannot " + "restrict SQL to calculated member :" + m); } continue; } boolean containsNullKey = false; Iterator it = c.iterator(); while (it.hasNext()) { m = it.next(); if (m.getKey() == RolapUtil.sqlNullValue) { containsNullKey = true; } } RolapLevel level = m.getLevel(); RolapHierarchy hierarchy = level.getHierarchy(); // this method can be called within the context of shared members, // outside of the normal rolap star, therefore we need to // check the level to see if it is a shared or cube level. RolapStar.Column column = null; if (level instanceof RolapCubeLevel) { column = ((RolapCubeLevel)level).getBaseStarKeyColumn(baseCube); } String q; if (column != null) { if (aggStar != null) { int bitPos = column.getBitPosition(); AggStar.Table.Column aggColumn = aggStar.lookupColumn(bitPos); if (aggColumn == null) { throw Util.newInternal( "AggStar " + aggStar + " has no column for " + column + " (bitPos " + bitPos + ")"); } AggStar.Table table = aggColumn.getTable(); table.addToFrom(sqlQuery, false, true); q = aggColumn.generateExprString(sqlQuery); } else { RolapStar.Table targetTable = column.getTable(); hierarchy.addToFrom(sqlQuery, targetTable); q = column.generateExprString(sqlQuery); } } else { assert (aggStar == null); hierarchy.addToFrom(sqlQuery, level.getKeyExp()); q = level.getKeyExp().getExpression(sqlQuery); } StarColumnPredicate cc = getColumnPredicates(column, c); if (!dialect.supportsUnlimitedValueList() && cc instanceof ListColumnPredicate && ((ListColumnPredicate) cc).getPredicates().size() > maxConstraints) { // Simply get them all, do not create where-clause. // Below are two alternative approaches (and code). They // both have problems. } else { String where = RolapStar.Column.createInExpr( q, cc, level.getDatatype(), sqlQuery); if (!where.equals("true")) { if (!firstLevel) { if (exclude) { condition += " or "; } else { condition += " and "; } } else { firstLevel = false; } if (exclude) { where = "not (" + where + ")"; if (!containsNullKey) { // Null key fails all filters so should add it here // if not already excluded. E.g., if the original // exclusion filter is : // // not(year = '1997' and quarter in ('Q1','Q3')) // // then with IS NULL checks added, the filter // becomes: // // (not(year = '1997') or year is null) or // (not(quarter in ('Q1','Q3')) or quarter is null) where = "(" + where + " or " + "(" + q + " is null))"; } } condition += where; } } if (m.getLevel().isUnique() || m.getLevel() == fromLevel) { break; // no further qualification needed } } return condition; } } // End SqlConstraintUtils.java mondrian-3.4.1/src/main/mondrian/rolap/RolapCubeMember.java0000644000175000017500000001777411735330606023554 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.mdx.HierarchyExpr; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.fun.VisualTotalsFunDef.VisualTotalMember; import mondrian.util.Bug; /** * RolapCubeMember wraps RolapMembers and binds them to a specific cube. * RolapCubeMember wraps or overrides RolapMember methods that directly * reference the wrapped Member. Methods that only contain calls to other * methods do not need wrapped. * * @author Will Gorman, 19 October 2007 */ public class RolapCubeMember extends DelegatingRolapMember implements RolapMemberInCube { protected final RolapCubeLevel cubeLevel; protected final RolapCubeMember parentCubeMember; /** * Creates a RolapCubeMember. * * @param parent Parent member * @param member Member of underlying (non-cube) hierarchy * @param cubeLevel Level */ public RolapCubeMember( RolapCubeMember parent, RolapMember member, RolapCubeLevel cubeLevel) { super(member); this.parentCubeMember = parent; this.cubeLevel = cubeLevel; assert !member.isAll() || getClass() != RolapCubeMember.class; } @Override public String getUniqueName() { // We are making a hard design decision to compute uniqueName every // time it is requested rather than storing it. RolapCubeMember is thin // wrapper, so cheap to construct that we don't need to cache instances. // // Storing uniqueName would make creation of RolapCubeMember more // expensive and use significantly more memory, so we don't do that. // That meakes each call to getUniqueName more expensive, so we try to // minimize the number of calls to this method. return cubeLevel.getHierarchy().convertMemberName( member.getUniqueName()); } /** * Returns the underlying member. This is a member of a shared dimension and * does not belong to a cube. * * @return Underlying member */ public final RolapMember getRolapMember() { return member; } // final is important for performance public final RolapCube getCube() { return cubeLevel.getCube(); } public final RolapCubeMember getDataMember() { RolapMember member = (RolapMember) super.getDataMember(); if (member == null) { return null; } return new RolapCubeMember(parentCubeMember, member, cubeLevel); } public int compareTo(Object o) { // light wrapper around rolap member compareTo RolapCubeMember other = null; if (o instanceof VisualTotalMember) { // REVIEW: Maybe VisualTotalMember should extend/implement // RolapCubeMember. Then we can remove special-cases such as this. other = (RolapCubeMember) ((VisualTotalMember) o).getMember(); } else { other = (RolapCubeMember) o; } return member.compareTo(other.member); } public String toString() { return getUniqueName(); } public int hashCode() { return member.hashCode(); } public boolean equals(Object o) { if (o == this) { return true; } if (o instanceof RolapCubeMember) { return equals((RolapCubeMember) o); } if (o instanceof Member) { assert !Bug.BugSegregateRolapCubeMemberFixed; return getUniqueName().equals(((Member) o).getUniqueName()); } return false; } public boolean equals(OlapElement o) { return o.getClass() == RolapCubeMember.class && equals((RolapCubeMember) o); } private boolean equals(RolapCubeMember that) { assert that != null; // public method should have checked // Assume that RolapCubeLevel is canonical. (Besides, its equals method // is very slow.) return this.cubeLevel == that.cubeLevel && this.member.equals(that.member); } // override with stricter return type; final important for performance public final RolapCubeHierarchy getHierarchy() { return cubeLevel.getHierarchy(); } // override with stricter return type; final important for performance public final RolapCubeDimension getDimension() { return cubeLevel.getDimension(); } /** * {@inheritDoc} * *

    This method is central to how RolapCubeMember works. It allows * a member from the cache to be used within different usages of the same * shared dimension. The cache member is the same, but the RolapCubeMembers * wrapping the cache member report that they belong to different levels, * and hence different hierarchies, dimensions, and cubes. */ // override with stricter return type; final important for performance public final RolapCubeLevel getLevel() { return cubeLevel; } public void setProperty(String name, Object value) { synchronized (this) { super.setProperty(name, value); } } public Object getPropertyValue(String propertyName, boolean matchCase) { // we need to wrap these children as rolap cube members Property property = Property.lookup(propertyName, matchCase); if (property != null) { switch (property.ordinal) { case Property.DIMENSION_UNIQUE_NAME_ORDINAL: return getDimension().getUniqueName(); case Property.HIERARCHY_UNIQUE_NAME_ORDINAL: return getHierarchy().getUniqueName(); case Property.LEVEL_UNIQUE_NAME_ORDINAL: return getLevel().getUniqueName(); case Property.MEMBER_UNIQUE_NAME_ORDINAL: return getUniqueName(); case Property.MEMBER_NAME_ORDINAL: return getName(); case Property.MEMBER_CAPTION_ORDINAL: return getCaption(); case Property.PARENT_UNIQUE_NAME_ORDINAL: return parentCubeMember == null ? null : parentCubeMember.getUniqueName(); case Property.MEMBER_KEY_ORDINAL: case Property.KEY_ORDINAL: return this == this.getHierarchy().getAllMember() ? 0 : getKey(); } } // fall through to rolap member return member.getPropertyValue(propertyName, matchCase); } public final RolapCubeMember getParentMember() { return parentCubeMember; } // this method is overridden to make sure that any HierarchyExpr returns // the cube hierarchy vs. shared hierarchy. this is the case for // SqlMemberSource.RolapParentChildMemberNoClosure public Exp getExpression() { Exp exp = member.getExpression(); if (exp instanceof ResolvedFunCall) { // convert any args to RolapCubeHierarchies ResolvedFunCall fcall = (ResolvedFunCall)exp; for (int i = 0; i < fcall.getArgCount(); i++) { if (fcall.getArg(i) instanceof HierarchyExpr) { HierarchyExpr expr = (HierarchyExpr)fcall.getArg(i); if (expr.getHierarchy().equals( member.getHierarchy())) { fcall.getArgs()[i] = new HierarchyExpr(this.getHierarchy()); } } } } return exp; } public OlapElement lookupChild( SchemaReader schemaReader, Id.Segment childName, MatchType matchType) { return schemaReader.lookupMemberChildByName(this, childName, matchType); } } // End RolapCubeMember.java mondrian-3.4.1/src/main/mondrian/rolap/CellKey.java0000644000175000017500000004432511735330606022070 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. // // jhyde, 10 August, 2001 */ package mondrian.rolap; import java.io.Serializable; import java.util.Arrays; /** * A CellKey is used as a key in maps which access cells by their * position. * *

    CellKey is also used within * {@link mondrian.rolap.agg.SparseSegmentDataset} to store values within * aggregations. * *

    It is important that CellKey is memory-efficient, and that the * {@link Object#hashCode} and {@link Object#equals} methods are extremely * efficient. There are particular implementations for the most likely cases * where the number of axes is 1, 2, 3 and 4 as well as a general * implementation. * *

    To create a key, call the * {@link mondrian.rolap.CellKey.Generator#newCellKey(int[])} method. * * @author jhyde * @since 10 August, 2001 */ public interface CellKey extends Serializable { /** * Returns the number of axes. * * @return number of axes */ int size(); /** * Returns the axis keys as an array. * *

    Note: caller should treat the array as immutable. If the contents of * the array are modified, behavior is unspecified. * * @return Array of axis keys */ int[] getOrdinals(); /** * This method make a copy of the int array parameter. * Throws a RuntimeException if the int array size is not the * size of the CellKey. * * @param pos Array of axis keys */ void setOrdinals(int[] pos); /** * Returns the axisth axis value. * * @param axis Axis ordinal * @return Value of the axisth axis * @throws ArrayIndexOutOfBoundsException if axis is out of range */ int getAxis(int axis); /** * Sets a given axis. * * @param axis Axis ordinal * @param value Value * @throws RuntimeException if axis parameter is larger than {@link #size()} */ void setAxis(int axis, int value); /** * Returns a mutable copy of this CellKey. * * @return Mutable copy */ CellKey copy(); /** * Returns the offset of the cell in a raster-scan order. * *

    For example, if the axes have lengths {2, 5, 10} then cell (2, 3, 4) * has offset * *

    * (2 * mulitiplers[0]) + (3 * multipliers[1]) + (4 * multipliers[2])
    * = (2 * 50) + (3 * 10) + (4 * 1)
    * = 134
    * *

    The multipliers are the product of the lengths of all later axes, in * this case (50, 10, 1). * * @param axisMultipliers For each axis, the product of the lengths of later * axes * @return The raster-scan ordinal of this cell */ int getOffset(int[] axisMultipliers); public abstract class Generator { /** * Creates a CellKey with a given number of axes. * * @param size Number of axes * @return new CellKey */ public static CellKey newCellKey(int size) { switch (size) { case 0: return Zero.INSTANCE; case 1: return new One(0); case 2: return new Two(0, 0); case 3: return new Three(0, 0, 0); case 4: return new Four(0, 0, 0, 0); default: return new Many(new int[size]); } } /** * Creates a CellKey populated with the given coordinates. * * @param pos Coordinate array * @return CellKey */ public static CellKey newCellKey(int[] pos) { switch (pos.length) { case 0: return Zero.INSTANCE; case 1: return new One(pos[0]); case 2: return new Two(pos[0], pos[1]); case 3: return new Three(pos[0], pos[1], pos[2]); case 4: return new Four(pos[0], pos[1], pos[2], pos[3]); default: return new Many(pos.clone()); } } /** * Creates a CellKey implemented by an array to hold the coordinates, * regardless of the size. * *

    This is used for testing only. The CellKey will fail to compare * equal to naturally created keys of size 0, 1, 2 or 3. * * @param size Number of coordinates * @return CallKey */ static CellKey newManyCellKey(int size) { return new Many(new int[size]); } public static int getOffset( int[] ordinals, int[] axisMultipliers) { // TODO: We know that axisMultiplers[ordinals.length - 1] == 1. Can // we avoid multiplying by it? int offset = 0; for (int i = 0; i < ordinals.length; i++) { offset += ordinals[i] * axisMultipliers[i]; } return offset; } } public class Zero implements CellKey { private static final long serialVersionUID = 6063541581473797367L; private static final int[] EMPTY_INT_ARRAY = new int[0]; public static final Zero INSTANCE = new Zero(); /** * Use singleton {@link #INSTANCE}. */ private Zero() { } public Zero copy() { // no need to make copy since there is no state return this; } public int getOffset(int[] axisMultipliers) { return 0; } public boolean equals(Object o) { return o == this; } public int hashCode() { return 11; } public int size() { return 0; } public int[] getOrdinals() { return EMPTY_INT_ARRAY; } public void setOrdinals(int[] pos) { if (pos.length != 0) { throw new IllegalArgumentException(); } } public int getAxis(int axis) { throw new ArrayIndexOutOfBoundsException(axis); } public void setAxis(int axis, int value) { throw new ArrayIndexOutOfBoundsException(axis); } } public class One implements CellKey { private static final long serialVersionUID = 2160238882970820960L; private int ordinal0; /** * Creates a One. * * @param ordinal0 Ordinate #0 */ private One(int ordinal0) { this.ordinal0 = ordinal0; } public int size() { return 1; } public int[] getOrdinals() { return new int[] {ordinal0}; } public void setOrdinals(int[] pos) { if (pos.length != 1) { throw new IllegalArgumentException(); } ordinal0 = pos[0]; } public int getAxis(int axis) { switch (axis) { case 0: return ordinal0; default: throw new ArrayIndexOutOfBoundsException(axis); } } public void setAxis(int axis, int value) { switch (axis) { case 0: ordinal0 = value; break; default: throw new ArrayIndexOutOfBoundsException(axis); } } public One copy() { return new One(ordinal0); } public int getOffset(int[] axisMultipliers) { return ordinal0; } public boolean equals(Object o) { // here we cheat, we know that all CellKey's will be the same size if (o instanceof One) { One other = (One) o; return (this.ordinal0 == other.ordinal0); } else { return false; } } public String toString() { return "(" + ordinal0 + ")"; } public int hashCode() { return 17 + ordinal0; } } public class Two implements CellKey { private static final long serialVersionUID = 1901188836648369359L; private int ordinal0; private int ordinal1; /** * Creates a Two. * * @param ordinal0 Ordinate #0 * @param ordinal1 Ordinate #1 */ private Two(int ordinal0, int ordinal1) { this.ordinal0 = ordinal0; this.ordinal1 = ordinal1; } public String toString() { return "(" + ordinal0 + ", " + ordinal1 + ")"; } public Two copy() { return new Two(ordinal0, ordinal1); } public int getOffset(int[] axisMultipliers) { return ordinal0 * axisMultipliers[0] + ordinal1; } public boolean equals(Object o) { if (o instanceof Two) { Two other = (Two) o; return (other.ordinal0 == this.ordinal0) && (other.ordinal1 == this.ordinal1); } else { return false; } } public int hashCode() { int h0 = 17 + ordinal0; return h0 * 37 + ordinal1; } public int size() { return 2; } public int[] getOrdinals() { return new int[] {ordinal0, ordinal1}; } public void setOrdinals(int[] pos) { if (pos.length != 2) { throw new IllegalArgumentException(); } ordinal0 = pos[0]; ordinal1 = pos[1]; } public int getAxis(int axis) { switch (axis) { case 0: return ordinal0; case 1: return ordinal1; default: throw new ArrayIndexOutOfBoundsException(axis); } } public void setAxis(int axis, int value) { switch (axis) { case 0: ordinal0 = value; break; case 1: ordinal1 = value; break; default: throw new ArrayIndexOutOfBoundsException(axis); } } } class Three implements CellKey { private static final long serialVersionUID = -2645858781233421151L; private int ordinal0; private int ordinal1; private int ordinal2; /** * Creates a Three. * * @param ordinal0 Ordinate #0 * @param ordinal1 Ordinate #1 * @param ordinal2 Ordinate #2 */ private Three(int ordinal0, int ordinal1, int ordinal2) { this.ordinal0 = ordinal0; this.ordinal1 = ordinal1; this.ordinal2 = ordinal2; } public String toString() { return "(" + ordinal0 + ", " + ordinal1 + ", " + ordinal2 + ")"; } public Three copy() { return new Three(ordinal0, ordinal1, ordinal2); } public int getOffset(int[] axisMultipliers) { return ordinal0 * axisMultipliers[0] + ordinal1 * axisMultipliers[1] + ordinal2; } public boolean equals(Object o) { // here we cheat, we know that all CellKey's will be the same size if (o instanceof Three) { Three other = (Three) o; return (other.ordinal0 == this.ordinal0) && (other.ordinal1 == this.ordinal1) && (other.ordinal2 == this.ordinal2); } else { return false; } } public int hashCode() { int h0 = 17 + ordinal0; int h1 = h0 * 37 + ordinal1; return h1 * 37 + ordinal2; } public int getAxis(int axis) { switch (axis) { case 0: return ordinal0; case 1: return ordinal1; case 2: return ordinal2; default: throw new ArrayIndexOutOfBoundsException(axis); } } public void setAxis(int axis, int value) { switch (axis) { case 0: ordinal0 = value; break; case 1: ordinal1 = value; break; case 2: ordinal2 = value; break; default: throw new ArrayIndexOutOfBoundsException(axis); } } public int size() { return 3; } public int[] getOrdinals() { return new int[] {ordinal0, ordinal1, ordinal2}; } public void setOrdinals(int[] pos) { if (pos.length != 3) { throw new IllegalArgumentException(); } ordinal0 = pos[0]; ordinal1 = pos[1]; ordinal2 = pos[2]; } } class Four implements CellKey { private static final long serialVersionUID = -2645858781233421151L; private int ordinal0; private int ordinal1; private int ordinal2; private int ordinal3; /** * Creates a Four. */ private Four( int ordinal0, int ordinal1, int ordinal2, int ordinal3) { this.ordinal0 = ordinal0; this.ordinal1 = ordinal1; this.ordinal2 = ordinal2; this.ordinal3 = ordinal3; } public String toString() { return "(" + ordinal0 + ", " + ordinal1 + ", " + ordinal2 + ", " + ordinal3 + ")"; } public Four copy() { return new Four(ordinal0, ordinal1, ordinal2, ordinal3); } public int getOffset(int[] axisMultipliers) { return ordinal0 * axisMultipliers[0] + ordinal1 * axisMultipliers[1] + ordinal2 * axisMultipliers[2] + ordinal3; } public boolean equals(Object o) { // here we cheat, we know that all CellKey's will be the same size if (o instanceof Four) { Four other = (Four) o; return (other.ordinal0 == this.ordinal0) && (other.ordinal1 == this.ordinal1) && (other.ordinal2 == this.ordinal2) && (other.ordinal3 == this.ordinal3); } else { return false; } } public int hashCode() { int h0 = 17 + ordinal0; int h1 = h0 * 37 + ordinal1; int h2 = h1 * 37 + ordinal2; return h2 * 37 + ordinal3; } public int getAxis(int axis) { switch (axis) { case 0: return ordinal0; case 1: return ordinal1; case 2: return ordinal2; case 3: return ordinal3; default: throw new ArrayIndexOutOfBoundsException(axis); } } public void setAxis(int axis, int value) { switch (axis) { case 0: ordinal0 = value; break; case 1: ordinal1 = value; break; case 2: ordinal2 = value; break; case 3: ordinal3 = value; break; default: throw new ArrayIndexOutOfBoundsException(axis); } } public int size() { return 4; } public int[] getOrdinals() { return new int[] {ordinal0, ordinal1, ordinal2, ordinal3}; } public void setOrdinals(int[] pos) { if (pos.length != 4) { throw new IllegalArgumentException(); } ordinal0 = pos[0]; ordinal1 = pos[1]; ordinal2 = pos[2]; ordinal3 = pos[3]; } } public class Many implements CellKey { private static final long serialVersionUID = 3438398157192694834L; private final int[] ordinals; /** * Creates a Many. * @param ordinals Ordinates */ protected Many(int[] ordinals) { this.ordinals = ordinals; } public final int size() { return this.ordinals.length; } public final void setOrdinals(int[] pos) { if (ordinals.length != pos.length) { throw new IllegalArgumentException(); } System.arraycopy(pos, 0, this.ordinals, 0, ordinals.length); } public final int[] getOrdinals() { return this.ordinals; } public void setAxis(int axis, int value) { this.ordinals[axis] = value; } public int getAxis(int axis) { return this.ordinals[axis]; } public String toString() { StringBuilder buf = new StringBuilder(); buf.append('('); for (int i = 0; i < ordinals.length; i++) { if (i > 0) { buf.append(','); } buf.append(ordinals[i]); } buf.append(')'); return buf.toString(); } public Many copy() { return new Many(this.ordinals.clone()); } public int getOffset(int[] axisMultipliers) { return Generator.getOffset(ordinals, axisMultipliers); } public int hashCode() { int h = 17; for (int ordinal : ordinals) { h = (h * 37) + ordinal; } return h; } public boolean equals(Object o) { if (o instanceof Many) { Many that = (Many) o; return Arrays.equals(this.ordinals, that.ordinals); } return false; } } } // End CellKey.java mondrian-3.4.1/src/main/mondrian/rolap/RolapConnectionPool.java0000644000175000017500000002077411735330606024471 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2006 Robin Bagot and others // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.Util; import org.apache.commons.dbcp.*; import org.apache.commons.pool.ObjectPool; import org.apache.commons.pool.impl.GenericObjectPool; import java.util.*; import javax.sql.DataSource; /** * Singleton class that holds a connection pool. * Call RolapConnectionPool.instance().getPoolingDataSource(connectionFactory) * to get a DataSource in return that is a pooled data source. * * @author jhyde * @author Robin Bagot * @since 7 July, 2003 */ class RolapConnectionPool { public static RolapConnectionPool instance() { return instance; } private static final RolapConnectionPool instance = new RolapConnectionPool(); private final Map mapConnectKeyToPool = new HashMap(); private final Map dataSourceMap = new WeakHashMap(); private RolapConnectionPool() { } /** * Sets up a pooling data source for connection pooling. * This can be used if the application server does not have a pooling * dataSource already configured. * *

    This takes a normal jdbc connection string, and requires a jdbc * driver to be loaded, and then uses a * {@link DriverManagerConnectionFactory} to create connections to the * database. * *

    An alternative method of configuring a pooling driver is to use an * external configuration file. See the the Apache jakarta-commons * commons-pool documentation. * * @param key Identifies which connection factory to use. A typical key is * a JDBC connect string, since each JDBC connect string requires a * different connection factory. * @param connectionFactory Creates connections from an underlying * JDBC connect string or DataSource * @return a pooling DataSource object */ public synchronized DataSource getPoolingDataSource( Object key, ConnectionFactory connectionFactory) { ObjectPool connectionPool = getPool(key, connectionFactory); // create pooling datasource return new PoolingDataSource(connectionPool); } /** * Clears the connection pool for testing purposes */ void clearPool() { mapConnectKeyToPool.clear(); } public synchronized DataSource getDriverManagerPoolingDataSource( String jdbcConnectString, Properties jdbcProperties) { // First look for a data source with identical specification. This in // turn helps us to use the cache of Dialect objects. // Need to include user name to define the pool key as some DBMSs // like Oracle don't include schemas in the JDBC URL - instead the // user drives the schema. This makes JDBC pools act like JNDI pools, // with, in effect, a pool per DB user. List key = Arrays.asList( "DriverManagerPoolingDataSource", jdbcConnectString, jdbcProperties); DataSource dataSource = dataSourceMap.get(key); if (dataSource != null) { return dataSource; } // use the DriverManagerConnectionFactory to create connections ConnectionFactory connectionFactory = new DriverManagerConnectionFactory( jdbcConnectString, jdbcProperties); try { String propertyString = jdbcProperties.toString(); dataSource = getPoolingDataSource( jdbcConnectString + propertyString, connectionFactory); } catch (Throwable e) { throw Util.newInternal( e, "Error while creating connection pool (with URI " + jdbcConnectString + ")"); } dataSourceMap.put(key, dataSource); return dataSource; } public synchronized DataSource getDataSourcePoolingDataSource( DataSource dataSource, String dataSourceName, String jdbcUser, String jdbcPassword) { // First look for a data source with identical specification. This in // turn helps us to use the cache of Dialect objects. List key = Arrays.asList( "DataSourcePoolingDataSource", dataSource, jdbcUser, jdbcPassword); DataSource pooledDataSource = dataSourceMap.get(key); if (pooledDataSource != null) { return pooledDataSource; } ConnectionFactory connectionFactory; if (jdbcUser != null || jdbcPassword != null) { connectionFactory = new DataSourceConnectionFactory( dataSource, jdbcUser, jdbcPassword); } else { connectionFactory = new DataSourceConnectionFactory(dataSource); } try { pooledDataSource = getPoolingDataSource( dataSourceName, connectionFactory); } catch (Exception e) { throw Util.newInternal( e, "Error while creating connection pool (with URI " + dataSourceName + ")"); } dataSourceMap.put(key, pooledDataSource); return dataSource; } /** * Gets or creates a connection pool for a particular connect * specification. */ private synchronized ObjectPool getPool( Object key, ConnectionFactory connectionFactory) { ObjectPool connectionPool = mapConnectKeyToPool.get(key); if (connectionPool == null) { // use GenericObjectPool, which provides for resource limits connectionPool = new GenericObjectPool( null, // PoolableObjectFactory, can be null 50, // max active GenericObjectPool.WHEN_EXHAUSTED_BLOCK, // action when exhausted 3000, // max wait (milli seconds) 10, // max idle false, // test on borrow false, // test on return 60000, // time between eviction runs (millis) 5, // number to test on eviction run 30000, // min evictable idle time (millis) true); // test while idle // create a PoolableConnectionFactory AbandonedConfig abandonedConfig = new AbandonedConfig(); // flag to remove abandoned connections from pool abandonedConfig.setRemoveAbandoned(true); // timeout (seconds) before removing abandoned connections abandonedConfig.setRemoveAbandonedTimeout(300); // Flag to log stack traces for application code which abandoned a // Statement or Connection abandonedConfig.setLogAbandoned(true); PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory( // the connection factory connectionFactory, // the object pool connectionPool, // statement pool factory for pooling prepared statements, // or null for no pooling null, // validation query (must return at least 1 row e.g. Oracle: // select count(*) from dual) to test connection, can be // null null, // default "read only" setting for borrowed connections false, // default "auto commit" setting for returned connections true, // AbandonedConfig object configures how to handle abandoned // connections abandonedConfig); // "poolableConnectionFactory" has registered itself with // "connectionPool", somehow, so we don't need the value any more. Util.discard(poolableConnectionFactory); mapConnectKeyToPool.put(key, connectionPool); } return connectionPool; } } // End RolapConnectionPool.java mondrian-3.4.1/src/main/mondrian/rolap/RolapAxis.java0000644000175000017500000000531611735330606022437 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.TupleList; import mondrian.olap.*; import java.util.AbstractList; import java.util.List; /** * Implementation of the Axis interface. * * @author Richard M. Emberson * @author Julian Hyde */ public class RolapAxis implements Axis { private final TupleList list; public RolapAxis(TupleList list) { this.list = list; } public TupleList getTupleList() { return list; } public List getPositions() { return new PositionList(list); } public static String toString(Axis axis) { List pl = axis.getPositions(); return toString(pl); } public static String toString(List pl) { StringBuilder buf = new StringBuilder(); for (Position p : pl) { buf.append('{'); boolean firstTime = true; for (Member m : p) { if (! firstTime) { buf.append(", "); } buf.append(m.getUniqueName()); firstTime = false; } buf.append('}'); buf.append('\n'); } return buf.toString(); } /** * List of positions. */ private static class PositionList extends AbstractList { private final TupleList list; PositionList(TupleList list) { this.list = list; } public boolean isEmpty() { // may be considerably cheaper than computing size return list.isEmpty(); } public int size() { return list.size(); } public Position get(int index) { return new PositionImpl(list, index); } } /** * Implementation of {@link Position} that reads from a given location in * a {@link TupleList}. */ private static class PositionImpl extends AbstractList implements Position { private final TupleList tupleList; private final int offset; PositionImpl(TupleList tupleList, int offset) { this.tupleList = tupleList; this.offset = offset; } public Member get(int index) { return tupleList.get(index, offset); } public int size() { return tupleList.getArity(); } } } // End RolapAxis.java mondrian-3.4.1/src/main/mondrian/rolap/RolapNative.java0000644000175000017500000000543711735330606022765 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho // Copyright (C) 2004-2005 TONBELLER AG // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import java.util.EventObject; /** * A factory for {@link mondrian.olap.NativeEvaluator}. * If the instance returns null, * then the interpreter must compute the result itself. * If it returns a NativeEvaluator * the interpreter may choose to let the NativeEvaluator compute the result. * *

    There exist multiple RolapNative implementations, e.g. for CrossJoin, * TopCount, Filter etc. If the arguments of these functions are simple enough * to be evaluated in SQL then a NativeEvaluator will be returned that performs * the computations in SQL. Otherwise null will be returned. */ public abstract class RolapNative { private boolean enabled; static class NativeEvent extends EventObject { private final NativeEvaluator neval; public NativeEvent(Object source, NativeEvaluator neval) { super(source); this.neval = neval; } NativeEvaluator getNativeEvaluator() { return neval; } } static class TupleEvent extends EventObject { private final TupleReader tupleReader; public TupleEvent(Object source, TupleReader tupleReader) { super(source); this.tupleReader = tupleReader; } TupleReader getTupleReader() { return tupleReader; } } interface Listener { void foundEvaluator(NativeEvent e); void foundInCache(TupleEvent e); void executingSql(TupleEvent e); } protected Listener listener; /** * If function can be implemented in SQL, returns a NativeEvaluator that * computes the result; otherwise returns null. */ abstract NativeEvaluator createEvaluator( RolapEvaluator evaluator, FunDef fun, Exp[] args); /** * if enabled == false, then createEvaluator will always return null */ boolean isEnabled() { return enabled; } void setEnabled(boolean enabled) { this.enabled = enabled; } Listener getListener() { return listener; } void setListener(Listener listener) { this.listener = listener; } /** * Sets whether to use hard caching for testing. * When using soft references, we can not test caching * because things may be garbage collected during the tests. */ abstract void useHardCache(boolean hard); } // End RolapNative.java mondrian-3.4.1/src/main/mondrian/rolap/CacheMemberReader.java0000644000175000017500000002235311735330606024013 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho and others // All Rights Reserved. // // jhyde, 21 December, 2001 */ package mondrian.rolap; import mondrian.olap.*; import mondrian.rolap.TupleReader.MemberBuilder; import mondrian.rolap.sql.MemberChildrenConstraint; import mondrian.rolap.sql.TupleConstraint; import java.util.*; /** * CacheMemberReader implements {@link MemberReader} by reading * from a pre-populated array of {@link mondrian.olap.Member}s. *

    Note: CacheMemberReader can not handle ragged hierarchies. (HR * Tests fail if {@link SmartMemberReader} is replaced with * CacheMemberReader). * * @author jhyde * @since 21 December, 2001 */ class CacheMemberReader implements MemberReader, MemberCache { private final MemberSource source; private final List members; /** Maps a {@link MemberKey} to a {@link RolapMember}. */ private final Map mapKeyToMember; CacheMemberReader(MemberSource source) { this.source = source; if (false) { // we don't want the reader to write back to our cache Util.discard(source.setCache(this)); } this.mapKeyToMember = new HashMap(); this.members = source.getMembers(); for (int i = 0; i < members.size(); i++) { RolapMember member = RolapUtil.strip(members.get(i)); ((RolapMemberBase) member).setOrdinal(i); } } // implement MemberReader public RolapHierarchy getHierarchy() { return source.getHierarchy(); } public boolean setCache(MemberCache cache) { // we do not support cache writeback -- we must be masters of our // own cache return false; } public RolapMember substitute(RolapMember member) { return member; } public RolapMember desubstitute(RolapMember member) { return member; } // implement MemberReader public List getMembers() { return members; } // implement MemberCache public Object makeKey(RolapMember parent, Object key) { return new MemberKey(parent, key); } // implement MemberCache public RolapMember getMember(Object key) { return mapKeyToMember.get(key); } public RolapMember getMember(Object key, boolean mustCheckCacheStatus) { return mapKeyToMember.get(key); } // implement MemberCache public Object putMember(Object key, RolapMember value) { return mapKeyToMember.put(key, value); } // don't need to implement this MemberCache method because we're never // used in a context where it is needed public void putChildren( RolapMember member, MemberChildrenConstraint constraint, List children) { throw new UnsupportedOperationException(); } // don't need to implement this MemberCache method because we're never // used in a context where it is needed public void putChildren( RolapLevel level, TupleConstraint constraint, List children) { throw new UnsupportedOperationException(); } // this cache is immutable public boolean isMutable() { return false; } public RolapMember removeMember(Object key) { throw new UnsupportedOperationException(); } public RolapMember removeMemberAndDescendants(Object key) { throw new UnsupportedOperationException(); } // don't need to implement this MemberCache method because we're never // used in a context where it is needed public List getChildrenFromCache( RolapMember member, MemberChildrenConstraint constraint) { return null; } // don't need to implement this MemberCache method because we're never // used in a context where it is needed public List getLevelMembersFromCache( RolapLevel level, TupleConstraint constraint) { return null; } public RolapMember lookupMember( List uniqueNameParts, boolean failIfNotFound) { return RolapUtil.lookupMember(this, uniqueNameParts, failIfNotFound); } public List getRootMembers() { List list = new ArrayList(); for (RolapMember member : members) { if (member.getParentMember() == null) { list.add(member); } } return list; } public List getMembersInLevel( RolapLevel level, int startOrdinal, int endOrdinal) { List list = new ArrayList(); int levelDepth = level.getDepth(); for (RolapMember member : members) { if ((member.getLevel().getDepth() == levelDepth) && (startOrdinal <= member.getOrdinal()) && (member.getOrdinal() < endOrdinal)) { list.add(member); } } return list; } public List getMembersInLevel( RolapLevel level, int startOrdinal, int endOrdinal, TupleConstraint constraint) { return getMembersInLevel(level, startOrdinal, endOrdinal); } public int getLevelMemberCount(RolapLevel level) { int count = 0; int levelDepth = level.getDepth(); for (Member member : members) { if (member.getLevel().getDepth() == levelDepth) { ++count; } } return count; } public void getMemberChildren( RolapMember parentMember, List children) { for (Member member : members) { if (member.getParentMember() == parentMember) { ((List)children).add(member); } } } public void getMemberChildren( RolapMember member, List children, MemberChildrenConstraint constraint) { getMemberChildren(member, children); } public void getMemberChildren( List parentMembers, List children) { for (Member member : members) { if (parentMembers.contains(member.getParentMember())) { ((List)children).add(member); } } } public void getMemberChildren( List parentMembers, List children, MemberChildrenConstraint constraint) { getMemberChildren(parentMembers, children); } public RolapMember getLeadMember(RolapMember member, int n) { if (n >= 0) { for (int ordinal = member.getOrdinal(); ordinal < members.size(); ordinal++) { if ((members.get(ordinal).getLevel() == member.getLevel()) && (n-- == 0)) { return members.get(ordinal); } } return (RolapMember) member.getHierarchy().getNullMember(); } else { for (int ordinal = member.getOrdinal(); ordinal >= 0; ordinal--) { if ((members.get(ordinal).getLevel() == member.getLevel()) && (n++ == 0)) { return members.get(ordinal); } } return (RolapMember) member.getHierarchy().getNullMember(); } } public void getMemberRange( RolapLevel level, RolapMember startMember, RolapMember endMember, List list) { assert startMember != null; assert endMember != null; assert startMember.getLevel() == endMember.getLevel(); final int endOrdinal = endMember.getOrdinal(); for (int i = startMember.getOrdinal(); i <= endOrdinal; i++) { if (members.get(i).getLevel() == endMember.getLevel()) { list.add(members.get(i)); } } } public int getMemberCount() { return members.size(); } public int compare( RolapMember m1, RolapMember m2, boolean siblingsAreEqual) { if (m1 == m2) { return 0; } if (siblingsAreEqual && (m1.getParentMember() == m2.getParentMember())) { return 0; } Util.assertTrue(members.get(m1.getOrdinal()) == m1); Util.assertTrue(members.get(m2.getOrdinal()) == m2); return (m1.getOrdinal() < m2.getOrdinal()) ? -1 : 1; } public MemberBuilder getMemberBuilder() { return null; } public RolapMember getDefaultMember() { RolapMember defaultMember = (RolapMember) getHierarchy().getDefaultMember(); if (defaultMember != null) { return defaultMember; } return getRootMembers().get(0); } public RolapMember getMemberParent(RolapMember member) { return member.getParentMember(); } } // End CacheMemberReader.java mondrian-3.4.1/src/main/mondrian/rolap/doc-files/0000755000175000017500000000000011735330606021532 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/rolap/doc-files/MemberReader.gif0000644000175000017500000002463611735330606024566 0ustar drazzibdrazzibGIF89aÃ)óïïïÛÛÛæææÿÿÿÈÈÈ´´´ÑÑѵµµ!ù,Ã)þÈI«½8ëÍ»ÿ`(Ždižhª®lë¾p,Ïtmßx®ï|ïÿÀ pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°xL.›Ïè´h»ßð¸|N¯Ûïø·`ðÌûÿ€‚ƒ{k†‡ˆ‰Š‹(‘’“”•–—˜™š}›¡¢£¤¥¤ŸŒª«¬­®^ަ²³´“ µ»¼½›·¹¯ÂÃÄÅÆ:±¾ÊË‘ÁMÉÌÓÔ§ÏÇØÙÚÛÜÒÕà¦ÎÐLßáçèãÝìíîï†æéó•ëNòôù½öðýþÿ£àÓ—_9‚} Ȱ¡Ã‡6&D·P‰Ä‰3U„ȱ£Çþ¤]¼$r$§Pnö]»é›Iq’^¶DÙ†ÙF8sê˜Ì‘Ìfê8É|Ù“×Í#=“R«)tљʎîœJµ*±¢±êœ ê³iJ¯YÕee*¶k3²aË~]»2ZK¥Mߎ5;6®Ø·x…¾‘{¯<´[™’½Û²­ÕÈ/JË8æÉ°]×>z×gã¼E)C~ Ùð¬\;)Zódº¥/«æ›ºµiÊê<+žM»öË)ÓªwîТáÀ úuÉܲ“€¦ûµl\Õ’#ë]m\²cã®åöLn»»÷ïn‹_N-¸fËÃa¾Ú%w¤3Ë»ÜÊ{üé×P©ÏWïZ¿­÷àþ(à€; †Únùy¥×}\%¥›s "„R‘™nk冖`¬gcr¡]æ5b…¦¨âŠ)üTF³ 8„‹0Â(#‹8樣7¥ V£SZøã¹ã‘H²H#‘²ÜÄ’Lêãd’TVY”QIÎgYvɉ‘V†)æaXz©É”?”i&8hŽéæ› "çœtÒKuæ©çžÜ çŸ€æ¤æš–£ ¡C袌:4(¢¶øé¤?*Ú襘ºó(¥…mi§5¶™é¨¤"²)¨¢òp*¨´¤Zê«°Ž±*¥®"ÃjFµÆªë®XÌ ©Táë­ÖxÊë±ÈÞFl«Üþ¹!ì² åšì´Ô ­8² öìµR‚Yí·à1,¡¸-·ôHîºìÊ0îšü˜ìŒèäm»øæ;ûf®ã#'çÖKѽúlp‹‹âÌ¿-œ0›,ñÄðë%ÃgñYçÃKñÇ WÌñ™`¬ŽÃ#ÛqÈ,lq—þþ…rÊQ­ÜòÍ¿œe¼.ÍL³Jã,4È:G¹H>ÿ¼‹ºC7¬ÆPG ‡¤h;£ÔXg-ÕNw-qÑLZZ³ô*m×^§/ØDŠmÁ¼B°-°Ûj×½®Ü•Ú¬ªÙ*m÷ßOóýŸßÖ 4àˆ‹w¨zh¸B'.9œ‹Ûyþ•_Ëôäœ'™9F›Óðù²¡wn:Ž£G{9©ßZúé°Ø:A¯Ç0;ª«Ç®{Š·ÛKxÒ»ûðöžnîoòÄ7¥ò±ý^6ô12ïüõˆ?Oí0h(÷؇“÷J=¶Ò‹¯¾m䟾 í÷kýúôw?Äé?y~õù×ï?U÷«ÆûZÀÍïü‡ÖX'´)‡Œà˜À RE06ÈÁzðƒ aÉDHš°„ðÄ0¨Áºð…0Œ¡ g¸Á¦Â‚8¬Êp€úð‡@ ¢‡ØÃñˆHLb á„*ñ‰PŒ¢§HE 21‡X|þܲÈÅ.îd‹^ £Æ1šñŒï(#×ÈÆ6ºñpŒ£çHÇ:ÚñŽxÌc©Ô¨Ç>ú1 ¤ ?3ÈB2n‡L¤"1·ÈF:²{Œ¤$'IÉJZò’˜Ì¤&7ÉÉNz’€Ÿ e"ù(ÊRâ‘”¦LåQ©ÊVº‘•®Œåa)ËZÚò–¸Ì¥.wÉË^úò—ÀÔ%-ƒIÌØ ³˜ÈäÜ1“ÉLÀ-³™ÐLÛ3£IM¢å¡šØÚE¦™Ín†k›Þ gÈÂMqš3põ8§:&³uºsm¶x§<ñÕ³yÚó› »§>«Õ“}úsZ>ù§@ÐNN‚M¨øð6…:4kWÄÜCþ#Qƒ2ª€¿RáÛöW Tì-eµè¥0ú=’tgÍè&ÒâqrÆ:©Ñè™ ¯¥}éá6ªSô}tdÄ)mlz±ä5Q4íÀQQ’T¡²¯§K3*T{ÔÔ¬´ªNÍRgÓ©ÞÔV ÅjVƒ’À$=…ºJ~ÑœµJª{шZçJ‰¡Dië/H"V ` #[PÙ1Q©Ut …•Óaut ºž•&&Úë™ âÖzÀ•²i¬Y ÃYÉÊ•>ž­k_y„×ë¶´=¢«eû7¾z…ÔTœz-’Ì¢žÌt¶9bÎqì²!íl¨$~¡lˆd"7˜”?¼NMàþ’¡Œ%·¸Âg—+ÜÝžÈA)ÝI€Áx×·×íKdMö%Ö‚d«‘2/,b›ÝÑÖ?Øy|7s ùö· ocXꛑF¿ý { àó&=öei ÈIWûâ‡ÀûÁì຃ޘŒöS´º°GÞ;¸Þ/{>tßÓ'®%20dŽ‹¡æ8?$îŠ!Üáø"(ÅsY±zV(î†8Æ)ÖÎx·©amT¸E†O†wLaüìçÉ4°|LKà•ÆÊöáÊe¯lžçWÄ2”¹ìeâ$Y¥ò‹ÅüeÕŠ–É9r§aÓÁžBb …šë!×V?ý2ˆQŒþ[m„à âó‰™ûÛìÄx.Îur‡ =èèýô䉈z«gªÂ9N®=sá¾'jGµ*¿n•󡽪¥K?ÌÎÜsyéÛR7dPäÕˆ¬WÍj¦~º²^m“RÏ.ÅÖqÓ¬-Œ½SÏõÚ§<}v‘~M‚`¿yØŽ•o±7‹é];ÿµò²¹Mlo[šJÖT¥=mW' ÖÛø‹!½gðâ9Dâ%ÑœÁM¢g¨.Ó¡÷m}LxÛÝ­Ž6Â…í¸°RûÖã–°ƒí _‰O˜ßfóƒËlqƒÇ9± טICNòA´×vW}8Cäo@û9È5ø·a+sÙ:BîwVþtO„¿ 'ÉÉ»—rlG\ãX®r›Nó¥W\ãŽÇAô3·êuú |Žd•÷¼Û Ò´‡'„g_Óú³aÇqÌÝntc½íc{ûÅÁ Tž #ØS5¹ì®@¹óµ«~ß7Ý9–wcàï>¨3âûÁu„ð:ðçü«ÏŠÃ{ Š¿¼UO»uC>ïòr3¹ñškfiÞ–?û¯(ŸFÈÏœœ/_Ã[ ºÙC õéRëÝO%ö¾#íçE-¯ ý–Ò´¿«¿ŸCpÁç]…Ÿ©}ÿE×ÏöÖý²§\fêÜꨎþUt¯úéW øÇüð©¯ÐËçÑä‘¶9sçþk•üü^òù­oaõ^û£1`óç}‚~w5kO}¬'yð²€í€~ÛãyÿG|ãÖb.i ¦s-i]gtÐ"~ú£nÇö‡fÙGç¤ã€¬Ó{ûG‚w®€uÇ~Øi¢w z¥lLwCa2E‚<ƒ1ˆ}3ˆ‚58l¶•ƒåv€™õV6xÊvZîÂÝéðx…ÂGxV˜,']ÇÇv¶Åv÷¦B˜&¡]ó]Òõw/ƒZèw4è…\kz6qd–câu_¸`4q!(aPx~%wˆr2rˆ¸ˆv uðÈ|∂RjW}v‰•6cšñ‡*Uncf‰þ‚VzEx$XHW÷y’ȱ§`drbQ–t6FqJ·‰w¨žå`°hW…8‚rxŠÿ—Š+°ŠÀø8]‹ær2i&v‘çƒØrÇXbDÁ‰ÙPŠUètˆ„v¨‚Ä’†‰ç‚¼H‚s(wu8yµhdø×tæŽü7ŽoWŽïFˆ•ŽÎ(‚ÎF„îˆuð87ò¨©—«wŽqF„ÍX5'(&˜‚ È-ÞØ™‡â(Ùˆ°Šý˜˜ˆ),i' qø(‘ä¨æÈ®3Œ]Pg(i’[Ø…énüø‘ã}+¹*)“_’Ø8’i‘8ywôàxr(’ïH’ñøþ“¯ðê¸wJ‰z™U·A©LY ‘íh”úˆ”1É’¸ó”9”³•\9•^Y•d¥–I–¤h–;y”=Yt ©9¹un¹#ÖRtT©€bYylÉyÉXy˜r ˆˆ™‘5i‹I'IFƒyI%,DC˜™™š Bΰ™dCp™ž9š#Dš¦B ¹¢yš¥Éš0”šÏ˜åç”UâDUt›¸™›BdDºiE U¶Ù›Ây¼9œÆ9D‹UÁyœ>qœT”œ­(›¹ŽTN6ÙŠÖ (W)”ö˜@Ù™ß ý”,Û9݉@áyé©)ù„N y—ðS˜õþ³žU@ŸWä9™fP™cuŸøIPúYüÙŸèOõ˜y™½‚ Æ˜À֑ЙMät,«éš%Ô™£ ›‡P¡Úšš©¡V?²eNÊ+ËéœHԜáfp¢(Jœê œ,j>m£ Å`ŸS€£ØNSPîu2‚i@zàV}$Ñ£í);Iº0ŸR £¡~ LK* ã© "ê¤Qj*Ù£;õ4 _ʤBj?EJ&^j VЦ³‰TC eºyÆTW`*§ŒP|@øŒ··‹ó¥ìT(R¢kº„æÆmº‡öħ9ã§à¨Ó銼¥Œç]ׇz:< –@Ú¨7–eþ—hqʧ¥£d©xõ—Ú”’æd/'n8G©qò¦U¨ksMì#«¦*ˆÝç¨X‚+çªD TçmÆWcmUhòzŽÂ«¿G<°ú7«2+ ËZ]:<ÑZ7ÍZ3mš’ÈúEÊÊX®¥ÙÊÕjÓº;ãz¬­®÷t®ú®»š®M®–ä®ôä­éÚ‘6LôºOû*…F“¯ÀÔ¯ëú­0°w·­Rê«’9¦q†°‚¢°¦Æ°­Ê¥fJ­X¤® K±pj±9„±ëƒåê¥ö¤\E¦ûªÝš#‹a%[©‹:+±ò´²àB³i³-»§Û86 zH6ûNA+.ñjþ² [±;;„2‹®G»±IÛwK{k;>)‹CKFS{^UkAWË´ñ²Æ4²«ÐµR{²½ú²Jò­ø*¯•4´%¶XZ°lKInKžp[§r;°O[m=Û6?kHu{N›lQû®M‹²{ •…ÛsYKŒ‰«¸9‹µf›¬h[E›±‡{¶æ ;dÓP¨D¶†ûµH+²*ë¦hæWVµ¸<Ѹ¶µîeºé ±› «NƒË+·[°[Nµ+¸®«"¹ë˜USm—«³Á lÃ;½K¢Ç‹¼·ÑË;LÍ‹z¬k´ÏKXÅ+¹™K¹Ù«½ª«@׋¹¤kµÛ뵈¶œ{±k‹½éþ;ºa[ºcÛÔ¡ªQ+ºN[¾iKgdã¢/ZE3êMÉ +|£ñðL«°ÀÀŽ[¹ÀË»+¥ª,Gš°ß«Td ÀÔRÁëuÁ›ÁpÀÌËÁø”N¬¨,Âû9Áí¨ЪÂ!ÌÂ`ÓNJ§Ü ÁÚäÂ7¬¦)üŸÞË¿8cÂE,Äû‰Ä+LÄŒ6,MUšÃƒ¸›+¿W#½ÎÅAœ©C¿0›&Fœ8>ê\ÜÅëkÅ…Åœ[Æ„IÂLÃíÇ:>¼a ZÇv|ÇxœÇ9Àó©Çn,{I‚,È#Ú±¬RÈR:ÈŠ¼È+e°¢Š*ŽŒ‡Œ<É”l—s »þ–ÑYÉœÜÉÔ©µvÈ žž\ʦ¹|pš¼y§ÜÊ®üs£ü¤Ä(Ê—,+¯|˸,{TÊ«üª¹|ÈÛ6z¿¼þèÇQÃÇPÐ_Á¬k ÖQ4/ ¬jvtÙÖm„8ÌyS˪8Uˆ¬žÈeÍOH…¦?§{ Ó¼pøæašæ ’:dùÖYÚÌ8ÜŒÞÉjX†ú,paXvc¸[¡3†7Ï•Ò`ƒøŠKGЖSÏ*Îzj“›º‡¡h«zhfšÍ;ªÐ¡2oÒ8Ñ_F¬‘¥Ñ°ÌÐ<ÖSÏŠ0ɈªY¶q›æ}ÈqÑç<Ð"m#•˜`3MÏæiÒ±Œ}t¸Hþc'½Ó96™¦v›j]EÊI UC}ˆfŒb†R‰-]FºÔZMÐO­]NÝÓ#GÒ¼ÕfýË]í.7&Î=Ö̵ƒÌv}Z &•“Ö”yÖxËvMt‚šÍ×LΧ–§Oh¬¤Ü˔׈ÝÊÜ“¼Q}`{Ö×ÓµÎjA†ÕÅ ]øü†ý{‰ÝÙž¼ØŸBh­Ò¹ˆÐšÈÒBÙc¹ÙYíÙ®½È¯óÄD›ÒHf¨­‰ªÒV-¬ê Úª‚Ǿί=ÜŠ Ú¶øÓ·Št@ít6­edÝ~‡Ü2MÜÔ‘»ÇØáìoÿÆUgÕ!ƒ†|b·×"pT¬xgÕÞGþøÜŽØú¤´¼Éê=ß]ÉÞÀæÞÒ)®ñÍÊôÝß~ Ñ ݃÷ºòÙZþÍ‚½ÌÛ%ŠL˜à ×P1üÕ$­Æ —•ü á¥oq=ÎM¨áü#á€ìÍZjáa}àè âgb|ϥϖÍiò¬âàÕfÌP#ÝîráŸÌÙ4.WÜWÑW=ÚO÷ãLgânÒ:î.[dÞ`]ŸF^ZïçgÐAå9‡Ô å…ˆä#îÔK¾/м߾¬åÅÆ}¥}Û¶zæQGæ…Áå¶Láâêà~kßsÍæÙVÛúEÙÙ-©¸ªvp8þÞèq æ‡ýçˆ>ynÞåéþx ‹nÁ>×aÎÚ‰Léž¾‚Ðpéú çêÙä†î㟞êniãÌKâö]FNî轢괎áþ晎yjëdBè¾þëÀì¿NA¢¢®ž£¤”Ç_”µÞì“]¬®TǾ¥´ݲÎΞía³ì„›ë£Î^×nìÚ>î:MƤþíçÙÚä¾îcMÔ/EÞÈ{êêÎîôž…O^SÓŽî¶~èõÞïºL˜çÑœ>‰þ^ðøãîï.ïnð¿ 1ÇYìŸ!ìsÂíˆâ³üð{³ûñc3ë^î ©ñ¼Üñ(â°·¼!í'®Sç Ij=ðžò6ÿU1íò#œï7°þ¬¼žá7ô$³%p£óó&ÛñIó).ôNÿÐoôÐðáÕ&¯ÊOŸõr=ñEÓ"ïí=oõéÞðZ_öX-Å`_çLÏä|$ñ˜nöZ¯ðÝLõ…}õæìõ+×p_öroÏtÿõc/:,¿òR_1øˆŸøŠŸX/Í<¯éwŒñ]¯*Bj¿˜Ÿùžyùšßù¤ÉùžúŸ/ú.ú˜¢MöYPù¤/Càö¶3ø‡À»Ù|û¸Ïœ1šû¼ïœ*ÚûÀïû»ü)jûÁÌúžð÷NûÄÿœ|Ʊ÷7Jñ7XøxiýÚ¸°µª¯UÛßjŒÑ,ØO´Sþ<¶Z·ç/ͱ[þ¶ø÷³úý­KþËÆªçþKoÿªÒþXô$O™$ Õ^œõæÝ0¯€*sTW¶uµ€åôµï;ž‰÷–nÖ†NÇbR¹ÄwOhT:¥V­Wlq`v½_œD &—WH”Y½FA‰l¸ÅI‹×;sžý¶÷x¬@ÁABA-.¿Dų±EÇ—”ÈÇI@>Ê%KL8Í8I“ÍP1ÒRÓSÔTÕUÖVW DÑYERZÚšÜÛM@ºÝ¢Þ¼_¦àK/Ý ãáEèhéiêj-ëlíhìmïoƒer¶ÒòDdPôô)eöbø?wN¹ûùǃþ~ÿ€3ÐàÁ~.dx@À€|»Ø’¦ÇÅŠk‚ùʯʻŽ6 3ƒ_H‰|¨DÙrÖ9—aHÌŒ9ñcM7qÞÑY’f²èJ ]™dP¤j(&×Ä)Ó0®˜¾‚*Gj"Y«Þú”ÃèV°u–†Ýp”l ³IÓn]kñì.“Öµ}[×L»tíîåÛQo_2qïÎ\8K° /fJqc`"å®KÙ2¤W™5·zxìògиBÛZúÌÉÊ£Uó,ÔšPqžWϦm®v`ÔÕ¥¾]{¤kà;Ä’Ýû)ßÇ;“'Ynœƒ`ÞF~Rv®úwðà‡ŠW§ž·qþs à¹ONºøñj±¯w·e;wô)¿?Š?^+ùQÇM§¿|}!í&â/ººê󋾉a.ÁÙüûoäÏÀy(¬ÈB´äJÃÐPÂÁ‰0“1j>GLäP¦/ì.¼!ÄpGD f´bÄ[Ô¾1Q±ÇcNùp"£ðOˆ^®ÃqA ›\íG'E€2Ê ¤‡†"y@2FdÄò‰.i¨1F0ÝJ4!›Ò¹úÖL³(+WârK¾,2Ì:ÁüOÝØsO/ý”…Ä #t™Kï8´&7=‹Î< õÑÊ>ôê´þáY{k†Û­4ÜÅÌ–›î‹óv™màúöúoäËp‡\JÄ]SNp‡Wéɥ뼜½…¾œÅÏKŸÙô›óC½–Ê[Ý«Õc_\vžS§]¬ÖìóÛ{a¼EªÚw‹rmwÒ‰W^õ}ƒ·}y¬Œ‡yØ3GÔqO€G5è‹—¾[·ý¶Þ»ÍÅæ×ùîëþü÷ª“\ìIk~ûôÍY? ê güúò«=Ÿþú)å~WxþÆW øÙfyï#^è`VÀÉð- œç”GÁÞuLƒš!ÚªX? r(„´s Â†¾Ù}0}#d cWÂ{°}*¼ öÞFCû p](äþkø¿ú°$:Üá ãB¬$P#6Ÿ5BD*@p0û#_àâE/ô9Ñ{P4Rþ"hŸwYÑ‚X ‡ÄÅ.ò0yÒÒœÍ7¿ šñhL£ FÃzHD ÕUÀ RbèŒÆyHD¢¢#Hd#™™Eæà‘“¤$!!D9FŽeR£ 6éºØã“| %#G EØŒCN§\_*™˜É-ެ4D™CËÚ²¸¼Ÿ WÉËÖùްŒå'g Ì"Š™ì‘¢–i¼fŽâ™ˆ‹&ØÊHÌ0Ðò˜ÓtƒÑÂÍìxÓ™àd[5ŸCΊÓ:×ĿŴ™??¶R‘C§ëæþÙ†z¦³“”ËgˆÌ š,¶F¬Üf=ÿi„~NoŸMHh ïY–†.졟 ¨@)Kx&ô »Œ(ïφn”¡ÝÖDûÃN‹áûŒ)lt£;1_6ÑR1ìtSÒÈæì°>òSá@( ‘QM:R¢ µT8…)“šÓ™&¨¢)½X%©K‘”*IYÈ’L¥ Ri‚JOZ"“!¸ÊÔÄ}T«gu®”ÚÔ¥Bõ«G%ÁZ$¦²U®ÎÚXÄGÓ5ãß laÁQÊsÞJ±=½Õ¢ÜZS¼’•WŽÅ*ìZj¤1õ4¯»2—„ãVK(J²ÀʬZCûXÈnµœ-…gUkþWÉNV¶±CeUŠ‡Îº¶µ¬U­KgKÛË:ö³¸Z­Ç^IU ì£!Ëe.C" Qßžj¬|ò­¨æ`]R]V˜~clLG¥Ýì†WºZå¬x±['žÆ5¨ãÅ.xÙkÞìF Uïý®mÏ0[Qy7Lî=o!UÞ¸ú½ö­`‘[¼>å—Q¤:Õ‚e;ßFµ”ÀŒˆ.|Ñ{áš ˜±ó¥o~yÕRÓbÂÿÝ/†«ká›*ú%1–¶ëÎõ*8³â¯ƒûkcDzֺ ~ñ¹ |à9ŠVËb0Šá â…þ® B¦±‰§kaïvÊm»/“¥Tþ>™¾Qñ–]LWgZyÈYŽñ”UÜåþóɯýë!'[WÀgžñŠÉËeIU©»,ojwü«“ÐÂõlzSkgÚæX¼~Žï Ï¬[ùêøËIöÈ¡ÿLÝ>ã8T‚V0¤%ý`*K¥nVNvØ%0Ÿ3Š$54I›•jOº:­”^gIͬÄõ’k´¦çª;ÚjYÏúJÁÖ¯ÕĽ5Þ:, •Þå’Ãl‚ª•ØÂ¾è´Ùw6d32ÛÐaž²m¥QvEí›Öæä}É]ìaŸÛ£ÆÎHÔÜ =Mj&€w¼“a–{{ ù\’¸ï¢î]›à×ÎæÀokБ'g&‰Ø¸ý<}³Åª1z(~qWLXxçø5n„Žþ‡œƒì¾ÐÀöƒ3•ø‹ÞÿjÚ»=ñÄh”hþ†1À{\iƒðã͹;v†KŒ&›ÉÃió˜÷S†4ÏÁo.ÍžßèçPº‘¦Ž®®øKë+ŸwõŽ.p3½à6—vÕ£:v³§Šä'µõ×kíÜ]ÜéeO{7×>Òº\Ômw»KàÌkXèïÆ¤{ÞõŽö¼¯Ù)ì{POM÷ˆs7öãÁ©x|>Ôîâ{ã;nYH^¥¶æ7ßtÓc¾0Sõ¼_@Ÿ²“¾ðšÇ¼å—©z<¶¹õ~ýɪMîÒÓá¶G&îE¸{Ç÷¾ Ë!>*goøÚ›þôòÊ7òùÉïÝ=þ›ò—~ôSðUßú<7(øñ®îàCøÝ7ÿñÇt埑앗þáQ/ü»»óï×[üo){ú«¿é›¼ïË?%R2þC‰æ3¡ö§ùã¾L·Äk@þi¢l7ÿ†íëµúó>ük¡Q»@¸ÈÀ}C¿ç›@LöKÁÅÓ=¬‘k¤«c¤ŒÁ¬Al…,‹ìÁRØAŠ Á| ™cÁœ ½@¬J=&tñºRº'¤:ôKB|ºÑ£½*ü+) ¥£ÀX{@\A ¬;ãÓ(üÂ1,?#lB24¥,\¿Ä$6ì¿0|Ãó;·ôCÁ3L»4¬@Æ»CÐ!Áþ?œ¶>DC:üÀ½³@BTCü=kKD@\DõÓCÀÄG†t(Lt@ À´D?lDMÜÄÌCšŠÃë3CI4»@¬¢SDE\ˆDľTD#ìÄ•Áý£EJàEÃÌ EdÄ[¬ÄOôE`äD[¼?c,Ã:LFX$F*jÆZTEebE„šÃ]tÅaÂF®xÆš‹F9ÅoœF2ÇTľelC>ÌEe<Ī‹EúEvlmÌ„ âÌÀ,ÄMÏÌK¼¤*Ë4MkÊÄÔL©ÕdM€MTMÙ|LÚ,LlŠÍÛDšÞ¼›ÉüM›áMáÜÏ,N¬!Nät•à\ÎŒQNçô‘æŒN‡N)Ùëþ”ÅÆ¤Î¼ÉNN‰ËÛ)Mî¬ïüšðsËñ M×lË+ÏôD—Ü” vrÏ÷T‹ø£ö¬ÏÏ¡Oý\¢ãìOÆPÝœ"Mþ,Ps™K}έÍílP;ÔNö”ñ„Ðr9Pˆ›ÐÃäȳ;X%2:K35ã³@ó/\LR¥˜½FmUsU4cÕb¸T3VÅ2m½UnýÂhÖÓéÑ8%´MUG“TOå-¼Z-´Ê³ Q¬;ÙÔ[¬fý°IÝ(eCsPrÒã1Ô!Ê5"úWr%Óe‹¡ËWM²ç3X) Øþ$؉ÅR0íM‹ÕOŒMØl•µˆÂrÅNêóÑYìþ MÌ“OQ‘Õ•½ÃÙÌ Ùf|Ùóô1.üD‘-Â%ÕU÷IYèÓÙReÙÚõŒQteÈÝØÛäY½4Z¨­–5ÌŽ­Ï‘=µìÔ¬%Ì­}Ï®í¥p¬²-X¦eتHÛïé·sÛg¡Øñ»!õ»«(”¼¥V/=9°ÍRhU¸¶í‰#"\¾ H›%BÀuÌóÜ(‚Ãõ™LÜl”ÚËÜ ú”û¨‡S@ÊÇÅeÜÜH:#aΕ¹…&œ ¤°5ʬëR0)‘Ò}ZEüÜÊu阆]Ùå]ÚGÛuŒÖu]ÝÝÝ!h!ßí\]¼7¬ Ý\%Š9^5”^<þíÛ¤½]tRØ$ E¢Þß­Æ Þ^-%ß·#‰é[²Y]Ž«,ôm_÷ÕAºA”ù6öͦüÕƒŸ=º_HÝ,@Z9š_.`>àIÑOéß/5(6#VB fµE•m^Ëõ¼ž`^ÚÀ¹`<ÌàCí`.atû`ëZܵ¾ 6a&íµÑ« áF¾~a>¥†ÄÆàÞ½Îa!~Ò’åáf‡ ~Ub&6áþÛ# ómÏ&t%Ô*Ö¡'vƦáì-bÊbS+3#ª×pS1>á<a$žbäJâ_û•{Ý3c×¼Š-<ö©4ÆŸú…âþåmà/îãÚã·Â)l5d3«UB^SKac)vÞ¯ƒãˆ4bݰk­³9¹c=^d£äÿudþáÖ“ä_Ó2eµÖCcN^7qe#e ^åf‰/P[WÔ:4Y&YOnÇ?N 7VÍ\æxÜeÐâPäºfe&¶-þ_¦¤ö„g>…hv"R^flVbÃèÉ ìfoΆ¡”nþfrž†pâlNç_Ɖ°,Kw~gPàahgx®çg}»fuVçff[“Ýg€~Xböç Ôç€^æ~&è‚>h††a0Vèímh‰Ž¶†èdžhŒ>Ò‡¶è4èŒ^䄿è%ægUÔØþä+–èitæç[–+3>’Œ^i–Že—~­|%®Ï"“Ó‚Ô;Îfš®é¾óhn¯C¾²2Fåtj¡Žä}6j~mUMLn4¢ž&¦nj˜»êg‚êTFêk WõêÖj›^êK¾1GÓé¨N4òâêÛ+k³ê®k-–ë¹vj»ÞëfÃë¼Þj¾lÀ«è¿ÎgÁ>l¾ñëÂö6¸FlÎêÅvËižlÊ®ìÉ®æÈ¶aÇÞlÂìÌeÎí¡!ìÏvKÑ>m].m:EmÖV(Õ¾ËÆnmt|í)•mÛN&ÚÉØ¾í.ÎmlÜmÞîeßÎGànñî‡,nãŽ:ÈFî^îåtnnçÖ"å†î~œîµîà–nìö¡êÖnsìnZünðÆEÒoê.oÛænôþ òVofVìö¶fË®oû¾ïŒ<ïùÆ¡q.gÿþopp/pÂ:çýfCz¶goppp Ÿð€Àç¿p Ïp ßð/Œ;mondrian-3.4.1/src/main/mondrian/rolap/doc-files/CellReader.gif0000644000175000017500000003200711735330606024225 0ustar drazzibdrazzibGIF89a'üóïïïÛÛÛæææÿÿÿÈÈÈ´´´ÑÑѵµµ½½½«««ªªª³³³!ù,'üþÈI«½8ëÍ»ÿ`(Ždižhª®lë¾p,Ïtmßx®ï|ïÿÀ pH,ȤrÉl:ŸÐ¨tJ­Z¯Ø¬vËíz¿à°¸(›Ïè´zÍn»Õã¸|N¯ÛïºÀUïûÿ€‚[|U…ƒˆ‰Š‹Œ(‡SŽ“”•–—q’Qš˜žŸ ¡>œO¤¢§¨©ª«¦M®¬±²³´ƒ°K·µº»¼½{¹HÀ¾ÃÄÅÆÂeVÉÇÎÏе‡fÔË×…ÖÖ‘ÑÞßà´h|åäè×èØTÍáïðñ€ÓéÙçö÷zìÝòýþÿ詘¯Â¹}RÜ\Ȱa$vgêm[‡ŠB‡3jr±Úžþ CŠ4ÒqBI 'Gª\ÉGÊQ-cÊœ‰ò#Í›8s¾xÙƒ§ÎŸ@[úÜ14¨Ñ£‹æPŠ´©SxLoD}Jµª±©5°ZÝÊu–Ö_»Š *l ³dÓªm„vçÚ·p‡µu17®Ý»cê²Ð‹·¯_,|UþK¸ð+›†+¶#©±ÅÅ#ç5Hù±ä˘³˜«\*³çÏ =š º´é`¢ëu>ͺuM‰ü\Ëž-5Ýà·ië¾\0öîßÀ©ËM‚xðã~õ ô¼¹ó ܘ?Ÿ>=:iêØñ¾Ùν»÷6ìÍNÞnèÓ«_Ͼ½û÷ðãËP@ÀøòøÕž—Ï¿¿ÿÿíþ`À÷åg`Wû¨à‚ ¢®`Ü>“`ƒføÞƒ Vá‡O]¨áˆ#rX ˆ(%"‰,2h¢„)ÆøÓŠ-Öèß‹ʨ#Mç±áe¤Gc€>¸8¦0!2ß5éä“P–ÞŽTZУî É^‚ûiiäê! ቯØhæ™BÖWåšäÀ¥o:¤›sºyäœfȹå]ÖÉe—cÂhš„Ö(`‡lîØcž6jg˜Žþ'–ùe—‘6š¤pƒꩆ›&ã••bZ*žŽBzª©–†‰ê£}ª Krà姸Æ7«¨ ’ k¦¿§©¬ª ,±©ºhŽ¥þäê,€»òZ¡¯RZ$£rV{m²^ë'œ=.«ä&Ï–Ë_´Òxë|æê*.§Í¶+ï–ï¦;O”ør7¥ ëê:o–õÒJn‘_žûmØÞxì¹ÛÛG¿òÒg¿ÿš‰®µn@m–[Ûq²ç¶ŠpÃßq»‡ÚR±‡±ë™*£ØŠH3žÝ ®«˜æYm¶’\r'›ÛòE¯Ì0¢ð:±èÁÅ »3ÈQ¯ˆ¬ÔX_.ÓCã‘ô³GË‘ïØd£1 ™L:ì̯ZÍvÕ{f½°Üv†Ýu_;kw^J·˜² Í®M÷Õƒ‹¬õ܇K½÷Ý^äëâx÷ÍâßÌ:]*œþÖÞÜ­ŸOž0ÔS÷yªÇ3Nˆäôrý0ê% 1¹:.yé¦k¶eÂ"ÏwðȪ{Í:¨®» {ƒ²÷M{í{Ä]iîð λʿÇ</C½ÞÓ#ß…ÛlSJg¬Þý½ÌafÃ-åDtÁfÊ\O¼ùÈ ï~¡ÇkßÎí /õøTƒtïMm•šõqÆVó[Pý¬$¿žiök`°öG3ÿ±ª‚©;ŸÉ nt"ù 6LbŠâE¯lã° WÈÂ6œ-‚HS^âfè±ý•€¸Q>î!ÀÞ¨¦‡¶ b+-ø5Žˆ„¢ ùf8ÌEJs<Ãܶ2èp"4Gþ…¨C+!ñFFÜÞøÂ%2‘D&¤"]6ÈÅ6 n„EYAfŒÑba4cBüf¨<æH‡! ExÅŠ˜äŽóñ£æˆÈêñŒ¿«£í¹!EN’’­Ãá#ã>Ir“²ä'A™!On’$åéHy$UÚ‘•ÒÓä) ÑB–q°t¤‰ISÎr¼´Ø-ÿL¥ùR‚¹£,¹Çë)‘˜µŒ&w†É˜dºÈ•Ì$B1û¸L±YÓMÔ$Ú7tÌlŽ"•ÝÌÄ7ŸYÍqþ§œæäÁ6ý&ÍzÖröÌg¾ÜùNlÆ%è„^2ái9~>Œ¤?“gM‚–É KChBþé¸ÐW´¢È„¨%z?gb´£Mçæ9F‡r”$EãG#±Î•:MŸ0}R8OZк4£½¼iÚ4:Ÿ™Òt§©R1â‚§ºòéO•@Tà 5r 5*S‘Ú<©.• ¥XTŸÊ…¦:ФW¥KVÒR®®’ª¡4kXå9V\n•˜h˜Zך‡¶Âõ­»ŒkZéúÒ˜6I©âÄëêôÚʹòU*,¼Z.°N•°º<ìcO¨X¤ùUŸ•}­dH¹Qc¬dƒaW? @€jWËÚÖºöµ°]íƒbKÛÚ¶ÖjW øCÞ¾¡ýßh'«PÃna8€r—þËÜæ:÷¹Ðen¢KÝê>w_Mû`AÈÙAVc‹ÔІxË\á—´AµP)"’ÂÞðˆpŒ¯{ÛÈØù9ö¼#¨ï5Šêµâ»òý,|çà룼Êâ/~Åš^gøw³Ë 0%Ç Á÷]0õJ+fpÀÞý°íaåpˆúm°†Éa_<س n†WŒŒ÷âÅÐYïewì†ÌÒ8+6æޱpÚÔÚöÈHN²’—ìZÜNìÇUñU|\ëZùÊXβ–£‹](ŸSÊŲ—1“brZub³dÊ\D"Íj† ›ûéfOÀ9Ί™³2Õ‹ç®ñØ;>–Åû\þ˜"3ùµ³=tlÌgB;¬Ê[~ît#]Ý.‡ÙÑ'ô$4iÝpš-–è§1êP·¦ÔŠ@µ©M£jD´zÕŸy5¿`mNY„ÖÙ´õ ld'§!Àh¶³µ³ÂxÚK”6`˜m3j1¾í¶ý¾4qÃÜb3wÑÝn«[{ì–NŽß¼x«Ð:ÿ¥wíì½ wë›qü¶Lüþ ð™œàH9xã~7…oá~68ćæð®N¼d_öÅí•ñE®¯³øÞ8v:®+.ç€"'É·ÍM˜8å*—xËaž¢•ƒ64ïÕÀ¡mrþ›çüèKÊÇéóŸƒÃÖHÑž‘ ;déLúÎõ¨S]æV—QÕûu­c½ë(ÚºÀÁ®s™ˆìPù:Ú)4˜?»ýíp{ - L¹Û]JâÙåÝãNwAÿ@Ïüär‹JÁÃéy÷á%&ä¿þñ% ´K _7ÄVðìôû—)Ïy0³ò]²¼É?c¯9¾ó¨§h÷By.‰>°˜?ó±7ŸúÚW¬ôÌn}˜^ÃÅËÞô´·½ðöû܃þH¼W§ïWæÓÿù`+~ûŽüÄ+?öÌOµó‘J0åénRß“ðq?}Ò(ùPÕßýÇ1cá1û‰h{\ÁïþæþÕ]ìW?êÉ_cêo ý#¥)ž’4œ“DÒG4ÛÇS“²;PD58#:áãDYƒS3\â£øG?í;m€bT7Q$>83‚ãã3X3?Ã?ŸÃ9+h’÷f ¨QC8sÃ?ú7­BCtƒVü‡ÎóNŸ2$D ðd‘#8H'øE7c(1¸GØ„S$)_ˆ,WB‡„A±Ò3oØ UHUJX‡W؃ƒ¸„_èƒZZ)ô‡…76˜3PèˆN¥NHˆr(ˆw(ˆ˜¸5|þ4è=PÓ€(+z˜† Ø67ˆ‰3cˆ‡%8‰$h3«¨†d+¸SJ­˜´ˆƒä‹(:/X‡§ø=x˜…?Xn ¸Ї=ax ¸Šà—8ÜR‹H~³‹(Õ~L‰¨5…õ‰=ñ|€W{É芸‡'X'Çâ3옇6S‚ÞB‚™t}·ã*I·rŽ™0ƒÍøï‡^ÃÇÛ¸|âÈV™¤ë} ¤Çxã Y‘±$ã'‘g‘Ï8k£°w ’"ùv}X©‘ç3’‰’;“ykËØ“HYþUEYA¹” Y6 ;™”T)WÙ‹Þ2ø1c„aBùpGY•d™`W¹‚]¨•þ"„õ·ið]]ô–ƒrÉAq™tu¹C4ùaiqcY–T•Õ‚?㎛3‚fˆƒ¶HAï˜øx9‹¹QRÉ‘I€y™BÒ—o²‰™è„¹cˆ6”‰Ýh^CE™»0•˜˜š‰%œ9‡œØ˜ ‰¢™-DØ—¬g§ù”©É“‚yH¨6iɃZ›”hˆàŽ_Y^½)–㸛e¹œ×PC°œä³Žgˆ…ìø‹¸ã9’˜ÈYš¸© ¨éœ¼¹šäh›bšâ©›äY‘й—_·þ‰}•YWí©šN‰1Mi`™Ÿ³×œ÷‰”﹟É)cèYr 9 çéŸIŸ¹™  ÚŒ z’j™á) ì9¡ZxÊŸÊy ,×*Y¢&z¢eó“CÙléNò)!( S*êãÉ¡ÿ8…QV0-:N/*DYŸKa£0‰£Û7Šh‡ÙY˜ðIg *c3 j*¤¶W¡v8‰›HÂùU"*†**¥æhžÆøD´)‚­Y¦¸øŽöµ¥Ð¨ž¥`ºbŠ•Oø›O…ˆÔ£Jâ¦^ §qÊyŠŒ.Xˆ[x‹D¤§\ ¡ëé§ zbš‘jŸŠú¦Ú¨ZKz¨lJ+þ|ê_j©Ž©ý÷¡ZˆêaŒ ª÷ž1*wOŠ‘ý ¤“§ª І¦h¸š«ºª«ŒVªP¹©í–ª´j ¢ÊFiÈš¬Êº¬}‚“Ú§•:¬©wªb‘©+C­Á­ÒÚyØÊÖz{À*o³º­Ó®[ñ­ÿÒ­×ñ äÊ­æjè:/êÊuÚÚ®šñZTŪŒ$Úªþú¯[¯Ê+M«}>p«I–h»Ú°û°®Å°;±«d{h½Z! [±‡v±Ûd„G©ìÌ•›É«gHR̸µÎ›ÊÒÌxf5þðºþöǰüÉnÛ¶òÎʀ̻fËÔ<µÖ ÍóÌ» ÉeëÎÅ\½È‹ÉbœÌõ,Êg¬Ëã\Êý,ÇàÛ;6Íz»«pÐiÐ=ËÐ#éÐm»†`ª!«Ç KŒwII¹Ñª<ÉBXDº¬¯ät°˜(=Rêì¡ze“µkÒm¶Hd9¯n Ð@%ÒÓý® Y•6­þÐòc/½Ó%ÝÓû:ŸíÓB-Î9]ÔIm¹¢ ÓLÊÑÊÔyÐÒ˜jªFѬÔQÓXC½wÍ”,Š&ǹ•̳WÊPÖMrÖ.?`¢Ž,£”4Ý}mm˜Þ§*Õ)Í›í£FâU²ã+f–NTm1…ýÔMt)ƒÈG•$Ø{Úˆ~½'ˆ -hÉ2cÖ=9Ò m)×9‹Õ©‚O3:ÞÉšWˆ†ß‰€¸Ò™Öäã9˜…ªmŒjš™–-† øÛ®]§Àx¤¥m¹­‚„ܨܲݺK}H"¯É… )Š×ØfY~¬ØÛƧ£Ä˜?ÆÙ–¢eG5Xœ7Hœ¡‰ÞWþœì­8Í «/YNH⚨ŠôØ„à€Õ9Þ興ܭÝmMßTŒÄÒ†Ø ÖƒyAØißq¸„ňÛáM-ö áhÚ#ï]ÆÏ½¢‘]ÝÓ݉¤S’ÙßÛÝLƒ§Ù‹²ùˆVi;—XÝè-ÝÙ3wÚápƒà£=¤Å=™‹éŒz2Œ¿¬‹¤ ÛÜg®^’˜ÔãÇh˜ûÍß÷óÛ;ކ(>¨†ª9˜‚ß­5ËM6ž_Î`ø7š²8;æÓØžýߣ 6ÜDÓ±3攘•Æ“Ù_®ŸØ×Ø=„9$©+ñj7#~á—ÙõGRêªgÅTç&Ô/Išt´Ÿ æI”çþ%bL{þ«Œ ™-~k}OØ$JŠ.Öjd“^Üž¤tˆ‚µ½Ûm.æP{R^¨5Ã'¦M˜·Í†dj‹ðhQVÚ‘nçëGãµøâ Ý\éå=Aç½;æ}¦9¸Û¤$Ú5ÕÑø®Mú…VÞä>®†@ÞTòmª¯éFªÜØíð¨Ží¾3½Îð¢ñ7 Ô*ÏlšrnSÊŽéÝí"5‰äØìåYê&ùþ8‡©; ´ñ4àätóX°ÆÞ™¬Ý•…+ Píþ\ßô8>ó(ÞŽ@H´›ëžzJ0ä8/ÃJ£’b^8,±¶o<×wÞŒL8$Ç¢T"5?O%)ê™N×@ ›…mµØï5|»Ø^Z½f·Ý¸ß9§×íwí[¿ç÷sq¼9&(§ÁÑ©BÇF(„ƒ€¹#H¿LÍ@³ÀOÐ'ÄÇÑÄ'CÈÆ ËÐXYÑÍZÛÛ*ÉÙ]^AÜ_à=ÀÞ NcÃã¨ÓgÇ%Æhèé c ƒÉ€–Ë`ðµá.bóläkëÒÇ+ÅååS4Š+W2sü¤ð}~þUÝ|€³0õ#l\¯ éI¸d›€J.Œ4TîàŒ€»(VlÓ*ADOñmäX’ÏE‘)}™d™ ¥À–ØHî ÙêÀ$ê‹eH•vfö„“"§ÄrA5eúæ¥R¨]šNíñ4ÖPª ÿ­ÌZë)Ш<»òø™4l -iÕ®eÛÖí[¸qå®ÝvÖn±±y9mE¨×–U!XýÒäø®ØÁ> Ÿ9|Gpâ’6‡} ™ `P•-ßÀœg³ÓÅ“¿}Þ kˆYÑD4“Þ'9uÐÕ¬uF+´éѶÓÐ~MK6ïÞ§u3êÉ]†6N,»þv­¸—‚¯Ê]8ù?æ»ûv®£–ë—d >ž£õõÌí+ÄŸ{?g÷<û/ñòÃË6«BR°3ÌâO L ¬ùä/@?ùÎB²0ÄcÃÄžã°´ Óí«ùR| Ul#úÌ¢Oªùé¯Ä$æÒqG{ô‘®eq°ç~4òÈë‘ÆßÌ[° ï!çE1ÈaìJÄj4ÈÃ%éRKF”ô5ÜQ¥šUªÚ.ÈúFt­Ë™†J³ Uè´“@ùà[±>õ¤ò´÷ DâK0c³Ë M̾4[äNiÄYS?-ß\2N5ëüƒ&C3cð %,&)kS˜ðTå¸þªè9fŠ9¥Hæ‚tÖ9‰KB³J•2T¬x†ydЇLd­¨f Wóë•Õ šUU5h½’ÖzLÉöšXy†”ïD¬ôÚÃ4˜Üb½Ý”2úU´gY}WÕxÁœ·ÂhÌÜžSa—}!†W{ɽËÜ%ømGaný·á~£=Îjo¹—âŠ'=G‘tÙuX€‹tà‰Wåðb•yäZ9^ämñœÖÄŒ[-yfÿjo¤…d™¾U×xŽÝÙË‚oîm耱ÚØwLxjY¥–g&uîeëWiÇÈ¢Y7¼¾zФé=ÑॵÖkSݾgM©1º£‚þÄ­‘k‘ÌÞlýþn å>Ú;$Wü-Fs¶ïŒ—|®ÆqžürÌ3¯œp® gƒoÁ‰²Ôñ!(t\FÇY€ @àuØc—}vÚ¨ývÜoÿ!wÞiß½wÞâüðu>ìâ??~ùBõvsý¨œÿ þó磡sïôÕœÒöŒykÒÿö7Î; < RägÐÊ}'£Ÿ×fÀêA Äß3˜²þ‰£‚&Û›KˆÁr]ðƒÇéÓŒ‚¿=­0r!,Ó³6@´á°k*”!ZþZصö ™#b}½ãHWFd¢\ØÚLˆŽT Öó©>M‘¬sð¼øE0†1v¿£…·1E‡‹eìÙøFÚ‘€íAá'¤'(âÑŠZœ…ôÈ÷G@RÕûÞ i>4l~4d÷Æ×HHn‘s$éfvÇ*“Yäc'…·(FƆžr¡'¯ÂCSJ{|á'WYÊTÆR5 Tb;ÈJYf•¹ääaéCWf—¼$¦0Õ–ÆQúЃÅÔ%‚’yI舿T4WÉÌbRÓYµlÌŸ°7:~SšC‰8ÍyÍæq³\A<'ÒœiËj“…çhç9åþ Euڋìg…ÞYÏ{j¨œýüf@ƒÓD„:žñá¤A±™P">1—A ·5±åhìäH=R´˜íhIÁøQqŠte¦J'ÃÈH:ò1¥éô&¿qò“ 0­iO¹sºTŸ,m)KKÅK唨KåœPÏâT¦‚Ò¢„*áPsÕ¨fUª‘¨VMYÕRµ`õjYŸÚ·®šU‹dm 7SªV¸Ò ­ôŒëÙz@·^´®{ Ü\ÈWÞµ~ûìV{Ø×Ö˜­Dl[Ò·6V²+åà_';ØÇfR±Ð¼lg¡²Yrzvx µ\NI 'Ѧ֯ÍS­UÇjþZ½¶V¶0”ël9Ùkžˆ¶å-CYÛÛßNµŸºî^A{Wâ7˜v½ßQûPåF×K«¦tK”\ÊÎÈ…Ø ®u½«TºZö»šäã>á£ÝáŽW½½Lku×{Ðò>™}¯wÛÞúv3¾ì­&}óÝû†÷¿ú]«|“ÞXðío‚C»Öñ¼Ðupq©Éݰ•Süå9 ¢{NÜM‡Èg@÷¨vq*Ùœ!ç¹séë†RÊÍ4ºÇF ³¢›BÕцYh=ã;[úÒý“ŽA!gº–Ò¡58--SošDA%r¥[>LÚx²–ؤã<߯ÞZH›.õ®emI¬}ÚÈÀö¤²|j½×ÇNµa›<‘7XØIŒ˜Wºíƒ»q+h’‚ƒ®Ãf¦u¡Û_cÚÂ=Šee @vìª ­ímÎe³!7Ù†õŠ /XQBB ^¶vÿ»ß¢µ½™½æ|73fDƒ‡Õœæ4gëiþ.Z!бqŠþ M¢0ÁD¾æ¡ŽŒW俸»nUð„!#‡ÆÈƒíð†G,¡Ýì-·•¹Ð¾ßød{ÂWguÖµÎó·ÿ¼è>g™Ç¥wE˜ûUçòX¬ø±>X’ÿ™!ªÝâÏÓ¼è\vÎýî¥sahª¸ÊÇ.zV”i¥o;Ý¥½ø*¤\jd_{ÇWFñÔ“"X¬_9¿žØ0Œ*øS*U„Dª*ÿAú#oß?ß&ÁÝN«ùXE^@â|þÛú—±0ªzSü£Šðž¬Î¬?ߟàÎÕ™í³MwÚÄÿ¬íúòƒüÄ¢[«~Ôr>õ#!Ϲ>wâµß1ÃÚ?Añ¿^Ú¤6À<\3 Xãµ^û*ãca'i¡eòÀQ3gI¿ dW£7Ò!5î›ä»6KÁ 4Ô‘»ÓÜÁ$aÁýa>Bó5„¾â¡º1Ñ@#3œÃ­!¤LËAW£¡$”¥ó;A ¬Àúë \4)|A¬µ+À,ôÅÃ>bóB9“A!lB4œ-Ì!DÃîBA%dÃõsA 4¶3ôAÌZÂØ²Ã*Â2<@jãÃþó©Â+¢C1ÜÃÑÃ.4ÄÑÃD ÄÃGÓÀ dBEŒ%D¬¯1¤ŠßûıHÀ4ÜDC£Ä6”›PÄDR¤Œæj.I\C@ŦXE$”ÃÏz®íŠEÏ£DZ$ŽAä ´£ÅZ¦½ÓDd´Ã_`„ÃB$F {¸0äDT ÄlÆ\AhÔ)<3Å›«Æeì‰7ÌFA„Dx1°nœÆS¬F,ÇFœ¿G «òû¥Nl¾IôEÌǶÈASÓGÜGsTµ_ëÅY$6(tÄ' HkóÆfcÇv,Àƒ„Ç„”Å{LF6 ǘ°Å8TÈdãE‹lBŒ¬Ž IfäÈÒòà ôEF¤"‰´D‚¬þÈ?TÉxdÉW¹E“¼-5|É‹\I¡@H—üH ¬:‡$I“G­³IŠLÊ¢bG¢¥ÍÑ<Ó2[®3Ô°üS@=FMRa³SEBeÓz”ÆHÔÄlÔ6ÅÐLÕTÊäÔƒù¬“TPµ–5UÎë«R=ÕgTÕ°âSWIXõ4YÕ ­Õ>ÜÑ!íU_íÕ*ÕÕ<T\%µ`V¥2%SÒb…ÈT­ÕCõ­DmVùDÖ!«ÏO¥Öþ|VX½Ô:SmÝVk¥Sl½5S ײ©T2õV/5WtUEu×#ÖwÕF•×emÑs­×âV|]ÐÝW~-ý×G¥QXÂ(XNÖyW…­–{U‡WˆXy‰W+eW1›VŒÍX†mTŽ-SýXd Y@YG%þM“Yƒåªr͖ͧ—ÅI„ýUœÍÙÙDÙš¦MNç£å Z¢-Z8Z£ ž±ìÙ“ÜÑòTOA:O¨Zª­)©­Úð4¦ÅÒ­E³ÉRÙ®…Ô|•ÖžÛH [;‚‹ãP[æ`Û5Û¦BÛèüƒa£[¨¬€š…[”[¼R…º…Ÿ\‚¼åÛ€\˜à }ó²%\}5Ü¿e„Ä…ÜÁe\UÜi²ÜÈÜÅ¥\-¹#ä„Sª‚­ÕÛ…äÜÑÝÏÅÛÐÕÜÍM-Ò5ÝÜ,c èÚ×mZØ=DÖý\¨šÝÑý±ÜýÁÚ¢á]ŠÛµ´ã%ÕàÕ]Å¢‡F ÛäÙå}*þå3öäÕéÍÒc[ZCÍÞ’ÈîõÞl¥ÕußñíÍòU­è}ÛPM_×5ßnjß.]_3…ßø‘ß^û}*ƒà N)I=¥_®Ý_ òÆc7ÿÅ›©Ùž=3>Q染H=Ÿ¹Î¸c9¥`ëdàŽà<ƒŽ–<%<‘!™ö`8£Ž 6ИÐÖ[Òk?_³p™>ád˜¶Ôvàþ…•uhaÑ“9‹ÐØ'{à/ýa ŽÌüU_&¡Wõa(Žâ&&ßùÕ_+&Þ)v_ÑRbdMØ­Aâ%cVÝb.vâ:Eã2Æß5fc!c¦aû‚ã»…×¶áºJª‡ýÖþhŒ`9îa/Ö² )ä+q)>òSdFþá@>œAÎ*>~Àþs‘Š‘•_BI:æäº9â<î23þ,CŽÀü<>,±’(þ’¡ÁáÉ+Ó å,eœò“JÆåJ‘In忘NvØ=#¾8b)æ·‰äÃ*¿Dæ?V&dcW^·Cˆº<)RPy²åaµãÔu »‘;Úƒ7†qM2öDôãf‹æf;Ê[çkJ½ëXtÞ°MÎ` ¥NÔûºù¡åËæy~±Gžl>çþ°€NW~Nb-.hnSc7]h†ÎÜ\}ã÷èÕÅâ'®h‹&¥êM¨cUFˆÞèɔ¾ÈþiB_*Féü:h½‡ÌeÐS½ª‘¹ÙÃy|î÷º•ñjž¸ 5ÐxX˜—ù„c€÷‹&eK¡á”×¾fX€ïQúƒŸõ¦V‚#ʨ¿KŽÏ}Ñ `€¬¯™‡mƒ}ÓbwBÛâ†W†Êú@ûì醭§ôš¯0·/pü¿~ø×äãß%rÒj/Î:î?Š#Yzrh+Ûº/Ë3]Û7žë;ð?0(‹Ž)©\2™(•цlR« g4«Ýr»ÞoÌ“Ëæ[ ­^³Ûî7<.Ÿ³ Ðó‘®ßóv<` à WŸá!b€Àa£#™@e¥%Še¦æ&g§§&f§AÀcäägªêjæhé#l¬ì씕ímIé,o¯Î€Àð01G1r²ò2s3²±óâ#°sµþõõ²´ï6wwQ-n¸¸ß·ù¹‹± :z»û;~ûòçǯŸÿ?€î¶·|' > !¸ th &8sZØV"jGmzSayŠÈ˜ƒRÁو݀ÈQŠ-bh"z(ºØËŠ…ÌxãT%ˆP„8:Rã@ú8$\;êÔ#‘I*¹d:i’LJ9%•48ù$FQV¹%—]Np%–Bié%™e& f˜WŒiæ7 ²ùæ6h¦yšp!dxÚ¹gysÖs Ÿwº(¡ëøI …î §Œ*þúhK‡öW¤7 Z)¦…H:i¦‚RØ)¨ynŠK¢¡šzjQrúY*ª­ºJ’ªs²ú*­µ9ª-³Úº+¯ðlø+°öÊ‚£¡ {l¸æ*,²=\Ú,²±¦)#´Î~Zm³Ò†©ë°Åá-¶Šj‹%·½‚ëi¸Ý* !¥éJp®¥îš»nåÊ{/¾BŒû¤½ùúû¯ôžX'À,þFö{0à )pzíº /A‡šðŽ ×J1[¼$Æ0jL«Ç:”ü1‘!›8ò«'ãà2Ê8ªü Ë®Â,E̘Î\`Í­Þ\ÃÏ9§¸3= }´ºó(1ÒMû ,Ô2ë4Õ÷žÂ ÖYþkÝÊ.U{/5؈=6ÙÉh“oÐ3¤ý5Û¨®FÛqü6 tË}·¸Ïâ½÷½v§Ã7ààE=8῞­vá‰+nÈá;žÑJ;Õµ•’ãªË+kŸå«NíSç›R»9éa„.+ÁÉž¾jꥻþîêÛ¶nA䱃5ûë¤×n;L§Ã»ì¾ç><ìÀ‹Œ;RÆó‹<ñïüÈÏ+otóm;fÐ]1–uPq}»½¶Ùçd×÷àƒ•>qÌW¿w\rÒ¥}pÝÛøÑK÷§Xçgv¿úðê½ÏkqÁŽv¶³½þ¥¤Õ1ßuª³?JP€ ÜÎèLîF«aàt´þÓ†Šð1ó£`w†Â5|®€®; q4ãÀ¾d/†6<ÎÿGÃÒF|+@ßü,#CÉ$ð†ÂÁ¡j„(D1ƒ.| ÉW ÆDŠHÔKvޏÃ$¦Š•ia ”¸E,ZQ}MÑâd²ìp±Šü¢Ÿè¸øp8¤¡kŒ(F-¢ñ*4†ûÅ>âïŽûÛc !ƒCB&1á“có®×ÀÙÔ0‚´d"Õ¸@vðŒLô À8>&V’;¤Ää$EèGS¦F’e4a)S¹QBÒyür Äœ:úÏ‚Óe-'½†˜a ͆¹¬8oÊ\þ”—±@6ÓiÏ<]4¥¹þ2jVi×,f ¿ MnvSh‹;':Ý@Ë ¤³î\g9­§MžÁSuó¤'3ãù5qZΘ£¼gÑê©ÏªñSrÙhŒÈ9P‹TiEè‘ò¹PkB4¢«èù:Q†54—áÄhF%ºÑ£uô—éI8R€•”^EéŸ4ºR–Â{'­©ME:S”µt]/Å)©dºS´u7-*§†·ž*ë§H­—P•*/¦^î¨OÍUT¥š.ªŽÊ©WuBVµŠ-®ŠÎª_˜NÅj0²JÊ«gM‚JÕ:Ö·¢õ¢t­«\©æÎ½*N ´ã+` ç×¼Òô®lò ;Ä–¨Š…+c‹÷Xç§D¶±S,”Òú0ÌÂ5¬–å[åVΖųŸµUhY§YÉ’öv«=m¸R‹º×ÊV´¦…mËZ+&Úê6K¯Å-´j<»öVM¿n´Š›³*7®È%™r{GÜè>ò¹.¹˜[\çZ×gÔ­nå¾[ÚãvwWØ]oÅ Þòì¼ Óno¹ËÞ‹}w´¤•ï|;åÞi¦W¼øÍ¯Îê _ÝþÀ ,‚UÙ3xC•5ð«®¶µ S¸Â¦„+l á s¸ÃÞD†!œ¯°•­Ä&>1Š‡Ñ¸8Å.~1Œ³ÁÓ¸Æ6¾1Ž•;mondrian-3.4.1/src/main/mondrian/rolap/RolapStar.java0000644000175000017500000021632011735330606022443 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. // // jhyde, 12 August, 2001 */ package mondrian.rolap; import mondrian.olap.*; import mondrian.resource.MondrianResource; import mondrian.rolap.agg.*; import mondrian.rolap.aggmatcher.AggStar; import mondrian.rolap.sql.SqlQuery; import mondrian.server.Locus; import mondrian.spi.*; import mondrian.util.Bug; import org.apache.commons.collections.map.ReferenceMap; import org.apache.log4j.Logger; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.ref.SoftReference; import java.sql.Connection; import java.sql.*; import java.util.*; import javax.sql.DataSource; /** * A RolapStar is a star schema. It is the means to read cell * values. * *

    todo: Move this class into a package that specializes in relational * aggregation, doesn't know anything about hierarchies etc. * * @author jhyde * @since 12 August, 2001 */ public class RolapStar { private static final Logger LOGGER = Logger.getLogger(RolapStar.class); private final RolapSchema schema; // not final for test purposes private DataSource dataSource; private final Table factTable; /** * Number of columns (column and columnName). */ private int columnCount; /** * Keeps track of the columns across all tables. Should have * a number of elements equal to columnCount. */ private final List columnList = new ArrayList(); private final Dialect sqlQueryDialect; /** * If true, then database aggregation information is cached, otherwise * it is flushed after each query. */ private boolean cacheAggregations; /** * Partially ordered list of AggStars associated with this RolapStar's fact * table. */ private final List aggStars = new LinkedList(); private DataSourceChangeListener changeListener; // temporary model, should eventually use RolapStar.Table and // RolapStar.Column private StarNetworkNode factNode; private Map nodeLookup = new HashMap(); /** * Creates a RolapStar. Please use * {@link RolapSchema.RolapStarRegistry#getOrCreateStar} to create a * {@link RolapStar}. */ RolapStar( final RolapSchema schema, final DataSource dataSource, final MondrianDef.Relation fact) { this.cacheAggregations = true; this.schema = schema; this.dataSource = dataSource; this.factTable = new RolapStar.Table(this, fact, null, null); // phase out and replace with Table, Column network this.factNode = new StarNetworkNode(null, factTable.alias, null, null, null); this.sqlQueryDialect = schema.getDialect(); this.changeListener = schema.getDataSourceChangeListener(); } /** * Retrieves the value of the cell identified by a cell request, if it * can be found in the local cache of the current statement (thread). * *

    If it is not in the local cache, returns null. The client's next * step will presumably be to request a segment that contains the cell * from the global cache, external cache, or by issuing a SQL statement. * *

    Returns {@link Util#nullValue} if a segment contains the cell and the * cell's value is null. * *

    If pinSet is not null, pins the segment that holds it * into the local cache. pinSet ensures that a segment is * only pinned once. * * @param request Cell request * * @param pinSet Set into which to pin the segment; or null * * @return Cell value, or {@link Util#nullValue} if the cell value is null, * or null if the cell is not in any segment in the local cache. */ public Object getCellFromCache( CellRequest request, RolapAggregationManager.PinSet pinSet) { // REVIEW: Is it possible to optimize this so not every cell lookup // causes an AggregationKey to be created? AggregationKey aggregationKey = new AggregationKey(request); final Bar bar = localBars.get(); for (SegmentWithData segment : Util.GcIterator.over(bar.segmentRefs)) { if (!segment.getConstrainedColumnsBitKey().equals( request.getConstrainedColumnsBitKey())) { continue; } if (!segment.matches(aggregationKey, request.getMeasure())) { continue; } Object o = segment.getCellValue(request.getSingleValues()); if (o != null) { if (pinSet != null) { ((AggregationManager.PinSetImpl) pinSet).add(segment); } return o; } } // No segment contains the requested cell. return null; } public Object getCellFromAllCaches(final CellRequest request) { // First, try the local/thread cache. Object result = getCellFromCache(request, null); if (result != null) { return result; } // Now ask the segment cache manager. return getCellFromExternalCache(request); } private Object getCellFromExternalCache(CellRequest request) { final SegmentWithData segment = Locus.peek().getServer().getAggregationManager() .cacheMgr.peek(request); if (segment == null) { return null; } return segment.getCellValue(request.getSingleValues()); } public void register(SegmentWithData segment) { localBars.get().segmentRefs.add( new SoftReference(segment)); } /** * Temporary. Contains the local cache for a particular thread. Because * it is accessed via a thread-local, the data structures can be accessed * without acquiring locks. * * @see Util#deprecated(Object) */ public static class Bar { /** Holds all thread-local aggregations of this star. */ private final Map aggregations = new ReferenceMap(ReferenceMap.WEAK, ReferenceMap.WEAK); private final List> segmentRefs = new ArrayList>(); } private final ThreadLocal localBars = new ThreadLocal() { protected Bar initialValue() { return new Bar(); } }; private static class StarNetworkNode { private StarNetworkNode parent; private MondrianDef.Relation origRel; private String foreignKey; private String joinKey; private StarNetworkNode( StarNetworkNode parent, String alias, MondrianDef.Relation origRel, String foreignKey, String joinKey) { this.parent = parent; this.origRel = origRel; this.foreignKey = foreignKey; this.joinKey = joinKey; } private boolean isCompatible( StarNetworkNode compatibleParent, MondrianDef.Relation rel, String compatibleForeignKey, String compatibleJoinKey) { return parent == compatibleParent && origRel.getClass().equals(rel.getClass()) && foreignKey.equals(compatibleForeignKey) && joinKey.equals(compatibleJoinKey); } } private MondrianDef.RelationOrJoin cloneRelation( MondrianDef.Relation rel, String possibleName) { if (rel instanceof MondrianDef.Table) { MondrianDef.Table tbl = (MondrianDef.Table)rel; return new MondrianDef.Table( tbl.schema, tbl.name, possibleName, tbl.tableHints); } else if (rel instanceof MondrianDef.View) { MondrianDef.View view = (MondrianDef.View)rel; MondrianDef.View newView = new MondrianDef.View(view); newView.alias = possibleName; return newView; } else if (rel instanceof MondrianDef.InlineTable) { MondrianDef.InlineTable inlineTable = (MondrianDef.InlineTable) rel; MondrianDef.InlineTable newInlineTable = new MondrianDef.InlineTable(inlineTable); newInlineTable.alias = possibleName; return newInlineTable; } else { throw new UnsupportedOperationException(); } } /** * Generates a unique relational join to the fact table via re-aliasing * MondrianDef.Relations * * currently called in the RolapCubeHierarchy constructor. This should * eventually be phased out and replaced with RolapStar.Table and * RolapStar.Column references * * @param rel the relation needing uniqueness * @param factForeignKey the foreign key of the fact table * @param primaryKey the join key of the relation * @param primaryKeyTable the join table of the relation * @return if necessary a new relation that has been re-aliased */ public MondrianDef.RelationOrJoin getUniqueRelation( MondrianDef.RelationOrJoin rel, String factForeignKey, String primaryKey, String primaryKeyTable) { return getUniqueRelation( factNode, rel, factForeignKey, primaryKey, primaryKeyTable); } private MondrianDef.RelationOrJoin getUniqueRelation( StarNetworkNode parent, MondrianDef.RelationOrJoin relOrJoin, String foreignKey, String joinKey, String joinKeyTable) { if (relOrJoin == null) { return null; } else if (relOrJoin instanceof MondrianDef.Relation) { int val = 0; MondrianDef.Relation rel = (MondrianDef.Relation) relOrJoin; String newAlias = joinKeyTable != null ? joinKeyTable : rel.getAlias(); while (true) { StarNetworkNode node = nodeLookup.get(newAlias); if (node == null) { if (val != 0) { rel = (MondrianDef.Relation) cloneRelation(rel, newAlias); } node = new StarNetworkNode( parent, newAlias, rel, foreignKey, joinKey); nodeLookup.put(newAlias, node); return rel; } else if (node.isCompatible( parent, rel, foreignKey, joinKey)) { return node.origRel; } newAlias = rel.getAlias() + "_" + (++val); } } else if (relOrJoin instanceof MondrianDef.Join) { // determine if the join starts from the left or right side MondrianDef.Join join = (MondrianDef.Join)relOrJoin; if (join.left instanceof MondrianDef.Join) { throw MondrianResource.instance().IllegalLeftDeepJoin.ex(); } final MondrianDef.RelationOrJoin left; final MondrianDef.RelationOrJoin right; if (join.getLeftAlias().equals(joinKeyTable)) { // first manage left then right left = getUniqueRelation( parent, join.left, foreignKey, joinKey, joinKeyTable); parent = nodeLookup.get( ((MondrianDef.Relation) left).getAlias()); right = getUniqueRelation( parent, join.right, join.leftKey, join.rightKey, join.getRightAlias()); } else if (join.getRightAlias().equals(joinKeyTable)) { // right side must equal right = getUniqueRelation( parent, join.right, foreignKey, joinKey, joinKeyTable); parent = nodeLookup.get( ((MondrianDef.Relation) right).getAlias()); left = getUniqueRelation( parent, join.left, join.rightKey, join.leftKey, join.getLeftAlias()); } else { throw new MondrianException( "failed to match primary key table to join tables"); } if (join.left != left || join.right != right) { join = new MondrianDef.Join( left instanceof MondrianDef.Relation ? ((MondrianDef.Relation) left).getAlias() : null, join.leftKey, left, right instanceof MondrianDef.Relation ? ((MondrianDef.Relation) right).getAlias() : null, join.rightKey, right); } return join; } return null; } /** * Returns this RolapStar's column count. After a star has been created with * all of its columns, this is the number of columns in the star. */ public int getColumnCount() { return columnCount; } /** * This is used by the {@link Column} constructor to get a unique id (per * its parent {@link RolapStar}). */ private int nextColumnCount() { return columnCount++; } /** * Decrements the column counter; used if a newly * created column is found to already exist. */ private int decrementColumnCount() { return columnCount--; } /** * Place holder in case in the future we wish to be able to * reload aggregates. In that case, if aggregates had already been loaded, * i.e., this star has some aggstars, then those aggstars are cleared. */ public void prepareToLoadAggregates() { aggStars.clear(); } /** * Adds an {@link AggStar} to this star. * *

    Internally the AggStars are added in sort order, smallest row count * to biggest, so that the most efficient AggStar is encountered first; * ties do not matter. */ public void addAggStar(AggStar aggStar) { // Add it before the first AggStar which is larger, if there is one. int size = aggStar.getSize(); ListIterator lit = aggStars.listIterator(); while (lit.hasNext()) { AggStar as = lit.next(); if (as.getSize() >= size) { lit.previous(); lit.add(aggStar); return; } } // There is no larger star. Add at the end of the list. aggStars.add(aggStar); } /** * Clears the list of agg stars. */ void clearAggStarList() { aggStars.clear(); } /** * Reorder the list of aggregate stars. This should be called if the * algorithm used to order the AggStars has been changed. */ public void reOrderAggStarList() { List oldList = new ArrayList(aggStars); aggStars.clear(); for (AggStar aggStar : oldList) { addAggStar(aggStar); } } /** * Returns this RolapStar's aggregate table AggStars, ordered in ascending * order of size. */ public List getAggStars() { return aggStars; } /** * Returns the fact table at the center of this RolapStar. * * @return fact table */ public Table getFactTable() { return factTable; } /** * Clones an existing SqlQuery to create a new one (this cloning creates one * with an empty sql query). */ public SqlQuery getSqlQuery() { return new SqlQuery(getSqlQueryDialect()); } /** * Returns this RolapStar's SQL dialect. */ public Dialect getSqlQueryDialect() { return sqlQueryDialect; } /** * Sets whether to cache database aggregation information; if false, cache * is flushed after each query. * *

    This method is called only by the RolapCube and is only called if * caching is to be turned off. Note that the same RolapStar can be * associated with more than on RolapCube. If any one of those cubes has * caching turned off, then caching is turned off for all of them. * * @param cacheAggregations Whether to cache database aggregation */ void setCacheAggregations(boolean cacheAggregations) { // this can only change from true to false this.cacheAggregations = cacheAggregations; clearCachedAggregations(false); } /** * Returns whether the this RolapStar cache aggregates. * * @see #setCacheAggregations(boolean) */ boolean isCacheAggregations() { return this.cacheAggregations; } boolean isCacheDisabled() { return MondrianProperties.instance().DisableCaching.get(); } /** * Clears the aggregate cache. This only does something if aggregate caching * is disabled (see {@link #setCacheAggregations(boolean)}). * * @param forced If true, clears cached aggregations regardless of any other * settings. If false, clears only cache from the current thread */ void clearCachedAggregations(boolean forced) { if (forced || !cacheAggregations || isCacheDisabled()) { if (LOGGER.isDebugEnabled()) { StringBuilder buf = new StringBuilder(100); buf.append("RolapStar.clearCachedAggregations: schema="); buf.append(schema.getName()); buf.append(", star="); buf.append(getFactTable().getAlias()); LOGGER.debug(buf.toString()); } // Clear aggregation cache for the current thread context. localBars.get().aggregations.clear(); localBars.get().segmentRefs.clear(); } } /** * Looks up an aggregation or creates one if it does not exist in an * atomic (synchronized) operation. * *

    When a new aggregation is created, it is marked as thread local. * * @param aggregationKey this is the constrained column bitkey */ public Aggregation lookupOrCreateAggregation( AggregationKey aggregationKey) { Aggregation aggregation = lookupSegment(aggregationKey); if (aggregation != null) { return aggregation; } aggregation = new Aggregation( aggregationKey); localBars.get().aggregations.put( aggregationKey, aggregation); // Let the change listener get the opportunity to register the // first time the aggregation is used if (this.cacheAggregations && !isCacheDisabled() && changeListener != null) { Util.discard( changeListener.isAggregationChanged(aggregationKey)); } return aggregation; } /** * Looks for an existing aggregation over a given set of columns, in the * local segment cache, returning null if there is none. * *

    Must be called from synchronized context. * * @see Util#deprecated(Object) currently always returns null -- remove */ public Aggregation lookupSegment(AggregationKey aggregationKey) { return localBars.get().aggregations.get(aggregationKey); } /** For testing purposes only. */ public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * Returns the DataSource used to connect to the underlying DBMS. * * @return DataSource */ public DataSource getDataSource() { return dataSource; } /** * Retrieves the {@link RolapStar.Measure} in which a measure is stored. */ public static Measure getStarMeasure(Member member) { return (Measure) ((RolapStoredMeasure) member).getStarMeasure(); } /** * Retrieves a named column, returns null if not found. */ public Column[] lookupColumns(String tableAlias, String columnName) { final Table table = factTable.findDescendant(tableAlias); return (table == null) ? null : table.lookupColumns(columnName); } /** * This is used by TestAggregationManager only. */ public Column lookupColumn(String tableAlias, String columnName) { final Table table = factTable.findDescendant(tableAlias); return (table == null) ? null : table.lookupColumn(columnName); } public BitKey getBitKey(String[] tableAlias, String[] columnName) { BitKey bitKey = BitKey.Factory.makeBitKey(getColumnCount()); Column starColumn; for (int i = 0; i < tableAlias.length; i ++) { starColumn = lookupColumn(tableAlias[i], columnName[i]); if (starColumn != null) { bitKey.set(starColumn.getBitPosition()); } } return bitKey; } /** * Returns a list of all aliases used in this star. */ public List getAliasList() { List aliasList = new ArrayList(); if (factTable != null) { collectAliases(aliasList, factTable); } return aliasList; } /** * Finds all of the table aliases in a table and its children. */ private static void collectAliases(List aliasList, Table table) { aliasList.add(table.getAlias()); for (Table child : table.children) { collectAliases(aliasList, child); } } /** * Collects all columns in this table and its children. * If joinColumn is specified, only considers child tables * joined by the given column. */ public static void collectColumns( Collection columnList, Table table, MondrianDef.Column joinColumn) { if (joinColumn == null) { columnList.addAll(table.columnList); } for (Table child : table.children) { if (joinColumn == null || child.getJoinCondition().left.equals(joinColumn)) { collectColumns(columnList, child, null); } } } private boolean containsColumn(String tableName, String columnName) { Connection jdbcConnection; try { jdbcConnection = dataSource.getConnection(); } catch (SQLException e1) { throw Util.newInternal( e1, "Error while creating connection from data source"); } try { final DatabaseMetaData metaData = jdbcConnection.getMetaData(); final ResultSet columns = metaData.getColumns(null, null, tableName, columnName); return columns.next(); } catch (SQLException e) { throw Util.newInternal( "Error while retrieving metadata for table '" + tableName + "', column '" + columnName + "'"); } finally { try { jdbcConnection.close(); } catch (SQLException e) { // ignore } } } /** * Adds a column to the star's list of all columns across all tables. * * @param c the column to add */ private void addColumn(Column c) { columnList.add(c.getBitPosition(), c); } /** * Look up the column at the given bit position. * * @param bitPos bit position to look up * @return column at the given position */ public Column getColumn(int bitPos) { return columnList.get(bitPos); } public RolapSchema getSchema() { return schema; } /** * Generates a SQL statement to read all instances of the given attributes. * *

    The SQL statement is of the form {@code SELECT ... FROM ... JOIN ... * GROUP BY ...}. It is useful for populating an aggregate table. * * @param columnList List of columns (attributes and measures) * @param columnNameList List of column names (must have same cardinality * as {@code columnList}) * @return SQL SELECT statement */ public String generateSql( List columnList, List columnNameList) { final SqlQuery query = new SqlQuery(sqlQueryDialect, true); query.addFrom( factTable.relation, factTable.relation.getAlias(), false); int k = -1; for (Column column : columnList) { ++k; column.table.addToFrom(query, false, true); String columnExpr = column.generateExprString(query); if (column instanceof Measure) { Measure measure = (Measure) column; columnExpr = measure.getAggregator().getExpression(columnExpr); } final String columnName = columnNameList.get(k); String alias = query.addSelect(columnExpr, null, columnName); if (!(column instanceof Measure)) { query.addGroupBy(columnExpr, alias); } } // remove whitespace from query - in particular, the trailing newline return query.toString().trim(); } public String toString() { StringWriter sw = new StringWriter(256); PrintWriter pw = new PrintWriter(sw); print(pw, "", true); pw.flush(); return sw.toString(); } /** * Prints the state of this RolapStar * * @param pw Writer * @param prefix Prefix to print at the start of each line * @param structure Whether to print the structure of the star */ public void print(PrintWriter pw, String prefix, boolean structure) { if (structure) { pw.print(prefix); pw.println("RolapStar:"); String subprefix = prefix + " "; factTable.print(pw, subprefix); for (AggStar aggStar : getAggStars()) { aggStar.print(pw, subprefix); } } } /** * Returns the listener for changes to this star's underlying database. * * @return Returns the Data source change listener. */ public DataSourceChangeListener getChangeListener() { return changeListener; } /** * Sets the listener for changes to this star's underlying database. * * @param changeListener The Data source change listener to set */ public void setChangeListener(DataSourceChangeListener changeListener) { this.changeListener = changeListener; } // -- Inner classes -------------------------------------------------------- /** * A column in a star schema. */ public static class Column { public static final Comparator COMPARATOR = new Comparator() { public int compare( Column object1, Column object2) { return Util.compare( object1.getBitPosition(), object2.getBitPosition()); } }; private final Table table; private final MondrianDef.Expression expression; private final Dialect.Datatype datatype; private final SqlStatement.Type internalType; private final String name; /** * When a Column is a column, and not a Measure, the parent column * is the coloumn associated with next highest Level. */ private final Column parentColumn; /** * This is used during both aggregate table recognition and aggregate * table generation. For multiple dimension usages, multiple shared * dimension or unshared dimension with the same column names, * this is used to disambiguate aggregate column names. */ private final String usagePrefix; /** * This is only used in RolapAggregationManager and adds * non-constraining columns making the drill-through queries easier for * humans to understand. */ private final Column nameColumn; private boolean isNameColumn; /** this has a unique value per star */ private final int bitPosition; /** * The estimated cardinality of the column. * {@link Integer#MIN_VALUE} means unknown. */ private int approxCardinality = Integer.MIN_VALUE; private Column( String name, Table table, MondrianDef.Expression expression, Dialect.Datatype datatype) { this( name, table, expression, datatype, null, null, null, null, Integer.MIN_VALUE, table.star.nextColumnCount()); } private Column( String name, Table table, MondrianDef.Expression expression, Dialect.Datatype datatype, SqlStatement.Type internalType, Column nameColumn, Column parentColumn, String usagePrefix, int approxCardinality, int bitPosition) { this.name = name; this.table = table; this.expression = expression; this.datatype = datatype; this.internalType = internalType; this.bitPosition = bitPosition; this.nameColumn = nameColumn; this.parentColumn = parentColumn; this.usagePrefix = usagePrefix; this.approxCardinality = approxCardinality; if (nameColumn != null) { nameColumn.isNameColumn = true; } if (table != null) { table.star.addColumn(this); } } /** * Fake column. * * @param datatype Datatype */ protected Column(Dialect.Datatype datatype) { this( null, null, null, datatype, null, null, null, null, Integer.MIN_VALUE, 0); } public boolean equals(Object obj) { if (! (obj instanceof RolapStar.Column)) { return false; } RolapStar.Column other = (RolapStar.Column) obj; // Note: both columns have to be from the same table return other.table == this.table && Util.equals(other.expression, this.expression) && other.datatype == this.datatype && other.name.equals(this.name); } public int hashCode() { int h = name.hashCode(); h = Util.hash(h, table); return h; } public String getName() { return name; } public int getBitPosition() { return bitPosition; } public RolapStar getStar() { return table.star; } public RolapStar.Table getTable() { return table; } public SqlQuery getSqlQuery() { return getTable().getStar().getSqlQuery(); } public RolapStar.Column getNameColumn() { return nameColumn; } public RolapStar.Column getParentColumn() { return parentColumn; } public String getUsagePrefix() { return usagePrefix; } public boolean isNameColumn() { return isNameColumn; } public MondrianDef.Expression getExpression() { return expression; } /** * Generates a SQL expression, which typically this looks like * this: tableName.columnName. */ public String generateExprString(SqlQuery query) { return getExpression().getExpression(query); } /** * Get column cardinality from the schema cache if possible; * otherwise issue a select count(distinct) query to retrieve * the cardinality and stores it in the cache. * * @return the column cardinality. */ public int getCardinality() { if (approxCardinality == Integer.MIN_VALUE) { RolapStar star = getStar(); RolapSchema schema = star.getSchema(); Integer card = schema.getCachedRelationExprCardinality( table.getRelation(), expression); if (card != null) { approxCardinality = card.intValue(); } else { // If not cached, issue SQL to get the cardinality for // this column. approxCardinality = getCardinality(star.getDataSource()); schema.putCachedRelationExprCardinality( table.getRelation(), expression, approxCardinality); } } return approxCardinality; } private int getCardinality(DataSource dataSource) { SqlQuery sqlQuery = getSqlQuery(); if (sqlQuery.getDialect().allowsCountDistinct()) { // e.g. "select count(distinct product_id) from product" sqlQuery.addSelect( "count(distinct " + generateExprString(sqlQuery) + ")", null); // no need to join fact table here table.addToFrom(sqlQuery, true, false); } else if (sqlQuery.getDialect().allowsFromQuery()) { // Some databases (e.g. Access) don't like 'count(distinct)', // so use, e.g., "select count(*) from (select distinct // product_id from product)" SqlQuery inner = sqlQuery.cloneEmpty(); inner.setDistinct(true); inner.addSelect(generateExprString(inner), null); boolean failIfExists = true, joinToParent = false; table.addToFrom(inner, failIfExists, joinToParent); sqlQuery.addSelect("count(*)", null); sqlQuery.addFrom(inner, "init", failIfExists); } else { throw Util.newInternal( "Cannot compute cardinality: this " + "database neither supports COUNT DISTINCT nor SELECT in " + "the FROM clause."); } String sql = sqlQuery.toString(); final SqlStatement stmt = RolapUtil.executeQuery( dataSource, sql, new Locus( Locus.peek().execution, "RolapStar.Column.getCardinality", "while counting distinct values of column '" + expression.getGenericExpression())); try { ResultSet resultSet = stmt.getResultSet(); Util.assertTrue(resultSet.next()); ++stmt.rowCount; return resultSet.getInt(1); } catch (SQLException e) { throw stmt.handle(e); } finally { stmt.close(); } } /** * Generates a predicate that a column matches one of a list of values. * *

    * Several possible outputs, depending upon whether the there are * nulls:

      * *
    • One not-null value: foo.bar = 1 * *
    • All values not null: foo.bar in (1, 2, 3)
    • Null and not null values: * (foo.bar is null or foo.bar in (1, 2)) * *
    • Only null values: * foo.bar is null
    • * *
    • String values: foo.bar in ('a', 'b', 'c')
    • * *
    */ public static String createInExpr( final String expr, StarColumnPredicate predicate, Dialect.Datatype datatype, SqlQuery sqlQuery) { // Sometimes a column predicate is created without a column. This // is unfortunate, and we will fix it some day. For now, create // a fake column with all of the information needed by the toSql // method, and a copy of the predicate wrapping that fake column. if (!Bug.BugMondrian313Fixed || !Bug.BugMondrian314Fixed && predicate.getConstrainedColumn() == null) { Column column = new Column(datatype) { public String generateExprString(SqlQuery query) { return expr; } }; predicate = predicate.cloneWithColumn(column); } StringBuilder buf = new StringBuilder(64); predicate.toSql(sqlQuery, buf); return buf.toString(); } public String toString() { StringWriter sw = new StringWriter(256); PrintWriter pw = new PrintWriter(sw); print(pw, ""); pw.flush(); return sw.toString(); } /** * Prints this column. * * @param pw Print writer * @param prefix Prefix to print first, such as spaces for indentation */ public void print(PrintWriter pw, String prefix) { SqlQuery sqlQuery = getSqlQuery(); pw.print(prefix); pw.print(getName()); pw.print(" ("); pw.print(getBitPosition()); pw.print("): "); pw.print(generateExprString(sqlQuery)); } public Dialect.Datatype getDatatype() { return datatype; } /** * Returns a string representation of the datatype of this column, in * the dialect specified. For example, 'DECIMAL(10, 2) NOT NULL'. * * @param dialect Dialect * @return String representation of column's datatype */ public String getDatatypeString(Dialect dialect) { final SqlQuery query = new SqlQuery(dialect); query.addFrom( table.star.factTable.relation, table.star.factTable.alias, false); query.addFrom(table.relation, table.alias, false); query.addSelect(expression.getExpression(query), null); final String sql = query.toString(); Connection jdbcConnection = null; try { jdbcConnection = table.star.dataSource.getConnection(); final PreparedStatement pstmt = jdbcConnection.prepareStatement(sql); final ResultSetMetaData resultSetMetaData = pstmt.getMetaData(); assert resultSetMetaData.getColumnCount() == 1; final String type = resultSetMetaData.getColumnTypeName(1); int precision = resultSetMetaData.getPrecision(1); final int scale = resultSetMetaData.getScale(1); if (type.equals("DOUBLE")) { precision = 0; } String typeString; if (precision == 0) { typeString = type; } else if (scale == 0) { typeString = type + "(" + precision + ")"; } else { typeString = type + "(" + precision + ", " + scale + ")"; } pstmt.close(); jdbcConnection.close(); jdbcConnection = null; return typeString; } catch (SQLException e) { throw Util.newError( e, "Error while deriving type of column " + toString()); } finally { if (jdbcConnection != null) { try { jdbcConnection.close(); } catch (SQLException e) { // ignore } } } } public SqlStatement.Type getInternalType() { return internalType; } } /** * Definition of a measure in a star schema. * *

    A measure is basically just a column; except that its * {@link #aggregator} defines how it is to be rolled up. */ public static class Measure extends Column { private final String cubeName; private final RolapAggregator aggregator; public Measure( String name, String cubeName, RolapAggregator aggregator, Table table, MondrianDef.Expression expression, Dialect.Datatype datatype) { super(name, table, expression, datatype); this.cubeName = cubeName; this.aggregator = aggregator; } public RolapAggregator getAggregator() { return aggregator; } public boolean equals(Object o) { if (! (o instanceof RolapStar.Measure)) { return false; } RolapStar.Measure that = (RolapStar.Measure) o; if (!super.equals(that)) { return false; } // Measure names are only unique within their cube - and remember // that a given RolapStar can support multiple cubes if they have // the same fact table. if (!cubeName.equals(that.cubeName)) { return false; } // Note: both measure have to have the same aggregator return (that.aggregator == this.aggregator); } public int hashCode() { int h = super.hashCode(); h = Util.hash(h, aggregator); return h; } public void print(PrintWriter pw, String prefix) { SqlQuery sqlQuery = getSqlQuery(); pw.print(prefix); pw.print(getName()); pw.print(" ("); pw.print(getBitPosition()); pw.print("): "); pw.print( aggregator.getExpression( getExpression() == null ? null : generateExprString(sqlQuery))); } public String getCubeName() { return cubeName; } } /** * Definition of a table in a star schema. * *

    A 'table' is defined by a * {@link mondrian.olap.MondrianDef.RelationOrJoin} so may, in fact, be a * view. * *

    Every table in the star schema except the fact table has a parent * table, and a condition which specifies how it is joined to its parent. * So the star schema is, in effect, a hierarchy with the fact table at * its root. */ public static class Table { private final RolapStar star; private final MondrianDef.Relation relation; private final List columnList; private final Table parent; private List

  • children; private final Condition joinCondition; private final String alias; private Table( RolapStar star, MondrianDef.Relation relation, Table parent, Condition joinCondition) { this.star = star; this.relation = relation; this.alias = chooseAlias(); this.parent = parent; final AliasReplacer aliasReplacer = new AliasReplacer(relation.getAlias(), this.alias); this.joinCondition = aliasReplacer.visit(joinCondition); if (this.joinCondition != null) { this.joinCondition.table = this; } this.columnList = new ArrayList(); this.children = Collections.emptyList(); Util.assertTrue((parent == null) == (joinCondition == null)); } /** * Returns the condition by which a dimension table is connected to its * {@link #getParentTable() parent}; or null if this is the fact table. */ public Condition getJoinCondition() { return joinCondition; } /** * Returns this table's parent table, or null if this is the fact table * (which is at the center of the star). */ public Table getParentTable() { return parent; } private void addColumn(Column column) { columnList.add(column); } /** * Adds to a list all columns of this table or a child table * which are present in a given bitKey. * *

    Note: This method is slow, but that's acceptable because it is * only used for tracing. It would be more efficient to store an * array in the {@link RolapStar} mapping column ordinals to columns. */ private void collectColumns(BitKey bitKey, List list) { for (Column column : getColumns()) { if (bitKey.get(column.getBitPosition())) { list.add(column); } } for (Table table : getChildren()) { table.collectColumns(bitKey, list); } } /** * Returns an array of all columns in this star with a given name. */ public Column[] lookupColumns(String columnName) { List l = new ArrayList(); for (Column column : getColumns()) { if (column.getExpression() instanceof MondrianDef.Column) { MondrianDef.Column columnExpr = (MondrianDef.Column) column.getExpression(); if (columnExpr.name.equals(columnName)) { l.add(column); } } else if (column.getExpression() instanceof MondrianDef.KeyExpression) { MondrianDef.KeyExpression columnExpr = (MondrianDef.KeyExpression) column.getExpression(); if (columnExpr.toString().equals(columnName)) { l.add(column); } } } return l.toArray(new Column[l.size()]); } public Column lookupColumn(String columnName) { for (Column column : getColumns()) { if (column.getExpression() instanceof MondrianDef.Column) { MondrianDef.Column columnExpr = (MondrianDef.Column) column.getExpression(); if (columnExpr.name.equals(columnName)) { return column; } } else if (column.getExpression() instanceof MondrianDef.KeyExpression) { MondrianDef.KeyExpression columnExpr = (MondrianDef.KeyExpression) column.getExpression(); if (columnExpr.toString().equals(columnName)) { return column; } } else if (column.getName().equals(columnName)) { return column; } } return null; } /** * Given a MondrianDef.Expression return a column with that expression * or null. */ public Column lookupColumnByExpression(MondrianDef.Expression xmlExpr) { for (Column column : getColumns()) { if (column instanceof Measure) { continue; } if (column.getExpression().equals(xmlExpr)) { return column; } } return null; } public boolean containsColumn(Column column) { return getColumns().contains(column); } /** * Look up a {@link Measure} by its name. * Returns null if not found. */ public Measure lookupMeasureByName(String cubeName, String name) { for (Column column : getColumns()) { if (column instanceof Measure) { Measure measure = (Measure) column; if (measure.getName().equals(name) && measure.getCubeName().equals(cubeName)) { return measure; } } } return null; } RolapStar getStar() { return star; } private SqlQuery getSqlQuery() { return getStar().getSqlQuery(); } public MondrianDef.Relation getRelation() { return relation; } /** Chooses an alias which is unique within the star. */ private String chooseAlias() { List aliasList = star.getAliasList(); for (int i = 0;; ++i) { String candidateAlias = relation.getAlias(); if (i > 0) { candidateAlias += "_" + i; } if (!aliasList.contains(candidateAlias)) { return candidateAlias; } } } public String getAlias() { return alias; } /** * Sometimes one need to get to the "real" name when the table has * been given an alias. */ public String getTableName() { if (relation instanceof MondrianDef.Table) { MondrianDef.Table t = (MondrianDef.Table) relation; return t.name; } else { return null; } } synchronized void makeMeasure(RolapBaseCubeMeasure measure) { // Remove assertion to allow cube to be recreated // assert lookupMeasureByName( // measure.getCube().getName(), measure.getName()) == null; RolapStar.Measure starMeasure = new RolapStar.Measure( measure.getName(), measure.getCube().getName(), measure.getAggregator(), this, measure.getMondrianDefExpression(), measure.getDatatype()); measure.setStarMeasure(starMeasure); // reverse mapping if (containsColumn(starMeasure)) { star.decrementColumnCount(); } else { addColumn(starMeasure); } } /** * This is only called by RolapCube. If the RolapLevel has a non-null * name expression then two columns will be made, otherwise only one. * Updates the RolapLevel to RolapStar.Column mapping associated with * this cube. * * @param cube Cube * @param level Level * @param parentColumn Parent column */ synchronized Column makeColumns( RolapCube cube, RolapCubeLevel level, Column parentColumn, String usagePrefix) { Column nameColumn = null; if (level.getNameExp() != null) { // make a column for the name expression nameColumn = makeColumnForLevelExpr( cube, level, level.getName(), level.getNameExp(), Dialect.Datatype.String, null, null, null, null); } // select the column's name depending upon whether or not a // "named" column, above, has been created. String name = (level.getNameExp() == null) ? level.getName() : level.getName() + " (Key)"; // If the nameColumn is not null, then it is associated with this // column. Column column = makeColumnForLevelExpr( cube, level, name, level.getKeyExp(), level.getDatatype(), level.getInternalType(), nameColumn, parentColumn, usagePrefix); if (column != null) { level.setStarKeyColumn(column); } return column; } private Column makeColumnForLevelExpr( RolapCube cube, RolapLevel level, String name, MondrianDef.Expression xmlExpr, Dialect.Datatype datatype, SqlStatement.Type internalType, Column nameColumn, Column parentColumn, String usagePrefix) { Table table = this; if (xmlExpr instanceof MondrianDef.Column) { final MondrianDef.Column xmlColumn = (MondrianDef.Column) xmlExpr; String tableName = xmlColumn.table; table = findAncestor(tableName); if (table == null) { throw Util.newError( "Level '" + level.getUniqueName() + "' of cube '" + this + "' is invalid: table '" + tableName + "' is not found in current scope" + Util.nl + ", star:" + Util.nl + getStar()); } RolapStar.AliasReplacer aliasReplacer = new RolapStar.AliasReplacer(tableName, table.getAlias()); xmlExpr = aliasReplacer.visit(xmlExpr); } // does the column already exist?? Column c = lookupColumnByExpression(xmlExpr); RolapStar.Column column; // Verify Column is not null and not the same as the // nameColumn created previously (bug 1438285) if (c != null && !c.equals(nameColumn)) { // Yes, well just reuse it // You might wonder why the column need be returned if it // already exists. Well, it might have been created for one // cube, but for another cube using the same fact table, it // still needs to be put into the cube level to column map. // Trust me, return null and a junit test fails. column = c; } else { // Make a new column and add it column = new RolapStar.Column( name, table, xmlExpr, datatype, internalType, nameColumn, parentColumn, usagePrefix, level.getApproxRowCount(), star.nextColumnCount()); addColumn(column); } return column; } /** * Extends this 'leg' of the star by adding relation * joined by joinCondition. If the same expression is * already present, does not create it again. Stores the unaliased * table names to RolapStar.Table mapping associated with the * input cube. */ synchronized Table addJoin( RolapCube cube, MondrianDef.RelationOrJoin relationOrJoin, RolapStar.Condition joinCondition) { if (relationOrJoin instanceof MondrianDef.Relation) { final MondrianDef.Relation relation = (MondrianDef.Relation) relationOrJoin; RolapStar.Table starTable = findChild(relation, joinCondition); if (starTable == null) { starTable = new RolapStar.Table( star, relation, this, joinCondition); if (this.children.isEmpty()) { this.children = new ArrayList

    (); } this.children.add(starTable); } return starTable; } else if (relationOrJoin instanceof MondrianDef.Join) { MondrianDef.Join join = (MondrianDef.Join) relationOrJoin; RolapStar.Table leftTable = addJoin(cube, join.left, joinCondition); String leftAlias = join.leftAlias; if (leftAlias == null) { // REVIEW: is cast to Relation valid? leftAlias = ((MondrianDef.Relation) join.left).getAlias(); if (leftAlias == null) { throw Util.newError( "missing leftKeyAlias in " + relationOrJoin); } } assert leftTable.findAncestor(leftAlias) == leftTable; // switch to uniquified alias leftAlias = leftTable.getAlias(); String rightAlias = join.rightAlias; if (rightAlias == null) { // the right relation of a join may be a join // if so, we need to use the right relation join's // left relation's alias. if (join.right instanceof MondrianDef.Join) { MondrianDef.Join joinright = (MondrianDef.Join) join.right; // REVIEW: is cast to Relation valid? rightAlias = ((MondrianDef.Relation) joinright.left) .getAlias(); } else { // REVIEW: is cast to Relation valid? rightAlias = ((MondrianDef.Relation) join.right) .getAlias(); } if (rightAlias == null) { throw Util.newError( "missing rightKeyAlias in " + relationOrJoin); } } joinCondition = new RolapStar.Condition( new MondrianDef.Column(leftAlias, join.leftKey), new MondrianDef.Column(rightAlias, join.rightKey)); RolapStar.Table rightTable = leftTable.addJoin( cube, join.right, joinCondition); return rightTable; } else { throw Util.newInternal("bad relation type " + relationOrJoin); } } /** * Returns a child relation which maps onto a given relation, or null * if there is none. */ public Table findChild( MondrianDef.Relation relation, Condition joinCondition) { for (Table child : getChildren()) { if (child.relation.equals(relation)) { Condition condition = joinCondition; if (!Util.equalName(relation.getAlias(), child.alias)) { // Make the two conditions comparable, by replacing // occurrence of this table's alias with occurrences // of the child's alias. AliasReplacer aliasReplacer = new AliasReplacer( relation.getAlias(), child.alias); condition = aliasReplacer.visit(joinCondition); } if (child.joinCondition.equals(condition)) { return child; } } } return null; } /** * Returns a descendant with a given alias, or null if none found. */ public Table findDescendant(String seekAlias) { if (getAlias().equals(seekAlias)) { return this; } for (Table child : getChildren()) { Table found = child.findDescendant(seekAlias); if (found != null) { return found; } } return null; } /** * Returns an ancestor with a given alias, or null if not found. */ public Table findAncestor(String tableName) { for (Table t = this; t != null; t = t.parent) { if (t.relation.getAlias().equals(tableName)) { return t; } } return null; } public boolean equalsTableName(String tableName) { if (this.relation instanceof MondrianDef.Table) { MondrianDef.Table mt = (MondrianDef.Table) this.relation; if (mt.name.equals(tableName)) { return true; } } return false; } /** * Adds this table to the FROM clause of a query, and also, if * joinToParent, any join condition. * * @param query Query to add to * @param failIfExists Pass in false if you might have already added * the table before and if that happens you want to do nothing. * @param joinToParent Pass in true if you are constraining a cell * calculation, false if you are retrieving members. */ public void addToFrom( SqlQuery query, boolean failIfExists, boolean joinToParent) { query.addFrom(relation, alias, failIfExists); Util.assertTrue((parent == null) == (joinCondition == null)); if (joinToParent) { if (parent != null) { parent.addToFrom(query, failIfExists, joinToParent); } if (joinCondition != null) { query.addWhere(joinCondition.toString(query)); } } } /** * Returns a list of child {@link Table}s. */ public List
    getChildren() { return children; } /** * Returns a list of this table's {@link Column}s. */ public List getColumns() { return columnList; } /** * Finds the child table of the fact table with the given columnName * used in its left join condition. This is used by the AggTableManager * while characterizing the fact table columns. */ public RolapStar.Table findTableWithLeftJoinCondition( final String columnName) { for (Table child : getChildren()) { Condition condition = child.joinCondition; if (condition != null) { if (condition.left instanceof MondrianDef.Column) { MondrianDef.Column mcolumn = (MondrianDef.Column) condition.left; if (mcolumn.name.equals(columnName)) { return child; } } } } return null; } /** * This is used during aggregate table validation to make sure that the * mapping from for the aggregate join condition is valid. It returns * the child table with the matching left join condition. */ public RolapStar.Table findTableWithLeftCondition( final MondrianDef.Expression left) { for (Table child : getChildren()) { Condition condition = child.joinCondition; if (condition != null) { if (condition.left instanceof MondrianDef.Column) { MondrianDef.Column mcolumn = (MondrianDef.Column) condition.left; if (mcolumn.equals(left)) { return child; } } } } return null; } /** * Note: I do not think that this is ever true. */ public boolean isFunky() { return (relation == null); } public boolean equals(Object obj) { if (!(obj instanceof Table)) { return false; } Table other = (Table) obj; return getAlias().equals(other.getAlias()); } public int hashCode() { return getAlias().hashCode(); } public String toString() { StringWriter sw = new StringWriter(256); PrintWriter pw = new PrintWriter(sw); print(pw, ""); pw.flush(); return sw.toString(); } /** * Prints this table and its children. */ public void print(PrintWriter pw, String prefix) { pw.print(prefix); pw.println("Table:"); String subprefix = prefix + " "; pw.print(subprefix); pw.print("alias="); pw.println(getAlias()); if (this.relation != null) { pw.print(subprefix); pw.print("relation="); pw.println(relation); } pw.print(subprefix); pw.println("Columns:"); String subsubprefix = subprefix + " "; for (Column column : getColumns()) { column.print(pw, subsubprefix); pw.println(); } if (this.joinCondition != null) { this.joinCondition.print(pw, subprefix); } for (Table child : getChildren()) { child.print(pw, subprefix); } } /** * Returns whether this table has a column with the given name. */ public boolean containsColumn(String columnName) { if (relation instanceof MondrianDef.Relation) { return star.containsColumn( ((MondrianDef.Relation) relation).getAlias(), columnName); } else { // todo: Deal with join. return false; } } } public static class Condition { private static final Logger LOGGER = Logger.getLogger(Condition.class); private final MondrianDef.Expression left; private final MondrianDef.Expression right; // set in Table constructor Table table; Condition( MondrianDef.Expression left, MondrianDef.Expression right) { assert left != null; assert right != null; if (!(left instanceof MondrianDef.Column)) { // TODO: Will this ever print?? if not then left should be // of type MondrianDef.Column. LOGGER.debug( "Condition.left NOT Column: " + left.getClass().getName()); } this.left = left; this.right = right; } public MondrianDef.Expression getLeft() { return left; } public String getLeft(final SqlQuery query) { return this.left.getExpression(query); } public MondrianDef.Expression getRight() { return right; } public String getRight(final SqlQuery query) { return this.right.getExpression(query); } public String toString(SqlQuery query) { return left.getExpression(query) + " = " + right.getExpression(query); } public int hashCode() { return left.hashCode() ^ right.hashCode(); } public boolean equals(Object obj) { if (!(obj instanceof Condition)) { return false; } Condition that = (Condition) obj; return this.left.equals(that.left) && this.right.equals(that.right); } public String toString() { StringWriter sw = new StringWriter(256); PrintWriter pw = new PrintWriter(sw); print(pw, ""); pw.flush(); return sw.toString(); } /** * Prints this table and its children. */ public void print(PrintWriter pw, String prefix) { SqlQuery sqlQueuy = table.getSqlQuery(); pw.print(prefix); pw.println("Condition:"); String subprefix = prefix + " "; pw.print(subprefix); pw.print("left="); // print the foreign key bit position if we can figure it out if (left instanceof MondrianDef.Column) { MondrianDef.Column c = (MondrianDef.Column) left; Column col = table.star.getFactTable().lookupColumn(c.name); if (col != null) { pw.print(" ("); pw.print(col.getBitPosition()); pw.print(") "); } } pw.println(left.getExpression(sqlQueuy)); pw.print(subprefix); pw.print("right="); pw.println(right.getExpression(sqlQueuy)); } } /** * Creates a copy of an expression, everywhere replacing one alias * with another. */ public static class AliasReplacer { private final String oldAlias; private final String newAlias; public AliasReplacer(String oldAlias, String newAlias) { this.oldAlias = oldAlias; this.newAlias = newAlias; } private Condition visit(Condition condition) { if (condition == null) { return null; } if (newAlias.equals(oldAlias)) { return condition; } return new Condition( visit(condition.left), visit(condition.right)); } public MondrianDef.Expression visit(MondrianDef.Expression expression) { if (expression == null) { return null; } if (newAlias.equals(oldAlias)) { return expression; } if (expression instanceof MondrianDef.Column) { MondrianDef.Column column = (MondrianDef.Column) expression; return new MondrianDef.Column(visit(column.table), column.name); } else { throw Util.newInternal("need to implement " + expression); } } private String visit(String table) { return table.equals(oldAlias) ? newAlias : table; } } /** * Comparator to compare columns based on their name */ public static class ColumnComparator implements Comparator { public static ColumnComparator instance = new ColumnComparator(); private ColumnComparator() { } public int compare(Column o1, Column o2) { return o1.getName().compareTo(o2.getName()); } } } // End RolapStar.java mondrian-3.4.1/src/main/mondrian/rolap/RolapCalculation.java0000644000175000017500000000464711735330606023777 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.Calc; /** * Entry in the evaluation context that indicates a calculation that needs to * be performed before we get to the atomic stored cells. * *

    Most of the time it's just members that are calculated, but calculated * tuples arise when the slicer is a set of tuples. * *

    The evaluator uses this interface to efficiently expand a member or tuple * from the evaluation context. When evaluated, a calculation will change one or * more elements of the context (calculated members just change one, but tuple * sets in the slicer can change more than one). * * @author jhyde * @since May 15, 2009 */ interface RolapCalculation { /** * Pushes this calculated member or tuple onto the stack of evaluation * contexts, and sets the context to the default member of the hierarchy. * * @param evaluator Evaluator */ void setContextIn(RolapEvaluator evaluator); /** * Returns the solve order of this calculation. Identifies which order * calculations are expanded. * * @return Solve order */ int getSolveOrder(); /** * Returns the ordinal of this calculation; to resolve ties. * * @return Ordinal or calculation */ int getHierarchyOrdinal(); /** * Returns whether this calculation is a member is computed from a * {@code WITH MEMBER} clause in an MDX query. * * @return whether this calculation is computed in an MDX query */ boolean isCalculatedInQuery(); /** * Returns the compiled expression to evaluate the scalar value of the * current cell. This method will be called frequently, so the * implementation should probably compile once and cache the result. * * @param root Root evaluation context * @return Compiled scalar expression */ Calc getCompiledExpression(RolapEvaluatorRoot root); /** * Returns whether this calculation contains an aggregate function. * * @return Whether this calculation contains an aggregate function. */ boolean containsAggregateFunction(); } // End RolapCalculation.java mondrian-3.4.1/src/main/mondrian/rolap/StarColumnPredicate.java0000644000175000017500000000712111735330606024441 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho // All Rights Reserved. */ package mondrian.rolap; import java.util.Collection; /** * Refinement of {@link StarPredicate} which constrains precisely one column. * * @author jhyde * @since Jan 15, 2007 */ public interface StarColumnPredicate extends StarPredicate { /** * Adds the values in this constraint to a collection. * * @param collection Collection to add values to */ void values(Collection collection); /** * Returns whether this constraint would return true for a * given value. * * @param value Value * @return Whether predicate is true */ boolean evaluate(Object value); /** * Returns the column constrained by this predicate. * * @return Column constrained by this predicate. */ RolapStar.Column getConstrainedColumn(); /** * Applies this predicate to a predicate from the axis of * a segment, and tests for overlap. The result might be that there * is no overlap, full overlap (so the constraint can be removed), * or partial overlap (so the constraint will need to be replaced with * a stronger constraint, say 'x > 10' is replaced with 'x > 20'). * * @param predicate Predicate * @return description of overlap between predicates, if any */ Overlap intersect(StarColumnPredicate predicate); /** * Returns whether this predicate might intersect another predicate. * That is, whether there might be a value which holds true for both * constraints. * * @param other Other constraint * @return Whether constraints intersect */ boolean mightIntersect(StarPredicate other); // override with stricter return type StarColumnPredicate minus(StarPredicate predicate); /** * Returns this union of this Predicate with another. * *

    Unlike {@link #or}, the other predicate must be on this column, and * the result is a column predicate. * * @param predicate Another predicate on this column * @return Union predicate on this column */ StarColumnPredicate orColumn(StarColumnPredicate predicate); /** * This method is required because unfortunately some ColumnPredicate * objects are created without a column. * *

    We call this method to provide a fake column, then call * {@link #toSql(mondrian.rolap.sql.SqlQuery, StringBuilder)}. * *

    todo: remove this method when * {@link mondrian.util.Bug#BugMondrian313Fixed bug MONDRIAN-313} and * {@link mondrian.util.Bug#BugMondrian314Fixed bug MONDRIAN-314} are fixed. */ StarColumnPredicate cloneWithColumn(RolapStar.Column column); /** * Returned by * {@link mondrian.rolap.StarColumnPredicate#intersect}, * describes whether two predicates overlap, and if so, the remaining * predicate. */ public static class Overlap { public final boolean matched; public final StarColumnPredicate remaining; public final float selectivity; public Overlap( boolean matched, StarColumnPredicate remaining, float selectivity) { this.matched = matched; this.remaining = remaining; this.selectivity = selectivity; } } } // End StarColumnPredicate.java mondrian-3.4.1/src/main/mondrian/rolap/DelegatingRolapMember.java0000644000175000017500000001025111735330606024720 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2008-2010 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import java.util.List; import java.util.Map; /** * Implementation of {@link mondrian.rolap.RolapMember} that delegates all calls * to an underlying member. * * @author jhyde * @since Mar 16, 2010 */ public class DelegatingRolapMember extends RolapMemberBase { protected final RolapMember member; protected DelegatingRolapMember(RolapMember member) { super(); this.member = member; } public RolapLevel getLevel() { return member.getLevel(); } public Object getKey() { return member.getKey(); } public RolapMember getParentMember() { return member.getParentMember(); } public RolapHierarchy getHierarchy() { return member.getHierarchy(); } public String getParentUniqueName() { return member.getParentUniqueName(); } public MemberType getMemberType() { return member.getMemberType(); } public boolean isParentChildLeaf() { return member.isParentChildLeaf(); } public void setName(String name) { member.setName(name); } public boolean isAll() { return member.isAll(); } public boolean isMeasure() { return member.isMeasure(); } public boolean isNull() { return member.isNull(); } public boolean isChildOrEqualTo(Member member2) { return member.isChildOrEqualTo(member2); } public boolean isCalculated() { return member.isCalculated(); } public boolean isEvaluated() { return member.isEvaluated(); } public int getSolveOrder() { return member.getSolveOrder(); } public Exp getExpression() { return member.getExpression(); } public List getAncestorMembers() { return member.getAncestorMembers(); } public boolean isCalculatedInQuery() { return member.isCalculatedInQuery(); } public Object getPropertyValue(String propertyName) { return member.getPropertyValue(propertyName); } public Object getPropertyValue(String propertyName, boolean matchCase) { return member.getPropertyValue(propertyName, matchCase); } public String getPropertyFormattedValue(String propertyName) { return member.getPropertyFormattedValue(propertyName); } public void setProperty(String name, Object value) { member.setProperty(name, value); } public Property[] getProperties() { return member.getProperties(); } public int getOrdinal() { return member.getOrdinal(); } public Comparable getOrderKey() { return member.getOrderKey(); } public boolean isHidden() { return member.isHidden(); } public int getDepth() { return member.getDepth(); } public Member getDataMember() { return member.getDataMember(); } @SuppressWarnings({"unchecked"}) public int compareTo(Object o) { return member.compareTo(o); } public String getUniqueName() { return member.getUniqueName(); } public String getName() { return member.getName(); } public String getDescription() { return member.getDescription(); } public OlapElement lookupChild( SchemaReader schemaReader, Id.Segment s, MatchType matchType) { return member.lookupChild(schemaReader, s, matchType); } public Map getAnnotationMap() { return member.getAnnotationMap(); } public String getQualifiedName() { return member.getQualifiedName(); } public String getCaption() { return member.getCaption(); } public Dimension getDimension() { return member.getDimension(); } public boolean isAllMember() { return member.isAllMember(); } } // End DelegatingRolapMember.java mondrian-3.4.1/src/main/mondrian/rolap/DefaultTupleConstraint.java0000644000175000017500000000304611735330606025176 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2010 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.Evaluator; import mondrian.rolap.aggmatcher.AggStar; import mondrian.rolap.sql.*; /** * TupleConstraint which does not restrict the result. */ public class DefaultTupleConstraint implements TupleConstraint { private static final TupleConstraint instance = new DefaultTupleConstraint(); protected DefaultTupleConstraint() { } public void addConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar) { } public void addLevelConstraint( SqlQuery query, RolapCube baseCube, AggStar aggStar, RolapLevel level) { } public MemberChildrenConstraint getMemberChildrenConstraint( RolapMember parent) { return DefaultMemberChildrenConstraint.instance(); } public String toString() { return "DefaultTupleConstraint"; } public Object getCacheKey() { // we have no state, so all instances are equal return this; } public static TupleConstraint instance() { return instance; } public Evaluator getEvaluator() { return null; } } // End DefaultTupleConstraint.java mondrian-3.4.1/src/main/mondrian/rolap/SqlConstraintFactory.java0000644000175000017500000001030411735330606024662 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.olap.*; import mondrian.rolap.sql.*; import java.util.List; import java.util.Set; /** * Creates the right constraint for common tasks. * * @author av * @since Nov 21, 2005 */ public class SqlConstraintFactory { static boolean enabled; private static final SqlConstraintFactory instance = new SqlConstraintFactory(); /** * singleton */ private SqlConstraintFactory() { } private boolean enabled(final Evaluator context) { if (context != null) { return enabled && context.nativeEnabled(); } return enabled; } public static SqlConstraintFactory instance() { setNativeNonEmptyValue(); return instance; } public static void setNativeNonEmptyValue() { enabled = MondrianProperties.instance().EnableNativeNonEmpty.get(); } public MemberChildrenConstraint getMemberChildrenConstraint( Evaluator context) { if (!enabled(context) || !SqlContextConstraint.isValidContext(context, false)) { return DefaultMemberChildrenConstraint.instance(); } return new SqlContextConstraint((RolapEvaluator) context, false); } public TupleConstraint getLevelMembersConstraint(Evaluator context) { return getLevelMembersConstraint(context, null); } /** * Returns a constraint that restricts the members of a level to those that * are non-empty in the given context. If the constraint cannot be * implemented (say if native constraints are disabled) returns null. * * @param context Context within which members must be non-empty * @param levels levels being referenced in the current context * @return Constraint */ public TupleConstraint getLevelMembersConstraint( Evaluator context, Level[] levels) { if (context == null) { return DefaultTupleConstraint.instance(); } if (!enabled(context)) { return DefaultTupleConstraint.instance(); } if (!SqlContextConstraint.isValidContext( context, false, levels, false)) { return DefaultTupleConstraint.instance(); } if (context.isNonEmpty()) { Set joinArgs = new CrossJoinArgFactory(false).buildConstraintFromAllAxes( (RolapEvaluator) context); if (joinArgs.size() > 0) { return new RolapNativeCrossJoin.NonEmptyCrossJoinConstraint( joinArgs.toArray( new CrossJoinArg[joinArgs.size()]), (RolapEvaluator) context); } } return new SqlContextConstraint((RolapEvaluator) context, false); } public MemberChildrenConstraint getChildByNameConstraint( RolapMember parent, Id.Segment childName) { // Ragged hierarchies span multiple levels, so SQL WHERE does not work // there if (!enabled || parent.getHierarchy().isRagged()) { return DefaultMemberChildrenConstraint.instance(); } return new ChildByNameConstraint(childName); } /** * Returns a constraint that allows to read all children of multiple parents * at once using a LevelMember query style. This does not work * for parent/child hierarchies. * * @param parentMembers List of parents (all must belong to same level) * @param mcc The constraint that would return the children for * each single parent * @return constraint */ public TupleConstraint getDescendantsConstraint( List parentMembers, MemberChildrenConstraint mcc) { return new DescendantsConstraint(parentMembers, mcc); } } // End SqlConstraintFactory.java mondrian-3.4.1/src/main/mondrian/rolap/RolapNativeTopCount.java0000644000175000017500000001610511735330606024453 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.mdx.MemberExpr; import mondrian.olap.*; import mondrian.rolap.aggmatcher.AggStar; import mondrian.rolap.sql.*; import mondrian.spi.Dialect; import java.util.ArrayList; import java.util.List; import javax.sql.DataSource; /** * Computes a TopCount in SQL. * * @author av * @since Nov 21, 2005 */ public class RolapNativeTopCount extends RolapNativeSet { public RolapNativeTopCount() { super.setEnabled( MondrianProperties.instance().EnableNativeTopCount.get()); } static class TopCountConstraint extends SetConstraint { Exp orderByExpr; boolean ascending; Integer topCount; public TopCountConstraint( int count, CrossJoinArg[] args, RolapEvaluator evaluator, Exp orderByExpr, boolean ascending) { super(args, evaluator, true); this.orderByExpr = orderByExpr; this.ascending = ascending; this.topCount = new Integer(count); } /** * {@inheritDoc} * *

    TopCount always needs to join the fact table because we want to * evaluate the top count expression which involves a fact. */ protected boolean isJoinRequired() { return true; } public void addConstraint( SqlQuery sqlQuery, RolapCube baseCube, AggStar aggStar) { if (orderByExpr != null) { RolapNativeSql sql = new RolapNativeSql( sqlQuery, aggStar, getEvaluator(), null); String orderBySql = sql.generateTopCountOrderBy(orderByExpr); Dialect dialect = sqlQuery.getDialect(); boolean nullable = deduceNullability(orderByExpr); if (dialect.requiresOrderByAlias()) { String alias = sqlQuery.nextColumnAlias(); alias = dialect.quoteIdentifier(alias); sqlQuery.addSelect(orderBySql, null, alias); sqlQuery.addOrderBy(alias, ascending, true, nullable); } else { sqlQuery.addOrderBy(orderBySql, ascending, true, nullable); } } super.addConstraint(sqlQuery, baseCube, aggStar); } private boolean deduceNullability(Exp expr) { if (!(expr instanceof MemberExpr)) { return true; } final MemberExpr memberExpr = (MemberExpr) expr; if (!(memberExpr.getMember() instanceof RolapStoredMeasure)) { return true; } final RolapStoredMeasure measure = (RolapStoredMeasure) memberExpr.getMember(); return measure.getAggregator() != RolapAggregator.DistinctCount; } public Object getCacheKey() { List key = new ArrayList(); key.add(super.getCacheKey()); // Note: need to use string in order for caching to work if (orderByExpr != null) { key.add(orderByExpr.toString()); } key.add(ascending); key.add(topCount); return key; } } protected boolean restrictMemberTypes() { return true; } NativeEvaluator createEvaluator( RolapEvaluator evaluator, FunDef fun, Exp[] args) { boolean ascending; if (!isEnabled()) { return null; } if (!TopCountConstraint.isValidContext( evaluator, restrictMemberTypes())) { return null; } // is this "TopCount(, , [])" String funName = fun.getName(); if ("TopCount".equalsIgnoreCase(funName)) { ascending = false; } else if ("BottomCount".equalsIgnoreCase(funName)) { ascending = true; } else { return null; } if (args.length < 2 || args.length > 3) { return null; } // extract the set expression List allArgs = crossJoinArgFactory().checkCrossJoinArg(evaluator, args[0]); // checkCrossJoinArg returns a list of CrossJoinArg arrays. The first // array is the CrossJoin dimensions. The second array, if any, // contains additional constraints on the dimensions. If either the list // or the first array is null, then native cross join is not feasible. if (allArgs == null || allArgs.isEmpty() || allArgs.get(0) == null) { return null; } CrossJoinArg[] cjArgs = allArgs.get(0); if (isPreferInterpreter(cjArgs, false)) { return null; } // extract count if (!(args[1] instanceof Literal)) { return null; } int count = ((Literal) args[1]).getIntValue(); // extract "order by" expression SchemaReader schemaReader = evaluator.getSchemaReader(); DataSource ds = schemaReader.getDataSource(); // generate the ORDER BY Clause // Need to generate top count order by to determine whether // or not it can be created. The top count // could change to use an aggregate table later in evaulation SqlQuery sqlQuery = SqlQuery.newQuery(ds, "NativeTopCount"); RolapNativeSql sql = new RolapNativeSql( sqlQuery, null, evaluator, null); Exp orderByExpr = null; if (args.length == 3) { orderByExpr = args[2]; String orderBySQL = sql.generateTopCountOrderBy(args[2]); if (orderBySQL == null) { return null; } } LOGGER.debug("using native topcount"); final int savepoint = evaluator.savepoint(); overrideContext(evaluator, cjArgs, sql.getStoredMeasure()); CrossJoinArg[] predicateArgs = null; if (allArgs.size() == 2) { predicateArgs = allArgs.get(1); } CrossJoinArg[] combinedArgs; if (predicateArgs != null) { // Combined the CJ and the additional predicate args to form the // TupleConstraint. combinedArgs = Util.appendArrays(cjArgs, predicateArgs); } else { combinedArgs = cjArgs; } TupleConstraint constraint = new TopCountConstraint( count, combinedArgs, evaluator, orderByExpr, ascending); evaluator.restore(savepoint); SetEvaluator sev = new SetEvaluator(cjArgs, schemaReader, constraint); sev.setMaxRows(count); return sev; } } // End RolapNativeTopCount.java mondrian-3.4.1/src/main/mondrian/rolap/RolapTupleCalculation.java0000644000175000017500000000535211735330606025003 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.Calc; import mondrian.olap.Util; import java.util.List; /** * Implementation of {@link mondrian.rolap.RolapCalculation} * that changes one or more dimensions, then evaluates a given calculation. * *

    It is used to implement sets in slicers, in particular sets of tuples in * the slicer. * * @author jhyde * @since May 15, 2009 */ class RolapTupleCalculation implements RolapCalculation { private final List hierarchyList; private final Calc calc; private final int hashCode; /** * Creates a RolapTupleCalculation. * * @param hierarchyList List of hierarchies to be replaced. * @param calc Compiled scalar expression to compute cell */ public RolapTupleCalculation( List hierarchyList, Calc calc) { this.hierarchyList = hierarchyList; this.calc = calc; this.hashCode = Util.hash(hierarchyList.hashCode(), calc); } @Override public int hashCode() { return hashCode; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof RolapTupleCalculation) { RolapTupleCalculation calculation = (RolapTupleCalculation) obj; return this.hierarchyList.equals(calculation.hierarchyList) && this.calc.equals(calculation.calc); } return false; } @Override public String toString() { return calc.toString(); } public void setContextIn(RolapEvaluator evaluator) { // Restore default member for each hierarchy // in the tuple. for (RolapHierarchy hierarchy : hierarchyList) { final int ordinal = hierarchy.getOrdinalInCube(); final RolapMember defaultMember = evaluator.root.defaultMembers[ordinal]; evaluator.setContext(defaultMember); } evaluator.removeCalculation(this, true); } public int getSolveOrder() { return Integer.MIN_VALUE; } public int getHierarchyOrdinal() { throw new UnsupportedOperationException(); } public Calc getCompiledExpression(RolapEvaluatorRoot root) { return calc; } public boolean containsAggregateFunction() { return false; } public boolean isCalculatedInQuery() { return true; } } // End RolapTupleCalculation.java mondrian-3.4.1/src/main/mondrian/rolap/StringList.java0000644000175000017500000000276211735330606022641 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2006 Pentaho and others // All Rights Reserved. // // jhyde, 29 December, 2001 */ package mondrian.rolap; import mondrian.olap.Util; /** * StringList makes it easy to build up a comma-separated string. * * @author jhyde * @since 29 December, 2001 */ class StringList { private final StringBuilder buf; private final String first, mid, last; private int count; StringList(String first, String mid) { this.buf = new StringBuilder(first); this.count = 0; this.first = first; this.mid = mid; this.last = ""; } StringList(String first) { this(first, ", "); } int getCount() { return count; } boolean isEmpty() { return count == 0; } /** Creates a new item. */ void newItem(String s) { if (count++ > 0) { buf.append(mid); } buf.append(s); } /** Appends to an existing item. */ void append(String s) { Util.assertTrue(count > 0); buf.append(s); } // override Object public String toString() { buf.append(last); return buf.toString(); } }; // End StringList.java mondrian-3.4.1/src/main/mondrian/rolap/RolapResult.java0000644000175000017500000022065111735330606023012 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.mdx.*; import mondrian.olap.*; import mondrian.olap.fun.*; import mondrian.olap.fun.VisualTotalsFunDef.VisualTotalMember; import mondrian.olap.type.ScalarType; import mondrian.resource.MondrianResource; import mondrian.rolap.agg.AggregationManager; import mondrian.rolap.agg.CellRequestQuantumExceededException; import mondrian.server.Execution; import mondrian.server.Locus; import mondrian.spi.CellFormatter; import mondrian.util.*; import org.apache.log4j.Logger; import java.util.*; /** * A RolapResult is the result of running a query. * * @author jhyde * @since 10 August, 2001 */ public class RolapResult extends ResultBase { static final Logger LOGGER = Logger.getLogger(ResultBase.class); private RolapEvaluator evaluator; RolapEvaluator slicerEvaluator; private final CellKey point; private CellInfoContainer cellInfos; private FastBatchingCellReader batchingReader; private final CellReader aggregatingReader; private Modulos modulos = null; private final int maxEvalDepth = MondrianProperties.instance().MaxEvalDepth.get(); private final Map positionsHighCardinality = new HashMap(); private final Map positionsIterators = new HashMap(); private final Map positionsIndexes = new HashMap(); private final Map>> positionsCurrent = new HashMap>>(); /** * Creates a RolapResult. * * @param execution Execution of a statement * @param execute Whether to execute the query */ RolapResult( final Execution execution, boolean execute) { super(execution, null); this.point = CellKey.Generator.newCellKey(axes.length); final AggregationManager aggMgr = execution.getMondrianStatement() .getMondrianConnection() .getServer().getAggregationManager(); this.aggregatingReader = aggMgr.getCacheCellReader(); final int expDeps = MondrianProperties.instance().TestExpDependencies.get(); if (expDeps > 0) { this.evaluator = new RolapDependencyTestingEvaluator(this, expDeps); } else { final RolapEvaluatorRoot root = new RolapResultEvaluatorRoot(this); if (statement.getProfileHandler() != null) { this.evaluator = new RolapProfilingEvaluator(root); } else { this.evaluator = new RolapEvaluator(root); } } RolapCube cube = (RolapCube) query.getCube(); this.batchingReader = new FastBatchingCellReader(execution, cube, aggMgr); this.cellInfos = (query.axes.length > 4) ? new CellInfoMap(point) : new CellInfoPool(query.axes.length); if (!execute) { return; } boolean normalExecution = true; try { // This call to clear the cube's cache only has an // effect if caching has been disabled, otherwise // nothing happens. // Clear the local cache before a query has run cube.clearCachedAggregations(); ///////////////////////////////////////////////////////////////// // // Evaluation Algorithm // // There are three basic steps to the evaluation algorithm: // 1) Determine all Members for each axis but do not save // information (do not build the RolapAxis), // 2) Save all Members for each axis (build RolapAxis). // 3) Evaluate and store each Cell determined by the Members // of the axes. // Step 1 converges on the stable set of Members pre axis. // Steps 1 and 2 make sure that the data has been loaded. // // More detail follows. // // Explicit and Implicit Members: // A Member is said to be 'explicit' if it appears on one of // the Axes (one of the RolapAxis Position List of Members). // A Member is 'implicit' if it is in the query but does not // end up on any Axes (its usage, for example, is in a function). // When for a Dimension none of its Members are explicit in the // query, then the default Member is used which is like putting // the Member in the Slicer. // // Special Dimensions: // There are 2 special dimensions. // The first is the Time dimension. If in a schema there is // no ALL Member, then Whatever happens to be the default // Member is used if Time Members are not explicitly set // in the query. // The second is the Measures dimension. This dimension // NEVER has an ALL Member. A cube's default Measure is set // by convention - its simply the first Measure defined in the // cube. // // First a RolapEvaluator is created. During its creation, // it gets a Member from each Hierarchy. Each Member is the // default Member of the Hierarchy. For most Hierarchies this // Member is the ALL Member, but there are cases where 1) // a Hierarchy does not have an ALL Member or 2) the Hierarchy // has an ALL Member but that Member is not the default Member. // In these cases, the default Member is still used, but its // use can cause evaluation issues (seemingly strange evaluation // results). // // Next, load all root Members for Hierarchies that have no ALL // Member and load ALL Members that are not the default Member. // // Determine the Members of the Slicer axis (Step 1 above). Any // Members found are added to the AxisMember object. If one of these // Members happens to be a Measure, then the Slicer is explicitly // specifying the query's Measure and this should be put into the // evaluator's context (replacing the default Measure which just // happens to be the first Measure defined in the cube). Other // Members found in the AxisMember object are also placed into the // evaluator's context since these also are explicitly specified. // Also, any other Members in the AxisMember object which have the // same Hierarchy as Members in the list of root Members for // Hierarchies that have no ALL Member, replace those Members - they // Slicer has explicitly determined which ones to use. The // AxisMember object is now cleared. // The Slicer does not depend upon the other Axes, but the other // Axes depend upon both the Slicer and each other. // // The AxisMember object also checks if the number of Members // exceeds the ResultLimit property throwing a // TotalMembersLimitExceeded Exception if it does. // // For all non-Slicer axes, the Members are determined (Step 1 // above). If a Measure is found in the AxisMember, then an // Axis is explicitly specifying a Measure. // If any Members in the AxisMember object have the same Hierarchy // as a Member in the set of root Members for Hierarchies that have // no ALL Member, then replace those root Members with the Member // from the AxisMember object. In this case, again, a Member // was explicitly specified in an Axis. If this replacement // occurs, then one must redo this step with the new Members. // // Now Step 3 above is done. First to the Slicer Axis and then // to the other Axes. Here the Axes are actually generated. // If a Member of an Axis is an Calculated Member (and the // Calculated Member is not a Member of the Measure Hierarchy), // then find the Dimension associated with the Calculated // Member and remove Members with the same Dimension in the set of // root Members for Hierarchies that have no ALL Member. // This is done because via the Calculated Member the Member // was implicitly specified in the query. If this removal occurs, // then the Axes must be re-evaluated repeating Step 3. // ///////////////////////////////////////////////////////////////// // The AxisMember object is used to hold Members that are found // during Step 1 when the Axes are determined. final AxisMember axisMembers = new AxisMember(); // list of ALL Members that are not default Members final List nonDefaultAllMembers = new ArrayList(); // List of Members of Hierarchies that do not have an ALL Member List> nonAllMembers = new ArrayList>(); // List of Measures final List measureMembers = new ArrayList(); // load all root Members for Hierarchies that have no ALL // Member and load ALL Members that are not the default Member. // Also, all Measures are are gathered. loadSpecialMembers( nonDefaultAllMembers, nonAllMembers, measureMembers); // clear evaluation cache query.clearEvalCache(); // Save, may be needed by some Expression Calc's query.putEvalCache("ALL_MEMBER_LIST", nonDefaultAllMembers); final List> emptyNonAllMembers = Collections.emptyList(); // Initial evaluator, to execute slicer. slicerEvaluator = evaluator.push(); ///////////////////////////////////////////////////////////////// // Determine Slicer // axisMembers.setSlicer(true); loadMembers( emptyNonAllMembers, evaluator, query.getSlicerAxis(), query.slicerCalc, axisMembers); axisMembers.setSlicer(false); // Save unadulterated context for the next time we need to evaluate // the slicer. final RolapEvaluator savedEvaluator = evaluator.push(); if (!axisMembers.isEmpty()) { for (Member m : axisMembers) { if (m == null) { break; } evaluator.setSlicerContext(m); if (m.isMeasure()) { // A Measure was explicitly declared in the // Slicer, don't need to worry about Measures // for this query. measureMembers.clear(); } } replaceNonAllMembers(nonAllMembers, axisMembers); axisMembers.clearMembers(); } // Save evaluator that has slicer as its context. slicerEvaluator = evaluator.push(); ///////////////////////////////////////////////////////////////// // Determine Axes // boolean changed = false; // reset to total member count axisMembers.clearTotalCellCount(); for (int i = 0; i < axes.length; i++) { final QueryAxis axis = query.axes[i]; final Calc calc = query.axisCalcs[i]; loadMembers( emptyNonAllMembers, evaluator, axis, calc, axisMembers); } if (!axisMembers.isEmpty()) { for (Member m : axisMembers) { if (m.isMeasure()) { // A Measure was explicitly declared on an // axis, don't need to worry about Measures // for this query. measureMembers.clear(); } } changed = replaceNonAllMembers(nonAllMembers, axisMembers); axisMembers.clearMembers(); } if (changed) { // only count number of members, do not collect any axisMembers.countOnly(true); // reset to total member count axisMembers.clearTotalCellCount(); final int savepoint = evaluator.savepoint(); for (int i = 0; i < axes.length; i++) { final QueryAxis axis = query.axes[i]; final Calc calc = query.axisCalcs[i]; loadMembers( nonAllMembers, evaluator, axis, calc, axisMembers); evaluator.restore(savepoint); } } // throws exception if number of members exceeds limit axisMembers.checkLimit(); ///////////////////////////////////////////////////////////////// // Execute Slicer // RolapEvaluator slicerEvaluator; do { TupleIterable tupleIterable = evalExecute( nonAllMembers, nonAllMembers.size() - 1, savedEvaluator, query.getSlicerAxis(), query.slicerCalc); // Materialize the iterable as a list. Although it may take // memory, we need the first member below, and besides, slicer // axes are generally small. TupleList tupleList = TupleCollections.materialize(tupleIterable, true); this.slicerAxis = new RolapAxis(tupleList); // Use the context created by the slicer for the other // axes. For example, "select filter([Customers], [Store // Sales] > 100) on columns from Sales where // ([Time].[1998])" should show customers whose 1998 (not // total) purchases exceeded 100. slicerEvaluator = this.evaluator; if (tupleList.size() > 1) { tupleList = AggregateFunDef.AggregateCalc.optimizeTupleList( slicerEvaluator, tupleList); final Calc valueCalc = new ValueCalc( new DummyExp(new ScalarType())); final TupleList tupleList1 = tupleList; final Calc calc = new GenericCalc( new DummyExp(query.slicerCalc.getType())) { public Object evaluate(Evaluator evaluator) { return AggregateFunDef.AggregateCalc.aggregate( valueCalc, evaluator, tupleList1); } }; final List hierarchyList = new AbstractList() { final List pos0 = tupleList1.get(0); public RolapHierarchy get(int index) { return ((RolapMember) pos0.get(index)) .getHierarchy(); } public int size() { return pos0.size(); } }; evaluator.addCalculation( new RolapTupleCalculation(hierarchyList, calc), true); } } while (phase()); ///////////////////////////////////////////////////////////////// // Execute Axes // final int savepoint = evaluator.savepoint(); do { try { boolean redo; do { evaluator.restore(savepoint); redo = false; for (int i = 0; i < axes.length; i++) { QueryAxis axis = query.axes[i]; final Calc calc = query.axisCalcs[i]; TupleIterable tupleIterable = evalExecute( nonAllMembers, nonAllMembers.size() - 1, evaluator, axis, calc); if (!nonAllMembers.isEmpty()) { final TupleIterator tupleIterator = tupleIterable.tupleIterator(); if (tupleIterator.hasNext()) { List tuple0 = tupleIterator.next(); // Only need to process the first tuple on // the axis. for (Member m : tuple0) { if (m.isCalculated()) { CalculatedMeasureVisitor visitor = new CalculatedMeasureVisitor(); m.getExpression().accept(visitor); Dimension dimension = visitor.dimension; if (removeDimension( dimension, nonAllMembers)) { redo = true; } } } } } this.axes[i] = new RolapAxis( TupleCollections.materialize( tupleIterable, false)); } } while (redo); } catch (CellRequestQuantumExceededException e) { // Safe to ignore. Need to call 'phase' and loop again. } } while (phase()); evaluator.restore(savepoint); // Get value for each Cell final Locus locus = new Locus(execution, null, "Loading cells"); Locus.push(locus); try { executeBody(slicerEvaluator, query, new int[axes.length]); } finally { Locus.pop(locus); } // If you are very close to running out of memory due to // the number of CellInfo's in cellInfos, then calling this // may cause the out of memory one is trying to aviod. // On the other hand, calling this can reduce the size of // the ObjectPool's internal storage by half (but, of course, // it will not reduce the size of the stored objects themselves). // Only call this if there are lots of CellInfo. if (this.cellInfos.size() > 10000) { this.cellInfos.trimToSize(); } } catch (ResultLimitExceededException ex) { // If one gets a ResultLimitExceededException, then // don't count on anything being worth caching. normalExecution = false; // De-reference data structures that might be holding // partial results but surely are taking up memory. evaluator = null; slicerEvaluator = null; cellInfos = null; batchingReader = null; for (int i = 0; i < axes.length; i++) { axes[i] = null; } slicerAxis = null; query.clearEvalCache(); throw ex; } finally { if (normalExecution) { // Expression cache duration is for each query. It is time to // clear out the whole expression cache at the end of a query. evaluator.clearExpResultCache(true); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("RolapResult: " + Util.printMemory()); } } } private boolean phase() { if (batchingReader.isDirty()) { execution.tracePhase( batchingReader.getHitCount(), batchingReader.getMissCount(), batchingReader.getPendingCount()); return batchingReader.loadAggregations(); } else { return false; } } @Override public void close() { super.close(); } protected boolean removeDimension( Dimension dimension, List> memberLists) { for (int i = 0; i < memberLists.size(); i++) { List memberList = memberLists.get(i); if (memberList.get(0).getDimension().equals(dimension)) { memberLists.remove(i); return true; } } return false; } public final Execution getExecution() { return execution; } private static class CalculatedMeasureVisitor extends MdxVisitorImpl { Dimension dimension; CalculatedMeasureVisitor() { } public Object visit(DimensionExpr dimensionExpr) { dimension = dimensionExpr.getDimension(); return null; } public Object visit(HierarchyExpr hierarchyExpr) { Hierarchy hierarchy = hierarchyExpr.getHierarchy(); dimension = hierarchy.getDimension(); return null; } public Object visit(MemberExpr memberExpr) { Member member = memberExpr.getMember(); dimension = member.getHierarchy().getDimension(); return null; } } protected boolean replaceNonAllMembers( List> nonAllMembers, AxisMember axisMembers) { boolean changed = false; List mList = new ArrayList(); for (ListIterator> it = nonAllMembers.listIterator(); it.hasNext();) { List ms = it.next(); Hierarchy h = ms.get(0).getHierarchy(); mList.clear(); for (Member m : axisMembers) { if (m.getHierarchy().equals(h)) { mList.add(m); } } if (! mList.isEmpty()) { changed = true; it.set(new ArrayList(mList)); } } return changed; } protected void loadMembers( List> nonAllMembers, RolapEvaluator evaluator, QueryAxis axis, Calc calc, AxisMember axisMembers) { int attempt = 0; evaluator.setCellReader(batchingReader); while (true) { axisMembers.clearAxisCount(); final int savepoint = evaluator.savepoint(); try { evalLoad( nonAllMembers, nonAllMembers.size() - 1, evaluator, axis, calc, axisMembers); } catch (CellRequestQuantumExceededException e) { // Safe to ignore. Need to call 'phase' and loop again. // Decrement count because it wasn't a recursive formula that // caused the iteration. --attempt; } finally { evaluator.restore(savepoint); } if (!phase()) { break; } else { // Clear invalid expression result so that the next evaluation // will pick up the newly loaded aggregates. evaluator.clearExpResultCache(false); } if (attempt++ > maxEvalDepth) { throw Util.newInternal( "Failed to load all aggregations after " + maxEvalDepth + " passes; there's probably a cycle"); } } } void evalLoad( List> nonAllMembers, int cnt, Evaluator evaluator, QueryAxis axis, Calc calc, AxisMember axisMembers) { if (cnt < 0) { final int savepoint = evaluator.savepoint(); executeAxis(evaluator, axis, calc, false, axisMembers); evaluator.restore(savepoint); } else { for (Member m : nonAllMembers.get(cnt)) { evaluator.setContext(m); evalLoad( nonAllMembers, cnt - 1, evaluator, axis, calc, axisMembers); } } } TupleIterable evalExecute( List> nonAllMembers, int cnt, RolapEvaluator evaluator, QueryAxis queryAxis, Calc calc) { final int arity = calc == null ? 0 : calc.getType().getArity(); if (cnt < 0) { final int savepoint = evaluator.savepoint(); final TupleIterable axis = executeAxis(evaluator, queryAxis, calc, true, null); evaluator.restore(savepoint); return axis; // No need to clear expression cache here as no new aggregates are // loaded(aggregatingReader reads from cache). } else { TupleList axisResult = TupleCollections.emptyList(arity); for (Member m : nonAllMembers.get(cnt)) { evaluator.setContext(m); TupleIterable axis = evalExecute( nonAllMembers, cnt - 1, evaluator, queryAxis, calc); boolean ordered = false; if (queryAxis != null) { ordered = queryAxis.isOrdered(); } axisResult = mergeAxes(axisResult, axis, ordered); } return axisResult; } } /** * Finds all root Members 1) whose Hierarchy does not have an ALL * Member, 2) whose default Member is not the ALL Member and 3) * all Measures. * * @param nonDefaultAllMembers List of all root Members for Hierarchies * whose default Member is not the ALL Member. * @param nonAllMembers List of root Members for Hierarchies that have no * ALL Member. * @param measureMembers List all Measures */ protected void loadSpecialMembers( List nonDefaultAllMembers, List> nonAllMembers, List measureMembers) { SchemaReader schemaReader = evaluator.getSchemaReader(); Member[] evalMembers = evaluator.getMembers(); for (Member em : evalMembers) { if (em.isCalculated()) { continue; } Hierarchy h = em.getHierarchy(); Dimension d = h.getDimension(); if (d.getDimensionType() == DimensionType.TimeDimension) { continue; } if (!em.isAll()) { List rootMembers = schemaReader.getHierarchyRootMembers(h); if (em.isMeasure()) { for (Member mm : rootMembers) { measureMembers.add(mm); } } else { if (h.hasAll()) { for (Member m : rootMembers) { if (m.isAll()) { nonDefaultAllMembers.add(m); break; } } } else { nonAllMembers.add(rootMembers); } } } } } protected Logger getLogger() { return LOGGER; } public final RolapCube getCube() { return evaluator.getCube(); } // implement Result public Axis[] getAxes() { return axes; } /** * Get the Cell for the given Cell position. * * @param pos Cell position. * @return the Cell associated with the Cell position. */ public Cell getCell(int[] pos) { if (pos.length != point.size()) { throw Util.newError( "coordinates should have dimension " + point.size()); } for (int i = 0; i < pos.length; i++) { if (positionsHighCardinality.get(i)) { executeBody(evaluator, statement.getQuery(), pos); break; } } CellInfo ci = cellInfos.lookup(pos); if (ci.value == null) { for (int i = 0; i < pos.length; i++) { int po = pos[i]; if (po < 0 || po >= axes[i].getPositions().size()) { throw Util.newError("coordinates out of range"); } } ci.value = Util.nullValue; } return new RolapCell(this, pos.clone(), ci); } private TupleIterable executeAxis( Evaluator evaluator, QueryAxis queryAxis, Calc axisCalc, boolean construct, AxisMember axisMembers) { if (queryAxis == null) { // Create an axis containing one position with no members (not // the same as an empty axis). return new DelegatingTupleList( 0, Collections.singletonList(Collections.emptyList())); } final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(queryAxis.isNonEmpty()); evaluator.setEvalAxes(true); final TupleIterable iterable = ((IterCalc) axisCalc).evaluateIterable(evaluator); if (axisCalc.getClass().getName().indexOf("OrderFunDef") != -1) { queryAxis.setOrdered(true); } if (iterable instanceof TupleList) { TupleList list = (TupleList) iterable; if (construct) { } else if (axisMembers != null) { axisMembers.mergeTupleList(list); } } else { // Iterable TupleCursor cursor = iterable.tupleCursor(); if (construct) { } else if (axisMembers != null) { axisMembers.mergeTupleIter(cursor); } } evaluator.restore(savepoint); return iterable; } private void executeBody( RolapEvaluator evaluator, Query query, final int[] pos) { // Compute the cells several times. The first time, use a dummy // evaluator which collects requests. int count = 0; while (true) { evaluator.setCellReader(batchingReader); final int savepoint = evaluator.savepoint(); try { executeStripe(query.axes.length - 1, evaluator, pos); } catch (CellRequestQuantumExceededException e) { // Safe to ignore. Need to call 'phase' and loop again. // Decrement count because it wasn't a recursive formula that // caused the iteration. --count; } evaluator.restore(savepoint); // Retrieve the aggregations collected. // if (!phase()) { // We got all of the cells we needed, so the result must be // correct. return; } else { // Clear invalid expression result so that the next evaluation // will pick up the newly loaded aggregates. evaluator.clearExpResultCache(false); } if (count++ > maxEvalDepth) { if (evaluator instanceof RolapDependencyTestingEvaluator) { // The dependency testing evaluator can trigger new // requests every cycle. So let is run as normal for // the first N times, then run it disabled. ((RolapDependencyTestingEvaluator.DteRoot) evaluator.root).disabled = true; if (count > maxEvalDepth * 2) { throw Util.newInternal( "Query required more than " + count + " iterations"); } } else { throw Util.newInternal( "Query required more than " + count + " iterations"); } } cellInfos.clear(); } } boolean isDirty() { return batchingReader.isDirty(); } /** * Evaluates an expression. Intended for evaluating named sets. * *

    Does not modify the contents of the evaluator. * * @param calc Compiled expression * @param evaluator Evaluation context * @return Result */ Object evaluateExp(Calc calc, RolapEvaluator evaluator) { int attempt = 0; final int savepoint = evaluator.savepoint(); boolean dirty = batchingReader.isDirty(); while (true) { evaluator.restore(savepoint); evaluator.setCellReader(batchingReader); Object preliminaryValue = calc.evaluate(evaluator); if (preliminaryValue instanceof TupleIterable && !(preliminaryValue instanceof TupleList)) { TupleIterable iterable = (TupleIterable) preliminaryValue; final TupleCursor cursor = iterable.tupleCursor(); while (cursor.forward()) { // ignore } } if (!phase()) { break; } else { // Clear invalid expression result so that the next evaluation // will pick up the newly loaded aggregates. evaluator.clearExpResultCache(false); } if (attempt++ > maxEvalDepth) { throw Util.newInternal( "Failed to load all aggregations after " + maxEvalDepth + "passes; there's probably a cycle"); } } // If there were pending reads when we entered, some of the other // expressions may have been evaluated incorrectly. Set the reader's // 'dirty' flag so that the caller knows that it must re-evaluate them. if (dirty) { batchingReader.setDirty(true); } evaluator.restore(savepoint); evaluator.setCellReader(aggregatingReader); final Object o = calc.evaluate(evaluator); evaluator.restore(savepoint); return o; } private void executeStripe( int axisOrdinal, RolapEvaluator revaluator, final int[] pos) { if (axisOrdinal < 0) { RolapAxis axis = (RolapAxis) slicerAxis; TupleList tupleList = axis.getTupleList(); for (List members : tupleList) { execution.checkCancelOrTimeout(); revaluator.setContext(members); Object o; try { o = revaluator.evaluateCurrent(); } catch (MondrianEvaluationException e) { LOGGER.warn("Mondrian: exception in executeStripe.", e); o = e; } CellInfo ci = null; // Get the Cell's format string and value formatting // Object. try { // This code is a combination of the code found in // the old RolapResult // getCellNoDefaultFormatString method and // the old RolapCell getFormattedValue method. // Create a CellInfo object for the given position // integer array. ci = cellInfos.create(point.getOrdinals()); String cachedFormatString = null; // Determine if there is a CellFormatter registered for // the current Cube's Measure's Dimension. If so, // then find or create a CellFormatterValueFormatter // for it. If not, then find or create a Locale based // FormatValueFormatter. final RolapCube cube = getCube(); Hierarchy measuresHierarchy = cube.getMeasuresHierarchy(); RolapMeasure m = (RolapMeasure) revaluator.getContext(measuresHierarchy); ValueFormatter valueFormatter = m.getFormatter(); if (valueFormatter == null) { cachedFormatString = revaluator.getFormatString(); Locale locale = statement.getMondrianConnection().getLocale(); valueFormatter = formatValueFormatters.get(locale); if (valueFormatter == null) { valueFormatter = new FormatValueFormatter(locale); formatValueFormatters.put(locale, valueFormatter); } } ci.formatString = cachedFormatString; ci.valueFormatter = valueFormatter; } catch (ResultLimitExceededException e) { // Do NOT ignore a ResultLimitExceededException!!! throw e; } catch (MondrianEvaluationException e) { // ignore but warn LOGGER.warn("Mondrian: exception in executeStripe.", e); } catch (Error e) { // Errors indicate fatal JVM problems; do not discard throw e; } catch (Throwable e) { LOGGER.warn("Mondrian: exception in executeStripe.", e); Util.discard(e); } if (o == RolapUtil.valueNotReadyException) { continue; } ci.value = o; } } else { RolapAxis axis = (RolapAxis) axes[axisOrdinal]; TupleList tupleList = axis.getTupleList(); Util.discard(tupleList.size()); // force materialize if (isAxisHighCardinality(axisOrdinal, tupleList)) { final int limit = MondrianProperties.instance().HighCardChunkSize.get(); if (positionsIterators.get(axisOrdinal) == null) { final TupleCursor tupleCursor = tupleList.tupleCursor(); positionsIterators.put(axisOrdinal, tupleCursor); positionsIndexes.put(axisOrdinal, 0); final List> subPositions = new ArrayList>(); for (int i = 0; i < limit && tupleCursor.forward(); i++) { subPositions.add(tupleCursor.current()); } positionsCurrent.put(axisOrdinal, subPositions); } final TupleCursor tupleCursor = positionsIterators.get(axisOrdinal); final int positionIndex = positionsIndexes.get(axisOrdinal); List> subTuples = positionsCurrent.get(axisOrdinal); if (subTuples == null) { return; } int pi; if (pos[axisOrdinal] > positionIndex + subTuples.size() - 1 && subTuples.size() == limit) { pi = positionIndex + subTuples.size(); positionsIndexes.put( axisOrdinal, positionIndex + subTuples.size()); subTuples.subList(0, subTuples.size()).clear(); for (int i = 0; i < limit && tupleCursor.forward(); i++) { subTuples.add(tupleCursor.current()); } positionsCurrent.put(axisOrdinal, subTuples); } else { pi = positionIndex; } for (final List tuple : subTuples) { point.setAxis(axisOrdinal, pi); revaluator.setContext(tuple); execution.checkCancelOrTimeout(); executeStripe(axisOrdinal - 1, revaluator, pos); pi++; } } else { for (List tuple : tupleList) { List measures = new ArrayList( statement.getQuery().getMeasuresMembers()); for (Member measure : measures) { if (measure instanceof RolapBaseCubeMeasure) { RolapBaseCubeMeasure baseCubeMeasure = (RolapBaseCubeMeasure) measure; if (baseCubeMeasure.getAggregator() == RolapAggregator.DistinctCount) { processDistinctMeasureExpr( tuple, baseCubeMeasure); } } } } int tupleIndex = 0; for (final List tuple : tupleList) { point.setAxis(axisOrdinal, tupleIndex); revaluator.setContext(tuple); execution.checkCancelOrTimeout(); executeStripe(axisOrdinal - 1, revaluator, pos); tupleIndex++; } } } } private boolean isAxisHighCardinality( int axisOrdinal, TupleList tupleList) { Boolean highCardinality = positionsHighCardinality.get(axisOrdinal); if (highCardinality == null) { highCardinality = false; //noinspection LoopStatementThatDoesntLoop for (List tuple : tupleList) { if (!tuple.isEmpty()) { highCardinality = tuple.get(0).getDimension().isHighCardinality(); } break; } positionsHighCardinality.put(axisOrdinal, highCardinality); } return highCardinality; } /* * Distinct counts are aggregated separately from other measures. * We need to apply filters to each level in the query. * * Replace VisualTotalMember expressions with new expressions * where all leaf level members are included. * * Example: * For MDX query: * WITH SET [XL_Row_Dim_0] AS * VisualTotals( * Distinct( * Hierarchize( * {Ascendants([Store].[All Stores].[USA].[CA]), * Descendants([Store].[All Stores].[USA].[CA])}))) * select NON EMPTY * Hierarchize( * Intersect( * {DrilldownLevel({[Store].[All Stores]})}, * [XL_Row_Dim_0])) ON COLUMNS * from [HR] * where [Measures].[Number of Employees] * * For member [Store].[All Stores]: * we replace aggregate expression * - Aggregate({[Store].[All Stores].[USA]}) * with * - Aggregate({[Store].[All Stores].[USA].[CA].[Alameda].[HQ], * [Store].[All Stores].[USA].[CA].[Beverly Hills].[Store 6], * [Store].[All Stores].[USA].[CA].[Los Angeles].[Store 7], * [Store].[All Stores].[USA].[CA].[San Diego].[Store 24], * [Store].[All Stores].[USA].[CA].[San Francisco].[Store 14] * }) * TODO: * Can be optimized. For that particular query * we don't need to go to the lowest level. * We can simply replace it with: * - Aggregate({[Store].[All Stores].[USA].[CA]}) * Because all children of [Store].[All Stores].[USA].[CA] are included. */ private List processDistinctMeasureExpr( List tuple, RolapBaseCubeMeasure measure) { for (Member member : tuple) { if (!(member instanceof VisualTotalMember)) { continue; } evaluator.setContext(measure); List exprMembers = new ArrayList(); processMemberExpr(member, exprMembers); ((VisualTotalMember) member).setExpression(evaluator, exprMembers); } return tuple; } private static void processMemberExpr(Object o, List exprMembers) { if (o instanceof Member && o instanceof RolapCubeMember) { exprMembers.add((Member) o); } else if (o instanceof VisualTotalMember) { VisualTotalMember member = (VisualTotalMember) o; Exp exp = member.getExpression(); processMemberExpr(exp, exprMembers); } else if (o instanceof Exp && !(o instanceof MemberExpr)) { Exp exp = (Exp)o; ResolvedFunCall funCall = (ResolvedFunCall)exp; Exp[] exps = funCall.getArgs(); processMemberExpr(exps, exprMembers); } else if (o instanceof Exp[]) { Exp[] exps = (Exp[]) o; for (Exp exp : exps) { processMemberExpr(exp, exprMembers); } } else if (o instanceof MemberExpr) { MemberExpr memberExp = (MemberExpr) o; Member member = memberExp.getMember(); processMemberExpr(member, exprMembers); } } /** * Converts a set of cell coordinates to a cell ordinal. * *

    This method can be expensive, because the ordinal is computed from the * length of the axes, and therefore the axes need to be instantiated. */ int getCellOrdinal(int[] pos) { if (modulos == null) { makeModulos(); } return modulos.getCellOrdinal(pos); } /* * Instantiates the calculator to convert cell coordinates to a cell ordinal * and vice versa. * *

    To create the calculator, any axis that is based upon an Iterable is * converted into a List - thus increasing memory usage. */ protected void makeModulos() { modulos = Modulos.Generator.create(axes); } /** * Called only by RolapCell. Use this when creating an Evaluator * is not required. * * @param pos Coordinates of cell * @return Members which form the context of the given cell */ RolapMember[] getCellMembers(int[] pos) { RolapMember[] members = (RolapMember[]) evaluator.getMembers().clone(); for (int i = 0; i < pos.length; i++) { Position position = axes[i].getPositions().get(pos[i]); for (Member member : position) { RolapMember m = (RolapMember) member; int ordinal = m.getHierarchy().getOrdinalInCube(); members[ordinal] = m; } } return members; } Evaluator getRootEvaluator() { return evaluator; } Evaluator getEvaluator(int[] pos) { // Set up evaluator's context, so that context-dependent format // strings work properly. Evaluator cellEvaluator = evaluator.push(); populateEvaluator(cellEvaluator, pos); return cellEvaluator; } void populateEvaluator(Evaluator evaluator, int[] pos) { for (int i = -1; i < axes.length; i++) { Axis axis; int index; if (i < 0) { axis = slicerAxis; if (axis.getPositions().isEmpty()) { continue; } index = 0; } else { axis = axes[i]; index = pos[i]; } Position position = axis.getPositions().get(index); evaluator.setContext(position); } } /** * Counts and collects Members found of the axes. * This class does two things. First it collects all Members * found during the Member-Determination phase. * Secondly, it counts how many Members are on each axis and * forms the product, the totalCellCount which is checked against * the ResultLimit property value. */ private static class AxisMember implements Iterable { private final List members; private final int limit; private boolean isSlicer; private int totalCellCount; private int axisCount; private boolean countOnly; AxisMember() { this.countOnly = false; this.members = new ConcatenableList(); this.totalCellCount = 1; this.axisCount = 0; // Now that the axes are evaluated, make sure that the number of // cells does not exceed the result limit. this.limit = MondrianProperties.instance().ResultLimit.get(); } public Iterator iterator() { return members.iterator(); } void setSlicer(final boolean isSlicer) { this.isSlicer = isSlicer; } boolean isEmpty() { return this.members.isEmpty(); } void countOnly(boolean countOnly) { this.countOnly = countOnly; } void checkLimit() { if (this.limit > 0) { this.totalCellCount *= this.axisCount; if (this.totalCellCount > this.limit) { throw MondrianResource.instance().TotalMembersLimitExceeded .ex( this.totalCellCount, this.limit); } this.axisCount = 0; } } void clearAxisCount() { this.axisCount = 0; } void clearTotalCellCount() { this.totalCellCount = 1; } void clearMembers() { this.members.clear(); this.axisCount = 0; this.totalCellCount = 1; } List members() { return this.members; } void mergeTupleList(TupleList list) { mergeTupleIter(list.tupleCursor()); } private void mergeTupleIter(TupleCursor cursor) { while (cursor.forward()) { mergeTuple(cursor); } } private Member getTopParent(Member m) { while (true) { Member parent = m.getParentMember(); if (parent == null) { return m; } m = parent; } } private void mergeTuple(final TupleCursor cursor) { final int arity = cursor.getArity(); /* final Member[] members1 = new Member[arity]; cursor.currentToArray(members1, 0); if (FunUtil.tupleContainsNullMember(members1)) { return; } */ for (int i = 0; i < arity; i++) { mergeMember(cursor.member(i)); } } private void mergeMember(final Member member) { this.axisCount++; if (! countOnly) { if (isSlicer) { if (! members.contains(member)) { members.add(member); } } else { if (member.isNull()) { return; } else if (member.isMeasure()) { return; } else if (member.isCalculated()) { return; } else if (member.isAll()) { return; } Member topParent = getTopParent(member); if (! this.members.contains(topParent)) { this.members.add(topParent); } } } } } /** * Extension to {@link RolapEvaluatorRoot} which is capable * of evaluating named sets.

    * * A given set is only evaluated once each time a query is executed; the * result is added to the {@link #namedSetEvaluators} cache on first execution * and re-used.

    * *

    Named sets are always evaluated in the context of the slicer.

    */ protected static class RolapResultEvaluatorRoot extends RolapEvaluatorRoot { /** * Maps the names of sets to their values. Populated on demand. */ private final Map namedSetEvaluators = new HashMap(); final RolapResult result; private static final Object CycleSentinel = new Object(); private static final Object NullSentinel = new Object(); public RolapResultEvaluatorRoot(RolapResult result) { super(result.execution); this.result = result; } protected Evaluator.NamedSetEvaluator evaluateNamedSet( final NamedSet namedSet, boolean create) { final String name = namedSet.getNameUniqueWithinQuery(); RolapNamedSetEvaluator value; if (namedSet.isDynamic() && !create) { value = null; } else { value = namedSetEvaluators.get(name); } if (value == null) { value = new RolapNamedSetEvaluator(this, namedSet); namedSetEvaluators.put(name, value); } return value; } public Object getParameterValue(ParameterSlot slot) { if (slot.isParameterSet()) { return slot.getParameterValue(); } // Look in other places for the value. Which places we look depends // on the scope of the parameter. Parameter.Scope scope = slot.getParameter().getScope(); switch (scope) { case System: // TODO: implement system params // fall through case Schema: // TODO: implement schema params // fall through case Connection: // if it's set in the session, return that value // fall through case Statement: break; default: throw Util.badValue(scope); } // Not set in any accessible scope. Evaluate the default value, // then cache it. Object liftedValue = slot.getCachedDefaultValue(); Object value; if (liftedValue != null) { if (liftedValue == CycleSentinel) { throw MondrianResource.instance() .CycleDuringParameterEvaluation.ex( slot.getParameter().getName()); } if (liftedValue == NullSentinel) { value = null; } else { value = liftedValue; } return value; } // Set value to a sentinel, so we can detect cyclic evaluation. slot.setCachedDefaultValue(CycleSentinel); value = result.evaluateExp( slot.getDefaultValueCalc(), result.slicerEvaluator); if (value == null) { liftedValue = NullSentinel; } else { liftedValue = value; } slot.setCachedDefaultValue(liftedValue); return value; } } /** * Formatter to convert values into formatted strings. * *

    Every Cell has a value, a format string (or CellFormatter) and a * formatted value string. * There are a wide range of possible values (pick a Double, any * Double - its a value). Because there are lots of possible values, * there are also lots of possible formatted value strings. On the * other hand, there are only a very small number of format strings * and CellFormatter's. These formatters are to be cached * in a synchronized HashMaps in order to limit how many copies * need to be kept around. * *

    * There are two implementations of the ValueFormatter interface:

      *
    • {@link CellFormatterValueFormatter}, which formats using a * user-registered {@link CellFormatter}; and *
    • {@link FormatValueFormatter}, which takes the {@link Locale} object. *
    */ interface ValueFormatter { /** * Formats a value according to a format string. * * @param value Value * @param formatString Format string * @return Formatted value */ String format(Object value, String formatString); /** * Formatter that always returns the empty string. */ public static final ValueFormatter EMPTY = new ValueFormatter() { public String format(Object value, String formatString) { return ""; } }; } /** * A CellFormatterValueFormatter uses a user-defined {@link CellFormatter} * to format values. */ static class CellFormatterValueFormatter implements ValueFormatter { final CellFormatter cf; /** * Creates a CellFormatterValueFormatter * * @param cf Cell formatter */ CellFormatterValueFormatter(CellFormatter cf) { this.cf = cf; } public String format(Object value, String formatString) { return cf.formatCell(value); } } /** * A FormatValueFormatter takes a {@link Locale} * as a parameter and uses it to get the {@link mondrian.util.Format} * to be used in formatting an Object value with a * given format string. */ static class FormatValueFormatter implements ValueFormatter { final Locale locale; /** * Creates a FormatValueFormatter. * * @param locale Locale */ FormatValueFormatter(Locale locale) { this.locale = locale; } public String format(Object value, String formatString) { if (value == Util.nullValue) { value = null; } if (value instanceof Throwable) { return "#ERR: " + value.toString(); } Format format = getFormat(formatString); return format.format(value); } private Format getFormat(String formatString) { return Format.get(formatString, locale); } } /* * Generate a long ordinal based upon the values of the integers * stored in the cell position array. With this mechanism, the * Cell information can be stored using a long key (rather than * the array integer of positions) thus saving memory. The trick * is to use a 'large number' per axis in order to convert from * position array to long key where the 'large number' is greater * than the number of members in the axis. * The largest 'long' is java.lang.Long.MAX_VALUE which is * 9,223,372,036,854,776,000. The product of the maximum number * of members per axis must be less than this maximum 'long' * value (otherwise one gets hashing collisions). *

    * For a single axis, the maximum number of members is equal to * the max 'long' number, 9,223,372,036,854,776,000. *

    * For two axes, the maximum number of members is the square root * of the max 'long' number, 9,223,372,036,854,776,000, which is * slightly bigger than 2,147,483,647 (which is the maximum integer). *

    * For three axes, the maximum number of members per axis is the * cube root of the max 'long' which is about 2,000,000 *

    * For four axes the forth root is about 50,000. *

    * For five or more axes, the maximum number of members per axis * based upon the root of the maximum 'long' number, * start getting too small to guarantee that it will be * smaller than the number of members on a given axis and so * we must resort to the Map-base Cell container. */ /** * Synchronized Map from Locale to ValueFormatter. It is expected that * there will be only a small number of Locale's. * Should these be a WeakHashMap? */ protected static final Map formatValueFormatters = Collections.synchronizedMap(new HashMap()); /** * A CellInfo contains all of the information that a Cell requires. * It is placed in the cellInfos map during evaluation and * serves as a constructor parameter for {@link RolapCell}. * *

    During the evaluation stage they are mutable but after evaluation has * finished they are not changed. */ static class CellInfo { Object value; String formatString; ValueFormatter valueFormatter; long key; /** * Creates a CellInfo representing the position of a cell. * * @param key Ordinal representing the position of a cell */ CellInfo(long key) { this(key, null, null, ValueFormatter.EMPTY); } /** * Creates a CellInfo with position, value, format string and formatter * of a cell. * * @param key Ordinal representing the position of a cell * @param value Value of cell, or null if not yet known * @param formatString Format string of cell, or null * @param valueFormatter Formatter for cell, or null */ CellInfo( long key, Object value, String formatString, ValueFormatter valueFormatter) { this.key = key; this.value = value; this.formatString = formatString; this.valueFormatter = valueFormatter; } public int hashCode() { // Combine the upper 32 bits of the key with the lower 32 bits. // We used to use 'key ^ (key >>> 32)' but that was bad, because // CellKey.Two encodes (i, j) as // (i * Integer.MAX_VALUE + j), which is practically the same as // (i << 32, j). If i and j were // both k bits long, all of the hashcodes were k bits long too! return (int) (key ^ (key >>> 11) ^ (key >>> 24)); } public boolean equals(Object o) { if (o instanceof CellInfo) { CellInfo that = (CellInfo) o; return that.key == this.key; } else { return false; } } /** * Returns the formatted value of the Cell * @return formatted value of the Cell */ String getFormatValue() { return valueFormatter.format(value, formatString); } } /** * API for the creation and * lookup of {@link CellInfo} objects. There are two implementations, * one that uses a Map for storage and the other uses an ObjectPool. */ interface CellInfoContainer { /** * Returns the number of CellInfo objects in this container. * @return the number of CellInfo objects. */ int size(); /** * Reduces the size of the internal data structures needed to * support the current entries. This should be called after * all CellInfo objects have been added to container. */ void trimToSize(); /** * Removes all CellInfo objects from container. Does not * change the size of the internal data structures. */ void clear(); /** * Creates a new CellInfo object, adds it to the container * a location pos and returns it. * * @param pos where to store CellInfo object. * @return the newly create CellInfo object. */ CellInfo create(int[] pos); /** * Gets the CellInfo object at the location pos. * * @param pos where to find the CellInfo object. * @return the CellInfo found or null. */ CellInfo lookup(int[] pos); } /** * Implementation of {@link CellInfoContainer} which uses a {@link Map} to * store CellInfo Objects. * *

    Note that the CellKey point instance variable is the same * Object (NOT a copy) that is used and modified during * the recursive calls to executeStripe - the * create method relies on this fact. */ static class CellInfoMap implements CellInfoContainer { private final Map cellInfoMap; private final CellKey point; /** * Creates a CellInfoMap * * @param point Cell position */ CellInfoMap(CellKey point) { this.point = point; this.cellInfoMap = new HashMap(); } public int size() { return this.cellInfoMap.size(); } public void trimToSize() { // empty } public void clear() { this.cellInfoMap.clear(); } public CellInfo create(int[] pos) { CellKey key = this.point.copy(); CellInfo ci = this.cellInfoMap.get(key); if (ci == null) { ci = new CellInfo(0); this.cellInfoMap.put(key, ci); } return ci; } public CellInfo lookup(int[] pos) { CellKey key = CellKey.Generator.newCellKey(pos); return this.cellInfoMap.get(key); } } /** * Implementation of {@link CellInfoContainer} which uses an * {@link ObjectPool} to store {@link CellInfo} Objects. * *

    There is an inner interface (CellKeyMaker) and * implementations for 0 through 4 axes that convert the Cell * position integer array into a long. * *

    * It should be noted that there is an alternate approach. * As the executeStripe * method is recursively called, at each call it is known which * axis is being iterated across and it is known whether or * not the Position object for that axis is a List or just * an Iterable. It it is a List, then one knows the real * size of the axis. If it is an Iterable, then one has to * use one of the MAX_AXIS_SIZE values. Given that this information * is available when one recursives down to the next * executeStripe call, the Cell ordinal, the position * integer array could converted to an long, could * be generated on the call stack!! Just a thought for the future. */ static class CellInfoPool implements CellInfoContainer { /** * The maximum number of Members, 2,147,483,647, that can be any given * Axis when the number of Axes is 2. */ protected static final long MAX_AXIS_SIZE_2 = 2147483647; /** * The maximum number of Members, 2,000,000, that can be any given * Axis when the number of Axes is 3. */ protected static final long MAX_AXIS_SIZE_3 = 2000000; /** * The maximum number of Members, 50,000, that can be any given * Axis when the number of Axes is 4. */ protected static final long MAX_AXIS_SIZE_4 = 50000; /** * Implementations of CellKeyMaker convert the Cell * position integer array to a long. */ interface CellKeyMaker { long generate(int[] pos); } /** * For axis of size 0. */ static class Zero implements CellKeyMaker { public long generate(int[] pos) { return 0; } } /** * For axis of size 1. */ static class One implements CellKeyMaker { public long generate(int[] pos) { return pos[0]; } } /** * For axis of size 2. */ static class Two implements CellKeyMaker { public long generate(int[] pos) { long l = pos[0]; l += (MAX_AXIS_SIZE_2 * (long) pos[1]); return l; } } /** * For axis of size 3. */ static class Three implements CellKeyMaker { public long generate(int[] pos) { long l = pos[0]; l += (MAX_AXIS_SIZE_3 * (long) pos[1]); l += (MAX_AXIS_SIZE_3 * MAX_AXIS_SIZE_3 * (long) pos[2]); return l; } } /** * For axis of size 4. */ static class Four implements CellKeyMaker { public long generate(int[] pos) { long l = pos[0]; l += (MAX_AXIS_SIZE_4 * (long) pos[1]); l += (MAX_AXIS_SIZE_4 * MAX_AXIS_SIZE_4 * (long) pos[2]); l += (MAX_AXIS_SIZE_4 * MAX_AXIS_SIZE_4 * MAX_AXIS_SIZE_4 * (long) pos[3]); return l; } } private final ObjectPool cellInfoPool; private final CellKeyMaker cellKeyMaker; CellInfoPool(int axisLength) { this.cellInfoPool = new ObjectPool(); this.cellKeyMaker = createCellKeyMaker(axisLength); } CellInfoPool(int axisLength, int initialSize) { this.cellInfoPool = new ObjectPool(initialSize); this.cellKeyMaker = createCellKeyMaker(axisLength); } private static CellKeyMaker createCellKeyMaker(int axisLength) { switch (axisLength) { case 0: return new Zero(); case 1: return new One(); case 2: return new Two(); case 3: return new Three(); case 4: return new Four(); default: throw new RuntimeException( "Creating CellInfoPool with axisLength=" + axisLength); } } public int size() { return this.cellInfoPool.size(); } public void trimToSize() { this.cellInfoPool.trimToSize(); } public void clear() { this.cellInfoPool.clear(); } public CellInfo create(int[] pos) { long key = this.cellKeyMaker.generate(pos); return this.cellInfoPool.add(new CellInfo(key)); } public CellInfo lookup(int[] pos) { long key = this.cellKeyMaker.generate(pos); return this.cellInfoPool.add(new CellInfo(key)); } } static TupleList mergeAxes( TupleList axis1, TupleIterable axis2, boolean ordered) { if (axis1.isEmpty() && axis2 instanceof TupleList) { return (TupleList) axis2; } Set> set = new HashSet>(); TupleList list = TupleCollections.createList(axis2.getArity()); for (List tuple : axis1) { if (set.add(tuple)) { list.add(tuple); } } int halfWay = list.size(); for (List tuple : axis2) { if (set.add(tuple)) { list.add(tuple); } } // if there are unique members on both axes and no order function, // sort the list to ensure default order if (halfWay > 0 && halfWay < list.size() && !ordered) { list = FunUtil.hierarchizeTupleList(list, false); } return list; } } // End RolapResult.java mondrian-3.4.1/src/main/mondrian/rolap/RolapConnectionProperties.java0000644000175000017500000001361511735330606025710 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho // All Rights Reserved. */ package mondrian.rolap; /** * RolapConnectionProperties enumerates the allowable values of * keywords in a Mondrian connect string. * *

    Note to developers: If you add or modify a connection-string * property, you must also modify the * * Configuration Specification. * * @author jhyde, Mar 18, 2003 */ public enum RolapConnectionProperties { /** * The "Provider" property must have the value "Mondrian". */ Provider, /** * The "Jdbc" property is the URL of the JDBC database where the data is * stored. You must specify either {@link #DataSource} or {@link #Jdbc}. */ Jdbc, /** * The "JdbcDrivers" property is a comma-separated list of JDBC driver * classes, for example, * "sun.jdbc.odbc.JdbcOdbcDriver,oracle.jdbc.OracleDriver". */ JdbcDrivers, /** * The "JdbcUser" property is the name of the user to log on to the JDBC * database. (You don't need to specify this parameter if it is already * specified in the JDBC URL.) */ JdbcUser, /** * The "JdbcPassword" property is the password to log on to the JDBC * database. (You don't need to specify this parameter if it is already * specified in the JDBC URL.) */ JdbcPassword, /** * The "Catalog" property is the URL of the catalog, an XML file which * describes the schema: cubes, hierarchies, and so forth. * Catalogs are described in the Schema Guide. * See also {@link #CatalogContent}. */ Catalog, /** * The "CatalogContent" property is an XML string representing the schema: * cubes, hierarchies, and so forth. * Catalogs are described in the Schema Guide. * *

    When using this property, quote its value with either single or * double quotes, then escape all occurrences of that character within the * catalog content by using double single/double quotes. ie: * *

      CatalogContent="<Schema name=""My Schema""/>" * *

    See also {@link #Catalog}. */ CatalogContent, /** * The "CatalogName" property is not used. If, in future, we support * multiple catalogs, this property will specify which catalog to use. * See also {@link #Catalog}. */ CatalogName, /** * The "DataSource" property is the name of a data source class. It must * implement the {@link javax.sql.DataSource} interface. * You must specify either {@link #DataSource} or {@link #Jdbc}. */ DataSource, /** * The "PoolNeeded" property tells Mondrian whether to add a layer of * connection pooling. * *

    If no value is specified, we assume that:

      *
    • connections created via the {@link #Jdbc} property are not pooled, * and therefore need to be pooled, *
    • connections created via the {@link #DataSource} are already pooled. *
    */ PoolNeeded, /** * The "Role" property is the name of the {@link mondrian.olap.Role role} * to adopt. If not specified, the connection uses a role which has access * to every object in the schema. */ Role, /** * Allows to work with dynamically changing schema. If this property is set * to true and schema content has changed (previous checksum * doesn't equal with current), schema would be reloaded. Could be used in * combination with DynamicSchemaProcessor property */ UseContentChecksum, /** * The "UseSchemaPool" property disables the schema cache. If false, the * schema is not shared with connections which have a textually identical * schema. Default is "true". */ UseSchemaPool, /** * The name of a class implementing the * {@link mondrian.spi.DynamicSchemaProcessor} interface. * A dynamic schema processor is called at runtime in order to modify the * schema content. */ DynamicSchemaProcessor, /** * The "Locale" property is the requested Locale for the * LocalizingDynamicSchemaProcessor. Example values are "en", * "en_US", "hu". If Locale is not specified, then the name of system's * default will be used, as per {@link java.util.Locale#getDefault()}. */ Locale, /** * The name of a class implementing the * {@link mondrian.spi.DataSourceChangeListener} interface. * A data source change listener is used to flush the cache of * mondrian every time the datasource is changed. */ DataSourceChangeListener, /** * The "Ignore" property is a boolean value. If true, mondrian ignores * warnings and non-fatal errors while loading the schema. The resulting * errors can be obtained by calling * {@link mondrian.olap.Schema#getWarnings}. */ Ignore, /** * The "Instance" property is the unique identifier of a mondrian server * running in the current JVM. If there are multiple mondrian servers, it * ensures that the connection belongs to the correct one. */ Instance; /** * Any property beginning with this value will be added to the * JDBC connection properties, after removing this prefix. This * allows you to specify connection properties without a URL. */ public static final String JdbcPropertyPrefix = "jdbc."; } // End RolapConnectionProperties.java mondrian-3.4.1/src/main/mondrian/rolap/HighCardSqlTupleReader.java0000644000175000017500000001451111735330606025020 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.TupleList; import mondrian.calc.impl.DelegatingTupleList; import mondrian.calc.impl.UnaryTupleList; import mondrian.olap.*; import mondrian.olap.fun.FunUtil; import mondrian.rolap.sql.TupleConstraint; import mondrian.server.Locus; import mondrian.server.monitor.SqlStatementEvent; import mondrian.util.Pair; import mondrian.util.TraversalList; import java.sql.SQLException; import java.util.*; import javax.sql.DataSource; /** * Reads the members of a single level (level.members) or of multiple levels * (crossjoin). * * @author luis f. canals * @since Dec, 2007 */ public class HighCardSqlTupleReader extends SqlTupleReader { private ResultLoader resultLoader; private boolean moreRows; int maxRows = 0; public HighCardSqlTupleReader(final TupleConstraint constraint) { super(constraint); } public void addLevelMembers( final RolapLevel level, final MemberBuilder memberBuilder, final List srcMembers) { targets.add(new Target( level, memberBuilder, srcMembers, constraint, this)); } protected void prepareTuples( final DataSource dataSource, final TupleList partialResult, final List> newPartialResult) { String message = "Populating member cache with members for " + targets; SqlStatement stmt = null; boolean execQuery = (partialResult == null); try { if (execQuery) { // we're only reading tuples from the targets that are // non-enum targets List partialTargets = new ArrayList(); for (TargetBase target : targets) { if (target.getSrcMembers() == null) { partialTargets.add(target); } } final Pair> pair = makeLevelMembersSql(dataSource); String sql = pair.left; List types = pair.right; stmt = RolapUtil.executeQuery( dataSource, sql, types, maxRows, 0, new SqlStatement.StatementLocus( Locus.peek().execution, "HighCardSqlTupleReader.readTuples " + partialTargets, message, SqlStatementEvent.Purpose.TUPLES, 0), -1, -1); } for (TargetBase target : targets) { target.open(); } // determine how many enum targets we have int enumTargetCount = getEnumTargetCount(); int currPartialResultIdx = 0; if (execQuery) { this.moreRows = stmt.getResultSet().next(); if (this.moreRows) { ++stmt.rowCount; } } else { this.moreRows = currPartialResultIdx < partialResult.size(); } this.resultLoader = new ResultLoader( enumTargetCount, targets, stmt, execQuery, partialResult, newPartialResult); // Read first and second elements if exists (or marks // source as having "no more rows") readNextTuple(); readNextTuple(); } catch (SQLException sqle) { if (stmt != null) { throw stmt.handle(sqle); } else { throw Util.newError(sqle, message); } } finally { if (!moreRows) { if (stmt != null) { stmt.close(); } } } } public TupleList readMembers( final DataSource dataSource, final TupleList partialResult, final List> newPartialResult) { prepareTuples(dataSource, partialResult, newPartialResult); assert targets.size() == 1; return new UnaryTupleList( Util.cast(targets.get(0).close())); } public TupleList readTuples( final DataSource jdbcConnection, final TupleList partialResult, final List> newPartialResult) { prepareTuples(jdbcConnection, partialResult, newPartialResult); // List of tuples final int n = targets.size(); @SuppressWarnings({"unchecked"}) final List[] lists = new List[n]; for (int i = 0; i < n; i++) { lists[i] = targets.get(i).close(); } final List> list = new TraversalList(lists, Member.class); TupleList tupleList = new DelegatingTupleList(n, list); // need to hierarchize the columns from the enumerated targets // since we didn't necessarily add them in the order in which // they originally appeared in the cross product int enumTargetCount = getEnumTargetCount(); if (enumTargetCount > 0) { tupleList = FunUtil.hierarchizeTupleList(tupleList, false); } return tupleList; } /** * Reads next tuple notifing all internal targets. * * @return whether there are any more rows */ public boolean readNextTuple() { if (!this.moreRows) { return false; } try { this.moreRows = this.resultLoader.loadResult(); } catch (SQLException sqle) { this.moreRows = false; throw this.resultLoader.handle(sqle); } if (!this.moreRows) { this.resultLoader.close(); } return this.moreRows; } public void setMaxRows(int maxRows) { this.maxRows = maxRows; } public int getMaxRows() { return maxRows; } Collection getBaseCubeCollection(final Query query) { return query.getBaseCubes(); } } // End HighCardSqlTupleReader.java mondrian-3.4.1/src/main/mondrian/rolap/MemberReader.java0000644000175000017500000001220111735330606023056 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. // // jhyde, 10 August, 2001 */ package mondrian.rolap; import mondrian.olap.Hierarchy; import mondrian.rolap.TupleReader.MemberBuilder; import mondrian.rolap.sql.MemberChildrenConstraint; import mondrian.rolap.sql.TupleConstraint; import java.util.List; /** * A MemberReader implements common operations to retrieve members * from a hierarchy. * *

    MemberReader is an extension of {@link MemberSource}, which * implements only the very basic operations. {@link CacheMemberReader} is an * adapter which converts a {@link MemberSource} into a {@link MemberReader} * and does caching too. * * @author jhyde * @since 10 August, 2001 */ interface MemberReader extends MemberSource { /** * Returns the member n after member in the same * level (or before, if n is negative). * Returns {@link Hierarchy#getNullMember} if we run off the beginning or * end of the level. */ RolapMember getLeadMember(RolapMember member, int n); /** * Returns all of the members in level whose ordinal lies * between startOrdinal and endOrdinal. * *

    If this object * {@link MemberSource#setCache supports cache-writeback}, also * writes these members to the cache. * * @return {@link List} of {@link RolapMember} */ List getMembersInLevel( RolapLevel level, int startOrdinal, int endOrdinal); /** * Writes all members between startMember and * endMember into list. */ void getMemberRange( RolapLevel level, RolapMember startMember, RolapMember endMember, List list); /** * Compares two members according to their order in a prefix ordered * traversal. If siblingsAreEqual, then two members with the * same parent will compare equal. * * @return less than zero if m1 occurs before m2, * greater than zero if m1 occurs after m2, * zero if m1 is equal to m2, or if siblingsAreEqual and * m1 and m2 have the same parent */ int compare( RolapMember m1, RolapMember m2, boolean siblingsAreEqual); /** * Populates a list of the children of a Member, optionally applying a * constraint. * * @param member Members whose children to find * @param children List to populate with members * @param constraint Constraint */ void getMemberChildren( RolapMember member, List children, MemberChildrenConstraint constraint); /** * Populates a list of the children of a given set of Members, optionally * applying a constraint. * * @param parentMembers List of members whose children to find * @param children List to populate with members * @param constraint Constraint */ void getMemberChildren( List parentMembers, List children, MemberChildrenConstraint constraint); /** * Returns the members in the given Level, optionally between a range * of ordinals and applying a constraint. * * @param level Level * @param startOrdinal Ordinal of first member to retrieve * @param endOrdinal Ordinal of last member to upper bound * @param constraint Constraint * @return list of members */ List getMembersInLevel( RolapLevel level, int startOrdinal, int endOrdinal, TupleConstraint constraint); /** * Returns the number of members in this level. * * @param level Level * @return number of members in level */ int getLevelMemberCount(RolapLevel level); MemberBuilder getMemberBuilder(); RolapMember getDefaultMember(); RolapMember getMemberParent(RolapMember member); /** * Substitutes a given member. * If member is null, returns null. * *

    This method is called whenever a member is returned from the wrapped * member reader and is to be returned to the caller. You could say that it * translates 'to caller space'. * * @param member Member * @return Substitute member */ RolapMember substitute(RolapMember member); /** * Returns the member which was substituted. * If member is null, returns null. * *

    This method is called whenever the caller passes a member into a * method and needs to be passed to a method on the wrapped member reader. * You could say that it translates 'from caller space'. * * @param member Member * @return Internal member */ RolapMember desubstitute(RolapMember member); } // End MemberReader.java mondrian-3.4.1/src/main/mondrian/rolap/RolapMemberCalculation.java0000644000175000017500000000644511735330606025125 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2011 Pentaho // All Rights Reserved. */ package mondrian.rolap; import mondrian.calc.Calc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.Exp; import mondrian.olap.fun.AggregateFunDef; /** * Implementation of {@link mondrian.rolap.RolapCalculation} * that wraps a {@link RolapMember calculated member}. * * @author jhyde * @since May 15, 2009 */ class RolapMemberCalculation implements RolapCalculation { private final RolapMember member; private final int solveOrder; private Boolean containsAggregateFunction; /** * Creates a RolapMemberCalculation. * * @param member Calculated member */ public RolapMemberCalculation(RolapMember member) { this.member = member; // compute and solve order: it is used frequently solveOrder = this.member.getSolveOrder(); assert member.isEvaluated(); } public int hashCode() { return member.hashCode(); } public boolean equals(Object obj) { return obj instanceof RolapMemberCalculation && member == ((RolapMemberCalculation) obj).member; } public void setContextIn(RolapEvaluator evaluator) { final RolapMember defaultMember = evaluator.root.defaultMembers[getHierarchyOrdinal()]; // This method does not need to call RolapEvaluator.removeCalcMember. // That happens implicitly in setContext. evaluator.setContext(defaultMember); evaluator.setExpanding(member); } public int getSolveOrder() { return solveOrder; } public int getHierarchyOrdinal() { return member.getHierarchy().getOrdinalInCube(); } public Calc getCompiledExpression(RolapEvaluatorRoot root) { final Exp exp = member.getExpression(); return root.getCompiled(exp, true, null); } public boolean isCalculatedInQuery() { return member.isCalculatedInQuery(); } public boolean containsAggregateFunction() { // searching for agg functions is expensive, so cache result if (containsAggregateFunction == null) { containsAggregateFunction = foundAggregateFunction(member.getExpression()); } return containsAggregateFunction; } /** * Returns whether an expression contains a call to an aggregate * function such as "Aggregate" or "Sum". * * @param exp Expression * @return Whether expression contains a call to an aggregate function. */ private static boolean foundAggregateFunction(Exp exp) { if (exp instanceof ResolvedFunCall) { ResolvedFunCall resolvedFunCall = (ResolvedFunCall) exp; if (resolvedFunCall.getFunDef() instanceof AggregateFunDef) { return true; } else { for (Exp argExp : resolvedFunCall.getArgs()) { if (foundAggregateFunction(argExp)) { return true; } } } } return false; } } // End RolapMemberCalculation.java mondrian-3.4.1/src/main/mondrian/olap/0000755000175000017500000000000011744246550017507 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/olap/ParameterImpl.java0000644000175000017500000002046211735330606023114 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2000-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.calc.impl.GenericCalc; import mondrian.mdx.MemberExpr; import mondrian.olap.type.*; import java.util.List; /** * Implementation of {@link Parameter}. * * @author jhyde * @since Jul 22, 2006 */ public class ParameterImpl implements Parameter, ParameterCompilable { private final String name; private String description; private Exp defaultExp; private Type type; private ParameterSlot slot = new ParameterSlot() { Object value; boolean assigned; public Object getCachedDefaultValue() { throw new UnsupportedOperationException(); } public Calc getDefaultValueCalc() { throw new UnsupportedOperationException(); } public int getIndex() { throw new UnsupportedOperationException(); } public Parameter getParameter() { return ParameterImpl.this; } public Object getParameterValue() { return value; } public boolean isParameterSet() { return assigned; } public void unsetParameterValue() { this.assigned = false; this.value = null; } public void setCachedDefaultValue(Object value) { throw new UnsupportedOperationException(); } public void setParameterValue(Object value, boolean assigned) { this.assigned = true; this.value = value; // make sure caller called convert first assert !(value instanceof List && !(value instanceof TupleList)); assert !(value instanceof MemberExpr); assert !(value instanceof Literal); } }; public ParameterImpl( String name, Exp defaultExp, String description, Type type) { this.name = name; this.defaultExp = defaultExp; this.description = description; this.type = type; assert defaultExp != null; assert type instanceof StringType || type instanceof NumericType || type instanceof MemberType; } public Scope getScope() { return Scope.Statement; } public Type getType() { return type; } public Exp getDefaultExp() { return defaultExp; } public String getName() { return name; } public Object getValue() { if (slot == null) { // query has not been resolved yet, so it's not possible for the // parameter to have a value return null; } else { final Object value = slot.getParameterValue(); return convertBack(value); } } public void setValue(Object value) { slot.setParameterValue(convert(value), true); } public boolean isSet() { return slot != null && slot.isParameterSet(); } public void unsetValue() { slot.unsetParameterValue(); } public String getDescription() { return description; } // For the purposes of type inference and expression substitution, a // parameter is atomic; therefore, we ignore the child member, if any. public Object[] getChildren() { return null; } /** * Returns whether this parameter is equal to another, based upon name, * type and value */ public boolean equals(Object other) { if (!(other instanceof ParameterImpl)) { return false; } ParameterImpl that = (ParameterImpl) other; return that.getName().equals(this.getName()) && that.defaultExp.equals(this.defaultExp); } public int hashCode() { return Util.hash(getName().hashCode(), defaultExp.hashCode()); } /** * Returns whether the parameter can be modified. */ public boolean isModifiable() { return true; } public void setDescription(String description) { this.description = description; } public void setType(Type type) { assert type instanceof StringType || type instanceof NumericType || type instanceof MemberType || (type instanceof SetType && ((SetType) type).getElementType() instanceof MemberType) : type; this.type = type; } public void setDefaultExp(Exp defaultExp) { assert defaultExp != null; this.defaultExp = defaultExp; } public Calc compile(ExpCompiler compiler) { final ParameterSlot slot = compiler.registerParameter(this); if (this.slot != null) { // save previous value if (this.slot.isParameterSet()) { slot.setParameterValue( this.slot.getParameterValue(), true); } } this.slot = slot; if (type instanceof SetType) { return new MemberListParameterCalc(slot); } else { return new ParameterCalc(slot); } } protected Object convert(Object value) { // Convert from old-style tuple list (list of member or member[]) // to new-style list (TupleList). if (value instanceof List && !(value instanceof TupleList)) { List list = (List) value; return TupleCollections.asTupleList(list); } if (value instanceof MemberExpr) { return ((MemberExpr) value).getMember(); } if (value instanceof Literal) { return ((Literal) value).getValue(); } return value; } public static Object convertBack(Object value) { if (value instanceof TupleList) { TupleList tupleList = (TupleList) value; if (tupleList.getArity() == 1) { return tupleList.slice(0); } else { return TupleCollections.asMemberArrayList(tupleList); } } return value; } /** * Compiled expression which yields the value of a scalar, member, level, * hierarchy or dimension parameter. * *

    It uses a slot which has a unique id within the execution environment. * * @see MemberListParameterCalc */ private static class ParameterCalc extends GenericCalc { private final ParameterSlot slot; /** * Creates a ParameterCalc. * * @param slot Slot */ public ParameterCalc(ParameterSlot slot) { super(new DummyExp(slot.getParameter().getType()), new Calc[0]); this.slot = slot; } public Object evaluate(Evaluator evaluator) { Object value = evaluator.getParameterValue(slot); if (!slot.isParameterSet()) { // save value if not set (setting the default value) slot.setParameterValue(value, false); } return value; } } /** * Compiled expression which yields the value of parameter whose type is * a list of members. * *

    It uses a slot which has a unique id within the execution environment. * * @see ParameterCalc */ private static class MemberListParameterCalc extends AbstractListCalc { private final ParameterSlot slot; /** * Creates a MemberListParameterCalc. * * @param slot Slot */ public MemberListParameterCalc(ParameterSlot slot) { super(new DummyExp(slot.getParameter().getType()), new Calc[0]); this.slot = slot; } public TupleList evaluateList(Evaluator evaluator) { TupleList value = (TupleList) evaluator.getParameterValue(slot); if (!slot.isParameterSet()) { // save value if not set (setting the default value) slot.setParameterValue(value, false); } return value; } } } // End ParameterImpl.java mondrian-3.4.1/src/main/mondrian/olap/FunTable.java0000644000175000017500000000713011735330606022047 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.olap.fun.FunInfo; import mondrian.olap.fun.Resolver; import java.util.List; /** * List of all MDX functions. * *

    A function table can resolve a function call, using a particular * {@link Syntax} and set of arguments, to a * function definition ({@link FunDef}).

    * * @author jhyde, 3 March, 2002 */ public interface FunTable { /** * Returns whether a string is a reserved word. */ boolean isReserved(String s); /** * Returns whether a string is a property-style (postfix) * operator. This is used during parsing to disambiguate * functions from unquoted member names. */ boolean isProperty(String s); /** * Returns a list of words ({@link String}) which may not be used as * identifiers. */ List getReservedWords(); /** * Returns a list of {@link mondrian.olap.fun.Resolver} objects. */ List getResolvers(); /** * Returns a list of resolvers for an operator with a given name and syntax. * Never returns null; if there are no resolvers, returns the empty list. * * @param name Operator name * @param syntax Operator syntax * @return List of resolvers for the operator */ List getResolvers( String name, Syntax syntax); /** * Returns a list of {@link mondrian.olap.fun.FunInfo} objects. */ List getFunInfoList(); /** * This method is called from the constructor, to define the set of * functions and reserved words recognized. * *

    The implementing class calls {@link Builder} methods to declare * functions and reserved words. * *

    Derived class can override this method to add more functions. It must * call the base method. * * @param builder Builder */ void defineFunctions(Builder builder); /** * Builder that assists with the construction of a function table by * providing callbacks to define functions. * *

    An implementation of {@link mondrian.olap.FunTable} must register all * of its functions and operators by making callbacks during its * {@link mondrian.olap.FunTable#defineFunctions(mondrian.olap.FunTable.Builder)} * method. */ public interface Builder { /** * Defines a function. * * @param funDef Function definition */ void define(FunDef funDef); /** * Defines a resolver that will resolve overloaded function calls to * function definitions. * * @param resolver Function call resolver */ void define(Resolver resolver); /** * Defines a function info that is not matchd by an actual function. * The function will be implemented via implicit conversions, but * we still want the function info to appear in the metadata. * * @param funInfo Function info */ void define(FunInfo funInfo); /** * Defines a reserved word. * * @param keyword Reserved word * * @see mondrian.olap.FunTable#isReserved */ void defineReserved(String keyword); } } // End FunTable.java mondrian-3.4.1/src/main/mondrian/olap/Property.java0000644000175000017500000010007711735330606022177 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. // // jhyde, 12 September, 2002 */ package mondrian.olap; import mondrian.spi.PropertyFormatter; import java.util.*; /** * Property is the definition of a member property. * *

    The following properties are mandatory for members:

      *
    • {@link #CATALOG_NAME}
    • *
    • {@link #SCHEMA_NAME}
    • *
    • {@link #CUBE_NAME}
    • *
    • {@link #DIMENSION_UNIQUE_NAME}
    • *
    • {@link #HIERARCHY_UNIQUE_NAME}
    • *
    • {@link #LEVEL_UNIQUE_NAME}
    • *
    • {@link #LEVEL_NUMBER}
    • *
    • {@link #MEMBER_UNIQUE_NAME}
    • *
    • {@link #MEMBER_NAME}
    • *
    • {@link #MEMBER_TYPE}
    • *
    • {@link #MEMBER_GUID}
    • *
    • {@link #MEMBER_CAPTION}
    • *
    • {@link #MEMBER_ORDINAL}
    • *
    • {@link #CHILDREN_CARDINALITY}
    • *
    • {@link #PARENT_LEVEL}
    • *
    • {@link #PARENT_UNIQUE_NAME}
    • *
    • {@link #PARENT_COUNT}
    • *
    • {@link #DESCRIPTION}
    • *
    * * The following propertiess are mandatory for cells:
      *
    • {@link #BACK_COLOR}
    • *
    • {@link #CELL_EVALUATION_LIST}
    • *
    • {@link #CELL_ORDINAL}
    • *
    • {@link #FORE_COLOR}
    • *
    • {@link #FONT_NAME}
    • *
    • {@link #FONT_SIZE}
    • *
    • {@link #FONT_FLAGS}
    • *
    • {@link #FORMAT_STRING}
    • *
    • {@link #FORMATTED_VALUE}
    • *
    • {@link #NON_EMPTY_BEHAVIOR}
    • *
    • {@link #SOLVE_ORDER}
    • *
    • {@link #VALUE}
    • *
    * * @author jhyde */ public class Property extends EnumeratedValues.BasicValue { public enum Datatype { TYPE_STRING, TYPE_NUMERIC, TYPE_BOOLEAN, TYPE_DATE, TYPE_TIME, TYPE_TIMESTAMP, TYPE_OTHER } /** * For properties which have synonyms, maps from the synonym to the * property. */ private static final Map synonyms = new HashMap(); /** * Map of upper-case names to property definitions, for case-insensitive * match. Also contains synonyms. */ public static final Map mapUpperNameToProperties = new HashMap(); public static final int FORMAT_EXP_PARSED_ORDINAL = 0; /** * Definition of the internal property which * holds the parsed format string (an object of type {@link Exp}). */ public static final Property FORMAT_EXP_PARSED = new Property( "$format_exp", Datatype.TYPE_OTHER, FORMAT_EXP_PARSED_ORDINAL, true, false, false, null); public static final int AGGREGATION_TYPE_ORDINAL = 1; /** * Definition of the internal property which * holds the aggregation type. This is automatically set for stored * measures, based upon their SQL aggregation. */ public static final Property AGGREGATION_TYPE = new Property( "$aggregation_type", Datatype.TYPE_OTHER, AGGREGATION_TYPE_ORDINAL, true, false, false, null); public static final int NAME_ORDINAL = 2; /** * Definition of the internal property which * holds a member's name. */ public static final Property NAME = new Property( "$name", Datatype.TYPE_STRING, NAME_ORDINAL, true, false, false, null); public static final int CAPTION_ORDINAL = 3; /** * Definition of the internal property which * holds a member's caption. */ public static final Property CAPTION = new Property( "$caption", Datatype.TYPE_STRING, CAPTION_ORDINAL, true, false, false, null); public static final int CONTRIBUTING_CHILDREN_ORDINAL = 4; /** * Definition of the internal property which * holds, for a member of a parent-child hierarchy, a * {@link java.util.List} containing the member's data * member and all of its children (including non-visible children). * * @deprecated Property is not used and will be removed in mondrian-4.0; * use {@link mondrian.olap.SchemaReader#getParentChildContributingChildren} */ public static final Property CONTRIBUTING_CHILDREN = new Property( "$contributingChildren", Datatype.TYPE_OTHER, CONTRIBUTING_CHILDREN_ORDINAL, true, false, false, null); public static final int FORMULA_ORDINAL = 5; /** * Definition of the internal property which * returns a calculated member's {@link Formula} object. */ public static final Property FORMULA = new Property( "$formula", Datatype.TYPE_OTHER, FORMULA_ORDINAL, true, false, false, null); public static final int MEMBER_SCOPE_ORDINAL = 6; /** * Definition of the internal property which * describes whether a calculated member belongs to a query or a cube. */ public static final Property MEMBER_SCOPE = new Property( "$member_scope", Datatype.TYPE_OTHER, MEMBER_SCOPE_ORDINAL, true, true, false, null); public static final int CATALOG_NAME_ORDINAL = 10; /** * Definition of the property which * holds the name of the current catalog. */ public static final Property CATALOG_NAME = new Property( "CATALOG_NAME", Datatype.TYPE_STRING, CATALOG_NAME_ORDINAL, false, true, false, "Optional. The name of the catalog to which this member belongs. " + "NULL if the provider does not support catalogs."); public static final int SCHEMA_NAME_ORDINAL = 11; /** * Definition of the property which * holds the name of the current schema. */ public static final Property SCHEMA_NAME = new Property( "SCHEMA_NAME", Datatype.TYPE_STRING, SCHEMA_NAME_ORDINAL, false, true, false, "Optional. The name of the schema to which this member belongs. " + "NULL if the provider does not support schemas."); public static final int CUBE_NAME_ORDINAL = 12; /** * Definition of the property which * holds the name of the current cube. */ public static final Property CUBE_NAME = new Property( "CUBE_NAME", Datatype.TYPE_STRING, CUBE_NAME_ORDINAL, false, true, false, "Required. Name of the cube to which this member belongs."); public static final int DIMENSION_UNIQUE_NAME_ORDINAL = 13; /** * Definition of the property which * holds the unique name of the current dimension. */ public static final Property DIMENSION_UNIQUE_NAME = new Property( "DIMENSION_UNIQUE_NAME", Datatype.TYPE_STRING, DIMENSION_UNIQUE_NAME_ORDINAL, false, true, false, "Required. Unique name of the dimension to which this member " + "belongs. For providers that generate unique names by " + "qualification, each component of this name is delimited."); public static final int HIERARCHY_UNIQUE_NAME_ORDINAL = 14; /** * Definition of the property which * holds the unique name of the current hierarchy. */ public static final Property HIERARCHY_UNIQUE_NAME = new Property( "HIERARCHY_UNIQUE_NAME", Datatype.TYPE_STRING, HIERARCHY_UNIQUE_NAME_ORDINAL, false, true, false, "Required. Unique name of the hierarchy. If the member belongs " + "to more than one hierarchy, there is one row for each hierarchy " + "to which it belongs. For providers that generate unique names " + "by qualification, each component of this name is delimited."); public static final int LEVEL_UNIQUE_NAME_ORDINAL = 15; /** * Definition of the property which * holds the unique name of the current level. */ public static final Property LEVEL_UNIQUE_NAME = new Property( "LEVEL_UNIQUE_NAME", Datatype.TYPE_STRING, LEVEL_UNIQUE_NAME_ORDINAL, false, true, false, "Required. Unique name of the level to which the member belongs. " + "For providers that generate unique names by qualification, " + "each component of this name is delimited."); public static final int LEVEL_NUMBER_ORDINAL = 16; /** * Definition of the property which * holds the ordinal of the current level. */ public static final Property LEVEL_NUMBER = new Property( "LEVEL_NUMBER", Datatype.TYPE_STRING, LEVEL_NUMBER_ORDINAL, false, true, false, "Required. The distance of the member from the root of the " + "hierarchy. The root level is zero."); public static final int MEMBER_ORDINAL_ORDINAL = 17; /** * Definition of the property which * holds the ordinal of the current member. */ public static final Property MEMBER_ORDINAL = new Property( "MEMBER_ORDINAL", Datatype.TYPE_NUMERIC, MEMBER_ORDINAL_ORDINAL, false, true, false, "Required. Ordinal number of the member. Sort rank of the member " + "when members of this dimension are sorted in their natural sort " + "order. If providers do not have the concept of natural " + "ordering, this should be the rank when sorted by MEMBER_NAME."); public static final int MEMBER_NAME_ORDINAL = 18; /** * Definition of the property which * holds the name of the current member. */ public static final Property MEMBER_NAME = new Property( "MEMBER_NAME", Datatype.TYPE_STRING, MEMBER_NAME_ORDINAL, false, true, false, "Required. Name of the member."); public static final int MEMBER_UNIQUE_NAME_ORDINAL = 19; /** * Definition of the property which * holds the unique name of the current member. */ public static final Property MEMBER_UNIQUE_NAME = new Property( "MEMBER_UNIQUE_NAME", Datatype.TYPE_STRING, MEMBER_UNIQUE_NAME_ORDINAL, false, true, false, "Required. Unique name of the member. For providers that " + "generate unique names by qualification, each component of " + "this name is delimited."); public static final int MEMBER_TYPE_ORDINAL = 20; /** * Definition of the property which * holds the type of the member. */ public static final Property MEMBER_TYPE = new Property( "MEMBER_TYPE", Datatype.TYPE_STRING, MEMBER_TYPE_ORDINAL, false, true, false, "Required. Type of the member. Can be one of the following values: " + "MDMEMBER_TYPE_REGULAR, MDMEMBER_TYPE_ALL, " + "MDMEMBER_TYPE_FORMULA, MDMEMBER_TYPE_MEASURE, " + "MDMEMBER_TYPE_UNKNOWN. MDMEMBER_TYPE_FORMULA takes precedence " + "over MDMEMBER_TYPE_MEASURE. Therefore, if there is a formula " + "(calculated) member on the Measures dimension, it is listed as " + "MDMEMBER_TYPE_FORMULA."); public static final int MEMBER_GUID_ORDINAL = 21; /** * Definition of the property which * holds the GUID of the member */ public static final Property MEMBER_GUID = new Property( "MEMBER_GUID", Datatype.TYPE_STRING, MEMBER_GUID_ORDINAL, false, true, false, "Optional. Member GUID. NULL if no GUID exists."); public static final int MEMBER_CAPTION_ORDINAL = 22; /** * Definition of the property which * holds the label or caption associated with the member, or the * member's name if no caption is defined. * *

    "CAPTION" is a synonym for this property. */ public static final Property MEMBER_CAPTION = new Property( "MEMBER_CAPTION", Datatype.TYPE_STRING, MEMBER_CAPTION_ORDINAL, false, true, false, "Required. A label or caption associated with the member. Used " + "primarily for display purposes. If a caption does not exist, " + "MEMBER_NAME is returned."); public static final int CHILDREN_CARDINALITY_ORDINAL = 23; /** * Definition of the property which holds the * number of children this member has. */ public static final Property CHILDREN_CARDINALITY = new Property( "CHILDREN_CARDINALITY", Datatype.TYPE_NUMERIC, CHILDREN_CARDINALITY_ORDINAL, false, true, false, "Required. Number of children that the member has. This can be an " + "estimate, so consumers should not rely on this to be the exact " + "count. Providers should return the best estimate possible."); public static final int PARENT_LEVEL_ORDINAL = 24; /** * Definition of the property which holds the * distance from the root of the hierarchy of this member's parent. */ public static final Property PARENT_LEVEL = new Property( "PARENT_LEVEL", Datatype.TYPE_NUMERIC, PARENT_LEVEL_ORDINAL, false, true, false, "Required. The distance of the member's parent from the root level " + "of the hierarchy. The root level is zero."); public static final int PARENT_UNIQUE_NAME_ORDINAL = 25; /** * Definition of the property which holds the * Name of the current catalog. */ public static final Property PARENT_UNIQUE_NAME = new Property( "PARENT_UNIQUE_NAME", Datatype.TYPE_STRING, PARENT_UNIQUE_NAME_ORDINAL, false, true, false, "Required. Unique name of the member's parent. NULL is returned " + "for any members at the root level. For providers that generate " + "unique names by qualification, each component of this name is " + "delimited."); public static final int PARENT_COUNT_ORDINAL = 26; /** * Definition of the property which holds the * number of parents that this member has. Generally 1, or 0 for root * members. */ public static final Property PARENT_COUNT = new Property( "PARENT_COUNT", Datatype.TYPE_NUMERIC, PARENT_COUNT_ORDINAL, false, true, false, "Required. Number of parents that this member has."); public static final int DESCRIPTION_ORDINAL = 27; /** * Definition of the property which holds the * description of this member. */ public static final Property DESCRIPTION = new Property( "DESCRIPTION", Datatype.TYPE_STRING, DESCRIPTION_ORDINAL, false, true, false, "Optional. A human-readable description of the member."); public static final int VISIBLE_ORDINAL = 28; /** * Definition of the internal property which holds the * name of the system property which determines whether to show a member * (especially a measure or calculated member) in a user interface such as * JPivot. */ public static final Property VISIBLE = new Property( "$visible", Datatype.TYPE_BOOLEAN, VISIBLE_ORDINAL, true, false, false, null); public static final int CELL_FORMATTER_ORDINAL = 29; /** * Definition of the property which holds the * name of the class which formats cell values of this member. * *

    The class must implement the {@link mondrian.spi.CellFormatter} * interface. * *

    Despite its name, this is a member property. */ public static final Property CELL_FORMATTER = new Property( "CELL_FORMATTER", Datatype.TYPE_STRING, CELL_FORMATTER_ORDINAL, false, true, false, "Name of the class which formats cell values of this member."); public static final int CELL_FORMATTER_SCRIPT_LANGUAGE_ORDINAL = 51; /** * Definition of the property which holds the * name of the scripting language in which a scripted cell formatter is * implemented, e.g. 'JavaScript'. * *

    Despite its name, this is a member property. */ public static final Property CELL_FORMATTER_SCRIPT_LANGUAGE = new Property( "CELL_FORMATTER_SCRIPT_LANGUAGE", Datatype.TYPE_STRING, CELL_FORMATTER_SCRIPT_LANGUAGE_ORDINAL, false, true, false, "Name of the scripting language in which a scripted cell formatter" + "is implemented, e.g. 'JavaScript'."); public static final int CELL_FORMATTER_SCRIPT_ORDINAL = 52; /** * Definition of the property which holds the * script with which to format cell values of this member. * *

    Despite its name, this is a member property. */ public static final Property CELL_FORMATTER_SCRIPT = new Property( "CELL_FORMATTER_SCRIPT", Datatype.TYPE_STRING, CELL_FORMATTER_SCRIPT_ORDINAL, false, true, false, "Name of the class which formats cell values of this member."); // Cell properties public static final int BACK_COLOR_ORDINAL = 30; public static final Property BACK_COLOR = new Property( "BACK_COLOR", Datatype.TYPE_STRING, BACK_COLOR_ORDINAL, false, false, true, "The background color for displaying the VALUE or FORMATTED_VALUE " + "property. For more information, see FORE_COLOR and BACK_COLOR " + "Contents."); public static final int CELL_EVALUATION_LIST_ORDINAL = 31; public static final Property CELL_EVALUATION_LIST = new Property( "CELL_EVALUATION_LIST", Datatype.TYPE_STRING, CELL_EVALUATION_LIST_ORDINAL, false, false, true, "The semicolon-delimited list of evaluated formulas applicable to " + "the cell, in order from lowest to highest solve order. For more " + "information about solve order, see Understanding Pass Order and " + "Solve Order"); public static final int CELL_ORDINAL_ORDINAL = 32; public static final Property CELL_ORDINAL = new Property( "CELL_ORDINAL", Datatype.TYPE_NUMERIC, CELL_ORDINAL_ORDINAL, false, false, true, "The ordinal number of the cell in the dataset."); public static final int FORE_COLOR_ORDINAL = 33; public static final Property FORE_COLOR = new Property( "FORE_COLOR", Datatype.TYPE_STRING, FORE_COLOR_ORDINAL, false, false, true, "The foreground color for displaying the VALUE or FORMATTED_VALUE " + "property. For more information, see FORE_COLOR and BACK_COLOR " + "Contents."); public static final int FONT_NAME_ORDINAL = 34; public static final Property FONT_NAME = new Property( "FONT_NAME", Datatype.TYPE_STRING, FONT_NAME_ORDINAL, false, false, true, "The font to be used to display the VALUE or FORMATTED_VALUE " + "property."); public static final int FONT_SIZE_ORDINAL = 35; public static final Property FONT_SIZE = new Property( "FONT_SIZE", Datatype.TYPE_STRING, FONT_SIZE_ORDINAL, false, false, true, "Font size to be used to display the VALUE or FORMATTED_VALUE " + "property."); public static final int FONT_FLAGS_ORDINAL = 36; public static final Property FONT_FLAGS = new Property( "FONT_FLAGS", Datatype.TYPE_NUMERIC, FONT_FLAGS_ORDINAL, false, false, true, "The bitmask detailing effects on the font. The value is the " + "result of a bitwise OR operation of one or more of the " + "following constants: MDFF_BOLD = 1, MDFF_ITALIC = 2, " + "MDFF_UNDERLINE = 4, MDFF_STRIKEOUT = 8. For example, the value " + "5 represents the combination of bold (MDFF_BOLD) and underline " + "(MDFF_UNDERLINE) font effects."); public static final int FORMATTED_VALUE_ORDINAL = 37; /** * Definition of the property which * holds the formatted value of a cell. */ public static final Property FORMATTED_VALUE = new Property( "FORMATTED_VALUE", Datatype.TYPE_STRING, FORMATTED_VALUE_ORDINAL, false, false, true, "The character string that represents a formatted display of the " + "VALUE property."); public static final int FORMAT_STRING_ORDINAL = 38; /** * Definition of the property which * holds the format string used to format cell values. */ public static final Property FORMAT_STRING = new Property( "FORMAT_STRING", Datatype.TYPE_STRING, FORMAT_STRING_ORDINAL, false, false, true, "The format string used to create the FORMATTED_VALUE property " + "value. For more information, see FORMAT_STRING Contents."); public static final int NON_EMPTY_BEHAVIOR_ORDINAL = 39; public static final Property NON_EMPTY_BEHAVIOR = new Property( "NON_EMPTY_BEHAVIOR", Datatype.TYPE_STRING, NON_EMPTY_BEHAVIOR_ORDINAL, false, false, true, "The measure used to determine the behavior of calculated members " + "when resolving empty cells."); public static final int SOLVE_ORDER_ORDINAL = 40; /** * Definition of the property which * determines the solve order of a calculated member with respect to other * calculated members. */ public static final Property SOLVE_ORDER = new Property( "SOLVE_ORDER", Datatype.TYPE_NUMERIC, SOLVE_ORDER_ORDINAL, false, false, true, "The solve order of the cell."); public static final int VALUE_ORDINAL = 41; /** * Definition of the property which * holds the value of a cell. Is usually numeric (since most measures are * numeric) but is occasionally another type. * *

    It is also applicable to members. */ public static final Property VALUE = new Property( "VALUE", Datatype.TYPE_NUMERIC, VALUE_ORDINAL, false, true, true, "The unformatted value of the cell."); public static final int DATATYPE_ORDINAL = 42; /** * Definition of the property which * holds the datatype of a cell. Valid values are "String", * "Numeric", "Integer". The property's value derives from the * "datatype" attribute of the "Measure" element; if the datatype attribute * is not specified, the datatype is "Numeric" by default, except measures * whose aggregator is "Count", whose datatype is "Integer". */ public static final Property DATATYPE = new Property( "DATATYPE", Datatype.TYPE_STRING, DATATYPE_ORDINAL, false, false, true, "The datatype of the cell."); public static final int DEPTH_ORDINAL = 43; /** * Definition of the property which * holds the level depth of a member. * *

    Caution: Level depth of members in parent-child hierarchy isn't from * their levels. It's calculated from the underlying data dynamically. */ public static final Property DEPTH = new Property( "DEPTH", Datatype.TYPE_NUMERIC, DEPTH_ORDINAL, true, true, false, "The level depth of a member"); public static final int DISPLAY_INFO_ORDINAL = 44; /** * Definition of the property which * holds the DISPLAY_INFO required by XML/A. * Caution: This property's value is calculated based on a specified MDX * query, so it's value is dynamic at runtime. */ public static final Property DISPLAY_INFO = new Property( "DISPLAY_INFO", Datatype.TYPE_NUMERIC, DISPLAY_INFO_ORDINAL, false, true, false, "Display instruction of a member for XML/A"); public static final int MEMBER_KEY_ORDINAL = 45; /** * Definition of the property which * holds the member key of the current member. */ public static final Property MEMBER_KEY = new Property( "MEMBER_KEY", Datatype.TYPE_STRING, MEMBER_KEY_ORDINAL, false, true, false, "Member key."); public static final int KEY_ORDINAL = 46; /** * Definition of the property which * holds the key of the current member. */ public static final Property KEY = new Property( "KEY", Datatype.TYPE_STRING, KEY_ORDINAL, false, true, false, "Key."); public static final int SCENARIO_ORDINAL = 48; /** * Definition of the internal property which * holds the scenario object underlying a member of the scenario hierarchy. */ public static final Property SCENARIO = new Property( "$scenario", Datatype.TYPE_OTHER, SCENARIO_ORDINAL, true, true, false, null); public static final int DISPLAY_FOLDER_ORDINAL = 49; /** * Definition of the property which * holds the DISPLAY_FOLDER. For measures, a client tool may use this * folder to display measures in groups. This property has no meaning for * other members. */ public static final Property DISPLAY_FOLDER = new Property( "DISPLAY_FOLDER", Datatype.TYPE_STRING, DISPLAY_FOLDER_ORDINAL, false, true, false, "Folder in which to display a measure"); public static final int LANGUAGE_ORDINAL = 50; /** * Definition of the property which * holds the translation expressed as an LCID. * Only valid for property translations. */ public static final Property LANGUAGE = new Property( "LANGUAGE", Datatype.TYPE_NUMERIC, LANGUAGE_ORDINAL, false, false, true, "The translation expressed as an LCID. Only valid for property translations."); public static final int FORMAT_EXP_ORDINAL = 53; /** * Definition of the property which * holds the format string. */ public static final Property FORMAT_EXP = new Property( "FORMAT_EXP", Datatype.TYPE_STRING, FORMAT_EXP_ORDINAL, true, true, false, null); public static final int ACTION_TYPE_ORDINAL = 54; /** * Definition of the property which * holds the format string. */ public static final Property ACTION_TYPE = new Property( "ACTION_TYPE", Datatype.TYPE_NUMERIC, ACTION_TYPE_ORDINAL, false, false, true, null); public static final int DRILLTHROUGH_COUNT_ORDINAL = 55; /** * Definition of the property that * holds the number of fact rows that contributed to this cell. * If the cell is not drillable, returns -1. * *

    Note that this property may be expensive to compute for some * cubes.

    */ public static final Property DRILLTHROUGH_COUNT = new Property( "DRILLTHROUGH_COUNT", Datatype.TYPE_NUMERIC, DRILLTHROUGH_COUNT_ORDINAL, false, false, true, "Number of fact rows that contributed to this cell. If the cell is " + "not drillable, value is -1."); /** * The various property names which define a format string. */ static final Set FORMAT_PROPERTIES = new HashSet( Arrays.asList( "format", "format_string", "FORMAT", FORMAT_STRING.name)); // ~ Data members --------------------------------------------------------- /** * The datatype of the property. */ private final Datatype type; /** * Whether the property is internal. */ private final boolean internal; private final boolean member; private final boolean cell; private static int nextOrdinal = 100; // ~ Methods -------------------------------------------------------------- /** * Creates a property definition. If ordinal is negative, generates a * unique positive ordinal. */ protected Property( String name, Datatype type, int ordinal, boolean internal, boolean member, boolean cell, String description) { super(name, ordinal < 0 ? nextOrdinal++ : ordinal, description); this.type = type; this.internal = internal; this.member = member; this.cell = cell; } /** * Returns the datatype of the property. */ public Datatype getType() { return type; } public PropertyFormatter getFormatter() { return null; } /** * Returns the caption of this property. */ public String getCaption() { return name; } /** * Returns whether this property is for system use only. */ public boolean isInternal() { return internal; } /** * Returns whether this property is a standard member property. */ public boolean isMemberProperty() { return member; } /** * Returns whether this property is a standard cell property. */ public boolean isCellProperty() { return cell && isStandard(); } /** * Returns whether this property is standard. */ public boolean isStandard() { return ordinal < MAX_ORDINAL; } public static final EnumeratedValues enumeration = new EnumeratedValues( new Property[] { FORMAT_EXP_PARSED, AGGREGATION_TYPE, NAME, CAPTION, CONTRIBUTING_CHILDREN, FORMULA, CATALOG_NAME, SCHEMA_NAME, CUBE_NAME, DIMENSION_UNIQUE_NAME, HIERARCHY_UNIQUE_NAME, LEVEL_UNIQUE_NAME, LEVEL_NUMBER, MEMBER_UNIQUE_NAME, MEMBER_NAME, MEMBER_TYPE, MEMBER_GUID, MEMBER_CAPTION, MEMBER_ORDINAL, CHILDREN_CARDINALITY, PARENT_LEVEL, PARENT_UNIQUE_NAME, PARENT_COUNT, DESCRIPTION, VISIBLE, CELL_FORMATTER, CELL_FORMATTER_SCRIPT, CELL_FORMATTER_SCRIPT_LANGUAGE, BACK_COLOR, CELL_EVALUATION_LIST, CELL_ORDINAL, FORE_COLOR, FONT_NAME, FONT_SIZE, FONT_FLAGS, FORMAT_STRING, FORMATTED_VALUE, NON_EMPTY_BEHAVIOR, SOLVE_ORDER, VALUE, DATATYPE, MEMBER_KEY, KEY, SCENARIO, DISPLAY_FOLDER, FORMAT_EXP, ACTION_TYPE, DRILLTHROUGH_COUNT, }); private static final int MAX_ORDINAL = 56; static { // Populate synonyms. synonyms.put("CAPTION", MEMBER_CAPTION); synonyms.put("FORMAT", FORMAT_STRING); // Populate map of upper-case property names. for (String propertyName : enumeration.getNames()) { final Property property = enumeration.getValue(propertyName, true); mapUpperNameToProperties.put( propertyName.toUpperCase(), property); assert property.getOrdinal() < MAX_ORDINAL; } // Add synonyms. for (Map.Entry entry : synonyms.entrySet()) { mapUpperNameToProperties.put( entry.getKey().toUpperCase(), entry.getValue()); } } /** * Looks up a Property with a given ordinal. * Returns null if not found. */ public static Property lookup(int ordinal) { return enumeration.getValue(ordinal); } /** * Looks up a Property with a given name. * * @param name Name of property * @param matchCase Whether to perform case-sensitive match * @return Property with given name, or null if not found. */ public static Property lookup(String name, boolean matchCase) { if (matchCase) { Property property = enumeration.getValue(name, false); if (property != null) { return property; } return synonyms.get(name); } else { // No need to check synonyms separately - the map contains them. return mapUpperNameToProperties.get(name.toUpperCase()); } } } // End Property.java mondrian-3.4.1/src/main/mondrian/olap/Annotated.java0000644000175000017500000000127111735330606022264 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import java.util.Map; /** * An element that has annotations. * * @author jhyde */ public interface Annotated { /** * Returns a list of annotations. * *

    The map may be empty, never null. * * @return Map from annotation name to annotations. */ Map getAnnotationMap(); } // End Annotated.java mondrian-3.4.1/src/main/mondrian/olap/Mondrian_SW.xml0000644000175000017500000026503211735330606022415 0ustar drazzibdrazzib This is the XML model for Mondrian schemas as used by the Schema Workbench. It is based upon the Mondrian model Mondrian.xml, but has a number of differences:

    • The implementation of equals method for schema objects is changed to == (double equals), to compare two object references rather than their contents.
    • The attributes of the root tag <Model> refers to the generated file MondrianGuiDef.java and its import statements.
    • The 'aggregator' attribute of the Measure element requires the <Value> tags to generate a drop-down of possible options in the Schema Workbench.

    Revision is $Id$

    A schema is a collection of cubes and virtual cubes. It can also contain shared dimensions (for use by those cubes), named sets, roles, and declarations of user-defined functions.

    Name of this schema Description of this schema. Label for the measures dimension. Can be localized from Properties file using #{propertyname}. The name of the default role for connections to this schema Contains values of user-defined properties. This schema's parameter definitions. Shared dimensions in this schema. Cubes in this schema. Virtual cubes in this schema. Named sets in this schema. Roles in this schema. Declarations of user-defined functions in this schema. A CubeDimension is either a usage of a Dimension ('shared dimension', in MSOLAP parlance), or a 'private dimension'. A string being displayed instead of the Dimension's name. Can be localized from Properties file using #{propertyname}. Whether this dimension is visible in the user-interface. Default true. Description of this dimension. Can be localized from Properties file using #{propertyname}. The name of the column in the fact table which joins to the leaf level of this dimension. Required in a private Dimension or a DimensionUsage, but not in a public Dimension. Flag to mark this dimension as a high cardinality one and avoid caching. Contains values of user-defined properties. Never returns null; if the dimension cannot be * found, throws an error. * * @param schema Schema, never null * @pre schema != null * @post return != null */ public abstract Dimension getDimension(Schema schema);]]> Definition of a cube. Name of this cube. A string being displayed instead of the cube's name. Can be localized from Properties file using #{propertyname}. Whether this cube is visible in the user-interface. Default true. Description of this cube. Can be localized from Properties file using #{propertyname}. The name of the measure that would be taken as the default measure of the cube. Should the Fact table data for this Cube be cached by Mondrian or not. The default action is to cache the data. Whether element is enabled - if true, then the Cube is realized otherwise it is ignored. Contains values of user-defined properties. The fact table is the source of all measures in this cube. If this is a Table and the schema name is not present, table name is left unqualified. Calculated members in this cube. Named sets in this cube. A VirtualCube is a set of dimensions and measures gleaned from other cubes. Whether this element is enabled - if true, then the Virtual Cube is realized otherwise it is ignored. The name of the measure that would be taken as the default measure of the cube. A string being displayed instead of the cube's name. Can be localized from Properties file using #{propertyname}. Whether this cube is visible in the user-interface. Default true. Description of this virtual cube. Can be localized from Properties file using #{propertyname}. Contains values of user-defined properties. Calculated members that belong to this virtual cube. (Calculated members inherited from other cubes should not be in this list.) Named sets in this cube. List of base cubes used by the virtual cube. Name of the cube which the virtualCube uses. Unrelated dimensions to measures in this cube will be pushed to top level member. A VirtualCubeDimension is a usage of a Dimension in a VirtualCube. Name of the cube which the dimension belongs to, or unspecified if the dimension is shared. Name of the dimension. A VirtualCubeMeasure is a usage of a Measure in a VirtualCube. Name of the cube which the measure belongs to. Unique name of the measure within its cube. Whether this member is visible in the user-interface. Default true. Contains values of user-defined properties. A DimensionUsage is usage of a shared Dimension within the context of a cube. Name of the source dimension. Must be a dimension in this schema. Case-sensitive. Name of the level to join to. If not specified, joins to the lowest level of the dimension. If present, then this is prepended to the Dimension column names during the building of collapse dimension aggregates allowing 1) different dimension usages to be disambiguated during aggregate table recognition and 2) multiple shared dimensions that have common column names to be disambiguated. A Dimension is a collection of hierarchies. There are two kinds: a public dimension belongs to a Schema, and be used by several cubes; a private dimension belongs to a Cube. The foreignKey field is only applicable to private dimensions. The dimension's type may be one of "Standard" or "Time". A time dimension will allow the use of the MDX time functions (WTD, YTD, QTD, etc.). Use a standard dimension if the dimension is not a time-related dimension. The default value is "Standard". StandardDimension TimeDimension A string being displayed instead of the dimensions's name. Can be localized from Properties file using #{propertyname}. Description of this dimension. Can be localized from Properties file using #{propertyname}. If present, then this is prepended to the Dimension column names during the building of collapse dimension aggregates allowing 1) different dimensions to be disambiguated during aggregate table recognition. This should only be set for private dimensions. // implement CubeDimension public Dimension getDimension(Schema schema) { Util.assertPrecondition(schema != null, "schema != null"); return this; } // Return the dimension's enumerated type. public DimensionType getDimensionType() { if (type == null) { return null; //DimensionType.StandardDimension; } else { return DimensionType.valueOf(type); } } Defines a hierarchy.

    You must specify at most one <Relation> or memberReaderClass. If you specify none, the hierarchy is assumed to come from the same fact table of the current cube. Name of the hierarchy. If this is not specified, the hierarchy has the same name as its dimension. Whether this hierarchy is visible in the user-interface. Default true. Whether this hierarchy has an 'all' member. Name of the 'all' member. If this attribute is not specified, the all member is named 'All hierarchyName', for example, 'All Store'. A string being displayed instead as the all member's name. Can be localized from Properties file using #{propertyname}. Name of the 'all' level. If this attribute is not specified, the all member is named '(All)'. Can be localized from Properties file using #{propertyname}. The name of the column which identifies members, and which is referenced by rows in the fact table. If not specified, the key of the lowest level is used. See also CubeDimension.foreignKey. The name of the table which contains primaryKey. If the hierarchy has only one table, defaults to that; it is required. Name of the custom member reader class. Must implement the mondrian.rolap.MemberReader interface. A string to be displayed in the user interface. If not specified, the hierarchy's name is used. Can be localized from Properties file using #{propertyname}. Description of this hierarchy. Can be localized from Properties file using #{propertyname}. Should be set to the level (if such a level exists) at which depth it is known that all members have entirely unique rows, allowing SQL GROUP BY clauses to be completely eliminated from the query. Contains values of user-defined properties. The {@link MondrianGuiDef.Table table}, {@link MondrianGuiDef.Join set of tables}, {@link MondrianGuiDef.View SQL statement}, or {@link MondrianGuiDef.InlineTable inline table} which populates this hierarchy. The estimated number of members in this level. Setting this property improves the performance of MDSCHEMA_LEVELS, MDSCHEMA_HIERARCHIES and MDSCHEMA_DIMENSIONS XMLA requests Whether this level is visible in the user-interface. Default true. The name of the table that the column comes from. If this hierarchy is based upon just one table, defaults to the name of that table; otherwise, it is required. Can be localized from Properties file using #{propertyname}. The name of the column which holds the unique identifier of this level. The name of the column which holds the user identifier of this level. The name of the column which holds member ordinals. If this column is not specified, the key column is used for ordering. The name of the column which references the parent member in a parent-child hierarchy. Value which identifies null parents in a parent-child hierarchy. Typical values are 'NULL' and '0'. Indicates the type of this level's key column: String, Numeric, Integer, Boolean, Date, Time or Timestamp. When generating SQL statements, Mondrian encloses values for String columns in quotation marks, but leaves values for Integer and Numeric columns un-quoted.

    Date, Time, and Timestamp values are quoted according to the SQL dialect. For a SQL-compliant dialect, the values appear prefixed by their typename, for example, "DATE '2006-06-01'". String Numeric Integer Boolean Date Time Timestamp Indicates the Java type that Mondrian uses to store this level's key column. It also determines the JDBC method that Mondrian will call to retrieve the column; for example, if the Java type is 'int', Mondrian will call 'ResultSet.getInt(int)'.

    Usually this attribute is not needed, because Mondrian can choose a sensible type based on the type of the database column.

    Allowable values are: 'int', 'long', 'Object', 'String'. int long Object String Whether members are unique across all parents. For example, zipcodes are unique across all states. The first level's members are always unique. Whether this is a regular or a time-related level. The value makes a difference to time-related functions such as YTD (year-to-date).

    The "TimeHalfYear" value is deprecated and will be removed in mondrian-4.0; use "TimeHalfYears" instead.

    Regular TimeYears TimeHalfYears TimeHalfYear TimeQuarters TimeMonths TimeWeeks TimeDays TimeHours TimeMinutes TimeSeconds TimeUndefined Condition which determines whether a member of this level is hidden. If a hierarchy has one or more levels with hidden members, then it is possible that not all leaf members are the same distance from the root, and it is termed a ragged hierarchy.

    Allowable values are: Never (a member always appears; the default); IfBlankName (a member doesn't appear if its name is null, empty or all whitespace); and IfParentsName (a member appears unless its name matches the parent's.

    Never IfBlankName IfParentsName
    Name of a formatter class for the member labels being displayed. The class must implement the mondrian.spi.MemberFormatter interface.

    This attribute is deprecated. Please use a nested MemberFormatter element.

    A string being displayed instead of the level's name. Can be localized from Properties file using #{propertyname}. Description of this level. Can be localized from Properties file using #{propertyname}. The name of the column which holds the caption for members. Contains values of user-defined properties. The SQL expression used to populate this level's key. The SQL expression used to populate this level's name. If not specified, the level's key is used. The SQL expression used to populate this level's caption. If not specified, the level's name is used. The SQL expression used to populate this level's ordinal. The SQL expression used to join to the parent member in a parent-child hierarchy. Member formatter. public Expression getKeyExp() { if (keyExp != null) { return keyExp; } else if (column != null) { return new Column(table, column); } else { return null; } } public Expression getNameExp() { if (nameExp != null) { return nameExp; } else if (nameColumn != null) { return new Column(table, nameColumn); } else { return null; } } public Expression getCaptionExp() { if (captionExp != null) { return captionExp; } else if (captionColumn != null) { return new Column(table, captionColumn); } else { return null; } } public Expression getOrdinalExp() { if (ordinalExp != null) { return ordinalExp; } else if (ordinalColumn != null) { return new Column(table, ordinalColumn); } else { return null; } } public Expression getParentExp() { if (parentExp != null) { return parentExp; } else if (parentColumn != null) { return new Column(table, parentColumn); } else { return null; } } public Expression getPropertyExp(int i) { return new Column(table, properties[i].column); } public mondrian.spi.Dialect.Datatype getDatatype() { return mondrian.spi.Dialect.Datatype.valueOf(type); } Specifies the transitive closure of a parent-child hierarchy. Optional, but recommended for better performance. The closure is provided as a set of (parent/child) pairs: since it is the transitive closure these are actually (ancestor/descendant) pairs. Member property. Data type of this property: String, Numeric, Integer, Boolean, Date, Time or Timestamp. String Numeric Integer Boolean Date Time Timestamp

    Name of a formatter class for the appropriate property value being displayed.

    The class must implement the mondrian.spi.PropertyFormatter interface.

    This attribute is deprecated. Please use a nested PropertyFormatter element.

    A string being displayed instead of the name. Can be localized from Properties file using #{propertyname}. Description of this member property. Can be localized from Properties file using #{propertyname}. Should be set to true if the value of the property is functionally dependent on the level value. This permits the associated property column to be omitted from the GROUP BY clause (if the database permits columns in the SELECT that are not in the GROUP BY). This can be a significant performance enhancement on some databases, such as MySQL. Property formatter.
    Name of this measure. Column which is source of this measure's values. If not specified, a measure expression must be specified. The datatype of this measure: String, Numeric, Integer, Boolean, Date, Time or Timestamp.

    The default datatype of a measure is 'Integer' if the measure's aggregator is 'Count', otherwise it is 'Numeric'. String Numeric Integer Boolean Date Time Timestamp Format string with which to format cells of this measure. For more details, see the mondrian.util.Format class. Aggregation function. Allowed values are "sum", "count", "min", "max", "avg", and "distinct-count". ("distinct count" is allowed for backwards compatibility, but is deprecated because XML enumerated attributes in a DTD cannot legally contain spaces.) sum count min max avg distinct count distinct-count

    Name of a formatter class for the appropriate cell being displayed.

    The class must implement the mondrian.spi.CellFormatter interface.

    This attribute is deprecated. Please use a nested CellFormatter element.

    A string being displayed instead of the name. Can be localized from Properties file using #{propertyname}. Description of this measure. Can be localized from Properties file using #{propertyname}. Whether this member is visible in the user-interface. Default true. Contains values of user-defined properties. The SQL expression used to calculate a measure. Must be specified if a source column is not specified. Cell formatter.
    Name of this calculated member. Format string with which to format cells of this member. For more details, see {@link mondrian.util.Format}. A string being displayed instead of the name. Can be localized from Properties file using #{propertyname}. Description of this calculated member. Can be localized from Properties file using #{propertyname}. MDX expression which gives the value of this member. Equivalent to the Formula sub-element.

    Name of the dimension that this member belongs to.

    Deprecated: use {@code hierarchy} attribute instead.

    Name of the hierarchy that this member belongs to.

    Fully-qualified name of the parent member. If not specified, the member will be at the lowest level (besides the 'all' level) in the hierarchy. Whether this member is visible in the user-interface. Default true. Contains values of user-defined properties. MDX expression which gives the value of this member. Cell formatter. /** * Returns the formula, looking for a sub-element called * "Formula" first, then looking for an attribute called * "formula". */ public String getFormula() { if (formulaElement != null) { return formulaElement.cdata; } else { return formula; } }
    Property of a calculated member defined against a cube. It must have either an expression or a value. Name of this member property. A string being displayed instead of the name of this calculated member property. Can be localized from Properties file using #{propertyname}. Description of this calculated member property. Can be localized from Properties file using #{propertyname}. MDX expression which defines the value of this property. If the expression is a constant string, you could enclose it in quotes, or just specify the 'value' attribute instead. Value of this property. If the value is not constant, specify the 'expression' attribute instead. Defines a named set which can be used in queries in the same way as a set defined using a WITH SET clause.

    A named set can be defined against a particular cube, or can be global to a schema. If it is defined against a cube, it is only available to queries which use that cube.

    A named set defined against a cube is not inherited by a virtual cubes defined against that cube. (But you can define a named set against a virtual cube.)

    A named set defined against a schema is available in all cubes and virtual cubes in that schema. However, it is only valid if the cube contains dimensions with the names required to make the formula valid.

    ]]>
    Name of this named set. Caption of this named set. Can be localized from Properties file using #{propertyname}. Description of this named set. Can be localized from Properties file using #{propertyname}. MDX expression which gives the value of this set. Equivalent to the Formula sub-element. Contains values of user-defined properties. MDX expression which gives the value of this set. /** * Returns the formula, looking for a sub-element called * "Formula" first, then looking for an attribute called * "formula". */ public String getFormula() { if (formulaElement != null) { return formulaElement.cdata; } else { return formula; } }
    Not used A table or a join public abstract Relation find(String seekAlias); public boolean equals(Object o) { return this == o; } public int hashCode() { return System.identityHashCode(this); } A table, inline table or view public abstract String getAlias(); A collection of SQL statements, one per dialect. /** * Copy constructor. */ public View(View view) { this.alias = view.alias; this.selects = view.selects.clone(); } public String toString() { return selects[0].cdata; } public View find(String seekAlias) { if (seekAlias.equals(alias)) { return this; } else { return null; } } public String getAlias() { return alias; } public SqlQuery.CodeSet getCodeSet() { return SQL.toCodeSet(selects); } public void addCode(String dialect, String code) { if (selects == null) { selects = new SQL[1]; } else { SQL[] olds = selects; selects = new SQL[olds.length + 1]; System.arraycopy(olds, 0, selects, 0, olds.length); } SQL sql = new SQL(); sql.dialect = dialect; sql.cdata = code; selects[selects.length - 1] = sql; } public boolean equals(Object o) { if (o instanceof View) { View that = (View) o; if (!this.alias.equals(that.alias)) { return false; } if (this.selects == null || that.selects == null || this.selects.length != that.selects.length) { return false; } for (int i = 0; i < selects.length; i++) { if (!Util.equals(this.selects[i].dialect, that.selects[i].dialect) || !Util.equals(this.selects[i].cdata, that.selects[i].cdata)) { return false; } } return true; } else { return false; } } Dialect of SQL the view is intended for. Valid values include, but are not limited to:
    • generic
    • access
    • db2
    • derby
    • firebird
    • hsqldb
    • mssql
    • mysql
    • oracle
    • postgres
    • sybase
    • teradata
    • ingres
    • infobright
    • luciddb
    • vertica
    • neoview
    generic access db2 derby firebird hsqldb mssql mysql oracle postgres sybase teradata ingres infobright luciddb vertica neoview greenplum vectorwise hive
    Defaults to left's alias if left is a table, otherwise required. Defaults to right's alias if right is a table, otherwise required. /** Convenience constructor. */ public Join( String leftAlias, String leftKey, RelationOrJoin left, String rightAlias, String rightKey, RelationOrJoin right) { this.leftAlias = leftAlias; this.leftKey = leftKey; this.left = left; this.rightAlias = rightAlias; this.rightKey = rightKey; this.right = right; } /** * Returns the alias of the left join key, defaulting to left's * alias if left is a table. */ public String getLeftAlias() { if (leftAlias != null) { return leftAlias; } if (left instanceof Relation) { return ((Relation) left).getAlias(); } throw Util.newInternal( "alias is required because " + left + " is not a table"); } /** * Returns the alias of the right join key, defaulting to right's * alias if right is a table. */ public String getRightAlias() { if (rightAlias != null) { return rightAlias; } if (right instanceof Relation) { return ((Relation) right).getAlias(); } if (right instanceof Join) { return ((Join) right).getLeftAlias(); } throw Util.newInternal( "alias is required because " + right + " is not a table"); } public String toString() { return "(" + left + ") join (" + right + ") on " + leftAlias + "." + leftKey + " = " + rightAlias + "." + rightKey; } public Relation find(String seekAlias) { Relation relation = left.find(seekAlias); if (relation == null) { relation = right.find(seekAlias); } return relation; } public boolean equals(Object o) { return (this == o); } Optional qualifier for table. Alias to be used with this table when it is used to form queries. If not specified, defaults to the table name, but in any case, must be unique within the schema. (You can use the same table in different hierarchies, but it must have different aliases.) The SQL WHERE clause expression to be appended to any select statement Table optimization hints; may be ignored by dialect. hintMap; /** Convenience constructor. */ public Table(Table table) { this(table.schema, table.name, table.alias, table.tableHints); } public Table(String schema, String name, String alias, Hint[] tablehints) { this(); this.schema = schema; this.name = name; this.alias = alias; this.hintMap = buildHintMap(tablehints); } private java.util.Map buildHintMap(Hint[] th) { java.util.Map h = new java.util.HashMap(); if (th != null) { for (int i = 0; i < th.length; i++) { h.put(th[i].type, th[i].cdata); } } return h; } /** Returns the alias or, if it is null, the table name. */ public String getAlias() { return (alias != null) ? alias : name; } public String toString() { return (schema == null) ? name : schema + "." + name; } public Table find(String seekAlias) { return seekAlias.equals(name) ? this : (alias != null) && seekAlias.equals(alias) ? this : null; } public boolean equals(Object o) { if (true) return (this == o); // Following code is disabled to resolve problem of disappearing // schema elements from tree panel. if (o instanceof Table) { Table that = (Table) o; return this.name.equals(that.name) && Util.equals(this.alias, that.alias) && Util.equals(this.schema, that.schema); } else { return false; } } public int hashCode() { return toString().hashCode(); } public String getFilter() { return (filter == null) ? null : filter.cdata; } public AggExclude[] getAggExcludes() { return aggExcludes; } public AggTable[] getAggTables() { return aggTables; } public java.util.Map getHintMap() { if (hintMap == null) { hintMap = buildHintMap(this.tableHints); } return hintMap; } ]]> Dialect-specific table optimization hints. Type of hint, interpreted and applied on a per-dialect basis. Alias to be used with this table when it is used to form queries. If not specified, defaults to the table name, but in any case, must be unique within the schema. (You can use the same table in different hierarchies, but it must have different aliases.) "; } public InlineTable find(String seekAlias) { return seekAlias.equals(this.alias) ? this : null; } public boolean equals(Object o) { if (o instanceof InlineTable) { InlineTable that = (InlineTable) o; return this.alias.equals(that.alias); } else { return false; } } public int hashCode() { return toString().hashCode(); } ]]> Holder for an array of ColumnDef elements Column definition for an inline table. Name of the column. Type of the column: String, Numeric, Integer, Boolean, Date, Time or Timestamp. String Numeric Integer Boolean Date Time Timestamp Holder for an array of Row elements Row definition for an inline table. Must have one Column for each ColumnDef in the InlineTable. Column value for an inline table. The CDATA holds the value of the column. Name of the column. A definition of an aggregate table for a base fact table. This aggregate table must be in the same schema as the base fact table. Whether or not the match should ignore case. What does the fact_count column look like. public boolean isIgnoreCase() { return ignorecase.booleanValue(); } public AggFactCount getAggFactCount() { return factcount; } public AggIgnoreColumn[] getAggIgnoreColumns() { return ignoreColumns; } public AggForeignKey[] getAggForeignKeys() { return foreignKeys; } public AggMeasure[] getAggMeasures() { return measures; } public AggLevel[] getAggLevels() { return levels; } The Table name of a Specific aggregate table. The estimated number of rows in this aggregation table. Setting this property improves the performance of the aggregation optimizer and prevents it from issuing a 'select count(*)' query over the aggregation table. public String getNameAttribute() { return name; } public String getApproxRowCountAttribute() { return approxRowCount; } A Table pattern used to define a set of aggregate tables. public String getPattern() { return pattern; } public AggExclude[] getAggExcludes() { return excludes; } A Table pattern not to be matched. The Table name not to be matched. Whether or not the match should ignore case. public String getNameAttribute() { return name; } public String getPattern() { return pattern; } public boolean isIgnoreCase() { return ignorecase.booleanValue(); } The name of the fact count column. public String getColumnName() { return column; } The name of the column mapping from base fact table foreign key to aggregate table foreign key. The name of the base fact table foreign key. The name of the aggregate table foreign key. public String getFactFKColumnName() { return factColumn; } public String getAggregateFKColumnName() { return aggColumn; } The name of the column mapping to the level name. The name of the Dimension Hierarchy level. public String getNameAttribute() { return name; } public String getColumnName() { return column; } The name of the column mapping to the measure name. The name of the Cube measure. public String getNameAttribute() { return name; } public String getColumn() { return column; } public abstract String getExpression(SqlQuery query); public abstract String getGenericExpression(); public abstract String getTableAlias(); Alias of the table which contains this column. Not required if the query only has one table. Name of the column. A collection of SQL expressions, one per dialect. A role defines an access-control profile. It has a series of grants (or denials) for schema elements. Contains values of user-defined properties. Values correspond to Access. all custom none all_dimensions Grants (or denies) this role access to this schema. access may be "all", "all_dimensions", "custom" or "none". If access is "all_dimensions", the role has access to all dimensions but still needs explicit access to cubes. If access is "custom", no access will be inherited by cubes for which no explicit rule is set. If access is "all_dimensions", an implicut access is given to all dimensions of the schema's cubes, provided the cube's access attribute is either "custom" or "all". See mondrian.olap.Role#grant(mondrian.olap.Schema,int). Grants (or denies) this role access to a cube. access may be "all", "custom", or "none". If access is "custom", no access will be inherited by the dimensions of this cube, unless the parent SchemaGrant is set to "ALL_DIMENSIONS". See mondrian.olap.Role#grant(mondrian.olap.Cube,int). The unique name of the cube Grants (or denies) this role access to a dimension. access may be "all", "custom" or "none". Note that a role is implicitly given access to a dimension when it is given "ALL" acess to a cube. If access is "custom", no access will be inherited by the hierarchies of this dimension. If the parent schema access is "ALL_DIMENSIONS", this timension will inherit access "ALL". See also the "all_dimensions" option of the "SchemaGrant" element. See mondrian.olap.Role#grant(mondrian.olap.Dimension,int). The unique name of the dimension Grants (or denies) this role access to a hierarchy. access may be "all", "custom" or "none". If access is "custom", you may also specify the attributes topLevel, bottomLevel, and the member grants. If access is "custom", the child levels of this hierarchy will not inherit access rights from this hierarchy, should there be no explicit rules defined for the said child level. See mondrian.olap.Role#grant(mondrian.olap.Hierarchy, int, mondrian.olap.Level). The unique name of the hierarchy Unique name of the highest level of the hierarchy from which this role is allowed to see members. May only be specified if the HierarchyGrant.access is "custom". If not specified, role can see members up to the top level. Unique name of the lowest level of the hierarchy from which this role is allowed to see members. May only be specified if the HierarchyGrant.access is "custom". If not specified, role can see members down to the leaf level. Policy which determines how cell values are calculated if not all of the children of the current cell are visible to the current role. Allowable values are 'full' (the default), 'partial', and 'hidden'. Grants (or denies) this role access to a member. The children of this member inherit that access. You can implicitly see a member if you can see any of its children. See mondrian.olap.Role#grant(mondrian.olap.Member,int). The unique name of the member all none Body of a Role definition which defines a Role to be the union of several Roles. The RoleUsage elements must refer to Roles that have been declared earlier in this schema file. Usage of a Role in a union Role. A UserDefinedFunction is a function which extends the MDX language. It must be implemented by a Java class which implements the interface mondrian.spi.UserDefinedFunction. Name with which the user-defined function will be referenced in MDX expressions. Name of the class which implemenets this user-defined function. Must implement the mondrian.spi.UserDefinedFunction interface. Script to implement this user-defined function.

    Either the "Script" element or the "className" attribute must be specified.

    A Parameter defines a schema parameter. It can be referenced from an MDX statement using the ParamRef function and, if not final, its value can be overridden. Name of this parameter. Description of this parameter. Indicates the type of this parameter: String, Numeric, Integer, Boolean, Date, Time, Timestamp, or Member. String Numeric Integer Boolean Date Time Timestamp Member If false, statement cannot change the value of this parameter; the parameter becomes effectively constant (provided that its default value expression always returns the same value). Default is true. Expression for the default value of this parameter. Holder for an array of Annotation elements User-defined property value. Name of the annotation. Script fragment to implement an SPI such as user-defined function, member formatter, cell formatter. The language of the script. Must be a supported scripting language in the current JVM. See {@link javax.script.ScriptEngineManager}. Default value is 'JavaScript'. Plugin that formats the values of cells. It must be implemented by a Java class which implements the interface mondrian.spi.CellFormatter, or by a script. Name of the class which implemenets this cell formatter. Must implement the mondrian.spi.CellFormatter interface.

    Either the "Script" element or the "className" attribute must be specified.

    Script to implement this cell formatter.

    Either the "Script" element or the "className" attribute must be specified.

    Plugin that formats members. It must be implemented by a Java class which implements the interface mondrian.spi.MemberFormatter, or by a script. Name of the class which implemenets this member formatter. Must implement the mondrian.spi.MemberFormatter interface.

    Either the "Script" element or the "className" attribute must be specified.

    Script to implement this member formatter.

    Either the "Script" element or the "className" attribute must be specified.

    Plugin that formats properties. It must be implemented by a Java class which implements the interface mondrian.spi.PropertyFormatter, or by a script. Name of the class which implemenets this property formatter. Must implement the mondrian.spi.PropertyFormatter interface.

    Either the "Script" element or the "className" attribute must be specified.

    Script to implement this property formatter.

    Either the "Script" element or the "className" attribute must be specified.

    mondrian-3.4.1/src/main/mondrian/olap/LevelType.java0000644000175000017500000000536211735330606022265 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap; /** * Enumerates the types of levels. * * @deprecated Will be replaced with {@link org.olap4j.metadata.Level.Type} * before mondrian-4.0. * * @author jhyde * @since 5 April, 2004 */ public enum LevelType { /** Indicates that the level is not related to time. */ Regular, /** * Indicates that a level refers to years. * It must be used in a dimension whose type is * {@link DimensionType#TimeDimension}. */ TimeYears, /** * Indicates that a level refers to half years. * It must be used in a dimension whose type is * {@link DimensionType#TimeDimension}. */ TimeHalfYears, /** * Indicates that a level refers to quarters. * It must be used in a dimension whose type is * {@link DimensionType#TimeDimension}. */ TimeQuarters, /** * Indicates that a level refers to months. * It must be used in a dimension whose type is * {@link DimensionType#TimeDimension}. */ TimeMonths, /** * Indicates that a level refers to weeks. * It must be used in a dimension whose type is * {@link DimensionType#TimeDimension}. */ TimeWeeks, /** * Indicates that a level refers to days. * It must be used in a dimension whose type is * {@link DimensionType#TimeDimension}. */ TimeDays, /** * Indicates that a level refers to hours. * It must be used in a dimension whose type is * {@link DimensionType#TimeDimension}. */ TimeHours, /** * Indicates that a level refers to minutes. * It must be used in a dimension whose type is * {@link DimensionType#TimeDimension}. */ TimeMinutes, /** * Indicates that a level refers to seconds. * It must be used in a dimension whose type is * {@link DimensionType#TimeDimension}. */ TimeSeconds, /** * Indicates that a level is an unspecified time period. * It must be used in a dimension whose type is * {@link DimensionType#TimeDimension}. */ TimeUndefined, /** * Indicates that a level holds the null member. */ Null; /** * Returns whether this is a time level. * * @return Whether this is a time level. */ public boolean isTime() { return ordinal() >= TimeYears.ordinal() && ordinal() <= TimeUndefined.ordinal(); } } // End LevelType.java mondrian-3.4.1/src/main/mondrian/olap/NamedSet.java0000644000175000017500000000347611735330606022060 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2000-2005 Julian Hyde // Copyright (C) 2005-2006 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.olap.type.Type; /** * A named set of members or tuples. * *

    A set can be defined in a query, using a WITH SET clause, * or in a schema. Named sets in a schema can be defined against a particular * cube or virtual cube, or shared between all cubes.

    * * @author jhyde * @since 6 August, 2001 */ public interface NamedSet extends OlapElement, Annotated { /** * Sets the name of this named set. */ void setName(String newName); /** * Returns the type of this named set. */ Type getType(); /** * Returns the expression used to derive this named set. */ Exp getExp(); NamedSet validate(Validator validator); /** * Returns a name for this set that is unique within the query. * *

    This is necessary when there are several 'AS' expressions, or an 'AS' * expression overrides a named set defined using 'WITH MEMBER' clause or * against a cube. */ String getNameUniqueWithinQuery(); /** * Returns whether this named set is dynamic. * *

    Evaluation rules: *

      *
    • A dynamic set is evaluated each time it is used, and inherits the * context in which it is evaluated. *
    • A static set is evaluated only on first use, in the base context of * the cube. *
    * * @return Whether this named set is dynamic */ boolean isDynamic(); } // End NamedSet.java mondrian-3.4.1/src/main/mondrian/olap/fun/0000755000175000017500000000000011735330606020273 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/olap/fun/HierarchyDimensionFunDef.java0000644000175000017500000000330411735330606026012 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractDimensionCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the <Hierarchy>.Dimension MDX * builtin function. * * @author jhyde * @since Mar 23, 2006 */ public class HierarchyDimensionFunDef extends FunDefBase { static final HierarchyDimensionFunDef instance = new HierarchyDimensionFunDef(); private HierarchyDimensionFunDef() { super( "Dimension", "Returns the dimension that contains a specified hierarchy.", "pdh"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final HierarchyCalc hierarchyCalc = compiler.compileHierarchy(call.getArg(0)); return new CalcImpl(call, hierarchyCalc); } public static class CalcImpl extends AbstractDimensionCalc { private final HierarchyCalc hierarchyCalc; public CalcImpl(Exp exp, HierarchyCalc hierarchyCalc) { super(exp, new Calc[] {hierarchyCalc}); this.hierarchyCalc = hierarchyCalc; } public Dimension evaluateDimension(Evaluator evaluator) { Hierarchy hierarchy = hierarchyCalc.evaluateHierarchy(evaluator); return hierarchy.getDimension(); } } } // End HierarchyDimensionFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/NonEmptyCrossJoinFunDef.java0000644000175000017500000001101011735330606025622 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // Copyright (C) 2004-2005 SAS Institute, Inc. // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.rolap.RolapEvaluator; /** * Definition of the NonEmptyCrossJoin MDX function. * * @author jhyde * @since Mar 23, 2006 * * author 16 December, 2004 */ public class NonEmptyCrossJoinFunDef extends CrossJoinFunDef { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "NonEmptyCrossJoin", "NonEmptyCrossJoin(, )", "Returns the cross product of two sets, excluding empty tuples and tuples without associated fact table data.", new String[]{"fxxx"}, NonEmptyCrossJoinFunDef.class); public NonEmptyCrossJoinFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(final ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc1 = compiler.compileList(call.getArg(0)); final ListCalc listCalc2 = compiler.compileList(call.getArg(1)); return new AbstractListCalc( call, new Calc[] {listCalc1, listCalc2}, false) { public TupleList evaluateList(Evaluator evaluator) { SchemaReader schemaReader = evaluator.getSchemaReader(); // Evaluate the arguments in non empty mode, but remove from // the slicer any members that will be overridden by args to // the NonEmptyCrossjoin function. For example, in // // SELECT NonEmptyCrossJoin( // [Store].[USA].Children, // [Product].[Beer].Children) // FROM [Sales] // WHERE [Store].[Mexico] // // we want all beers, not just those sold in Mexico. final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(true); for (Member member : ((RolapEvaluator) evaluator).getSlicerMembers()) { if (getType().getElementType().usesHierarchy( member.getHierarchy(), true)) { evaluator.setContext( member.getHierarchy().getAllMember()); } } NativeEvaluator nativeEvaluator = schemaReader.getNativeSetEvaluator( call.getFunDef(), call.getArgs(), evaluator, this); if (nativeEvaluator != null) { evaluator.restore(savepoint); return (TupleList) nativeEvaluator.execute(ResultStyle.LIST); } final TupleList list1 = listCalc1.evaluateList(evaluator); if (list1.isEmpty()) { evaluator.restore(savepoint); return list1; } final TupleList list2 = listCalc2.evaluateList(evaluator); TupleList result = mutableCrossJoin(list1, list2); // remove any remaining empty crossings from the result result = nonEmptyList(evaluator, result, call); evaluator.restore(savepoint); return result; } public boolean dependsOn(Hierarchy hierarchy) { if (super.dependsOn(hierarchy)) { return true; } // Member calculations generate members, which mask the actual // expression from the inherited context. if (listCalc1.getType().usesHierarchy(hierarchy, true)) { return false; } if (listCalc2.getType().usesHierarchy(hierarchy, true)) { return false; } // The implicit value expression, executed to figure out // whether a given tuple is empty, depends upon all dimensions. return true; } }; } } // End NonEmptyCrossJoinFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/LinReg.java0000644000175000017500000004602211735330606022322 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractDoubleCalc; import mondrian.calc.impl.ValueCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.util.*; /** * Abstract base class for definitions of linear regression functions. * * @see InterceptFunDef * @see PointFunDef * @see R2FunDef * @see SlopeFunDef * @see VarianceFunDef * *

    Correlation coefficient

    *

    Correlation coefficient

    * *

    The correlation coefficient, r, ranges from -1 to + 1. The * nonparametric Spearman correlation coefficient, abbreviated rs, has * the same range.

    * *

    * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    Value of r (or rs)Interpretation
    r= 0The two variables do not vary together at all.
    0 > r > 1 *

    The two variables tend to increase or decrease together.

    *
    r = 1.0 *

    Perfect correlation.

    *
    -1 > r > 0 *

    One variable increases as the other decreases.

    *
    r = -1.0 *

    *

    Perfect negative or inverse correlation.

    *
    * *

    If r or rs is far from zero, there are four possible explanations:

    *

    The X variable helps determine the value of the Y variable.

    *
      *
    • The Y variable helps determine the value of the X variable. *
    • Another variable influences both X and Y. *
    • X and Y don't really correlate at all, and you just * happened to observe such a strong correlation by chance. The P value * determines how often this could occur. *
    *

    r2

    * *

    Perhaps the best way to interpret the value of r is to square it to * calculate r2. Statisticians call this quantity the coefficient of * determination, but scientists call it r squared. It is has a value * that ranges from zero to one, and is the fraction of the variance in * the two variables that is shared. For example, if r2=0.59, then 59% of * the variance in X can be explained by variation in Y.  Likewise, * 59% of the variance in Y can be explained by (or goes along with) * variation in X. More simply, 59% of the variance is shared between X * and Y.

    * *

    (Source). * *

    Also see: least squares fitting. */ public abstract class LinReg extends FunDefBase { /** Code for the specific function. */ final int regType; public static final int Point = 0; public static final int R2 = 1; public static final int Intercept = 2; public static final int Slope = 3; public static final int Variance = 4; static final Resolver InterceptResolver = new ReflectiveMultiResolver( "LinRegIntercept", "LinRegIntercept(, [, ])", "Calculates the linear regression of a set and returns the value of b in the regression line y = ax + b.", new String[]{"fnxn", "fnxnn"}, InterceptFunDef.class); static final Resolver PointResolver = new ReflectiveMultiResolver( "LinRegPoint", "LinRegPoint(, , [, ])", "Calculates the linear regression of a set and returns the value of y in the regression line y = ax + b.", new String[]{"fnnxn", "fnnxnn"}, PointFunDef.class); static final Resolver SlopeResolver = new ReflectiveMultiResolver( "LinRegSlope", "LinRegSlope(, [, ])", "Calculates the linear regression of a set and returns the value of a in the regression line y = ax + b.", new String[]{"fnxn", "fnxnn"}, SlopeFunDef.class); static final Resolver R2Resolver = new ReflectiveMultiResolver( "LinRegR2", "LinRegR2(, [, ])", "Calculates the linear regression of a set and returns R2 (the coefficient of determination).", new String[]{"fnxn", "fnxnn"}, R2FunDef.class); static final Resolver VarianceResolver = new ReflectiveMultiResolver( "LinRegVariance", "LinRegVariance(, [, ])", "Calculates the linear regression of a set and returns the variance associated with the regression line y = ax + b.", new String[]{"fnxn", "fnxnn"}, VarianceFunDef.class); public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final DoubleCalc yCalc = compiler.compileDouble(call.getArg(1)); final DoubleCalc xCalc = call.getArgCount() > 2 ? compiler.compileDouble(call.getArg(2)) : new ValueCalc(call); return new LinRegCalc(call, listCalc, yCalc, xCalc, regType); } ///////////////////////////////////////////////////////////////////////// // // Helper // ///////////////////////////////////////////////////////////////////////// static class Value { private List xs; private List ys; /** * The intercept for the linear regression model. Initialized * following a call to accuracy. */ double intercept; /** * The slope for the linear regression model. Initialized following a * call to accuracy. */ double slope; /** the coefficient of determination */ double rSquared = Double.MAX_VALUE; /** variance = sum square diff mean / n - 1 */ double variance = Double.MAX_VALUE; Value(double intercept, double slope, List xs, List ys) { this.intercept = intercept; this.slope = slope; this.xs = xs; this.ys = ys; } public double getIntercept() { return this.intercept; } public double getSlope() { return this.slope; } public double getRSquared() { return this.rSquared; } /** * strength of the correlation * * @param rSquared Strength of the correlation */ public void setRSquared(double rSquared) { this.rSquared = rSquared; } public double getVariance() { return this.variance; } public void setVariance(double variance) { this.variance = variance; } public String toString() { return "LinReg.Value: slope of " + slope + " and an intercept of " + intercept + ". That is, y=" + intercept + (slope > 0.0 ? " +" : " ") + slope + " * x."; } } /** * Definition of the LinRegIntercept MDX function. * *

    Synopsis: * *

    LinRegIntercept(<Numeric Expression>, * <Set>, <Numeric Expression>[, <Numeric * Expression>])
    */ public static class InterceptFunDef extends LinReg { public InterceptFunDef(FunDef funDef) { super(funDef, Intercept); } } /** * Definition of the LinRegPoint MDX function. * *

    Synopsis: * *

    LinRegPoint(<Numeric Expression>, * <Set>, <Numeric Expression>[, <Numeric * Expression>])
    */ public static class PointFunDef extends LinReg { public PointFunDef(FunDef funDef) { super(funDef, Point); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final DoubleCalc xPointCalc = compiler.compileDouble(call.getArg(0)); final ListCalc listCalc = compiler.compileList(call.getArg(1)); final DoubleCalc yCalc = compiler.compileDouble(call.getArg(2)); final DoubleCalc xCalc = call.getArgCount() > 3 ? compiler.compileDouble(call.getArg(3)) : new ValueCalc(call); return new PointCalc( call, xPointCalc, listCalc, yCalc, xCalc); } } private static class PointCalc extends AbstractDoubleCalc { private final DoubleCalc xPointCalc; private final ListCalc listCalc; private final DoubleCalc yCalc; private final DoubleCalc xCalc; public PointCalc( ResolvedFunCall call, DoubleCalc xPointCalc, ListCalc listCalc, DoubleCalc yCalc, DoubleCalc xCalc) { super(call, new Calc[]{xPointCalc, listCalc, yCalc, xCalc}); this.xPointCalc = xPointCalc; this.listCalc = listCalc; this.yCalc = yCalc; this.xCalc = xCalc; } public double evaluateDouble(Evaluator evaluator) { double xPoint = xPointCalc.evaluateDouble(evaluator); Value value = process(evaluator, listCalc, yCalc, xCalc); if (value == null) { return FunUtil.DoubleNull; } // use first arg to generate y position double yPoint = xPoint * value.getSlope() + value.getIntercept(); return yPoint; } } /** * Definition of the LinRegSlope MDX function. * *

    Synopsis: * *

    LinRegSlope(<Numeric Expression>, * <Set>, <Numeric Expression>[, <Numeric * Expression>])
    */ public static class SlopeFunDef extends LinReg { public SlopeFunDef(FunDef funDef) { super(funDef, Slope); } } /** * Definition of the LinRegR2 MDX function. * *

    Synopsis: * *

    LinRegR2(<Numeric Expression>, * <Set>, <Numeric Expression>[, <Numeric * Expression>])
    */ public static class R2FunDef extends LinReg { public R2FunDef(FunDef funDef) { super(funDef, R2); } } /** * Definition of the LinRegVariance MDX function. * *

    Synopsis: * *

    LinRegVariance(<Numeric Expression>, * <Set>, <Numeric Expression>[, <Numeric * Expression>])
    */ public static class VarianceFunDef extends LinReg { public VarianceFunDef(FunDef funDef) { super(funDef, Variance); } } protected static void debug(String type, String msg) { // comment out for no output // RME //System.out.println(type + ": " +msg); } protected LinReg(FunDef funDef, int regType) { super(funDef); this.regType = regType; } protected static LinReg.Value process( Evaluator evaluator, ListCalc listCalc, DoubleCalc yCalc, DoubleCalc xCalc) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); TupleList members = listCalc.evaluateList(evaluator); evaluator.restore(savepoint); SetWrapper[] sws = evaluateSet( evaluator, members, new DoubleCalc[] {yCalc, xCalc}); evaluator.restore(savepoint); SetWrapper swY = sws[0]; SetWrapper swX = sws[1]; if (swY.errorCount > 0) { debug("LinReg.process", "ERROR error(s) count =" + swY.errorCount); // TODO: throw exception return null; } else if (swY.v.size() == 0) { return null; } return linearReg(swX.v, swY.v); } public static LinReg.Value accuracy(LinReg.Value value) { // for variance double sumErrSquared = 0.0; double sumErr = 0.0; // for r2 // data double sumSquaredY = 0.0; double sumY = 0.0; // predicted double sumSquaredYF = 0.0; double sumYF = 0.0; // Obtain the forecast values for this model List yfs = forecast(value); // Calculate the Sum of the Absolute Errors Iterator ity = value.ys.iterator(); Iterator ityf = yfs.iterator(); while (ity.hasNext()) { // Get next data point Double dy = (Double) ity.next(); if (dy == null) { continue; } Double dyf = (Double) ityf.next(); if (dyf == null) { continue; } double y = dy.doubleValue(); double yf = dyf.doubleValue(); // Calculate error in forecast, and update sums appropriately // the y residual or error double error = yf - y; sumErr += error; sumErrSquared += error * error; sumY += y; sumSquaredY += (y * y); sumYF =+ yf; sumSquaredYF =+ (yf * yf); } // Initialize the accuracy indicators int n = value.ys.size(); // Variance // The estimate the value of the error variance is a measure of // variability of the y values about the estimated line. // http://home.ubalt.edu/ntsbarsh/Business-stat/opre504.htm // s2 = SSE/(n-2) = sum (y - yf)2 /(n-2) if (n > 2) { double variance = sumErrSquared / (n - 2); value.setVariance(variance); } // R2 // R2 = 1 - (SSE/SST) // SSE = sum square error = Sum((error-MSE)*(error-MSE)) // MSE = mean error = Sum(error)/n // SST = sum square y diff = Sum((y-MST)*(y-MST)) // MST = mean y = Sum(y)/n double MSE = sumErr / n; double MST = sumY / n; double SSE = 0.0; double SST = 0.0; ity = value.ys.iterator(); ityf = yfs.iterator(); while (ity.hasNext()) { // Get next data point Double dy = (Double) ity.next(); if (dy == null) { continue; } Double dyf = (Double) ityf.next(); if (dyf == null) { continue; } double y = dy.doubleValue(); double yf = dyf.doubleValue(); double error = yf - y; SSE += (error - MSE) * (error - MSE); SST += (y - MST) * (y - MST); } if (SST != 0.0) { double rSquared = 1 - (SSE / SST); value.setRSquared(rSquared); } return value; } public static LinReg.Value linearReg(List xlist, List ylist) { // y and x have same number of points int size = ylist.size(); double sumX = 0.0; double sumY = 0.0; double sumXX = 0.0; double sumXY = 0.0; debug("LinReg.linearReg", "ylist.size()=" + ylist.size()); debug("LinReg.linearReg", "xlist.size()=" + xlist.size()); int n = 0; for (int i = 0; i < size; i++) { Object yo = ylist.get(i); Object xo = xlist.get(i); if ((yo == null) || (xo == null)) { continue; } n++; double y = ((Double) yo).doubleValue(); double x = ((Double) xo).doubleValue(); debug("LinReg.linearReg", " " + i + " (" + x + "," + y + ")"); sumX += x; sumY += y; sumXX += x * x; sumXY += x * y; } double xMean = sumX / n; double yMean = sumY / n; debug("LinReg.linearReg", "yMean=" + yMean); debug( "LinReg.linearReg", "(n*sumXX - sumX*sumX)=" + (n * sumXX - sumX * sumX)); // The regression line is the line that minimizes the variance of the // errors. The mean error is zero; so, this means that it minimizes the // sum of the squares errors. double slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX); double intercept = yMean - slope * xMean; LinReg.Value value = new LinReg.Value(intercept, slope, xlist, ylist); debug("LinReg.linearReg", "value=" + value); return value; } public static List forecast(LinReg.Value value) { List yfs = new ArrayList(value.xs.size()); Iterator it = value.xs.iterator(); while (it.hasNext()) { Double d = (Double) it.next(); // If the value is missing we still must put a place // holder in the y axis, otherwise there is a discontinuity // between the data and the fit. if (d == null) { yfs.add(null); } else { double x = d.doubleValue(); double yf = value.intercept + value.slope * x; yfs.add(new Double(yf)); } } return yfs; } private static class LinRegCalc extends AbstractDoubleCalc { private final ListCalc listCalc; private final DoubleCalc yCalc; private final DoubleCalc xCalc; private final int regType; public LinRegCalc( ResolvedFunCall call, ListCalc listCalc, DoubleCalc yCalc, DoubleCalc xCalc, int regType) { super(call, new Calc[]{listCalc, yCalc, xCalc}); this.listCalc = listCalc; this.yCalc = yCalc; this.xCalc = xCalc; this.regType = regType; } public double evaluateDouble(Evaluator evaluator) { Value value = process(evaluator, listCalc, yCalc, xCalc); if (value == null) { return FunUtil.DoubleNull; } switch (regType) { case Intercept: return value.getIntercept(); case Slope: return value.getSlope(); case Variance: return value.getVariance(); case R2: return value.getRSquared(); default: case Point: throw Util.newInternal("unexpected value " + regType); } } } } // End LinReg.java mondrian-3.4.1/src/main/mondrian/olap/fun/AbstractAggregateFunDef.java0000644000175000017500000002077511735330606025613 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.DelegatingTupleList; import mondrian.mdx.UnresolvedFunCall; import mondrian.olap.*; import mondrian.resource.MondrianResource; import mondrian.rolap.RolapCube; import mondrian.rolap.RolapMember; import java.util.*; /** * Abstract base class for all aggregate functions (Aggregate, * Sum, Avg, et cetera). * * @author jhyde * @since 2005/8/14 */ public class AbstractAggregateFunDef extends FunDefBase { public AbstractAggregateFunDef(FunDef dummyFunDef) { super(dummyFunDef); } protected Exp validateArg( Validator validator, Exp[] args, int i, int category) { // If expression cache is enabled, wrap first expression (the set) // in a function which will use the expression cache. if (i == 0) { if (MondrianProperties.instance().EnableExpCache.get()) { Exp arg = args[0]; if (FunUtil.worthCaching(arg)) { final Exp cacheCall = new UnresolvedFunCall( CacheFunDef.NAME, Syntax.Function, new Exp[] {arg}); return validator.validate(cacheCall, false); } } } return super.validateArg(validator, args, i, category); } /** * Evaluates the list of members or tuples used in computing the aggregate. * If the measure for aggregation has to ignore unrelated dimensions * this method will push unrelated dimension members to top level member. * This behaviour is driven by the ignoreUnrelatedDimensions property * on a base cube usage specified in the virtual cube.Keeps track of the * number of iterations that will be required to iterate over the members * or tuples needed to compute the aggregate within the current context. * In doing so, also determines if the cross product of all iterations * across all parent evaluation contexts will exceed the limit set in the * properties file. * * @param listCalc calculator used to evaluate the member list * @param evaluator current evaluation context * @return list of evaluated members or tuples */ protected static TupleList evaluateCurrentList( ListCalc listCalc, Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); TupleList tuples = listCalc.evaluateList(evaluator); evaluator.restore(savepoint); int currLen = tuples.size(); crossProd(evaluator, currLen); return processUnrelatedDimensions(tuples, evaluator); } protected TupleIterable evaluateCurrentIterable( IterCalc iterCalc, Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); TupleIterable iterable = iterCalc.evaluateIterable(evaluator); int currLen = 0; crossProd(evaluator, currLen); evaluator.restore(savepoint); return iterable; } private static void crossProd(Evaluator evaluator, int currLen) { long iterationLimit = MondrianProperties.instance().IterationLimit.get(); final int productLen = currLen * evaluator.getIterationLength(); if (iterationLimit > 0) { if (productLen > iterationLimit) { throw MondrianResource.instance() .IterationLimitExceeded.ex(iterationLimit); } } evaluator.setIterationLength(currLen); } /** * Pushes unrelated dimensions to the top level member from the given list * of tuples if the ignoreUnrelatedDimensions property is set on the base * cube usage in the virtual cube. * *

    If IgnoreMeasureForNonJoiningDimension is set to true and * ignoreUnrelatedDimensions on CubeUsage is set to false then if a non * joining dimension exists in the aggregation list then return an empty * list else return the original list. * * @param tuplesForAggregation is a list of members or tuples used in * computing the aggregate * @param evaluator Evaluator * @return list of members or tuples */ private static TupleList processUnrelatedDimensions( TupleList tuplesForAggregation, Evaluator evaluator) { if (tuplesForAggregation.size() == 0) { return tuplesForAggregation; } RolapMember measure = (RolapMember) evaluator.getMembers()[0]; if (measure.isCalculated()) { return tuplesForAggregation; } RolapCube virtualCube = (RolapCube) evaluator.getCube(); RolapCube baseCube = (RolapCube) evaluator.getMeasureCube(); if (virtualCube.isVirtual() && baseCube != null) { if (virtualCube.shouldIgnoreUnrelatedDimensions(baseCube.getName())) { return ignoreUnrelatedDimensions( tuplesForAggregation, baseCube); } else if (MondrianProperties.instance() .IgnoreMeasureForNonJoiningDimension.get()) { return ignoreMeasureForNonJoiningDimension( tuplesForAggregation, baseCube); } } return tuplesForAggregation; } /** * If a non joining dimension exists in the aggregation list then return * an empty list else return the original list. * * @param tuplesForAggregation is a list of members or tuples used in * computing the aggregate * @param baseCube * @return list of members or tuples */ private static TupleList ignoreMeasureForNonJoiningDimension( TupleList tuplesForAggregation, RolapCube baseCube) { Set nonJoiningDimensions = nonJoiningDimensions(baseCube, tuplesForAggregation); if (nonJoiningDimensions.size() > 0) { return TupleCollections.emptyList(tuplesForAggregation.getArity()); } return tuplesForAggregation; } /** * Pushes unrelated dimensions to the top level member from the given list * of tuples if the ignoreUnrelatedDimensions property is set on the base * cube usage in the virtual cube. * * @param tuplesForAggregation is a list of members or tuples used in * computing the aggregate * @return list of members or tuples */ private static TupleList ignoreUnrelatedDimensions( TupleList tuplesForAggregation, RolapCube baseCube) { Set nonJoiningDimensions = nonJoiningDimensions(baseCube, tuplesForAggregation); final Set> processedTuples = new LinkedHashSet>(tuplesForAggregation.size()); for (List tuple : tuplesForAggregation) { List tupleCopy = tuple; for (int j = 0; j < tuple.size(); j++) { final Member member = tuple.get(j); if (nonJoiningDimensions.contains(member.getDimension())) { if (tupleCopy == tuple) { // Avoid making a copy until we have to change a tuple. tupleCopy = new ArrayList(tuple); } final Hierarchy hierarchy = member.getDimension().getHierarchy(); if (hierarchy.hasAll()) { tupleCopy.set(j, hierarchy.getAllMember()); } else { tupleCopy.set(j, hierarchy.getDefaultMember()); } } } processedTuples.add(tupleCopy); } return new DelegatingTupleList( tuplesForAggregation.getArity(), new ArrayList>( processedTuples)); } private static Set nonJoiningDimensions( RolapCube baseCube, TupleList tuplesForAggregation) { List tuple = tuplesForAggregation.get(0); return baseCube.nonJoiningDimensions( tuple.toArray(new Member[tuple.size()])); } } // End AbstractAggregateFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/DimensionDimensionFunDef.java0000644000175000017500000000252511735330606026025 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.Calc; import mondrian.calc.ExpCompiler; import mondrian.calc.impl.ConstantCalc; import mondrian.mdx.DimensionExpr; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.Dimension; import mondrian.olap.type.DimensionType; /** * Definition of the <Dimension>.Dimension * MDX builtin function. * * @author jhyde * @since Jul 20, 2009 */ class DimensionDimensionFunDef extends FunDefBase { public static final FunDefBase INSTANCE = new DimensionDimensionFunDef(); private DimensionDimensionFunDef() { super( "Dimension", "Returns the dimension that contains a specified hierarchy.", "pdd"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { Dimension dimension = ((DimensionExpr) call.getArg(0)).getDimension(); return new ConstantCalc( DimensionType.forDimension(dimension), dimension); } } // End DimensionDimensionFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/extra/0000755000175000017500000000000011735330606021416 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/olap/fun/extra/package.html0000644000175000017500000000026511735330606023702 0ustar drazzibdrazzib Defines MDX extension functions.

    These are not in the MDX standard, but are part of Mondrian because we consider them to be generally useful.

    mondrian-3.4.1/src/main/mondrian/olap/fun/extra/NthQuartileFunDef.java0000644000175000017500000000523611735330606025617 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun.extra; import mondrian.calc.*; import mondrian.calc.impl.AbstractDoubleCalc; import mondrian.calc.impl.ValueCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.fun.*; /** * Definition of the FirstQ and ThirdQ MDX extension * functions. * *

    These functions are not standard MDX. * * @author jhyde * @since Mar 23, 2006 */ public class NthQuartileFunDef extends AbstractAggregateFunDef { private final int range; public static final MultiResolver ThirdQResolver = new ReflectiveMultiResolver( "ThirdQ", "ThirdQ([, ])", "Returns the 3rd quartile value of a numeric expression evaluated over a set.", new String[]{"fnx", "fnxn"}, NthQuartileFunDef.class); public static final MultiResolver FirstQResolver = new ReflectiveMultiResolver( "FirstQ", "FirstQ([, ])", "Returns the 1st quartile value of a numeric expression evaluated over a set.", new String[]{"fnx", "fnxn"}, NthQuartileFunDef.class); public NthQuartileFunDef(FunDef dummyFunDef) { super(dummyFunDef); this.range = dummyFunDef.getName().equals("FirstQ") ? 1 : 3; } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final DoubleCalc doubleCalc = call.getArgCount() > 1 ? compiler.compileDouble(call.getArg(1)) : new ValueCalc(call); return new AbstractDoubleCalc(call, new Calc[] {listCalc, doubleCalc}) { public double evaluateDouble(Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); TupleList members = evaluateCurrentList(listCalc, evaluator); final double quartile = quartile( evaluator, members, doubleCalc, range); evaluator.restore(savepoint); return quartile; } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } }; } } // End NthQuartileFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/extra/CalculatedChildFunDef.java0000644000175000017500000000517711735330606026370 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.fun.extra; import mondrian.calc.*; import mondrian.calc.impl.AbstractMemberCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.fun.FunDefBase; import java.util.List; /** * Definition of the CalculatedChild MDX function. * *

    Syntax: *

    <Member> * CalculatedChild(<String>)
    * * @author bchow * @since 2006/4/12 */ public class CalculatedChildFunDef extends FunDefBase { public static final CalculatedChildFunDef instance = new CalculatedChildFunDef(); CalculatedChildFunDef() { super( "CalculatedChild", "Returns an existing calculated child member with name from the specified .", "mmmS"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); final StringCalc stringCalc = compiler.compileString(call.getArg(1)); return new AbstractMemberCalc( call, new Calc[] {memberCalc, stringCalc}) { public Member evaluateMember(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); String name = stringCalc.evaluateString(evaluator); return getCalculatedChild(member, name, evaluator); } }; } private Member getCalculatedChild( Member parent, String childName, Evaluator evaluator) { final SchemaReader schemaReader = evaluator.getQuery().getSchemaReader(true); Level childLevel = parent.getLevel().getChildLevel(); if (childLevel == null) { return parent.getHierarchy().getNullMember(); } List calcMemberList = schemaReader.getCalculatedMembers(childLevel); for (Member child : calcMemberList) { // the parent check is required in case there are parallel children // with the same names if (child.getParentMember().equals(parent) && child.getName().equals(childName)) { return child; } } return parent.getHierarchy().getNullMember(); } } // End CalculatedChildFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/OrderFunDef.java0000644000175000017500000004046011735330606023305 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.util.*; /** * Definition of the Order MDX function. * * @author jhyde * @since Mar 23, 2006 */ class OrderFunDef extends FunDefBase { static final ResolverImpl Resolver = new ResolverImpl(); public OrderFunDef(ResolverBase resolverBase, int type, int[] types) { super(resolverBase, type, types); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final IterCalc listCalc = compiler.compileIter(call.getArg(0)); List keySpecList = new ArrayList(); buildKeySpecList(keySpecList, call, compiler); final int keySpecCount = keySpecList.size(); Calc[] calcList = new Calc[keySpecCount + 1]; // +1 for the listCalc calcList[0] = listCalc; assert keySpecCount >= 1; final Calc expCalc = keySpecList.get(0).getKey(); calcList[1] = expCalc; if (keySpecCount == 1) { if (expCalc.isWrapperFor(MemberValueCalc.class) || expCalc.isWrapperFor(MemberArrayValueCalc.class)) { List constantList = new ArrayList(); List variableList = new ArrayList(); final MemberCalc[] calcs = (MemberCalc[]) ((AbstractCalc) expCalc).getCalcs(); for (MemberCalc memberCalc : calcs) { if (memberCalc.isWrapperFor(ConstantCalc.class) && !listCalc.dependsOn( memberCalc.getType().getHierarchy())) { constantList.add(memberCalc); } else { variableList.add(memberCalc); } } if (constantList.isEmpty()) { // All members are non-constant -- cannot optimize } else if (variableList.isEmpty()) { // All members are constant. Optimize by setting entire // context first. calcList[1] = new ValueCalc( new DummyExp(expCalc.getType())); return new ContextCalc( calcs, new CalcImpl( call, calcList, keySpecList)); } else { // Some members are constant. Evaluate these before // evaluating the list expression. calcList[1] = MemberValueCalc.create( new DummyExp(expCalc.getType()), variableList.toArray( new MemberCalc[variableList.size()]), compiler.getEvaluator() .mightReturnNullForUnrelatedDimension()); return new ContextCalc( constantList.toArray( new MemberCalc[constantList.size()]), new CalcImpl( call, calcList, keySpecList)); } } } for (int i = 1; i < keySpecCount; i++) { final Calc expCalcs = keySpecList.get(i).getKey(); calcList[i + 1] = expCalcs; } return new CalcImpl(call, calcList, keySpecList); } private void buildKeySpecList( List keySpecList, ResolvedFunCall call, ExpCompiler compiler) { final int argCount = call.getArgs().length; int j = 1; // args[0] is the input set Calc key; Flag dir; Exp arg; while (j < argCount) { arg = call.getArg(j); key = compiler.compileScalar(arg, true); j++; if ((j >= argCount) || (call.getArg(j).getCategory() != Category.Symbol)) { dir = Flag.ASC; } else { dir = getLiteralArg(call, j, Flag.ASC, Flag.class); j++; } keySpecList.add(new SortKeySpec(key, dir)); } } private interface CalcWithDual extends Calc { public TupleList evaluateDual( Evaluator rootEvaluator, Evaluator subEvaluator); } private static class CalcImpl extends AbstractListCalc implements CalcWithDual { private final IterCalc iterCalc; private final Calc sortKeyCalc; private final List keySpecList; private final int originalKeySpecCount; private final int arity; public CalcImpl( ResolvedFunCall call, Calc[] calcList, List keySpecList) { super(call, calcList); // assert iterCalc.getResultStyle() == ResultStyle.MUTABLE_LIST; this.iterCalc = (IterCalc) calcList[0]; this.sortKeyCalc = calcList[1]; this.keySpecList = keySpecList; this.originalKeySpecCount = keySpecList.size(); this.arity = getType().getArity(); } public TupleList evaluateDual( Evaluator rootEvaluator, Evaluator subEvaluator) { assert originalKeySpecCount == 1; final TupleIterable iterable = iterCalc.evaluateIterable(rootEvaluator); // REVIEW: If iterable happens to be a list, we'd like to pass it, // but we cannot yet guarantee that it is mutable. final TupleList list = iterable instanceof TupleList && false ? (TupleList) iterable : null; Util.discard(iterCalc.getResultStyle()); Flag sortKeyDir = keySpecList.get(0).getDirection(); final TupleList tupleList; final int savepoint = subEvaluator.savepoint(); subEvaluator.setNonEmpty(false); if (arity == 1) { tupleList = new UnaryTupleList( sortMembers( subEvaluator, iterable.slice(0), list == null ? null : list.slice(0), sortKeyCalc, sortKeyDir.descending, sortKeyDir.brk)); } else { tupleList = sortTuples( subEvaluator, iterable, list, sortKeyCalc, sortKeyDir.descending, sortKeyDir.brk, arity); } subEvaluator.restore(savepoint); return tupleList; } public TupleList evaluateList(Evaluator evaluator) { final TupleIterable iterable = iterCalc.evaluateIterable(evaluator); // REVIEW: If iterable happens to be a list, we'd like to pass it, // but we cannot yet guarantee that it is mutable. final TupleList list = iterable instanceof TupleList && false ? (TupleList) iterable : null; // go by size of keySpecList before purging if (originalKeySpecCount == 1) { Flag sortKeyDir = keySpecList.get(0).getDirection(); final TupleList tupleList; final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); if (arity == 1) { tupleList = new UnaryTupleList( sortMembers( evaluator, iterable.slice(0), list == null ? null : list.slice(0), sortKeyCalc, sortKeyDir.descending, sortKeyDir.brk)); } else { tupleList = sortTuples( evaluator, iterable, list, sortKeyCalc, sortKeyDir.descending, sortKeyDir.brk, arity); } evaluator.restore(savepoint); return tupleList; } else { purgeKeySpecList(keySpecList, list); if (keySpecList.isEmpty()) { return list; } final TupleList tupleList; final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); if (arity == 1) { tupleList = new UnaryTupleList( sortMembers( evaluator, iterable.slice(0), list == null ? null : list.slice(0), keySpecList)); } else { tupleList = sortTuples( evaluator, iterable, list, keySpecList, arity); } evaluator.restore(savepoint); return tupleList; } } public void collectArguments(Map arguments) { super.collectArguments(arguments); // only good for original Order syntax assert originalKeySpecCount == 1; Flag sortKeyDir = keySpecList.get(0).getDirection(); arguments.put( "direction", (sortKeyDir.descending ? (sortKeyDir.brk ? Flag.BDESC : Flag.DESC) : (sortKeyDir.brk ? Flag.BASC : Flag.ASC))); } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } private void purgeKeySpecList( List keySpecList, TupleList list) { if (list == null || list.isEmpty()) { return; } if (keySpecList.size() == 1) { return; } List listHierarchies = new ArrayList(list.getArity()); for (Member member : list.get(0)) { listHierarchies.add(member.getHierarchy()); } // do not sort (remove sort key spec from the list) if // 1. evaluates to a member from a // level/dimension which is not used in the first argument // 2. evaluates to the same member for // all cells; for example, a report showing all quarters of // year 1998 will not be sorted if the sort key is on the constant // member [1998].[Q1] ListIterator iter = keySpecList.listIterator(); while (iter.hasNext()) { SortKeySpec key = iter.next(); Calc expCalc = key.getKey(); if (expCalc instanceof MemberOrderKeyFunDef.CalcImpl) { Calc[] calcs = ((MemberOrderKeyFunDef.CalcImpl) expCalc).getCalcs(); MemberCalc memberCalc = (MemberCalc) calcs[0]; if (memberCalc instanceof ConstantCalc || !listHierarchies.contains( memberCalc.getType().getHierarchy())) { iter.remove(); } } } } } private static class ContextCalc extends GenericIterCalc { private final MemberCalc[] memberCalcs; private final CalcWithDual calc; private final Member[] members; // workspace protected ContextCalc(MemberCalc[] memberCalcs, CalcWithDual calc) { super(new DummyExp(calc.getType()), xx(memberCalcs, calc)); this.memberCalcs = memberCalcs; this.calc = calc; this.members = new Member[memberCalcs.length]; } private static Calc[] xx( MemberCalc[] memberCalcs, CalcWithDual calc) { Calc[] calcs = new Calc[memberCalcs.length + 1]; System.arraycopy(memberCalcs, 0, calcs, 0, memberCalcs.length); calcs[calcs.length - 1] = calc; return calcs; } public Object evaluate(Evaluator evaluator) { // Evaluate each of the members, and set as context in the // sub-evaluator. for (int i = 0; i < memberCalcs.length; i++) { members[i] = memberCalcs[i].evaluateMember(evaluator); } final Evaluator subEval = evaluator.push(members); // Evaluate the expression in the new context. return calc.evaluateDual(evaluator, subEval); } public boolean dependsOn(Hierarchy hierarchy) { if (anyDepends(memberCalcs, hierarchy)) { return true; } // Member calculations generate members, which mask the actual // expression from the inherited context. for (MemberCalc memberCalc : memberCalcs) { if (memberCalc.getType().usesHierarchy(hierarchy, true)) { return false; } } return calc.dependsOn(hierarchy); } public ResultStyle getResultStyle() { return calc.getResultStyle(); } } private static class ResolverImpl extends ResolverBase { private final String[] reservedWords; static int[] argTypes; private ResolverImpl() { super( "Order", "Order( {, }...)", "Arranges members of a set, optionally preserving or breaking the hierarchy.", Syntax.Function); this.reservedWords = Flag.getNames(); } public FunDef resolve( Exp[] args, Validator validator, List conversions) { argTypes = new int[args.length]; if (args.length < 2) { return null; } // first arg must be a set if (!validator.canConvert(0, args[0], Category.Set, conversions)) { return null; } argTypes[0] = Category.Set; // after fist args, should be: value [, symbol] int i = 1; while (i < args.length) { if (!validator.canConvert( i, args[i], Category.Value, conversions)) { return null; } else { argTypes[i] = Category.Value; i++; } // if symbol is not specified, skip to the next if ((i == args.length)) { //done, will default last arg to ASC } else { if (!validator.canConvert( i, args[i], Category.Symbol, conversions)) { // continue, will default sort flag for prev arg to ASC } else { argTypes[i] = Category.Symbol; i++; } } } return new OrderFunDef(this, Category.Set, argTypes); } public String[] getReservedWords() { if (reservedWords != null) { return reservedWords; } return super.getReservedWords(); } } } // End OrderFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/ResolverBase.java0000644000175000017500000000304611735330606023535 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2006 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.olap.FunDef; import mondrian.olap.Syntax; /** * ResolverBase provides a skeleton implementation of * interface {@link Resolver} * * @author jhyde * @since 3 March, 2002 */ abstract class ResolverBase extends FunUtil implements Resolver { private final String name; private final String signature; private final String description; private final Syntax syntax; ResolverBase( String name, String signature, String description, Syntax syntax) { this.name = name; this.signature = signature; this.description = description; this.syntax = syntax; } public String getName() { return name; } public String getSignature() { return signature; } public FunDef getFunDef() { return null; } public String getDescription() { return description; } public Syntax getSyntax() { return syntax; } public boolean requiresExpression(int k) { return false; } public String[] getReservedWords() { return emptyStringArray; } } // End ResolverBase.java mondrian-3.4.1/src/main/mondrian/olap/fun/MemberLevelFunDef.java0000644000175000017500000000332511735330606024430 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2007 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractLevelCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.LevelType; import mondrian.olap.type.Type; /** * Definition of the <Member>.Level MDX builtin function. * * @author jhyde * @since Mar 23, 2006 */ public class MemberLevelFunDef extends FunDefBase { static final MemberLevelFunDef instance = new MemberLevelFunDef(); private MemberLevelFunDef() { super("Level", "Returns a member's level.", "plm"); } public Type getResultType(Validator validator, Exp[] args) { final Type argType = args[0].getType(); return LevelType.forType(argType); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new CalcImpl(call, memberCalc); } public static class CalcImpl extends AbstractLevelCalc { private final MemberCalc memberCalc; public CalcImpl(Exp exp, MemberCalc memberCalc) { super(exp, new Calc[] {memberCalc}); this.memberCalc = memberCalc; } public Level evaluateLevel(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); return member.getLevel(); } } } // End MemberLevelFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/IsNullFunDef.java0000644000175000017500000000303311735330606023433 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractBooleanCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the IS NULL MDX function. * * @author medstat * @since Aug 21, 2006 */ class IsNullFunDef extends FunDefBase { /** * Resolves calls to the IS NULL postfix operator. */ static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "IS NULL", " IS NULL", "Returns whether an object is null", new String[]{"Qbm", "Qbl", "Qbh", "Qbd"}, IsNullFunDef.class); public IsNullFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { assert call.getArgCount() == 1; final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractBooleanCalc(call, new Calc[]{memberCalc}) { public boolean evaluateBoolean(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); return member.isNull(); } }; } } // End IsNullFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/TopBottomPercentSumFunDef.java0000644000175000017500000001447711735330606026200 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.util.List; import java.util.Map; /** * Definition of the TopPercent, BottomPercent, * TopSum and BottomSum MDX builtin functions. * * @author jhyde * @since Mar 23, 2006 */ class TopBottomPercentSumFunDef extends FunDefBase { /** * Whether to calculate top (as opposed to bottom). */ final boolean top; /** * Whether to calculate percent (as opposed to sum). */ final boolean percent; static final ResolverImpl TopPercentResolver = new ResolverImpl( "TopPercent", "TopPercent(, , )", "Sorts a set and returns the top N elements whose cumulative total is at least a specified percentage.", new String[]{"fxxnn"}, true, true); static final ResolverImpl BottomPercentResolver = new ResolverImpl( "BottomPercent", "BottomPercent(, , )", "Sorts a set and returns the bottom N elements whose cumulative total is at least a specified percentage.", new String[]{"fxxnn"}, false, true); static final ResolverImpl TopSumResolver = new ResolverImpl( "TopSum", "TopSum(, , )", "Sorts a set and returns the top N elements whose cumulative total is at least a specified value.", new String[]{"fxxnn"}, true, false); static final ResolverImpl BottomSumResolver = new ResolverImpl( "BottomSum", "BottomSum(, , )", "Sorts a set and returns the bottom N elements whose cumulative total is at least a specified value.", new String[]{"fxxnn"}, false, false); public TopBottomPercentSumFunDef( FunDef dummyFunDef, boolean top, boolean percent) { super(dummyFunDef); this.top = top; this.percent = percent; } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = (ListCalc) compiler.compileAs( call.getArg(0), null, ResultStyle.MUTABLELIST_ONLY); final DoubleCalc doubleCalc = compiler.compileDouble(call.getArg(1)); final Calc calc = compiler.compileScalar(call.getArg(2), true); return new CalcImpl(call, listCalc, doubleCalc, calc); } private static class ResolverImpl extends MultiResolver { private final boolean top; private final boolean percent; public ResolverImpl( final String name, final String signature, final String description, final String[] signatures, boolean top, boolean percent) { super(name, signature, description, signatures); this.top = top; this.percent = percent; } protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) { return new TopBottomPercentSumFunDef(dummyFunDef, top, percent); } } private class CalcImpl extends AbstractListCalc { private final ListCalc listCalc; private final DoubleCalc doubleCalc; private final Calc calc; public CalcImpl( ResolvedFunCall call, ListCalc listCalc, DoubleCalc doubleCalc, Calc calc) { super(call, new Calc[]{listCalc, doubleCalc, calc}); this.listCalc = listCalc; this.doubleCalc = doubleCalc; this.calc = calc; } public TupleList evaluateList(Evaluator evaluator) { TupleList list = listCalc.evaluateList(evaluator); double target = doubleCalc.evaluateDouble(evaluator); if (list.isEmpty()) { return list; } Map, Object> mapMemberToValue = evaluateTuples(evaluator, calc, list); final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); list = sortTuples( evaluator, list, list, calc, top, true, getType().getArity()); evaluator.restore(savepoint); if (percent) { toPercent(list, mapMemberToValue); } double runningTotal = 0; int memberCount = list.size(); int nullCount = 0; for (int i = 0; i < memberCount; i++) { if (runningTotal >= target) { list = list.subList(0, i); break; } final List key = list.get(i); final Object o = mapMemberToValue.get(key); if (o == Util.nullValue) { nullCount++; } else if (o instanceof Number) { runningTotal += ((Number) o).doubleValue(); } else if (o instanceof Exception) { // ignore the error } else { throw Util.newInternal( "got " + o + " when expecting Number"); } } // MSAS exhibits the following behavior. If the value of all members // is null, then the first (or last) member of the set is returned // for percent operations. if (memberCount > 0 && percent && nullCount == memberCount) { return top ? list.subList(0, 1) : list.subList(memberCount - 1, memberCount); } return list; } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } } } // End TopBottomPercentSumFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/DimensionsStringFunDef.java0000644000175000017500000000510111735330606025522 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractHierarchyCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.HierarchyType; import mondrian.olap.type.Type; /** * Definition of the Dimensions(<String Expression>) * MDX builtin function. * *

    NOTE: Actually returns a hierarchy. This is consistent with Analysis * Services. * * @author jhyde * @since Jul 20, 2009 */ class DimensionsStringFunDef extends FunDefBase { public static final FunDefBase INSTANCE = new DimensionsStringFunDef(); private DimensionsStringFunDef() { super( "Dimensions", "Returns the hierarchy whose name is specified by a string.", "fhS"); } public Type getResultType(Validator validator, Exp[] args) { return HierarchyType.Unknown; } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final StringCalc stringCalc = compiler.compileString(call.getArg(0)); return new AbstractHierarchyCalc(call, new Calc[] {stringCalc}) { public Hierarchy evaluateHierarchy(Evaluator evaluator) { String dimensionName = stringCalc.evaluateString(evaluator); return findHierarchy(dimensionName, evaluator); } }; } /** * Looks up a hierarchy in the current cube with a given name. * * @param name Hierarchy name * @param evaluator Evaluator * @return Hierarchy */ Hierarchy findHierarchy(String name, Evaluator evaluator) { if (name.indexOf("[") == -1) { name = Util.quoteMdxIdentifier(name); } OlapElement o = evaluator.getSchemaReader().lookupCompound( evaluator.getCube(), parseIdentifier(name), false, Category.Hierarchy); if (o instanceof Hierarchy) { return (Hierarchy) o; } else if (o == null) { throw newEvalException( this, "Hierarchy '" + name + "' not found"); } else { throw newEvalException( this, "Hierarchy(" + name + ") found " + o); } } } // End DimensionsStringFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/DrilldownLevelTopBottomFunDef.java0000644000175000017500000001420311735330606027024 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.ScalarType; import java.util.ArrayList; import java.util.List; /** * Definition of the DrilldownLevelTop and * DrilldownLevelBottom MDX builtin functions. * *

    Syntax: * *

    * DrilldownLevelTop(Set_Expression, Count [, [Level_Expression][, * Numeric_Expression]])
    * DrilldownLevelBottom(Set_Expression, Count [, [Level_Expression][, * Numeric_Expression]]) *
    * * @author jhyde * @since Oct 18, 2007 */ class DrilldownLevelTopBottomFunDef extends FunDefBase { final boolean top; static final MultiResolver DrilldownLevelTopResolver = new MultiResolver( "DrilldownLevelTop", "DrilldownLevelTop(Set_Expression, Count [, [Level_Expression][, Numeric_Expression]])", "Drills down the topmost members of a set, at a specified level, to one level below.", new String[] {"fxxn", "fxxnl", "fxxnln", "fxxnen"}) { protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) { return new DrilldownLevelTopBottomFunDef(dummyFunDef, true); } }; static final MultiResolver DrilldownLevelBottomResolver = new MultiResolver( "DrilldownLevelBottom", "DrilldownLevelBottom(Set_Expression, Count [, [Level_Expression][, Numeric_Expression]])", "Drills down the bottommost members of a set, at a specified level, to one level below.", new String[] {"fxxn", "fxxnl", "fxxnln", "fxxnen"}) { protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) { return new DrilldownLevelTopBottomFunDef(dummyFunDef, false); } }; public DrilldownLevelTopBottomFunDef( FunDef dummyFunDef, final boolean top) { super(dummyFunDef); this.top = top; } public Calc compileCall(final ResolvedFunCall call, ExpCompiler compiler) { // Compile the member list expression. Ask for a mutable list, because // we're going to insert members into it later. final ListCalc listCalc = compiler.compileList(call.getArg(0), true); final IntegerCalc integerCalc = compiler.compileInteger(call.getArg(1)); final LevelCalc levelCalc = call.getArgCount() > 2 && call.getArg(2).getCategory() != Category.Empty ? compiler.compileLevel(call.getArg(2)) : null; final Calc orderCalc = call.getArgCount() > 3 ? compiler.compileScalar(call.getArg(3), true) : new ValueCalc( new DummyExp( new ScalarType())); return new AbstractListCalc( call, new Calc[] {listCalc, integerCalc, orderCalc}) { public TupleList evaluateList(Evaluator evaluator) { // Use a native evaluator, if more efficient. // TODO: Figure this out at compile time. SchemaReader schemaReader = evaluator.getSchemaReader(); NativeEvaluator nativeEvaluator = schemaReader.getNativeSetEvaluator( call.getFunDef(), call.getArgs(), evaluator, this); if (nativeEvaluator != null) { return (TupleList) nativeEvaluator.execute(ResultStyle.LIST); } TupleList list = listCalc.evaluateList(evaluator); int n = integerCalc.evaluateInteger(evaluator); if (n == FunUtil.IntegerNull || n <= 0) { return list; } Level level; if (levelCalc == null) { level = null; } else { level = levelCalc.evaluateLevel(evaluator); } List result = new ArrayList(); assert list.getArity() == 1; for (Member member : list.slice(0)) { result.add(member); if (level != null && member.getLevel() != level) { if (level.getDimension() != member.getDimension()) { throw newEvalException( DrilldownLevelTopBottomFunDef.this, "Level '" + level.getUniqueName() + "' not compatible with member '" + member.getUniqueName() + "'"); } continue; } List children = schemaReader.getMemberChildren(member); final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final List sortedChildren = sortMembers( evaluator, children, children, orderCalc, top, true); evaluator.restore(savepoint); int x = Math.min(n, sortedChildren.size()); for (int i = 0; i < x; i++) { result.add(sortedChildren.get(i)); } } return new UnaryTupleList(result); } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } }; } } // End DrilldownLevelTopBottomFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/MinMaxFunDef.java0000644000175000017500000000477411735330606023433 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractDoubleCalc; import mondrian.calc.impl.ValueCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the Min and Max MDX functions. * * @author jhyde * @since Mar 23, 2006 */ class MinMaxFunDef extends AbstractAggregateFunDef { static final ReflectiveMultiResolver MinResolver = new ReflectiveMultiResolver( "Min", "Min([, ])", "Returns the minimum value of a numeric expression evaluated over a set.", new String[]{"fnx", "fnxn"}, MinMaxFunDef.class); static final MultiResolver MaxResolver = new ReflectiveMultiResolver( "Max", "Max([, ])", "Returns the maximum value of a numeric expression evaluated over a set.", new String[]{"fnx", "fnxn"}, MinMaxFunDef.class); private final boolean max; public MinMaxFunDef(FunDef dummyFunDef) { super(dummyFunDef); this.max = dummyFunDef.getName().equals("Max"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final Calc calc = call.getArgCount() > 1 ? compiler.compileScalar(call.getArg(1), true) : new ValueCalc(call); return new AbstractDoubleCalc(call, new Calc[] {listCalc, calc}) { public double evaluateDouble(Evaluator evaluator) { TupleList memberList = evaluateCurrentList(listCalc, evaluator); final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final Double d = (Double) (max ? max(evaluator, memberList, calc) : min(evaluator, memberList, calc)); evaluator.restore(savepoint); return d; } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } }; } } // End MinMaxFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/UnorderFunDef.java0000644000175000017500000000261611735330606023651 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2008-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.Calc; import mondrian.calc.ExpCompiler; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.FunDef; /** * Definition of the Unorder MDX function. * * @author jhyde * @since Sep 06, 2008 */ class UnorderFunDef extends FunDefBase { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "Unorder", "Unorder()", "Removes any enforced ordering from a specified set.", new String[]{"fxx"}, UnorderFunDef.class); public UnorderFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { // Currently Unorder has no effect. In future, we may use the function // as a marker to weaken the ordering required from an expression and // therefore allow the compiler to use a more efficient implementation // that does not return a strict order. return compiler.compile(call.getArg(0)); } } // End UnorderFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/ExceptFunDef.java0000644000175000017500000000433211735330606023460 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.calc.impl.ArrayTupleList; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.util.*; /** * Definition of the Except MDX function. * * @author jhyde * @since Mar 23, 2006 */ class ExceptFunDef extends FunDefBase { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "Except", "Except(, [, ALL])", "Finds the difference between two sets, optionally retaining duplicates.", new String[]{"fxxx", "fxxxy"}, ExceptFunDef.class); public ExceptFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { // todo: implement ALL final ListCalc listCalc0 = compiler.compileList(call.getArg(0)); final ListCalc listCalc1 = compiler.compileList(call.getArg(1)); return new AbstractListCalc(call, new Calc[] {listCalc0, listCalc1}) { public TupleList evaluateList(Evaluator evaluator) { TupleList list0 = listCalc0.evaluateList(evaluator); if (list0.isEmpty()) { return list0; } TupleList list1 = listCalc1.evaluateList(evaluator); if (list1.isEmpty()) { return list0; } final Set> set1 = new HashSet>(list1); final TupleList result = new ArrayTupleList(list0.getArity(), list0.size()); for (List tuple1 : list0) { if (!set1.contains(tuple1)) { result.add(tuple1); } } return result; } }; } } // End ExceptFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/OpeningClosingPeriodFunDef.java0000644000175000017500000001707711735330606026323 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractMemberCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.MemberType; import mondrian.olap.type.Type; import mondrian.resource.MondrianResource; import mondrian.rolap.RolapCube; import mondrian.rolap.RolapHierarchy; import java.util.List; /** * Definition of the OpeningPeriod and ClosingPeriod * builtin functions. * * @author jhyde * @since 2005/8/14 */ class OpeningClosingPeriodFunDef extends FunDefBase { private final boolean opening; static final Resolver OpeningPeriodResolver = new MultiResolver( "OpeningPeriod", "OpeningPeriod([[, ]])", "Returns the first descendant of a member at a level.", new String[] {"fm", "fml", "fmlm"}) { protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) { return new OpeningClosingPeriodFunDef(dummyFunDef, true); } }; static final Resolver ClosingPeriodResolver = new MultiResolver( "ClosingPeriod", "ClosingPeriod([[, ]])", "Returns the last descendant of a member at a level.", new String[] {"fm", "fml", "fmlm", "fmm"}) { protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) { return new OpeningClosingPeriodFunDef(dummyFunDef, false); } }; public OpeningClosingPeriodFunDef( FunDef dummyFunDef, boolean opening) { super(dummyFunDef); this.opening = opening; } public Type getResultType(Validator validator, Exp[] args) { if (args.length == 0) { // With no args, the default implementation cannot // guess the hierarchy, so we supply the Time // dimension. RolapHierarchy defaultTimeHierarchy = ((RolapCube) validator.getQuery().getCube()).getTimeHierarchy( getName()); return MemberType.forHierarchy(defaultTimeHierarchy); } return super.getResultType(validator, args); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Exp[] args = call.getArgs(); final LevelCalc levelCalc; final MemberCalc memberCalc; RolapHierarchy defaultTimeHierarchy = null; switch (args.length) { case 0: defaultTimeHierarchy = ((RolapCube) compiler.getEvaluator().getCube()) .getTimeHierarchy(getName()); memberCalc = new HierarchyCurrentMemberFunDef.FixedCalcImpl( new DummyExp( MemberType.forHierarchy(defaultTimeHierarchy)), defaultTimeHierarchy); levelCalc = null; break; case 1: defaultTimeHierarchy = ((RolapCube) compiler.getEvaluator().getCube()) .getTimeHierarchy(getName()); levelCalc = compiler.compileLevel(call.getArg(0)); memberCalc = new HierarchyCurrentMemberFunDef.FixedCalcImpl( new DummyExp( MemberType.forHierarchy(defaultTimeHierarchy)), defaultTimeHierarchy); break; default: levelCalc = compiler.compileLevel(call.getArg(0)); memberCalc = compiler.compileMember(call.getArg(1)); break; } // Make sure the member and the level come from the same dimension. if (levelCalc != null) { final Dimension memberDimension = memberCalc.getType().getDimension(); final Dimension levelDimension = levelCalc.getType().getDimension(); if (!memberDimension.equals(levelDimension)) { throw MondrianResource.instance() .FunctionMbrAndLevelHierarchyMismatch.ex( opening ? "OpeningPeriod" : "ClosingPeriod", levelDimension.getUniqueName(), memberDimension.getUniqueName()); } } return new AbstractMemberCalc( call, new Calc[] {levelCalc, memberCalc}) { public Member evaluateMember(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); // If the level argument is present, use it. Otherwise use the // level immediately after that of the member argument. Level level; if (levelCalc == null) { int targetDepth = member.getLevel().getDepth() + 1; Level[] levels = member.getHierarchy().getLevels(); if (levels.length <= targetDepth) { return member.getHierarchy().getNullMember(); } level = levels[targetDepth]; } else { level = levelCalc.evaluateLevel(evaluator); } // Shortcut if the level is above the member. if (level.getDepth() < member.getLevel().getDepth()) { return member.getHierarchy().getNullMember(); } // Shortcut if the level is the same as the member if (level == member.getLevel()) { return member; } return getDescendant( evaluator.getSchemaReader(), member, level, opening); } }; } /** * Returns the first or last descendant of the member at the target level. * This method is the implementation of both OpeningPeriod and * ClosingPeriod. * * @param schemaReader The schema reader to use to evaluate the function. * @param member The member from which the descendant is to be found. * @param targetLevel The level to stop at. * @param returnFirstDescendant Flag indicating whether to return the first * or last descendant of the member. * @return A member. * @pre member.getLevel().getDepth() < level.getDepth(); */ static Member getDescendant( SchemaReader schemaReader, Member member, Level targetLevel, boolean returnFirstDescendant) { List children; final int targetLevelDepth = targetLevel.getDepth(); assertPrecondition(member.getLevel().getDepth() < targetLevelDepth, "member.getLevel().getDepth() < targetLevel.getDepth()"); for (;;) { children = schemaReader.getMemberChildren(member); if (children.size() == 0) { return targetLevel.getHierarchy().getNullMember(); } final int index = returnFirstDescendant ? 0 : (children.size() - 1); member = children.get(index); if (member.getLevel().getDepth() == targetLevelDepth) { if (member.isHidden()) { return member.getHierarchy().getNullMember(); } else { return member; } } } } } // End OpeningClosingPeriodFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/DrilldownMemberFunDef.java0000644000175000017500000001061411735330606025316 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.util.*; /** * Definition of the DrilldownMember MDX function. * * @author Grzegorz Lojek * @since 6 December, 2004 */ class DrilldownMemberFunDef extends FunDefBase { static final String[] reservedWords = new String[] {"RECURSIVE"}; static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "DrilldownMember", "DrilldownMember(, [, RECURSIVE])", "Drills down the members in a set that are present in a second specified set.", new String[]{"fxxx", "fxxxy"}, DrilldownMemberFunDef.class, reservedWords); public DrilldownMemberFunDef(FunDef funDef) { super(funDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc1 = compiler.compileList(call.getArg(0)); final ListCalc listCalc2 = compiler.compileList(call.getArg(1)); final String literalArg = getLiteralArg(call, 2, "", reservedWords); final boolean recursive = literalArg.equals("RECURSIVE"); return new AbstractListCalc( call, new Calc[] {listCalc1, listCalc2}) { public TupleList evaluateList(Evaluator evaluator) { final TupleList list1 = listCalc1.evaluateList(evaluator); final TupleList list2 = listCalc2.evaluateList(evaluator); return drilldownMember(list1, list2, evaluator); } /** * Drills down an element. * *

    Algorithm: If object is present in {@code memberSet} adds to * result children of the object. If flag {@code recursive} is set * then this method is called recursively for the children. * * @param evaluator Evaluator * @param tuple Tuple (may have arity 1) * @param memberSet Set of members * @param resultList Result */ protected void drillDownObj( Evaluator evaluator, Member[] tuple, Set memberSet, TupleList resultList) { for (int k = 0; k < tuple.length; k++) { Member member = tuple[k]; if (memberSet.contains(member)) { List children = evaluator.getSchemaReader().getMemberChildren( member); final Member[] tuple2 = tuple.clone(); for (Member childMember : children) { tuple2[k] = childMember; resultList.addTuple(tuple2); if (recursive) { drillDownObj( evaluator, tuple2, memberSet, resultList); } } break; } } } private TupleList drilldownMember( TupleList v0, TupleList v1, Evaluator evaluator) { assert v1.getArity() == 1; if (v0.isEmpty() || v1.isEmpty()) { return v0; } Set set1 = new HashSet(v1.slice(0)); TupleList result = TupleCollections.createList(v0.getArity()); int i = 0, n = v0.size(); final Member[] members = new Member[v0.getArity()]; while (i < n) { List o = v0.get(i++); o.toArray(members); result.add(o); drillDownObj(evaluator, members, set1, result); } return result; } }; } } // End DrilldownMemberFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/CorrelationFunDef.java0000644000175000017500000000434611735330606024516 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractDoubleCalc; import mondrian.calc.impl.ValueCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the Correlation MDX function. * * @author jhyde * @since Mar 23, 2006 */ class CorrelationFunDef extends AbstractAggregateFunDef { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "Correlation", "Correlation(, [, ])", "Returns the correlation of two series evaluated over a set.", new String[]{"fnxn", "fnxnn"}, CorrelationFunDef.class); public CorrelationFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final Calc calc1 = compiler.compileScalar(call.getArg(1), true); final Calc calc2 = call.getArgCount() > 2 ? compiler.compileScalar(call.getArg(2), true) : new ValueCalc(call); return new AbstractDoubleCalc( call, new Calc[] {listCalc, calc1, calc2}) { public double evaluateDouble(Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); TupleList list = evaluateCurrentList(listCalc, evaluator); final double correlation = correlation( evaluator, list, calc1, calc2); evaluator.restore(savepoint); return correlation; } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } }; } } // End CorrelationFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/TopBottomCountFunDef.java0000644000175000017500000001504011735330606025166 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.util.AbstractList; import java.util.List; /** * Definition of the TopCount and BottomCount * MDX builtin functions. * * @author jhyde * @since Mar 23, 2006 */ class TopBottomCountFunDef extends FunDefBase { boolean top; static final MultiResolver TopCountResolver = new MultiResolver( "TopCount", "TopCount(, [, ])", "Returns a specified number of items from the top of a set, optionally ordering the set first.", new String[]{"fxxnn", "fxxn"}) { protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) { return new TopBottomCountFunDef(dummyFunDef, true); } }; static final MultiResolver BottomCountResolver = new MultiResolver( "BottomCount", "BottomCount(, [, ])", "Returns a specified number of items from the bottom of a set, optionally ordering the set first.", new String[]{"fxxnn", "fxxn"}) { protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) { return new TopBottomCountFunDef(dummyFunDef, false); } }; public TopBottomCountFunDef(FunDef dummyFunDef, final boolean top) { super(dummyFunDef); this.top = top; } public Calc compileCall(final ResolvedFunCall call, ExpCompiler compiler) { // Compile the member list expression. Ask for a mutable list, because // we're going to sort it later. final ListCalc listCalc = compiler.compileList(call.getArg(0), true); final IntegerCalc integerCalc = compiler.compileInteger(call.getArg(1)); final Calc orderCalc = call.getArgCount() > 2 ? compiler.compileScalar(call.getArg(2), true) : null; final int arity = call.getType().getArity(); return new AbstractListCalc( call, new Calc[]{listCalc, integerCalc, orderCalc}) { public TupleList evaluateList(Evaluator evaluator) { // Use a native evaluator, if more efficient. // TODO: Figure this out at compile time. SchemaReader schemaReader = evaluator.getSchemaReader(); NativeEvaluator nativeEvaluator = schemaReader.getNativeSetEvaluator( call.getFunDef(), call.getArgs(), evaluator, this); if (nativeEvaluator != null) { return (TupleList) nativeEvaluator.execute(ResultStyle.LIST); } int n = integerCalc.evaluateInteger(evaluator); if (n == 0 || n == mondrian.olap.fun.FunUtil.IntegerNull) { return TupleCollections.emptyList(arity); } TupleList list = listCalc.evaluateList(evaluator); assert list.getArity() == arity; if (list.isEmpty()) { return list; } if (orderCalc == null) { if (list instanceof AbstractList && list.size() < n) { return list; } else { return list.subList(0, n); } } return partiallySortList( evaluator, list, hasHighCardDimension(list), n); } private TupleList partiallySortList( Evaluator evaluator, TupleList list, boolean highCard, int n) { if (highCard) { // sort list in chunks, collect the results final int chunkSize = 6400; // what is this really? TupleList allChunkResults = TupleCollections.createList( arity); for (int i = 0, next; i < list.size(); i = next) { next = Math.min(i + chunkSize, list.size()); final TupleList chunk = list.subList(i, next); TupleList chunkResult = partiallySortList( evaluator, chunk, false, n); allChunkResults.addAll(chunkResult); } // one last sort, to merge and cull return partiallySortList( evaluator, allChunkResults, false, n); } // normal case: no need for chunks final int savepoint = evaluator.savepoint(); switch (list.getArity()) { case 1: final List members = partiallySortMembers( evaluator.push(), list.slice(0), orderCalc, n, top); evaluator.restore(savepoint); return new UnaryTupleList(members); default: final List> tuples = partiallySortTuples( evaluator.push(), list, orderCalc, n, top); evaluator.restore(savepoint); return new DelegatingTupleList( list.getArity(), tuples); } } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } private boolean hasHighCardDimension(TupleList l) { final List trial = l.get(0); for (Member m : trial) { if (m.getHierarchy().getDimension().isHighCardinality()) { return true; } } return false; } }; } } // End TopBottomCountFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/NativizeSetFunDef.java0000644000175000017500000015172611735330606024507 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.calc.impl.DelegatingTupleList; import mondrian.mdx.*; import mondrian.olap.*; import mondrian.olap.type.Type; import mondrian.resource.MondrianResource; import org.apache.log4j.Logger; import org.olap4j.impl.Olap4jUtil; import java.util.*; import static mondrian.olap.fun.NativizeSetFunDef.NativeElementType.*; /** * Definition of the NativizeSet MDX function. * * @author jrand * @since Oct 14, 2009 */ public class NativizeSetFunDef extends FunDefBase { /* * Static final fields. */ protected static final Logger LOGGER = Logger.getLogger(NativizeSetFunDef.class); private static final String SENTINEL_PREFIX = "_Nativized_Sentinel_"; private static final String MEMBER_NAME_PREFIX = "_Nativized_Member_"; private static final String SET_NAME_PREFIX = "_Nativized_Set_"; private static final List> functionWhitelist = Arrays.>asList( CacheFunDef.class, SetFunDef.class, CrossJoinFunDef.class, NativizeSetFunDef.class); static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "NativizeSet", "NativizeSet()", "Tries to natively evaluate .", new String[] {"fxx"}, NativizeSetFunDef.class); /* * Instance final fields. */ private final SubstitutionMap substitutionMap = new SubstitutionMap(); private final HashSet dimensions = new LinkedHashSet(); private boolean isFirstCompileCall = true; /* * Instance non-final fields. */ private Exp originalExp; private static final String ESTIMATE_MESSAGE = "isHighCardinality=%b: estimate=%,d threshold=%,d"; private static final String PARTIAL_ESTIMATE_MESSAGE = "isHighCardinality=%b: partial estimate=%,d threshold=%,d"; public NativizeSetFunDef(FunDef dummyFunDef) { super(dummyFunDef); LOGGER.debug("---- NativizeSetFunDef constructor"); } public Exp createCall(Validator validator, Exp[] args) { LOGGER.debug("NativizeSetFunDef createCall"); ResolvedFunCall call = (ResolvedFunCall) super.createCall(validator, args); call.accept(new FindLevelsVisitor(substitutionMap, dimensions)); return call; } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { LOGGER.debug("NativizeSetFunDef compileCall"); Exp funArg = call.getArg(0); if (MondrianProperties.instance().UseAggregates.get() || MondrianProperties.instance().ReadAggregates.get()) { return funArg.accept(compiler); } final Calc[] calcs = {compiler.compileList(funArg, true)}; final int arity = calcs[0].getType().getArity(); assert arity >= 0; if (arity == 1 || substitutionMap.isEmpty()) { IterCalc calc = (IterCalc) funArg.accept(compiler); final boolean highCardinality = arity == 1 && isHighCardinality(funArg, compiler.getEvaluator()); if (calc == null) { // This can happen under JDK1.4: caller wants iterator // implementation, but compiler can only provide list. // Fall through and use native. } else if (calc instanceof ListCalc) { return new NonNativeListCalc((ListCalc) calc, highCardinality); } else { return new NonNativeIterCalc(calc, highCardinality); } } if (isFirstCompileCall) { isFirstCompileCall = false; originalExp = funArg.clone(); Query query = compiler.getEvaluator().getQuery(); call.accept( new AddFormulasVisitor(query, substitutionMap, dimensions)); call.accept(new TransformToFormulasVisitor(query)); query.resolve(); } return new NativeListCalc( call, calcs, compiler, substitutionMap, originalExp); } private boolean isHighCardinality(Exp funArg, Evaluator evaluator) { Level level = findLevel(funArg); if (level != null) { int cardinality = evaluator.getSchemaReader() .getLevelCardinality(level, false, true); final int minThreshold = MondrianProperties.instance() .NativizeMinThreshold.get(); final boolean isHighCard = cardinality > minThreshold; logHighCardinality( ESTIMATE_MESSAGE, minThreshold, cardinality, isHighCard); return isHighCard; } return false; } private Level findLevel(Exp exp) { exp.accept(new FindLevelsVisitor(substitutionMap, dimensions)); final Collection levels = substitutionMap.values(); if (levels.size() == 1) { return levels.iterator().next(); } return null; } private static void logHighCardinality( final String estimateMessage, long nativizeMinThreshold, long estimatedCardinality, boolean highCardinality) { LOGGER.debug( String.format( estimateMessage, highCardinality, estimatedCardinality, nativizeMinThreshold)); } static class NonNativeCalc implements Calc { final Calc parent; final boolean nativeEnabled; protected NonNativeCalc(Calc parent, final boolean nativeEnabled) { assert parent != null; this.parent = parent; this.nativeEnabled = nativeEnabled; } public Object evaluate(final Evaluator evaluator) { evaluator.setNativeEnabled(nativeEnabled); return parent.evaluate(evaluator); } public boolean dependsOn(final Hierarchy hierarchy) { return parent.dependsOn(hierarchy); } public Type getType() { return parent.getType(); } public void accept(final CalcWriter calcWriter) { parent.accept(calcWriter); } public ResultStyle getResultStyle() { return parent.getResultStyle(); } /** * {@inheritDoc} * * Default implementation just does 'instanceof TargetClass'. Subtypes * that are wrappers should override. */ public boolean isWrapperFor(Class iface) { return iface.isInstance(this); } /** * {@inheritDoc} * * Default implementation just casts to TargetClass. * Subtypes that are wrappers should override. */ public T unwrap(Class iface) { return iface.cast(this); } } static class NonNativeIterCalc extends NonNativeCalc implements IterCalc { protected NonNativeIterCalc(IterCalc parent, boolean highCardinality) { super(parent, highCardinality); } IterCalc parent() { return (IterCalc) parent; } public TupleIterable evaluateIterable(Evaluator evaluator) { evaluator.setNativeEnabled(nativeEnabled); return parent().evaluateIterable(evaluator); } } static class NonNativeListCalc extends NonNativeCalc implements ListCalc { protected NonNativeListCalc(ListCalc parent, boolean highCardinality) { super(parent, highCardinality); } ListCalc parent() { return (ListCalc) parent; } public TupleList evaluateList(Evaluator evaluator) { evaluator.setNativeEnabled(nativeEnabled); return parent().evaluateList(evaluator); } public TupleIterable evaluateIterable(Evaluator evaluator) { return evaluateList(evaluator); } } public static class NativeListCalc extends AbstractListCalc { private final SubstitutionMap substitutionMap; private final ListCalc simpleCalc; private final ExpCompiler compiler; private final Exp originalExp; protected NativeListCalc( ResolvedFunCall call, Calc[] calcs, ExpCompiler compiler, SubstitutionMap substitutionMap, Exp originalExp) { super(call, calcs); LOGGER.debug("---- NativeListCalc constructor"); this.substitutionMap = substitutionMap; this.simpleCalc = (ListCalc) calcs[0]; this.compiler = compiler; this.originalExp = originalExp; } public TupleList evaluateList(Evaluator evaluator) { return computeTuples(evaluator); } public TupleList computeTuples(Evaluator evaluator) { TupleList simplifiedList = evaluateSimplifiedList(evaluator); if (simplifiedList.isEmpty()) { return simplifiedList; } if (!isHighCardinality(evaluator, simplifiedList)) { return evaluateNonNative(evaluator); } return evaluateNative(evaluator, simplifiedList); } private TupleList evaluateSimplifiedList(Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); evaluator.setNativeEnabled(false); TupleList simplifiedList = simpleCalc.evaluateList(evaluator); evaluator.restore(savepoint); dumpListToLog("simplified list", simplifiedList); return simplifiedList; } private TupleList evaluateNonNative(Evaluator evaluator) { LOGGER.debug( "Disabling native evaluation. originalExp=" + originalExp); ListCalc calc = compiler.compileList(getOriginalExp(evaluator.getQuery())); final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(true); evaluator.setNativeEnabled(false); TupleList members = calc.evaluateList(evaluator); evaluator.restore(savepoint); return members; } private TupleList evaluateNative( Evaluator evaluator, TupleList simplifiedList) { CrossJoinAnalyzer analyzer = new CrossJoinAnalyzer(simplifiedList, substitutionMap); String crossJoin = analyzer.getCrossJoinExpression(); // If the crossjoin expression is empty, then the simplified list // already contains the fully evaluated tuple list, so we can // return it now without any additional work. if (crossJoin.length() == 0) { return simplifiedList; } // Force non-empty to true to create the native list. LOGGER.debug( "crossjoin reconstituted from simplified list: " + String.format( "%n" + crossJoin.replaceAll(",", "%n, "))); final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(true); evaluator.setNativeEnabled(true); TupleList members = analyzer.mergeCalcMembers( evaluateJoinExpression(evaluator, crossJoin)); evaluator.restore(savepoint); return members; } private Exp getOriginalExp(final Query query) { originalExp.accept( new TransformFromFormulasVisitor(query, compiler)); if (originalExp instanceof NamedSetExpr) { //named sets get their evaluator cached in RolapResult. //We do not want to use the cached evaluator, so pass along the //expression instead. return ((NamedSetExpr) originalExp).getNamedSet().getExp(); } return originalExp; } private boolean isHighCardinality( Evaluator evaluator, TupleList simplifiedList) { Util.assertTrue(!simplifiedList.isEmpty()); SchemaReader schema = evaluator.getSchemaReader(); List tuple = simplifiedList.get(0); long nativizeMinThreshold = MondrianProperties.instance().NativizeMinThreshold.get(); long estimatedCardinality = simplifiedList.size(); for (Member member : tuple) { String memberName = member.getName(); if (memberName.startsWith(MEMBER_NAME_PREFIX)) { Level level = member.getLevel(); Dimension dimension = level.getDimension(); Hierarchy hierarchy = dimension.getHierarchy(); String levelName = getLevelNameFromMemberName(memberName); Level hierarchyLevel = Util.lookupHierarchyLevel(hierarchy, levelName); long levelCardinality = getLevelCardinality(schema, hierarchyLevel); estimatedCardinality *= levelCardinality; if (estimatedCardinality >= nativizeMinThreshold) { logHighCardinality( PARTIAL_ESTIMATE_MESSAGE, nativizeMinThreshold, estimatedCardinality, true); return true; } } } boolean isHighCardinality = (estimatedCardinality >= nativizeMinThreshold); logHighCardinality( ESTIMATE_MESSAGE, nativizeMinThreshold, estimatedCardinality, isHighCardinality); return isHighCardinality; } private long getLevelCardinality(SchemaReader schema, Level level) { if (cardinalityIsKnown(level)) { return level.getApproxRowCount(); } return schema.getLevelCardinality(level, false, true); } private boolean cardinalityIsKnown(Level level) { return level.getApproxRowCount() > 0; } private TupleList evaluateJoinExpression( Evaluator evaluator, String crossJoinExpression) { Exp unresolved = evaluator.getQuery().getConnection() .parseExpression(crossJoinExpression); Exp resolved = compiler.getValidator().validate(unresolved, false); ListCalc calc = compiler.compileList(resolved); return calc.evaluateList(evaluator); } } static class FindLevelsVisitor extends MdxVisitorImpl { private final SubstitutionMap substitutionMap; private final Set dimensions; public FindLevelsVisitor( SubstitutionMap substitutionMap, HashSet dimensions) { this.substitutionMap = substitutionMap; this.dimensions = dimensions; } @Override public Object visit(ResolvedFunCall call) { if (call.getFunDef() instanceof LevelMembersFunDef) { if (call.getArg(0) instanceof LevelExpr) { Level level = ((LevelExpr) call.getArg(0)).getLevel(); substitutionMap.put(createMemberId(level), level); dimensions.add(level.getDimension()); } } else if ( functionWhitelist.contains(call.getFunDef().getClass())) { for (Exp arg : call.getArgs()) { arg.accept(this); } } turnOffVisitChildren(); return null; } @Override public Object visit(MemberExpr member) { dimensions.add(member.getMember().getDimension()); return null; } } static class AddFormulasVisitor extends MdxVisitorImpl { private final Query query; private final Collection levels; private final Set dimensions; public AddFormulasVisitor( Query query, SubstitutionMap substitutionMap, Set dimensions) { LOGGER.debug("---- AddFormulasVisitor constructor"); this.query = query; this.levels = substitutionMap.values(); this.dimensions = dimensions; } @Override public Object visit(ResolvedFunCall call) { if (call.getFunDef() instanceof NativizeSetFunDef) { addFormulasToQuery(); } turnOffVisitChildren(); return null; } private void addFormulasToQuery() { LOGGER.debug("FormulaResolvingVisitor addFormulas"); List formulas = new ArrayList(); for (Level level : levels) { Formula memberFormula = createDefaultMemberFormula(level); formulas.add(memberFormula); formulas.add(createNamedSetFormula(level, memberFormula)); } for (Dimension dim : dimensions) { Level level = dim.getHierarchy().getLevels()[0]; formulas.add(createSentinelFormula(level)); } query.addFormulas(formulas.toArray(new Formula[formulas.size()])); } private Formula createSentinelFormula(Level level) { Id memberId = createSentinelId(level); Exp memberExpr = query.getConnection() .parseExpression("101010"); LOGGER.debug( "createSentinelFormula memberId=" + memberId + " memberExpr=" + memberExpr); return new Formula(memberId, memberExpr, new MemberProperty[0]); } private Formula createDefaultMemberFormula(Level level) { Id memberId = createMemberId(level); Exp memberExpr = new UnresolvedFunCall( "DEFAULTMEMBER", Syntax.Property, new Exp[] {hierarchyId(level)}); LOGGER.debug( "createLevelMembersFormulas memberId=" + memberId + " memberExpr=" + memberExpr); return new Formula(memberId, memberExpr, new MemberProperty[0]); } private Formula createNamedSetFormula( Level level, Formula memberFormula) { Id setId = createSetId(level); Exp setExpr = query.getConnection() .parseExpression( "{" + memberFormula.getIdentifier().toString() + "}"); LOGGER.debug( "createNamedSetFormula setId=" + setId + " setExpr=" + setExpr); return new Formula(setId, setExpr); } } static class TransformToFormulasVisitor extends MdxVisitorImpl { private final Query query; public TransformToFormulasVisitor(Query query) { LOGGER.debug("---- TransformToFormulasVisitor constructor"); this.query = query; } @Override public Object visit(ResolvedFunCall call) { LOGGER.debug("visit " + call); Object result = null; if (call.getFunDef() instanceof LevelMembersFunDef) { result = replaceLevelMembersReferences(call); } else if ( functionWhitelist.contains(call.getFunDef().getClass())) { result = visitCallArguments(call); } turnOffVisitChildren(); return result; } private Object replaceLevelMembersReferences(ResolvedFunCall call) { LOGGER.debug("replaceLevelMembersReferences " + call); Level level = ((LevelExpr) call.getArg(0)).getLevel(); Id setId = createSetId(level); Formula formula = query.findFormula(setId.toString()); Exp exp = Util.createExpr(formula.getNamedSet()); return query.createValidator().validate(exp, false); } private Object visitCallArguments(ResolvedFunCall call) { Exp[] exps = call.getArgs(); LOGGER.debug("visitCallArguments " + call); for (int i = 0; i < exps.length; i++) { Exp transformedExp = (Exp) exps[i].accept(this); if (transformedExp != null) { exps[i] = transformedExp; } } if (exps.length > 1 && call.getFunDef() instanceof SetFunDef) { return flattenSetFunDef(call); } return null; } private Object flattenSetFunDef(ResolvedFunCall call) { List newArgs = new ArrayList(); flattenSetMembers(newArgs, call.getArgs()); addSentinelMembers(newArgs); if (newArgs.size() != call.getArgCount()) { return new ResolvedFunCall( call.getFunDef(), newArgs.toArray(new Exp[newArgs.size()]), call.getType()); } return null; } private void flattenSetMembers(List result, Exp[] args) { for (Exp arg : args) { if (arg instanceof ResolvedFunCall && ((ResolvedFunCall)arg).getFunDef() instanceof SetFunDef) { flattenSetMembers(result, ((ResolvedFunCall)arg).getArgs()); } else { result.add(arg); } } } private void addSentinelMembers(List args) { Exp prev = args.get(0); for (int i = 1; i < args.size(); i++) { Exp curr = args.get(i); if (prev.toString().equals(curr.toString())) { OlapElement element = null; if (curr instanceof NamedSetExpr) { element = ((NamedSetExpr) curr).getNamedSet(); } else if (curr instanceof MemberExpr) { element = ((MemberExpr) curr).getMember(); } if (element != null) { Level level = element.getHierarchy().getLevels()[0]; Id memberId = createSentinelId(level); Formula formula = query.findFormula(memberId.toString()); args.add(i++, Util.createExpr(formula.getMdxMember())); } } prev = curr; } } } static class TransformFromFormulasVisitor extends MdxVisitorImpl { private final Query query; private final ExpCompiler compiler; public TransformFromFormulasVisitor(Query query, ExpCompiler compiler) { LOGGER.debug("---- TransformFromFormulasVisitor constructor"); this.query = query; this.compiler = compiler; } @Override public Object visit(ResolvedFunCall call) { LOGGER.debug("visit " + call); Object result; result = visitCallArguments(call); turnOffVisitChildren(); return result; } @Override public Object visit(NamedSetExpr namedSetExpr) { String exprName = namedSetExpr.getNamedSet().getName(); Exp membersExpr; if (exprName.contains(SET_NAME_PREFIX)) { String levelMembers = exprName.replaceAll( SET_NAME_PREFIX, "\\[") .replaceAll("_$", "\\]") .replaceAll("_", "\\]\\.\\[") + ".members"; membersExpr = query.getConnection().parseExpression(levelMembers); membersExpr = compiler.getValidator().validate(membersExpr, false); } else { membersExpr = namedSetExpr.getNamedSet().getExp(); } return membersExpr; } private Object visitCallArguments(ResolvedFunCall call) { Exp[] exps = call.getArgs(); LOGGER.debug("visitCallArguments " + call); for (int i = 0; i < exps.length; i++) { Exp transformedExp = (Exp) exps[i].accept(this); if (transformedExp != null) { exps[i] = transformedExp; } } return null; } } private static class SubstitutionMap { private final Map map = new HashMap(); public boolean isEmpty() { return map.isEmpty(); } public boolean contains(Member member) { return map.containsKey(toKey(member)); } public Level get(Member member) { return map.get(toKey(member)); } public Level put(Id id, Level level) { return map.put(toKey(id), level); } public Collection values() { return map.values(); } @Override public String toString() { return map.toString(); } private String toKey(Id id) { return id.toString(); } private String toKey(Member member) { return member.getUniqueName(); } } public static class CrossJoinAnalyzer { private final int arity; private final Member[] tempTuple; private final List tempTupleAsList; private final int[] nativeIndices; private final int resultLimit; private final List> nativeMembers; private final ReassemblyGuide reassemblyGuide; private final TupleList resultList; public CrossJoinAnalyzer( TupleList simplifiedList, SubstitutionMap substitutionMap) { long nativizeMaxResults = MondrianProperties.instance().NativizeMaxResults.get(); arity = simplifiedList.getArity(); tempTuple = new Member[arity]; tempTupleAsList = Arrays.asList(tempTuple); resultLimit = nativizeMaxResults <= 0 ? Integer.MAX_VALUE : (int) Math.min(nativizeMaxResults, Integer.MAX_VALUE); resultList = TupleCollections.createList(arity); reassemblyGuide = classifyMembers(simplifiedList, substitutionMap); nativeMembers = findNativeMembers(); nativeIndices = findNativeIndices(); } public ReassemblyGuide classifyMembers( TupleList simplifiedList, SubstitutionMap substitutionMap) { ReassemblyGuide guide = new ReassemblyGuide(0); List cmdTuple = new ArrayList(arity); for (List srcTuple : simplifiedList) { cmdTuple.clear(); for (Member mbr : srcTuple) { cmdTuple.add(zz(substitutionMap, mbr)); } guide.addCommandTuple(cmdTuple); } return guide; } private ReassemblyCommand zz( SubstitutionMap substitutionMap, Member mbr) { ReassemblyCommand c; if (substitutionMap.contains(mbr)) { c = new ReassemblyCommand( substitutionMap.get(mbr), LEVEL_MEMBERS); } else if (mbr.getName().startsWith(SENTINEL_PREFIX)) { c = new ReassemblyCommand(mbr, SENTINEL); } else { NativeElementType nativeType = !isNativeCompatible(mbr) ? NON_NATIVE : mbr.getMemberType() == Member.MemberType.REGULAR ? ENUMERATED_VALUE : OTHER_NATIVE; c = new ReassemblyCommand(mbr, nativeType); } return c; } private List> findNativeMembers() { List> nativeMembers = new ArrayList>(arity); for (int i = 0; i < arity; i++) { nativeMembers.add(new LinkedHashSet()); } findNativeMembers(reassemblyGuide, nativeMembers); return nativeMembers; } private void findNativeMembers( ReassemblyGuide guide, List> nativeMembers) { List commands = guide.getCommands(); Set typesToAdd = ReassemblyCommand.getMemberTypes(commands); if (typesToAdd.contains(LEVEL_MEMBERS)) { typesToAdd.remove(ENUMERATED_VALUE); } int index = guide.getIndex(); for (ReassemblyCommand command : commands) { NativeElementType type = command.getMemberType(); if (type.isNativeCompatible() && typesToAdd.contains(type)) { nativeMembers.get(index).add(command.getElementName()); } if (command.hasNextGuide()) { findNativeMembers(command.forNextCol(), nativeMembers); } } } private int[] findNativeIndices() { int[] indices = new int[arity]; int nativeColCount = 0; for (int i = 0; i < arity; i++) { Collection natives = nativeMembers.get(i); if (!natives.isEmpty()) { indices[nativeColCount++] = i; } } if (nativeColCount == arity) { return indices; } int[] result = new int[nativeColCount]; System.arraycopy(indices, 0, result, 0, nativeColCount); return result; } private boolean isNativeCompatible(Member member) { return member.isParentChildLeaf() || (!member.isMeasure() && !member.isCalculated() && !member.isAll()); } private String getCrossJoinExpression() { return formatCrossJoin(nativeMembers); } private String formatCrossJoin(List> memberLists) { StringBuilder buf = new StringBuilder(); String left = toCsv(memberLists.get(0)); String right = memberLists.size() == 1 ? "" : formatCrossJoin(memberLists.subList(1, memberLists.size())); if (left.length() == 0) { buf.append(right); } else { if (right.length() == 0) { buf.append("{").append(left).append("}"); } else { buf.append("CrossJoin(") .append("{").append(left).append("},") .append(right).append(")"); } } return buf.toString(); } private TupleList mergeCalcMembers(TupleList nativeValues) { TupleList nativeList = adaptList(nativeValues, arity, nativeIndices); dumpListToLog("native list", nativeList); mergeCalcMembers(reassemblyGuide, new Range(nativeList), null); dumpListToLog("result list", resultList); return resultList; } private void mergeCalcMembers( ReassemblyGuide guide, Range range, Set> history) { int col = guide.getIndex(); if (col == arity - 1) { if (history == null) { appendMembers(guide, range); } else { appendMembers(guide, range, history); } return; } for (ReassemblyCommand command : guide.getCommands()) { ReassemblyGuide nextGuide = command.forNextCol(); tempTuple[col] = null; switch (command.getMemberType()) { case NON_NATIVE: tempTuple[col] = command.getMember(); mergeCalcMembers( nextGuide, range, (history == null ? new HashSet>() : history)); break; case ENUMERATED_VALUE: Member value = command.getMember(); Range valueRange = range.subRangeForValue(value, col); if (!valueRange.isEmpty()) { mergeCalcMembers(nextGuide, valueRange, history); } break; case LEVEL_MEMBERS: Level level = command.getLevel(); Range levelRange = range.subRangeForValue(level, col); for (Range subRange : levelRange.subRanges(col)) { mergeCalcMembers(nextGuide, subRange, history); } break; case OTHER_NATIVE: for (Range subRange : range.subRanges(col)) { mergeCalcMembers(nextGuide, subRange, history); } break; default: throw Util.unexpected(command.getMemberType()); } } } private void appendMembers(ReassemblyGuide guide, Range range) { int col = guide.getIndex(); for (ReassemblyCommand command : guide.getCommands()) { switch (command.getMemberType()) { case NON_NATIVE: tempTuple[col] = command.getMember(); appendTuple(range.getTuple(), tempTupleAsList); break; case ENUMERATED_VALUE: Member value = command.getMember(); Range valueRange = range.subRangeForValue(value, col); if (!valueRange.isEmpty()) { appendTuple(valueRange.getTuple()); } break; case LEVEL_MEMBERS: case OTHER_NATIVE: for (List tuple : range.getTuples()) { appendTuple(tuple); } break; default: throw Util.unexpected(command.getMemberType()); } } } private void appendMembers( ReassemblyGuide guide, Range range, Set> history) { int col = guide.getIndex(); for (ReassemblyCommand command : guide.getCommands()) { switch (command.getMemberType()) { case NON_NATIVE: tempTuple[col] = command.getMember(); if (range.isEmpty()) { appendTuple(tempTupleAsList, history); } else { appendTuple(range.getTuple(), tempTupleAsList, history); } break; case ENUMERATED_VALUE: Member value = command.getMember(); Range valueRange = range.subRangeForValue(value, col); if (!valueRange.isEmpty()) { appendTuple( valueRange.getTuple(), tempTupleAsList, history); } break; case LEVEL_MEMBERS: case OTHER_NATIVE: tempTuple[col] = null; for (List tuple : range.getTuples()) { appendTuple(tuple, tempTupleAsList, history); } break; default: throw Util.unexpected(command.getMemberType()); } } } private void appendTuple( List nonNatives, Set> history) { if (history.add(nonNatives)) { appendTuple(nonNatives); } } private void appendTuple( List natives, List nonNatives, Set> history) { List copy = copyOfTuple(natives, nonNatives); if (history.add(copy)) { appendTuple(copy); } } private void appendTuple( List natives, List nonNatives) { appendTuple(copyOfTuple(natives, nonNatives)); } private void appendTuple(List tuple) { resultList.add(tuple); checkNativeResultLimit(resultList.size()); } private List copyOfTuple( List natives, List nonNatives) { Member[] copy = new Member[arity]; for (int i = 0; i < arity; i++) { copy[i] = (nonNatives.get(i) == null) ? natives.get(i) : nonNatives.get(i); } return Arrays.asList(copy); } /** * Check the resultSize against the result limit setting. Throws * LimitExceededDuringCrossjoin exception if limit exceeded. *

    * It didn't seem appropriate to use the existing Mondrian * ResultLimit property, since the meaning and use of that * property seems to be a bit ambiguous, otherwise we could * simply call Util.checkCJResultLimit. * * @param resultSize Result limit * @throws mondrian.olap.ResourceLimitExceededException * */ private void checkNativeResultLimit(int resultSize) { // Throw an exeption if the size of the crossjoin exceeds the result // limit. if (resultLimit < resultSize) { throw MondrianResource.instance() .LimitExceededDuringCrossjoin.ex(resultSize, resultLimit); } } public TupleList adaptList( final TupleList sourceList, final int destSize, final int[] destIndices) { if (sourceList.isEmpty()) { return TupleCollections.emptyList(destIndices.length); } checkNativeResultLimit(sourceList.size()); TupleList destList = new DelegatingTupleList( destSize, new AbstractList>() { @Override public List get(int index) { final List sourceTuple = sourceList.get(index); final Member[] members = new Member[destSize]; for (int i = 0; i < destIndices.length; i++) { members[destIndices[i]] = sourceTuple.get(i); } return Arrays.asList(members); } @Override public int size() { return sourceList.size(); } } ); // The mergeCalcMembers method in this file assumes that the // resultList is random access - that calls to get(n) are constant // cost, regardless of n. Unfortunately, the TraversalList objects // created by HighCardSqlTupleReader are implemented using linked // lists, leading to pathologically long run times. // This presumes that the ResultStyle is LIST if (LOGGER.isDebugEnabled()) { String sourceListType = sourceList.getClass().getSimpleName(); String sourceElementType = String.format("Member[%d]", destSize); LOGGER.debug( String.format( "returning native %s<%s> without copying to new list.", sourceListType, sourceElementType)); } return destList; } } // REVIEW: Can we remove this class, and simply use TupleList? static class Range { private final TupleList list; private final int from; private final int to; public Range(TupleList list) { this(list, 0, list.size()); } private Range(TupleList list, int from, int to) { if (from < 0) { throw new IllegalArgumentException("from is must be >= 0"); } if (to > list.size()) { throw new IllegalArgumentException( "to must be <= to list size"); } if (from > to) { throw new IllegalArgumentException("from must be <= to"); } this.list = list; this.from = from; this.to = to; } public boolean isEmpty() { return size() == 0; } public int size() { return to - from; } public List getTuple() { if (from >= list.size()) { throw new NoSuchElementException(); } return list.get(from); } public List> getTuples() { if (from == 0 && to == list.size()) { return list; } return list.subList(from, to); } public Member getMember(int cursor, int col) { return list.get(cursor).get(col); } public String toString() { return "[" + from + " : " + to + "]"; } private Range subRange(int fromRow, int toRow) { return new Range(list, fromRow, toRow); } public Range subRangeForValue(Member value, int col) { int startAt = nextMatching(value, from, col); int endAt = nextNonMatching(value, startAt + 1, col); return subRange(startAt, endAt); } public Range subRangeForValue(Level level, int col) { int startAt = nextMatching(level, from, col); int endAt = nextNonMatching(level, startAt + 1, col); return subRange(startAt, endAt); } public Range subRangeStartingAt(int startAt, int col) { Member value = list.get(startAt).get(col); int endAt = nextNonMatching(value, startAt + 1, col); return subRange(startAt, endAt); } private int nextMatching(Member value, int startAt, int col) { for (int cursor = startAt; cursor < to; cursor++) { if (value.equals(list.get(cursor).get(col))) { return cursor; } } return to; } private int nextMatching(Level level, int startAt, int col) { for (int cursor = startAt; cursor < to; cursor++) { if (level.equals(list.get(cursor).get(col).getLevel())) { return cursor; } } return to; } private int nextNonMatching(Member value, int startAt, int col) { if (value == null) { return nextNonNull(startAt, col); } for (int cursor = startAt; cursor < to; cursor++) { if (!value.equals(list.get(cursor).get(col))) { return cursor; } } return to; } private int nextNonMatching(Level level, int startAt, int col) { if (level == null) { return nextNonNull(startAt, col); } for (int cursor = startAt; cursor < to; cursor++) { if (!level.equals(list.get(cursor).get(col).getLevel())) { return cursor; } } return to; } private int nextNonNull(int startAt, int col) { for (int cursor = startAt; cursor < to; cursor++) { if (list.get(cursor).get(col) != null) { return cursor; } } return to; } public Iterable subRanges(final int col) { final Range parent = this; return new Iterable() { final int rangeCol = col; public Iterator iterator() { return new RangeIterator(parent, rangeCol); } }; } public Iterable getMembers(final int col) { return new Iterable() { public Iterator iterator() { return new Iterator() { private int cursor = from; public boolean hasNext() { return cursor < to; } public Member next() { if (!hasNext()) { throw new NoSuchElementException(); } return getMember(cursor++, col); } public void remove() { throw new UnsupportedOperationException(); } }; } }; } } public static class RangeIterator implements Iterator { private final Range parent; private final int col; private Range precomputed; public RangeIterator(Range parent, int col) { this.parent = parent; this.col = col; precomputed = next(parent.from); } public boolean hasNext() { return precomputed != null; } private Range next(int cursor) { return (cursor >= parent.to) ? null : parent.subRangeStartingAt(cursor, col); } public Range next() { if (precomputed == null) { throw new NoSuchElementException(); } Range it = precomputed; precomputed = next(precomputed.to); return it; } public void remove() { throw new UnsupportedOperationException(); } } private static class ReassemblyGuide { private final int index; private final List commands = new ArrayList(); public ReassemblyGuide(int index) { this.index = index; } public int getIndex() { return index; } public List getCommands() { return Collections.unmodifiableList(commands); } private void addCommandTuple(List commandTuple) { ReassemblyCommand curr = currentCommand(commandTuple); if (index < commandTuple.size() - 1) { curr.forNextCol(index + 1).addCommandTuple(commandTuple); } } private ReassemblyCommand currentCommand( List commandTuple) { ReassemblyCommand curr = commandTuple.get(index); ReassemblyCommand prev = commands.isEmpty() ? null : commands.get(commands.size() - 1); if (prev != null && prev.getMemberType() == SENTINEL) { commands.set(commands.size() - 1, curr); } else if (prev == null || !prev.getElement().equals(curr.getElement())) { commands.add(curr); } else { curr = prev; } return curr; } public String toString() { return "" + index + ":" + commands.toString() .replaceAll("=null", "").replaceAll("=", " ") + " "; } } private static class ReassemblyCommand { private final OlapElement element; private final String elementName; private final NativeElementType memberType; private ReassemblyGuide nextColGuide; public ReassemblyCommand( Member member, NativeElementType memberType) { this.element = member; this.memberType = memberType; this.elementName = member.toString(); } public ReassemblyCommand( Level level, NativeElementType memberType) { this.element = level; this.memberType = memberType; this.elementName = level.toString() + ".members"; } public OlapElement getElement() { return element; } public String getElementName() { return elementName; } public Member getMember() { return (Member) element; } public Level getLevel() { return (Level) element; } public boolean hasNextGuide() { return nextColGuide != null; } public ReassemblyGuide forNextCol() { return nextColGuide; } public ReassemblyGuide forNextCol(int index) { if (nextColGuide == null) { nextColGuide = new ReassemblyGuide(index); } return nextColGuide; } public NativeElementType getMemberType() { return memberType; } public static Set getMemberTypes( Collection commands) { Set types = Olap4jUtil.enumSetNoneOf(NativeElementType.class); for (ReassemblyCommand command : commands) { types.add(command.getMemberType()); } return types; } @Override public String toString() { return memberType.toString() + ": " + getElementName(); } } enum NativeElementType { LEVEL_MEMBERS(true), ENUMERATED_VALUE(true), OTHER_NATIVE(true), NON_NATIVE(false), SENTINEL(false); private final boolean isNativeCompatible; private NativeElementType(boolean isNativeCompatible) { this.isNativeCompatible = isNativeCompatible; } public boolean isNativeCompatible() { return isNativeCompatible; } } private static Id createSentinelId(Level level) { return hierarchyId(level) .append(q(createMangledName(level, SENTINEL_PREFIX))); } private static Id createMemberId(Level level) { return hierarchyId(level) .append(q(createMangledName(level, MEMBER_NAME_PREFIX))); } private static Id createSetId(Level level) { return new Id( q(createMangledName(level, SET_NAME_PREFIX))); } private static Id hierarchyId(Level level) { Id id = new Id(q(level.getDimension().getName())); if (MondrianProperties.instance().SsasCompatibleNaming.get()) { id = id.append(q(level.getHierarchy().getName())); } return id; } private static Id.Segment q(String s) { return new Id.Segment(s, Id.Quoting.QUOTED); } private static String createMangledName(Level level, String prefix) { return prefix + level.getUniqueName().replaceAll("[\\[\\]]", "") .replaceAll("\\.", "_") + "_"; } private static void dumpListToLog( String heading, TupleList list) { if (LOGGER.isDebugEnabled()) { LOGGER.debug( String.format( "%s created with %,d rows.", heading, list.size())); StringBuilder buf = new StringBuilder(Util.nl); for (List element : list) { buf.append(Util.nl); buf.append(element); } LOGGER.debug(buf.toString()); } } private static String toCsv(Collection list) { StringBuilder buf = new StringBuilder(); String sep = ""; for (T element : list) { buf.append(sep).append(element); sep = ", "; } return buf.toString(); } private static String getLevelNameFromMemberName(String memberName) { // we assume that the last token is the level name String tokens[] = memberName.split("_"); return tokens[tokens.length - 1]; } } // End NativizeSetFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/package.html0000644000175000017500000000011211735330606022546 0ustar drazzibdrazzib Defines the set of MDX built-in functions. mondrian-3.4.1/src/main/mondrian/olap/fun/LevelDimensionFunDef.java0000644000175000017500000000255011735330606025145 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractDimensionCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the <Level>.Dimension * MDX builtin function. * * @author jhyde * @since Jul 20, 2009 */ class LevelDimensionFunDef extends FunDefBase { public static final FunDefBase INSTANCE = new LevelDimensionFunDef(); public LevelDimensionFunDef() { super( "Dimension", "Returns the dimension that contains a specified level.", "pdl"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final LevelCalc levelCalc = compiler.compileLevel(call.getArg(0)); return new AbstractDimensionCalc(call, new Calc[] {levelCalc}) { public Dimension evaluateDimension(Evaluator evaluator) { Level level = levelCalc.evaluateLevel(evaluator); return level.getDimension(); } }; } } // End LevelDimensionFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/AncestorFunDef.java0000644000175000017500000000504411735330606024007 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractMemberCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.LevelType; import mondrian.olap.type.Type; /** * Definition of the Ancestor MDX function. * * @author jhyde * @since Mar 23, 2006 */ class AncestorFunDef extends FunDefBase { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "Ancestor", "Ancestor(, {|})", "Returns the ancestor of a member at a specified level.", new String[] {"fmml", "fmmn"}, AncestorFunDef.class); public AncestorFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); final Type type1 = call.getArg(1).getType(); if (type1 instanceof LevelType) { final LevelCalc levelCalc = compiler.compileLevel(call.getArg(1)); return new AbstractMemberCalc( call, new Calc[] {memberCalc, levelCalc}) { public Member evaluateMember(Evaluator evaluator) { Level level = levelCalc.evaluateLevel(evaluator); Member member = memberCalc.evaluateMember(evaluator); int distance = member.getLevel().getDepth() - level.getDepth(); return ancestor(evaluator, member, distance, level); } }; } else { final IntegerCalc distanceCalc = compiler.compileInteger(call.getArg(1)); return new AbstractMemberCalc( call, new Calc[] {memberCalc, distanceCalc}) { public Member evaluateMember(Evaluator evaluator) { int distance = distanceCalc.evaluateInteger(evaluator); Member member = memberCalc.evaluateMember(evaluator); return ancestor(evaluator, member, distance, null); } }; } } } // End AncestorFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/AggregateFunDef.java0000644000175000017500000004701711735330606024125 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.Role.RollupPolicy; import mondrian.rolap.RolapAggregator; import mondrian.rolap.RolapEvaluator; import org.eigenbase.util.property.IntegerProperty; import java.util.*; /** * Definition of the AGGREGATE MDX function. * * @author jhyde * @since 2005/8/14 */ public class AggregateFunDef extends AbstractAggregateFunDef { private static final String TIMING_NAME = AggregateFunDef.class.getSimpleName(); static final ReflectiveMultiResolver resolver = new ReflectiveMultiResolver( "Aggregate", "Aggregate([, ])", "Returns a calculated value using the appropriate aggregate function, based on the context of the query.", new String[]{"fnx", "fnxn"}, AggregateFunDef.class); /** * Creates an AggregateFunDef. * * @param dummyFunDef Dummy function */ public AggregateFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final Calc calc = call.getArgCount() > 1 ? compiler.compileScalar(call.getArg(1), true) : new ValueCalc(call); return new AggregateCalc(call, listCalc, calc); } public static class AggregateCalc extends GenericCalc { private final ListCalc listCalc; private final Calc calc; public AggregateCalc(Exp exp, ListCalc listCalc, Calc calc) { super(exp, new Calc[]{listCalc, calc}); this.listCalc = listCalc; this.calc = calc; } public Object evaluate(Evaluator evaluator) { evaluator.getTiming().markStart(TIMING_NAME); try { TupleList list = evaluateCurrentList(listCalc, evaluator); return aggregate(calc, evaluator, list); } finally { evaluator.getTiming().markEnd(TIMING_NAME); } } /** * Computes an expression for each element of a list, and aggregates * the result according to the evaluation context's current aggregation * strategy. * * @param calc Compiled expression to evaluate a scalar * @param evaluator Evaluation context * @param tupleList List of members or tuples * @return Aggregated result */ public static Object aggregate( Calc calc, Evaluator evaluator, TupleList tupleList) { Aggregator aggregator = (Aggregator) evaluator.getProperty( Property.AGGREGATION_TYPE.name, null); if (aggregator == null) { throw newEvalException( null, "Could not find an aggregator in the current evaluation context"); } Aggregator rollup = aggregator.getRollup(); if (rollup == null) { throw newEvalException( null, "Don't know how to rollup aggregator '" + aggregator + "'"); } if (aggregator != RolapAggregator.DistinctCount) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final Object o = rollup.aggregate( evaluator, tupleList, calc); evaluator.restore(savepoint); return o; } // All that follows is logic for distinct count. It's not like the // other aggregators. if (tupleList.size() == 0) { return DoubleNull; } // Optimize the list // E.g. // List consists of: // (Gender.[All Gender], [Product].[All Products]), // (Gender.[F], [Product].[Drink]), // (Gender.[M], [Product].[Food]) // Can be optimized to: // (Gender.[All Gender], [Product].[All Products]) // // Similar optimization can also be done for list of members. if (evaluator instanceof RolapEvaluator && ((RolapEvaluator) evaluator).getDialect() .supportsUnlimitedValueList()) { // If the DBMS does not have an upper limit on IN list // predicate size, then don't attempt any list // optimization, since the current algorithm is // very slow. May want to revisit this if someone // improves the algorithm. } else { tupleList = optimizeTupleList(evaluator, tupleList); } // Can't aggregate distinct-count values in the same way // which is used for other types of aggregations. To evaluate a // distinct-count across multiple members, we need to gather // the members together, then evaluate the collection of // members all at once. To do this, we postpone evaluation, // and create a lambda function containing the members. Evaluator evaluator2 = evaluator.pushAggregation(tupleList); // cancel nonEmpty context evaluator2.setNonEmpty(false); return evaluator2.evaluateCurrent(); } /** * Analyzes a list of tuples and determines if the list can * be safely optimized. If a member of the tuple list is on * a hierarchy for which a rollup policy of PARTIAL is set, * it is not safe to optimize that list. */ private static boolean canOptimize( Evaluator evaluator, TupleList tupleList) { // If members of this hierarchy are controlled by a role which // enforces a rollup policy of partial, we cannot safely // optimize the tuples list as it might end up rolling up to // the parent while not all children are actually accessible. for (List tupleMembers : tupleList) { for (Member member : tupleMembers) { final RollupPolicy policy = evaluator.getSchemaReader().getRole() .getAccessDetails(member.getHierarchy()) .getRollupPolicy(); if (policy == RollupPolicy.PARTIAL) { return false; } } } return true; } public static TupleList optimizeTupleList( Evaluator evaluator, TupleList tupleList) { if (!canOptimize(evaluator, tupleList)) { return tupleList; } // FIXME: We remove overlapping tuple entries only to pass // AggregationOnDistinctCountMeasuresTest // .testOptimizeListWithTuplesOfLength3 on Access. Without // the optimization, we generate a statement 7000 // characters long and Access gives "Query is too complex". // The optimization is expensive, so we only want to do it // if the DBMS can't execute the query otherwise. if (false) { tupleList = removeOverlappingTupleEntries(tupleList); } tupleList = optimizeChildren( tupleList, evaluator.getSchemaReader(), evaluator.getMeasureCube()); checkIfAggregationSizeIsTooLarge(tupleList); return tupleList; } /** * In case of distinct count aggregation if a tuple which is a super * set of other tuples in the set exists then the child tuples can be * ignored. * *

    For example. A list consisting of: * (Gender.[All Gender], [Product].[All Products]), * (Gender.[F], [Product].[Drink]), * (Gender.[M], [Product].[Food]) * Can be optimized to: * (Gender.[All Gender], [Product].[All Products]) * * @param list List of tuples */ public static TupleList removeOverlappingTupleEntries( TupleList list) { TupleList trimmedList = list.cloneList(list.size()); Member[] tuple1 = new Member[list.getArity()]; Member[] tuple2 = new Member[list.getArity()]; final TupleCursor cursor1 = list.tupleCursor(); while (cursor1.forward()) { cursor1.currentToArray(tuple1, 0); if (trimmedList.isEmpty()) { trimmedList.addTuple(tuple1); } else { boolean ignore = false; final TupleIterator iterator = trimmedList.tupleIterator(); while (iterator.forward()) { iterator.currentToArray(tuple2, 0); if (isSuperSet(tuple1, tuple2)) { iterator.remove(); } else if (isSuperSet(tuple2, tuple1) || isEqual(tuple1, tuple2)) { ignore = true; break; } } if (!ignore) { trimmedList.addTuple(tuple1); } } } return trimmedList; } /** * Returns whether tuple1 is a superset of tuple2. * * @param tuple1 First tuple * @param tuple2 Second tuple * @return boolean Whether tuple1 is a superset of tuple2 */ public static boolean isSuperSet(Member[] tuple1, Member[] tuple2) { int parentLevelCount = 0; for (int i = 0; i < tuple1.length; i++) { Member member1 = tuple1[i]; Member member2 = tuple2[i]; if (!member2.isChildOrEqualTo(member1)) { return false; } if (member1.getLevel().getDepth() < member2.getLevel().getDepth()) { parentLevelCount++; } } return parentLevelCount > 0; } private static void checkIfAggregationSizeIsTooLarge(List list) { final IntegerProperty property = MondrianProperties.instance().MaxConstraints; final int maxConstraints = property.get(); if (list.size() > maxConstraints) { throw newEvalException( null, "Aggregation is not supported over a list" + " with more than " + maxConstraints + " predicates" + " (see property " + property.getPath() + ")"); } } public boolean dependsOn(Hierarchy hierarchy) { if (hierarchy.getDimension().isMeasures()) { return true; } return anyDependsButFirst(getCalcs(), hierarchy); } /** * In distinct Count aggregation, if tuple list is a result * m.children * n.children then it can be optimized to m * n * *

    * E.g. * List consist of: * (Gender.[F], [Store].[USA]), * (Gender.[F], [Store].[USA].[OR]), * (Gender.[F], [Store].[USA].[CA]), * (Gender.[F], [Store].[USA].[WA]), * (Gender.[F], [Store].[CANADA]) * (Gender.[M], [Store].[USA]), * (Gender.[M], [Store].[USA].[OR]), * (Gender.[M], [Store].[USA].[CA]), * (Gender.[M], [Store].[USA].[WA]), * (Gender.[M], [Store].[CANADA]) * Can be optimized to: * (Gender.[All Gender], [Store].[USA]) * (Gender.[All Gender], [Store].[CANADA]) * * * @param tuples Tuples * @param reader Schema reader * @param baseCubeForMeasure Cube * @return xxxx */ public static TupleList optimizeChildren( TupleList tuples, SchemaReader reader, Cube baseCubeForMeasure) { Map[] membersOccurencesInTuple = membersVersusOccurencesInTuple(tuples); int tupleLength = tuples.getArity(); //noinspection unchecked Set[] sets = new Set[tupleLength]; boolean optimized = false; for (int i = 0; i < tupleLength; i++) { if (areOccurencesEqual(membersOccurencesInTuple[i].values())) { Set members = membersOccurencesInTuple[i].keySet(); int originalSize = members.size(); sets[i] = optimizeMemberSet( new LinkedHashSet(members), reader, baseCubeForMeasure); if (sets[i].size() != originalSize) { optimized = true; } } } if (optimized) { return crossProd(sets); } return tuples; } /** * Finds member occurrences in tuple and generates a map of Members * versus their occurrences in tuples. * * @param tupleList List of tuples * @return Map of the number of occurrences of each member in a tuple */ public static Map[] membersVersusOccurencesInTuple( TupleList tupleList) { int tupleLength = tupleList.getArity(); //noinspection unchecked Map[] counters = new Map[tupleLength]; for (int i = 0; i < counters.length; i++) { counters[i] = new LinkedHashMap(); } for (List tuple : tupleList) { for (int i = 0; i < tuple.size(); i++) { Member member = tuple.get(i); Map map = counters[i]; if (map.containsKey(member)) { Integer count = map.get(member); map.put(member, ++count); } else { map.put(member, 1); } } } return counters; } private static Set optimizeMemberSet( Set members, SchemaReader reader, Cube baseCubeForMeasure) { boolean didOptimize; Set membersToBeOptimized = new LinkedHashSet(); Set optimizedMembers = new LinkedHashSet(); while (members.size() > 0) { Iterator iterator = members.iterator(); Member first = iterator.next(); if (first.isAll()) { optimizedMembers.clear(); optimizedMembers.add(first); return optimizedMembers; } membersToBeOptimized.add(first); iterator.remove(); Member firstParentMember = first.getParentMember(); while (iterator.hasNext()) { Member current = iterator.next(); if (current.isAll()) { optimizedMembers.clear(); optimizedMembers.add(current); return optimizedMembers; } Member currentParentMember = current.getParentMember(); if (firstParentMember == null && currentParentMember == null || (firstParentMember != null && firstParentMember.equals(currentParentMember))) { membersToBeOptimized.add(current); iterator.remove(); } } int childCountOfParent = -1; if (firstParentMember != null) { childCountOfParent = getChildCount(firstParentMember, reader); } if (childCountOfParent != -1 && membersToBeOptimized.size() == childCountOfParent && canOptimize(firstParentMember, baseCubeForMeasure)) { optimizedMembers.add(firstParentMember); didOptimize = true; } else { optimizedMembers.addAll(membersToBeOptimized); didOptimize = false; } membersToBeOptimized.clear(); if (members.size() == 0 && didOptimize) { Set temp = members; members = optimizedMembers; optimizedMembers = temp; } } return optimizedMembers; } /** * Returns whether tuples are equal. They must have the same length. * * @param tuple1 First tuple * @param tuple2 Second tuple * @return whether tuples are equal */ private static boolean isEqual(Member[] tuple1, Member[] tuple2) { for (int i = 0; i < tuple1.length; i++) { if (!tuple1[i].getUniqueName().equals( tuple2[i].getUniqueName())) { return false; } } return true; } private static boolean canOptimize( Member parentMember, Cube baseCube) { return dimensionJoinsToBaseCube( parentMember.getDimension(), baseCube) || !parentMember.isAll(); } private static TupleList crossProd(Set[] sets) { final List tupleLists = new ArrayList(); for (Set set : sets) { tupleLists.add( new UnaryTupleList( new ArrayList(set))); } if (tupleLists.size() == 1) { return tupleLists.get(0); } return CrossJoinFunDef.mutableCrossJoin(tupleLists); } private static boolean dimensionJoinsToBaseCube( Dimension dimension, Cube baseCube) { HashSet dimensions = new HashSet(); dimensions.add(dimension); return baseCube.nonJoiningDimensions(dimensions).size() == 0; } private static int getChildCount( Member parentMember, SchemaReader reader) { int childrenCountFromCache = reader.getChildrenCountFromCache(parentMember); if (childrenCountFromCache != -1) { return childrenCountFromCache; } return reader.getMemberChildren(parentMember).size(); } } } // End AggregateFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/DescendantsFunDef.java0000644000175000017500000004027511735330606024471 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.calc.impl.UnaryTupleList; import mondrian.mdx.*; import mondrian.olap.*; import mondrian.olap.type.*; import mondrian.resource.MondrianResource; import java.util.*; /** * Definition of the Descendants MDX function. * * @author jhyde * @since Mar 23, 2006 */ class DescendantsFunDef extends FunDefBase { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "Descendants", "Descendants([, [, ]])", "Returns the set of descendants of a member at a specified level, optionally including or excluding descendants in other levels.", new String[]{"fxm", "fxml", "fxmly", "fxmn", "fxmny", "fxmey"}, DescendantsFunDef.class, Flag.getNames()); static final ReflectiveMultiResolver Resolver2 = new ReflectiveMultiResolver( "Descendants", "Descendants([, [, ]])", "Returns the set of descendants of a set of members at a specified level, optionally including or excluding descendants in other levels.", new String[]{"fxx", "fxxl", "fxxly", "fxxn", "fxxny", "fxxey"}, DescendantsFunDef.class, Flag.getNames()); public DescendantsFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Type type0 = call.getArg(0).getType(); if (type0 instanceof SetType) { final SetType setType = (SetType) type0; if (setType.getElementType() instanceof TupleType) { throw MondrianResource.instance() .DescendantsAppliedToSetOfTuples.ex(); } MemberType memberType = (MemberType) setType.getElementType(); final Hierarchy hierarchy = memberType.getHierarchy(); if (hierarchy == null) { throw MondrianResource.instance().CannotDeduceTypeOfSet.ex(); } // Convert // Descendants(, ) // into // Generate(, Descendants(.CurrentMember, )) Exp[] descendantsArgs = call.getArgs().clone(); descendantsArgs[0] = new UnresolvedFunCall( "CurrentMember", Syntax.Property, new Exp[] { new HierarchyExpr(hierarchy) }); final ResolvedFunCall generateCall = (ResolvedFunCall) compiler.getValidator().validate( new UnresolvedFunCall( "Generate", new Exp[] { call.getArg(0), new UnresolvedFunCall( "Descendants", descendantsArgs) }), false); return generateCall.accept(compiler); } final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); Flag flag = Flag.SELF; if (call.getArgCount() == 1) { flag = Flag.SELF_BEFORE_AFTER; } final boolean depthSpecified = call.getArgCount() >= 2 && call.getArg(1).getType() instanceof NumericType; final boolean depthEmpty = call.getArgCount() >= 2 && call.getArg(1).getType() instanceof EmptyType; if (call.getArgCount() >= 3) { flag = FunUtil.getLiteralArg(call, 2, Flag.SELF, Flag.class); } if (call.getArgCount() >= 2 && depthEmpty) { if (flag != Flag.LEAVES) { throw Util.newError( "depth must be specified unless DESC_FLAG is LEAVES"); } } if ((depthSpecified || depthEmpty) && flag.leaves) { final IntegerCalc depthCalc = depthSpecified ? compiler.compileInteger(call.getArg(1)) : null; return new AbstractListCalc( call, new Calc[] {memberCalc, depthCalc}) { public TupleList evaluateList(Evaluator evaluator) { final Member member = memberCalc.evaluateMember(evaluator); List result = new ArrayList(); int depth = -1; if (depthCalc != null) { depth = depthCalc.evaluateInteger(evaluator); if (depth < 0) { depth = -1; // no limit } } final SchemaReader schemaReader = evaluator.getSchemaReader(); descendantsLeavesByDepth( member, result, schemaReader, depth); hierarchizeMemberList(result, false); return new UnaryTupleList(result); } }; } else if (depthSpecified) { final IntegerCalc depthCalc = compiler.compileInteger(call.getArg(1)); final Flag flag1 = flag; return new AbstractListCalc( call, new Calc[] {memberCalc, depthCalc}) { public TupleList evaluateList(Evaluator evaluator) { final Member member = memberCalc.evaluateMember(evaluator); List result = new ArrayList(); final int depth = depthCalc.evaluateInteger(evaluator); final SchemaReader schemaReader = evaluator.getSchemaReader(); descendantsByDepth( member, result, schemaReader, depth, flag1.before, flag1.self, flag1.after, evaluator); hierarchizeMemberList(result, false); return new UnaryTupleList(result); } }; } else { final LevelCalc levelCalc = call.getArgCount() > 1 ? compiler.compileLevel(call.getArg(1)) : null; final Flag flag2 = flag; return new AbstractListCalc( call, new Calc[] {memberCalc, levelCalc}) { public TupleList evaluateList(Evaluator evaluator) { final Evaluator context = evaluator.isNonEmpty() ? evaluator : null; final Member member = memberCalc.evaluateMember(evaluator); List result = new ArrayList(); final SchemaReader schemaReader = evaluator.getSchemaReader(); final Level level = levelCalc != null ? levelCalc.evaluateLevel(evaluator) : member.getLevel(); descendantsByLevel( schemaReader, member, level, result, flag2.before, flag2.self, flag2.after, flag2.leaves, context); hierarchizeMemberList(result, false); return new UnaryTupleList(result); } }; } } private static void descendantsByDepth( Member member, List result, final SchemaReader schemaReader, final int depthLimitFinal, final boolean before, final boolean self, final boolean after, final Evaluator context) { List children = new ArrayList(); children.add(member); for (int depth = 0;; ++depth) { if (depth == depthLimitFinal) { if (self) { result.addAll(children); } if (!after) { break; // no more results after this level } } else if (depth < depthLimitFinal) { if (before) { result.addAll(children); } } else { if (after) { result.addAll(children); } else { break; // no more results after this level } } children = schemaReader.getMemberChildren(children, context); if (children.size() == 0) { break; } } } /** * Populates 'result' with the descendants at the leaf level at depth * 'depthLimit' or less. If 'depthLimit' is -1, does not apply a depth * constraint. */ private static void descendantsLeavesByDepth( final Member member, final List result, final SchemaReader schemaReader, final int depthLimit) { if (!schemaReader.isDrillable(member)) { if (depthLimit >= 0) { result.add(member); } return; } List children = new ArrayList(); children.add(member); for (int depth = 0; depthLimit == -1 || depth <= depthLimit; ++depth) { children = schemaReader.getMemberChildren(children); if (children.size() == 0) { throw Util.newInternal("drillable member must have children"); } List nextChildren = new ArrayList(); for (Member child : children) { // TODO: Implement this more efficiently. The current // implementation of isDrillable for a parent-child hierarchy // simply retrieves the children sees whether there are any, // so we end up fetching each member's children twice. if (schemaReader.isDrillable(child)) { nextChildren.add(child); } else { result.add(child); } } if (nextChildren.isEmpty()) { return; } children = nextChildren; } } /** * Finds all descendants of a member which are before/at/after a level, * and/or are leaves (have no descendants) and adds them to a result list. * * @param schemaReader Member reader * @param ancestor Member to find descendants of * @param level Level relative to which to filter, must not be null * @param result Result list * @param before Whether to output members above level * @param self Whether to output members at level * @param after Whether to output members below level * @param leaves Whether to output members which are leaves * @param context Evaluation context; determines criteria by which the * result set should be filtered */ static void descendantsByLevel( SchemaReader schemaReader, Member ancestor, Level level, List result, boolean before, boolean self, boolean after, boolean leaves, Evaluator context) { // We find the descendants of a member by making breadth-first passes // down the hierarchy. Initially the list just contains the ancestor. // Then we find its children. We add those children to the result if // they fulfill the before/self/after conditions relative to the level. // // We add a child to the "fertileMembers" list if some of its children // might be in the result. Again, this depends upon the // before/self/after conditions. // // Note that for some member readers -- notably the // RestrictedMemberReader, when it is reading a ragged hierarchy -- the // children of a member do not always belong to the same level. For // example, the children of USA include WA (a state) and Washington // (a city). This is why we repeat the before/self/after logic for // each member. final int levelDepth = level.getDepth(); List members = Collections.singletonList(ancestor); // Each pass, "fertileMembers" has the same contents as "members", // except that we omit members whose children we are not interested // in. We allocate it once, and clear it each pass, to save a little // memory allocation. if (leaves) { assert !before && !self && !after; do { List nextMembers = new ArrayList(); for (Member member : members) { final int currentDepth = member.getLevel().getDepth(); List childMembers = schemaReader.getMemberChildren(member, context); if (childMembers.size() == 0) { // this member is a leaf -- add it if (currentDepth == levelDepth) { result.add(member); } } else { // this member is not a leaf -- add its children // to the list to be considered next iteration if (currentDepth <= levelDepth) { nextMembers.addAll(childMembers); } } } members = nextMembers; } while (members.size() > 0); } else { List fertileMembers = new ArrayList(); do { fertileMembers.clear(); for (Member member : members) { final int currentDepth = member.getLevel().getDepth(); if (currentDepth == levelDepth) { if (self) { result.add(member); } if (after) { // we are interested in member's children fertileMembers.add(member); } } else if (currentDepth < levelDepth) { if (before) { result.add(member); } fertileMembers.add(member); } else { if (after) { result.add(member); fertileMembers.add(member); } } } members = schemaReader.getMemberChildren(fertileMembers, context); } while (members.size() > 0); } } /** * Enumeration of the flags allowed to the DESCENDANTS * function. */ enum Flag { SELF(true, false, false, false), AFTER(false, true, false, false), BEFORE(false, false, true, false), BEFORE_AND_AFTER(false, true, true, false), SELF_AND_AFTER(true, true, false, false), SELF_AND_BEFORE(true, false, true, false), SELF_BEFORE_AFTER(true, true, true, false), LEAVES(false, false, false, true); private final boolean self; private final boolean after; private final boolean before; private final boolean leaves; Flag(boolean self, boolean after, boolean before, boolean leaves) { this.self = self; this.after = after; this.before = before; this.leaves = leaves; } public static String[] getNames() { List names = new ArrayList(); for (Flag flags : Flag.class.getEnumConstants()) { names.add(flags.name()); } return names.toArray(new String[names.size()]); } } } // End DescendantsFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/MemberDimensionFunDef.java0000644000175000017500000000260011735330606025301 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractDimensionCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the <Measure>.Dimension * MDX builtin function. * * @author jhyde * @since Jul 20, 2009 */ class MemberDimensionFunDef extends FunDefBase { public static final FunDefBase INSTANCE = new MemberDimensionFunDef(); private MemberDimensionFunDef() { super( "Dimension", "Returns the dimension that contains a specified member.", "pdm"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractDimensionCalc(call, new Calc[] {memberCalc}) { public Dimension evaluateDimension(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); return member.getDimension(); } }; } } // End MemberDimensionFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/MedianFunDef.java0000644000175000017500000000406611735330606023431 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractDoubleCalc; import mondrian.calc.impl.ValueCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the Median MDX functions. * * @author jhyde * @since Mar 23, 2006 */ class MedianFunDef extends AbstractAggregateFunDef { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "Median", "Median([, ])", "Returns the median value of a numeric expression evaluated over a set.", new String[]{"fnx", "fnxn"}, MedianFunDef.class); public MedianFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final Calc calc = call.getArgCount() > 1 ? compiler.compileScalar(call.getArg(1), true) : new ValueCalc(call); return new AbstractDoubleCalc(call, new Calc[] {listCalc, calc}) { public double evaluateDouble(Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); TupleList list = evaluateCurrentList(listCalc, evaluator); final double percentile = percentile( evaluator, list, calc, 0.5); evaluator.restore(savepoint); return percentile; } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } }; } } // End MedianFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/CustomizedFunctionTable.java0000644000175000017500000000364011735330606025745 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2008-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.olap.FunDef; import mondrian.olap.FunTable; import java.util.HashSet; import java.util.Set; /** * Interface to build a customized function table, selecting functions from the * set of supported functions in an instance of {@link BuiltinFunTable}. * * @author Rushan Chen */ public class CustomizedFunctionTable extends FunTableImpl { Set supportedBuiltinFunctions; Set specialFunctions; public CustomizedFunctionTable(Set builtinFunctions) { supportedBuiltinFunctions = builtinFunctions; this.specialFunctions = new HashSet(); } public CustomizedFunctionTable( Set builtinFunctions, Set specialFunctions) { this.supportedBuiltinFunctions = builtinFunctions; this.specialFunctions = specialFunctions; } public void defineFunctions(Builder builder) { final FunTable builtinFunTable = BuiltinFunTable.instance(); // Includes all the keywords form builtin function table for (String reservedWord : builtinFunTable.getReservedWords()) { builder.defineReserved(reservedWord); } // Add supported builtin functions for (Resolver resolver : builtinFunTable.getResolvers()) { if (supportedBuiltinFunctions.contains(resolver.getName())) { builder.define(resolver); } } // Add special function definitions for (FunDef funDef : specialFunctions) { builder.define(funDef); } } } // End CustomizedFunctionTable.java mondrian-3.4.1/src/main/mondrian/olap/fun/TupleFunDef.java0000644000175000017500000001200111735330606023311 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractTupleCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.*; import java.io.PrintWriter; import java.util.List; /** * TupleFunDef implements the '(...)' operator which builds * tuples, as in ([Time].CurrentMember, * [Stores].[USA].[California]). * * @author jhyde * @since 3 March, 2002 */ public class TupleFunDef extends FunDefBase { private final int[] argTypes; static final ResolverImpl Resolver = new ResolverImpl(); private TupleFunDef(int[] argTypes) { super( "()", "( [, ]...)", "Parenthesis operator constructs a tuple. If there is only one member, the expression is equivalent to the member expression.", Syntax.Parentheses, Category.Tuple, argTypes); this.argTypes = argTypes; } public int getReturnCategory() { return Category.Tuple; } public int[] getParameterCategories() { return argTypes; } public void unparse(Exp[] args, PrintWriter pw) { ExpBase.unparseList(pw, args, "(", ", ", ")"); } public Type getResultType(Validator validator, Exp[] args) { // _Tuple([,]...), which is written // ([,]...), has type [Hie1] x ... x [HieN]. // // If there is only one member, it merely represents a parenthesized // expression, whose Hierarchy is that of the member. if (args.length == 1) { return TypeUtil.toMemberType(args[0].getType()); } else { MemberType[] types = new MemberType[args.length]; for (int i = 0; i < args.length; i++) { Exp arg = args[i]; types[i] = TypeUtil.toMemberType(arg.getType()); } TupleType.checkHierarchies(types); return new TupleType(types); } } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Exp[] args = call.getArgs(); final MemberCalc[] memberCalcs = new MemberCalc[args.length]; for (int i = 0; i < args.length; i++) { memberCalcs[i] = compiler.compileMember(args[i]); } return new CalcImpl(call, memberCalcs); } public static class CalcImpl extends AbstractTupleCalc { private final MemberCalc[] memberCalcs; public CalcImpl(ResolvedFunCall call, MemberCalc[] memberCalcs) { super(call, memberCalcs); this.memberCalcs = memberCalcs; } public Member[] evaluateTuple(Evaluator evaluator) { final Member[] members = new Member[memberCalcs.length]; for (int i = 0; i < members.length; i++) { final Member member = members[i] = memberCalcs[i].evaluateMember(evaluator); if (member == null || member.isNull()) { return null; } } return members; } public MemberCalc[] getMemberCalcs() { return memberCalcs; } } private static class ResolverImpl extends ResolverBase { public ResolverImpl() { super("()", null, null, Syntax.Parentheses); } public FunDef resolve( Exp[] args, Validator validator, List conversions) { // Compare with TupleFunDef.getReturnCategory(). For example, // ([Gender].members) is a set, // ([Gender].[M]) is a member, // (1 + 2) is a numeric, // but // ([Gender].[M], [Marital Status].[S]) is a tuple. if (args.length == 1) { return new ParenthesesFunDef(args[0].getCategory()); } else { final int[] argTypes = new int[args.length]; for (int i = 0; i < args.length; i++) { // Arg must be a member: // OK: ([Gender].[S], [Time].[1997]) (member, member) // OK: ([Gender], [Time]) (dimension, dimension) // Not OK: // ([Gender].[S], [Store].[Store City]) (member, level) if (!validator.canConvert( i, args[i], Category.Member, conversions)) { return null; } argTypes[i] = Category.Member; } return new TupleFunDef(argTypes); } } } } // End TupleFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/CountFunDef.java0000644000175000017500000001106511735330606023321 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractIntegerCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the Count MDX function. * * @author jhyde * @since Mar 23, 2006 */ class CountFunDef extends AbstractAggregateFunDef { static final String[] ReservedWords = new String[] {"INCLUDEEMPTY", "EXCLUDEEMPTY"}; static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "Count", "Count([, EXCLUDEEMPTY | INCLUDEEMPTY])", "Returns the number of tuples in a set, empty cells included unless the optional EXCLUDEEMPTY flag is used.", new String[]{"fnx", "fnxy"}, CountFunDef.class, ReservedWords); public CountFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Calc calc = compiler.compileAs( call.getArg(0), null, ResultStyle.ITERABLE_ANY); final boolean includeEmpty = call.getArgCount() < 2 || ((Literal) call.getArg(1)).getValue().equals( "INCLUDEEMPTY"); return new AbstractIntegerCalc( call, new Calc[] {calc}) { public int evaluateInteger(Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final int count; if (calc instanceof IterCalc) { IterCalc iterCalc = (IterCalc) calc; TupleIterable iterable = evaluateCurrentIterable(iterCalc, evaluator); count = count(evaluator, iterable, includeEmpty); } else { // must be ListCalc ListCalc listCalc = (ListCalc) calc; TupleList list = evaluateCurrentList(listCalc, evaluator); count = count(evaluator, list, includeEmpty); } evaluator.restore(savepoint); return count; } public boolean dependsOn(Hierarchy hierarchy) { // COUNT(, INCLUDEEMPTY) is straightforward -- it // depends only on the dimensions that depends // on. if (super.dependsOn(hierarchy)) { return true; } if (includeEmpty) { return false; } // COUNT(, EXCLUDEEMPTY) depends only on the // dimensions that depends on, plus all // dimensions not masked by the set. return ! calc.getType().usesHierarchy(hierarchy, true); } }; /* RME OLD STUFF final ListCalc memberListCalc = compiler.compileList(call.getArg(0)); final boolean includeEmpty = call.getArgCount() < 2 || ((Literal) call.getArg(1)).getValue().equals( "INCLUDEEMPTY"); return new AbstractIntegerCalc( call, new Calc[] {memberListCalc}) { public int evaluateInteger(Evaluator evaluator) { List memberList = evaluateCurrentList(memberListCalc, evaluator); return count(evaluator, memberList, includeEmpty); } public boolean dependsOn(Dimension dimension) { // COUNT(, INCLUDEEMPTY) is straightforward -- it // depends only on the dimensions that depends // on. if (super.dependsOn(dimension)) { return true; } if (includeEmpty) { return false; } // COUNT(, EXCLUDEEMPTY) depends only on the // dimensions that depends on, plus all // dimensions not masked by the set. if (memberListCalc.getType().usesDimension(dimension, true)) { return false; } return true; } }; */ } } // End CountFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/ExistsFunDef.java0000644000175000017500000001027511735330606023512 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.util.ArrayList; import java.util.List; /** * Definition of the EXISTS MDX function. * * @author kvu * @since Mar 23, 2008 */ class ExistsFunDef extends FunDefBase { static final Resolver resolver = new ReflectiveMultiResolver( "Exists", "Exists(, ])", "Returns the the set of tuples of the first set that exist with one or more tuples of the second set.", new String[] {"fxxx"}, ExistsFunDef.class); public ExistsFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc1 = compiler.compileList(call.getArg(0)); final ListCalc listCalc2 = compiler.compileList(call.getArg(1)); return new AbstractListCalc(call, new Calc[] {listCalc1, listCalc2}) { public TupleList evaluateList(Evaluator evaluator) { TupleList leftTuples = listCalc1.evaluateList(evaluator); if (leftTuples.isEmpty()) { return TupleCollections.emptyList(leftTuples.getArity()); } TupleList rightTuples = listCalc2.evaluateList(evaluator); if (rightTuples.isEmpty()) { return TupleCollections.emptyList(leftTuples.getArity()); } TupleList result = TupleCollections.createList(leftTuples.getArity()); List leftDims = getDimensions(leftTuples.get(0)); List rightDims = getDimensions(rightTuples.get(0)); // map dimensions of right object to those in left object // return empty list if not all of right dims can be mapped int rightSize = rightDims.size(); int [] idxmap = new int[rightSize]; for (int i = 0; i < rightSize; i++) { Dimension d = rightDims.get(i); if (leftDims.contains(d)) { idxmap[i] = leftDims.indexOf(d); } else { return TupleCollections.emptyList( leftTuples.getArity()); } } leftLoop: for (List leftTuple : leftTuples) { rightLoop: for (List rightTuple : rightTuples) { for (int i = 0; i < rightSize; i++) { Member leftMem = leftTuple.get(idxmap[i]); Member rightMem = rightTuple.get(i); if (!isOnSameHierarchyChain(leftMem, rightMem)) { // Right tuple does not match left tuple. Try // next right tuple. continue rightLoop; } } // Left tuple matches one of the right tuples. Add it // to the result. result.add(leftTuple); continue leftLoop; } } return result; } }; } private static boolean isOnSameHierarchyChain(Member mA, Member mB) { return (FunUtil.isAncestorOf(mA, mB, false))|| (FunUtil.isAncestorOf(mB, mA, false)); } private static List getDimensions(List members) { List dimensions = new ArrayList(); for (Member member : members) { dimensions.add(member.getDimension()); } return dimensions; } } // End ExistsFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/CastFunDef.java0000644000175000017500000001502311735330606023121 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.Calc; import mondrian.calc.ExpCompiler; import mondrian.calc.impl.GenericCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.Type; import mondrian.olap.type.TypeUtil; import mondrian.resource.MondrianResource; import java.util.List; /** * Definition of the CAST MDX operator. * *

    CAST 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 CStr, CInt, etc. * The syntax for this operator was inspired by the CAST operator * in the SQL standard. * *

    Examples:

      *
    • CAST(1 + 2 AS STRING)
    • *
    • CAST('12.' || '56' AS NUMERIC)
    • *
    • CAST('tr' || 'ue' AS BOOLEAN)
    • *
    * * @author jhyde * @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 CalcImpl(arg, calc, targetType); } private static RuntimeException cannotConvert( Object o, final Type targetType) { return Util.newInternal( "cannot convert value '" + o + "' to targetType '" + targetType + "'"); } public static int toInt( Object o, final Type targetType) { if (o == null) { return FunUtil.IntegerNull; } if (o instanceof String) { return Integer.parseInt((String) o); } if (o instanceof Number) { return ((Number) o).intValue(); } throw cannotConvert(o, targetType); } private static double toDouble(Object o, final Type targetType) { if (o == null) { return FunUtil.DoubleNull; } if (o instanceof String) { return Double.valueOf((String) o); } if (o instanceof Number) { return ((Number) o).doubleValue(); } throw cannotConvert(o, targetType); } public static boolean toBoolean(Object o, final Type targetType) { if (o == null) { return FunUtil.BooleanNull; } if (o instanceof Boolean) { return (Boolean) o; } if (o instanceof String) { return Boolean.valueOf((String) o); } if (o instanceof Number) { return ((Number) o).doubleValue() > 0; } throw cannotConvert(o, targetType); } /** * Resolves calls to the CAST operator. */ private static class ResolverImpl extends ResolverBase { public ResolverImpl() { super( "Cast", "Cast( AS )", "Converts values to another type.", Syntax.Cast); } public FunDef resolve( Exp[] args, Validator validator, List conversions) { 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); } } private static class CalcImpl extends GenericCalc { private final Calc calc; private final Type targetType; private final int targetCategory; public CalcImpl(Exp arg, Calc calc, Type targetType) { super(arg); this.calc = calc; this.targetType = targetType; this.targetCategory = TypeUtil.typeToCategory(targetType); } public Calc[] getCalcs() { return new Calc[] {calc}; } public Object evaluate(Evaluator evaluator) { switch (targetCategory) { case Category.String: return evaluateString(evaluator); case Category.Integer: return FunUtil.box(evaluateInteger(evaluator)); case Category.Numeric: return FunUtil.box(evaluateDouble(evaluator)); case Category.DateTime: return evaluateDateTime(evaluator); case Category.Logical: return evaluateBoolean(evaluator); default: throw Util.newInternal("category " + targetCategory); } } public String evaluateString(Evaluator evaluator) { final Object o = calc.evaluate(evaluator); if (o == null) { return null; } return String.valueOf(o); } public int evaluateInteger(Evaluator evaluator) { final Object o = calc.evaluate(evaluator); return toInt(o, targetType); } public double evaluateDouble(Evaluator evaluator) { final Object o = calc.evaluate(evaluator); return toDouble(o, targetType); } public boolean evaluateBoolean(Evaluator evaluator) { final Object o = calc.evaluate(evaluator); return toBoolean(o, targetType); } } } // End CastFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/LevelMembersFunDef.java0000644000175000017500000000251611735330606024614 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.Evaluator; import mondrian.olap.Level; /** * Definition of the <Level>.Members MDX function. * * @author jhyde * @since Jan 17, 2009 */ public class LevelMembersFunDef extends FunDefBase { public static final LevelMembersFunDef INSTANCE = new LevelMembersFunDef(); private LevelMembersFunDef() { super("Members", "Returns the set of members in a level.", "pxl"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final LevelCalc levelCalc = compiler.compileLevel(call.getArg(0)); return new AbstractListCalc(call, new Calc[] {levelCalc}) { public TupleList evaluateList(Evaluator evaluator) { Level level = levelCalc.evaluateLevel(evaluator); return levelMembers(level, evaluator, false); } }; } } // End LevelMembersFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/NamedSetCurrentFunDef.java0000644000175000017500000000426611735330606025301 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.Calc; import mondrian.calc.ExpCompiler; import mondrian.calc.impl.AbstractMemberCalc; import mondrian.calc.impl.AbstractTupleCalc; import mondrian.mdx.NamedSetExpr; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.resource.MondrianResource; /** * Definition of the <Named Set>.Current MDX * builtin function. * * @author jhyde * @since Oct 19, 2008 */ public class NamedSetCurrentFunDef extends FunDefBase { static final NamedSetCurrentFunDef instance = new NamedSetCurrentFunDef(); private NamedSetCurrentFunDef() { super( "Current", "Returns the current member or tuple of a named set.", "ptx"); } public Exp createCall(Validator validator, Exp[] args) { assert args.length == 1; final Exp arg0 = args[0]; if (!(arg0 instanceof NamedSetExpr)) { throw MondrianResource.instance().NotANamedSet.ex(); } return super.createCall(validator, args); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Exp arg0 = call.getArg(0); assert arg0 instanceof NamedSetExpr : "checked this in createCall"; final NamedSetExpr namedSetExpr = (NamedSetExpr) arg0; if (arg0.getType().getArity() == 1) { return new AbstractMemberCalc(call, new Calc[0]) { public Member evaluateMember(Evaluator evaluator) { return namedSetExpr.getEval(evaluator).currentMember(); } }; } else { return new AbstractTupleCalc(call, new Calc[0]) { public Member[] evaluateTuple(Evaluator evaluator) { return namedSetExpr.getEval(evaluator).currentTuple(); } }; } } } // End NamedSetCurrentFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/IifFunDef.java0000644000175000017500000002256411735330606022746 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2008-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.*; /** * Definition of the Iif MDX function. * * @author jhyde * @since Jan 17, 2008 */ public class IifFunDef extends FunDefBase { /** * Creates an IifFunDef. * * @param name Name of the function, for example "Members". * @param description Description of the function * @param flags Encoding of the syntactic, return, and parameter types */ protected IifFunDef( String name, String description, String flags) { super(name, description, flags); } public Type getResultType(Validator validator, Exp[] args) { // This is messy. We have already decided which variant of Iif to use, // and that involves some upcasts. For example, Iif(b, n, NULL) resolves // to the type of n. We don't want to throw it away and take the most // general type. So, for scalar types we create a type based on // returnCategory. // // But for dimensional types (member, level, hierarchy, dimension, // tuple) we want to preserve as much type information as possible, so // we recompute the type based on the common types of all args. // // FIXME: We should pass more info into this method, such as the list // of conversions computed while resolving overloadings. switch (returnCategory) { case Category.Numeric: return new NumericType(); case Category.String: return new StringType(); case Category.Logical: return new BooleanType(); default: return TypeUtil.computeCommonType( true, args[1].getType(), args[2].getType()); } } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final BooleanCalc booleanCalc = compiler.compileBoolean(call.getArg(0)); final Calc calc1 = compiler.compileAs( call.getArg(1), call.getType(), ResultStyle.ANY_LIST); final Calc calc2 = compiler.compileAs( call.getArg(2), call.getType(), ResultStyle.ANY_LIST); if (call.getType() instanceof SetType) { return new GenericIterCalc(call) { public Object evaluate(Evaluator evaluator) { final boolean b = booleanCalc.evaluateBoolean(evaluator); Calc calc = b ? calc1 : calc2; return calc.evaluate(evaluator); } public Calc[] getCalcs() { return new Calc[] {booleanCalc, calc1, calc2}; } }; } else { return new GenericCalc(call) { public Object evaluate(Evaluator evaluator) { final boolean b = booleanCalc.evaluateBoolean(evaluator); Calc calc = b ? calc1 : calc2; return calc.evaluate(evaluator); } public Calc[] getCalcs() { return new Calc[] {booleanCalc, calc1, calc2}; } }; } } // IIf(, , ) static final FunDefBase STRING_INSTANCE = new FunDefBase( "IIf", "Returns one of two string values determined by a logical test.", "fSbSS") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final BooleanCalc booleanCalc = compiler.compileBoolean(call.getArg(0)); final StringCalc calc1 = compiler.compileString(call.getArg(1)); final StringCalc calc2 = compiler.compileString(call.getArg(2)); return new AbstractStringCalc( call, new Calc[] {booleanCalc, calc1, calc2}) { public String evaluateString(Evaluator evaluator) { final boolean b = booleanCalc.evaluateBoolean(evaluator); StringCalc calc = b ? calc1 : calc2; return calc.evaluateString(evaluator); } }; } }; // IIf(, , ) static final FunDefBase NUMERIC_INSTANCE = new IifFunDef( "IIf", "Returns one of two numeric values determined by a logical test.", "fnbnn") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final BooleanCalc booleanCalc = compiler.compileBoolean(call.getArg(0)); final Calc calc1 = compiler.compileScalar(call.getArg(1), true); final Calc calc2 = compiler.compileScalar(call.getArg(2), true); return new GenericCalc(call) { public Object evaluate(Evaluator evaluator) { final boolean b = booleanCalc.evaluateBoolean(evaluator); Calc calc = b ? calc1 : calc2; return calc.evaluate(evaluator); } public Calc[] getCalcs() { return new Calc[] {booleanCalc, calc1, calc2}; } }; } }; // IIf(, , ) static final FunDefBase TUPLE_INSTANCE = new IifFunDef( "IIf", "Returns one of two tuples determined by a logical test.", "ftbtt") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final BooleanCalc booleanCalc = compiler.compileBoolean(call.getArg(0)); final Calc calc1 = compiler.compileTuple(call.getArg(1)); final Calc calc2 = compiler.compileTuple(call.getArg(2)); return new GenericCalc(call) { public Object evaluate(Evaluator evaluator) { final boolean b = booleanCalc.evaluateBoolean(evaluator); Calc calc = b ? calc1 : calc2; return calc.evaluate(evaluator); } public Calc[] getCalcs() { return new Calc[] {booleanCalc, calc1, calc2}; } }; } }; // IIf(, , ) static final FunDefBase BOOLEAN_INSTANCE = new FunDefBase( "IIf", "Returns boolean determined by a logical test.", "fbbbb") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final BooleanCalc booleanCalc = compiler.compileBoolean(call.getArg(0)); final BooleanCalc booleanCalc1 = compiler.compileBoolean(call.getArg(1)); final BooleanCalc booleanCalc2 = compiler.compileBoolean(call.getArg(2)); Calc[] calcs = {booleanCalc, booleanCalc1, booleanCalc2}; return new AbstractBooleanCalc(call, calcs) { public boolean evaluateBoolean(Evaluator evaluator) { final boolean condition = booleanCalc.evaluateBoolean(evaluator); if (condition) { return booleanCalc1.evaluateBoolean(evaluator); } else { return booleanCalc2.evaluateBoolean(evaluator); } } }; } }; // IIf(, , ) static final IifFunDef MEMBER_INSTANCE = new IifFunDef( "IIf", "Returns one of two member values determined by a logical test.", "fmbmm"); // IIf(, , ) static final IifFunDef LEVEL_INSTANCE = new IifFunDef( "IIf", "Returns one of two level values determined by a logical test.", "flbll"); // IIf(, , ) static final IifFunDef HIERARCHY_INSTANCE = new IifFunDef( "IIf", "Returns one of two hierarchy values determined by a logical test.", "fhbhh"); // IIf(, , ) static final IifFunDef DIMENSION_INSTANCE = new IifFunDef( "IIf", "Returns one of two dimension values determined by a logical test.", "fdbdd"); // IIf(, , ) static final IifFunDef SET_INSTANCE = new IifFunDef( "IIf", "Returns one of two set values determined by a logical test.", "fxbxx"); } // End IifFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/HeadTailFunDef.java0000644000175000017500000000716411735330606023711 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.calc.impl.ConstantCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.Evaluator; import mondrian.olap.FunDef; /** * Definition of the Head and Tail * MDX builtin functions. * * @author jhyde * @since Mar 23, 2006 */ class HeadTailFunDef extends FunDefBase { static final Resolver TailResolver = new ReflectiveMultiResolver( "Tail", "Tail([, ])", "Returns a subset from the end of a set.", new String[] {"fxx", "fxxn"}, HeadTailFunDef.class); static final Resolver HeadResolver = new ReflectiveMultiResolver( "Head", "Head([, < Numeric Expression >])", "Returns the first specified number of elements in a set.", new String[] {"fxx", "fxxn"}, HeadTailFunDef.class); private final boolean head; public HeadTailFunDef(FunDef dummyFunDef) { super(dummyFunDef); head = dummyFunDef.getName().equals("Head"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final IntegerCalc integerCalc = call.getArgCount() > 1 ? compiler.compileInteger(call.getArg(1)) : ConstantCalc.constantInteger(1); if (head) { return new AbstractListCalc( call, new Calc[] {listCalc, integerCalc}) { public TupleList evaluateList(Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); TupleList list = listCalc.evaluateList(evaluator); int count = integerCalc.evaluateInteger(evaluator); evaluator.restore(savepoint); return head(count, list); } }; } else { return new AbstractListCalc( call, new Calc[] {listCalc, integerCalc}) { public TupleList evaluateList(Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); TupleList list = listCalc.evaluateList(evaluator); int count = integerCalc.evaluateInteger(evaluator); evaluator.restore(savepoint); return tail(count, list); } }; } } static TupleList tail(final int count, final TupleList members) { assert members != null; final int memberCount = members.size(); if (count >= memberCount) { return members; } if (count <= 0) { return TupleCollections.emptyList(members.getArity()); } return members.subList(members.size() - count, members.size()); } static TupleList head(final int count, final TupleList members) { assert members != null; if (count <= 0) { return TupleCollections.emptyList(members.getArity()); } return members.subList(0, Math.min(count, members.size())); } } // End HeadTailFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/ReflectiveMultiResolver.java0000644000175000017500000000470011735330606025764 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.olap.*; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; /** * Resolver which uses reflection to instantiate a {@link FunDef}. * This reduces the amount of anonymous classes. * * @author jhyde * @since Mar 23, 2006 */ public class ReflectiveMultiResolver extends MultiResolver { private final Constructor constructor; private final String[] reservedWords; public ReflectiveMultiResolver( String name, String signature, String description, String[] signatures, Class clazz) { this(name, signature, description, signatures, clazz, null); } public ReflectiveMultiResolver( String name, String signature, String description, String[] signatures, Class clazz, String[] reservedWords) { super(name, signature, description, signatures); try { this.constructor = clazz.getConstructor(new Class[] {FunDef.class}); } catch (NoSuchMethodException e) { throw Util.newInternal( e, "Error while registering resolver class " + clazz); } this.reservedWords = reservedWords; } protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) { try { return (FunDef) constructor.newInstance(new Object[] {dummyFunDef}); } catch (InstantiationException e) { throw Util.newInternal( e, "Error while instantiating FunDef '" + getSignature() + "'"); } catch (IllegalAccessException e) { throw Util.newInternal( e, "Error while instantiating FunDef '" + getSignature() + "'"); } catch (InvocationTargetException e) { throw Util.newInternal( e, "Error while instantiating FunDef '" + getSignature() + "'"); } } public String[] getReservedWords() { if (reservedWords != null) { return reservedWords; } return super.getReservedWords(); } } // End ReflectiveMultiResolver.java mondrian-3.4.1/src/main/mondrian/olap/fun/Resolver.java0000644000175000017500000000750711735330606022750 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.olap.*; import java.util.List; /** * A Resolver converts a function name, invocation type, and set * of arguments into a {@link FunDef}. * * @author jhyde * @since 3 March, 2002 */ public interface Resolver { /** * Returns the name of the function or operator. */ String getName(); /** * Returns the description of the function or operator. */ String getDescription(); /** * Returns the syntax with which the function or operator was invoked. */ Syntax getSyntax(); /** * Given a particular set of arguments the function is applied to, returns * the correct overloaded form of the function. * *

    The method adds an item to conversions every * time it performs an implicit type-conversion. If there are several * candidate functions with the same signature, the validator will choose * the one which used the fewest implicit conversions.

    * * @param args Expressions which this function call is applied to. * @param validator Validator * @param conversions List of implicit conversions performed (out) * * @return The function definition which matches these arguments, or null * if no function definition that this resolver knows about matches. */ FunDef resolve( Exp[] args, Validator validator, List conversions); /** * Returns whether a particular argument must be a scalar expression. * Returns false if any of the variants of this resolver * allows a set as its kth argument; true otherwise. */ boolean requiresExpression(int k); /** * Returns an array of symbolic constants which can appear as arguments * to this function. * *

    For example, the DrilldownMember may take the symbol * RECURSIVE as an argument. Most functions do not define * any symbolic constants. * * @return An array of the names of the symbolic constants */ String[] getReservedWords(); /** * Returns a string describing the syntax of this function, for example *

    StrToSet()
    */ String getSignature(); /** * Returns a representative example of the function which this Resolver * can produce, for purposes of describing the function set. May return * null if there is no representative function, or if the Resolver has * a way to describe itself in more detail. */ FunDef getFunDef(); /** * Description of an implicit conversion that occurred while resolving an * operator call. */ public interface Conversion { /** * Returns the cost of the conversion. If there are several matching * overloads, the one with the lowest overall cost will be preferred. * * @return Cost of conversion */ int getCost(); /** * Checks the viability of implicit conversions. Converting from a * dimension to a hierarchy is valid if is only one hierarchy. */ void checkValid(); /** * Applies this conversion to its argument, modifying the argument list * in place. * * @param validator Validator * @param args Argument list */ void apply(Validator validator, List args); } } // End Resolver.java mondrian-3.4.1/src/main/mondrian/olap/fun/MondrianEvaluationException.java0000644000175000017500000000130211735330606026610 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2005 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; /** * Thrown while evaluating a cell expression * * @author jhyde, 14 June, 2002 */ public class MondrianEvaluationException extends RuntimeException { public MondrianEvaluationException() { } public MondrianEvaluationException(String s) { super(s); } } // End MondrianEvaluationException.java mondrian-3.4.1/src/main/mondrian/olap/fun/SumFunDef.java0000644000175000017500000001114211735330606022771 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractDoubleCalc; import mondrian.calc.impl.ValueCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the Sum MDX function. * * @author jhyde * @since Mar 23, 2006 */ class SumFunDef extends AbstractAggregateFunDef { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "Sum", "Sum([, ])", "Returns the sum of a numeric expression evaluated over a set.", new String[]{"fnx", "fnxn"}, SumFunDef.class); public SumFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { // What is the desired type to use to get the underlying values for (ResultStyle r : compiler.getAcceptableResultStyles()) { Calc calc; switch (r) { case ITERABLE: case ANY: // Consumer wants ITERABLE or ANY to be used //return compileCallIterable(call, compiler); calc = compileCall(call, compiler, ResultStyle.ITERABLE); if (calc != null) { return calc; } break; case MUTABLE_LIST: // Consumer wants MUTABLE_LIST calc = compileCall(call, compiler, ResultStyle.MUTABLE_LIST); if (calc != null) { return calc; } break; case LIST: // Consumer wants LIST to be used //return compileCallList(call, compiler); calc = compileCall(call, compiler, ResultStyle.LIST); if (calc != null) { return calc; } break; } } throw ResultStyleException.generate( ResultStyle.ITERABLE_LIST_MUTABLELIST_ANY, compiler.getAcceptableResultStyles()); } protected Calc compileCall( final ResolvedFunCall call, ExpCompiler compiler, ResultStyle resultStyle) { final Calc ncalc = compiler.compileIter(call.getArg(0)); if (ncalc == null) { return null; } final Calc calc = call.getArgCount() > 1 ? compiler.compileScalar(call.getArg(1), true) : new ValueCalc(call); // we may have asked for one sort of Calc, but here's what we got. if (ncalc instanceof ListCalc) { return genListCalc(call, (ListCalc) ncalc, calc); } else { return genIterCalc(call, (IterCalc) ncalc, calc); } } protected Calc genIterCalc( final ResolvedFunCall call, final IterCalc iterCalc, final Calc calc) { return new AbstractDoubleCalc(call, new Calc[] {iterCalc, calc}) { public double evaluateDouble(Evaluator evaluator) { TupleIterable iterable = evaluateCurrentIterable(iterCalc, evaluator); final int savepoint = evaluator.savepoint(); final double d = sumDouble(evaluator, iterable, calc); evaluator.restore(savepoint); return d; } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } }; } protected Calc genListCalc( final ResolvedFunCall call, final ListCalc listCalc, final Calc calc) { return new AbstractDoubleCalc(call, new Calc[] {listCalc, calc}) { public double evaluateDouble(Evaluator evaluator) { TupleList memberList = evaluateCurrentList(listCalc, evaluator); final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final double sum = sumDouble( evaluator, memberList, calc); evaluator.restore(savepoint); return sum; } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } }; } } // End SumFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/ParameterFunDef.java0000644000175000017500000002626711735330606024163 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.mdx.*; import mondrian.olap.*; import mondrian.olap.type.*; import java.util.ArrayList; import java.util.List; /** * A ParameterFunDef is a pseudo-function describing calls to * Parameter and ParamRef functions. It exists only * fleetingly, and is then converted into a {@link mondrian.olap.Parameter}. * For internal use only. * * @author jhyde * @since Feb 14, 2003 */ public class ParameterFunDef extends FunDefBase { public final String parameterName; private final Type type; public final Exp exp; public final String parameterDescription; ParameterFunDef( FunDef funDef, String parameterName, Type type, int returnCategory, Exp exp, String description) { super( funDef.getName(), funDef.getSignature(), funDef.getDescription(), funDef.getSyntax(), returnCategory, funDef.getParameterCategories()); assertPrecondition( getName().equals("Parameter") || getName().equals("ParamRef")); this.parameterName = parameterName; this.type = type; this.exp = exp; this.parameterDescription = description; } public Exp createCall(Validator validator, Exp[] args) { Parameter parameter = validator.createOrLookupParam( this.getName().equals("Parameter"), parameterName, type, exp, parameterDescription); return new ParameterExpr(parameter); } public Type getResultType(Validator validator, Exp[] args) { return type; } private static boolean isConstant(Exp typeArg) { if (typeArg instanceof LevelExpr) { // e.g. "[Time].[Quarter]" return true; } if (typeArg instanceof HierarchyExpr) { // e.g. "[Time].[By Week]" return true; } if (typeArg instanceof DimensionExpr) { // e.g. "[Time]" return true; } if (typeArg instanceof FunCall) { // e.g. "[Time].CurrentMember.Hierarchy". They probably wrote // "[Time]", and the automatic type conversion did the rest. FunCall hierarchyCall = (FunCall) typeArg; if (hierarchyCall.getFunName().equals("Hierarchy") && hierarchyCall.getArgCount() > 0 && hierarchyCall.getArg(0) instanceof FunCall) { FunCall currentMemberCall = (FunCall) hierarchyCall.getArg(0); if (currentMemberCall.getFunName().equals("CurrentMember") && currentMemberCall.getArgCount() > 0 && currentMemberCall.getArg(0) instanceof DimensionExpr) { return true; } } } return false; } public static String getParameterName(Exp[] args) { if (args[0] instanceof Literal && args[0].getCategory() == Category.String) { return (String) ((Literal) args[0]).getValue(); } else { throw Util.newInternal("Parameter name must be a string constant"); } } /** * Returns an approximate type for a parameter, based upon the 1'th * argument. Does not use the default value expression, so this method * can safely be used before the expression has been validated. */ public static Type getParameterType(Exp[] args) { if (args[1] instanceof Id) { Id id = (Id) args[1]; String[] names = id.toStringArray(); if (names.length == 1) { final String name = names[0]; if (name.equals("NUMERIC")) { return new NumericType(); } if (name.equals("STRING")) { return new StringType(); } } } else if (args[1] instanceof Literal) { final Literal literal = (Literal) args[1]; if (literal.getValue().equals("NUMERIC")) { return new NumericType(); } else if (literal.getValue().equals("STRING")) { return new StringType(); } } else if (args[1] instanceof MemberExpr) { return new MemberType(null, null, null, null); } return new StringType(); } /** * Resolves calls to the Parameter MDX function. */ public static class ParameterResolver extends MultiResolver { private static final String[] SIGNATURES = { // Parameter(string const, symbol, string[, string const]): string "fS#yS#", "fS#yS", // Parameter(string const, symbol, numeric[, string const]): numeric "fn#yn#", "fn#yn", // Parameter(string const, hierarchy constant, member[, string // const[, symbol]]): member "fm#hm#", "fm#hm", // Parameter(string const, hierarchy constant, set[, string // const]): set "fx#hx#", "fx#hx", }; public ParameterResolver() { super( "Parameter", "Parameter(, , , , )", "Returns default value of parameter.", SIGNATURES); } public String[] getReservedWords() { return new String[]{"NUMERIC", "STRING"}; } protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) { String parameterName = getParameterName(args); Exp typeArg = args[1]; int category; Type type = typeArg.getType(); switch (typeArg.getCategory()) { case Category.Dimension: case Category.Hierarchy: case Category.Level: Dimension dimension = type.getDimension(); if (!isConstant(typeArg)) { throw newEvalException( dummyFunDef, "Invalid parameter '" + parameterName + "'. Type must be a NUMERIC, STRING, or a dimension, " + "hierarchy or level"); } if (dimension == null) { throw newEvalException( dummyFunDef, "Invalid dimension for parameter '" + parameterName + "'"); } type = new MemberType( type.getDimension(), type.getHierarchy(), type.getLevel(), null); category = Category.Member; break; case Category.Symbol: String s = (String) ((Literal) typeArg).getValue(); if (s.equalsIgnoreCase("NUMERIC")) { category = Category.Numeric; type = new NumericType(); break; } else if (s.equalsIgnoreCase("STRING")) { category = Category.String; type = new StringType(); break; } // fall through and throw error default: // Error is internal because the function call has already been // type-checked. throw newEvalException( dummyFunDef, "Invalid type for parameter '" + parameterName + "'; expecting NUMERIC, STRING or a hierarchy"); } // Default value Exp exp = args[2]; Validator validator = createSimpleValidator(BuiltinFunTable.instance()); final List conversionList = new ArrayList(); String typeName = Category.instance.getName(category).toUpperCase(); if (!validator.canConvert(2, exp, category, conversionList)) { throw newEvalException( dummyFunDef, "Default value of parameter '" + parameterName + "' is inconsistent with its type, " + typeName); } if (exp.getCategory() == Category.Set && category == Category.Member) { // Default value is a set; take this an indication that // the type is 'set of '. type = new SetType(type); } if (category == Category.Member) { Type expType = exp.getType(); if (expType instanceof SetType) { expType = ((SetType) expType).getElementType(); } if (distinctFrom(type.getDimension(), expType.getDimension()) || distinctFrom(type.getHierarchy(), expType.getHierarchy()) || distinctFrom(type.getLevel(), expType.getLevel())) { throw newEvalException( dummyFunDef, "Default value of parameter '" + parameterName + "' is not consistent with the parameter type '" + type); } } String parameterDescription = null; if (args.length > 3) { if (args[3] instanceof Literal && args[3].getCategory() == Category.String) { parameterDescription = (String) ((Literal) args[3]).getValue(); } else { throw newEvalException( dummyFunDef, "Description of parameter '" + parameterName + "' must be a string constant"); } } return new ParameterFunDef( dummyFunDef, parameterName, type, category, exp, parameterDescription); } private static boolean distinctFrom(T t1, T t2) { return t1 != null && t2 != null && !t1.equals(t2); } } /** * Resolves calls to the ParamRef MDX function. */ public static class ParamRefResolver extends MultiResolver { public ParamRefResolver() { super( "ParamRef", "ParamRef()", "Returns the current value of this parameter. If it is null, returns the default value.", new String[]{"fv#"}); } protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) { String parameterName = getParameterName(args); return new ParameterFunDef( dummyFunDef, parameterName, null, Category.Unknown, null, null); } } } // End ParameterFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/AsFunDef.java0000644000175000017500000000636711735330606022605 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractIterCalc; import mondrian.mdx.NamedSetExpr; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.util.List; /** * Definition of the AS MDX operator. * *

    Using AS, you can define an alias for an MDX expression * anywhere it appears in a query, and use that alias as you would a calculated * yet. * * @author jhyde * @since Oct 7, 2009 */ class AsFunDef extends FunDefBase { public static final Resolver RESOLVER = new ResolverImpl(); private final Query.ScopedNamedSet scopedNamedSet; /** * Creates an AsFunDef. * * @param scopedNamedSet Named set definition */ private AsFunDef(Query.ScopedNamedSet scopedNamedSet) { super( "AS", " AS ", "Assigns an alias to an expression", "ixxn"); this.scopedNamedSet = scopedNamedSet; } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { // Argument 0, the definition of the set, has been resolved since the // scoped named set was created. Implicit conversions, like converting // a member to a set, have been performed. Use the new expression. scopedNamedSet.setExp(call.getArg(0)); return new AbstractIterCalc(call, new Calc[0]) { public TupleIterable evaluateIterable( Evaluator evaluator) { final Evaluator.NamedSetEvaluator namedSetEvaluator = evaluator.getNamedSetEvaluator(scopedNamedSet, false); return namedSetEvaluator.evaluateTupleIterable(); } }; } private static class ResolverImpl extends ResolverBase { public ResolverImpl() { super("AS", null, null, Syntax.Infix); } public FunDef resolve( Exp[] args, Validator validator, List conversions) { final Exp exp = args[0]; if (!validator.canConvert( 0, args[0], Category.Set, conversions)) { return null; } // By the time resolve is called, the id argument has already been // resolved... to a named set, namely itself. That's not pretty. // We'd rather it stayed as an id, and we'd rather that a named set // was not visible in the scope that defines it. But we can work // with this. final String name = ((NamedSetExpr) args[1]).getNamedSet().getName(); final Query.ScopedNamedSet scopedNamedSet = (Query.ScopedNamedSet) ((NamedSetExpr) args[1]).getNamedSet(); // validator.getQuery().createScopedNamedSet( // name, (QueryPart) exp, exp); return new AsFunDef(scopedNamedSet); } } } // End AsFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/LevelHierarchyFunDef.java0000644000175000017500000000301211735330606025130 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2007 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractHierarchyCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the <Level>.Hierarchy MDX builtin function. * * @author jhyde * @since Mar 23, 2006 */ public class LevelHierarchyFunDef extends FunDefBase { static final LevelHierarchyFunDef instance = new LevelHierarchyFunDef(); private LevelHierarchyFunDef() { super("Hierarchy", "Returns a level's hierarchy.", "phl"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final LevelCalc levelCalc = compiler.compileLevel(call.getArg(0)); return new CalcImpl(call, levelCalc); } public static class CalcImpl extends AbstractHierarchyCalc { private final LevelCalc levelCalc; public CalcImpl(Exp exp, LevelCalc levelCalc) { super(exp, new Calc[] {levelCalc}); this.levelCalc = levelCalc; } public Hierarchy evaluateHierarchy(Evaluator evaluator) { Level level = levelCalc.evaluateLevel(evaluator); return level.getHierarchy(); } } } // End LevelHierarchyFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/GenerateFunDef.java0000644000175000017500000001546711735330606023775 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.*; import java.util.*; /** * Definition of the Generate MDX function. * * @author jhyde * @since Mar 23, 2006 */ class GenerateFunDef extends FunDefBase { static final ReflectiveMultiResolver ListResolver = new ReflectiveMultiResolver( "Generate", "Generate(, [, ALL])", "Applies a set to each member of another set and joins the resulting sets by union.", new String[] {"fxxx", "fxxxy"}, GenerateFunDef.class); static final ReflectiveMultiResolver StringResolver = new ReflectiveMultiResolver( "Generate", "Generate(, [, ])", "Applies a set to a string expression and joins resulting sets by string concatenation.", new String[] {"fSxS", "fSxSS"}, GenerateFunDef.class); private static final String[] ReservedWords = new String[] {"ALL"}; public GenerateFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Type getResultType(Validator validator, Exp[] args) { final Type type = args[1].getType(); if (type instanceof StringType) { // Generate(, [, ]) return type; } else { final Type memberType = TypeUtil.toMemberOrTupleType(type); return new SetType(memberType); } } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final IterCalc iterCalc = compiler.compileIter(call.getArg(0)); if (call.getArg(1).getType() instanceof StringType) { final StringCalc stringCalc = compiler.compileString(call.getArg(1)); final StringCalc delimCalc; if (call.getArgCount() == 3) { delimCalc = compiler.compileString(call.getArg(2)); } else { delimCalc = ConstantCalc.constantString(""); } return new GenerateStringCalcImpl( call, (IterCalc) iterCalc, stringCalc, delimCalc); } else { final ListCalc listCalc2 = compiler.compileList(call.getArg(1)); final String literalArg = getLiteralArg(call, 2, "", ReservedWords); final boolean all = literalArg.equalsIgnoreCase("ALL"); final int arityOut = call.getType().getArity(); return new GenerateListCalcImpl( call, iterCalc, listCalc2, arityOut, all); } } private static class GenerateListCalcImpl extends AbstractListCalc { private final IterCalc iterCalc1; private final ListCalc listCalc2; private final int arityOut; private final boolean all; public GenerateListCalcImpl( ResolvedFunCall call, IterCalc iterCalc, ListCalc listCalc2, int arityOut, boolean all) { super(call, new Calc[]{iterCalc, listCalc2}); this.iterCalc1 = iterCalc; this.listCalc2 = listCalc2; this.arityOut = arityOut; this.all = all; } public TupleList evaluateList(Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final TupleIterable iterable1 = iterCalc1.evaluateIterable(evaluator); evaluator.restore(savepoint); TupleList result = TupleCollections.createList(arityOut); if (all) { final TupleCursor cursor = iterable1.tupleCursor(); while (cursor.forward()) { cursor.setContext(evaluator); final TupleList result2 = listCalc2.evaluateList(evaluator); result.addAll(result2); } } else { final Set> emitted = new HashSet>(); final TupleCursor cursor = iterable1.tupleCursor(); while (cursor.forward()) { cursor.setContext(evaluator); final TupleList result2 = listCalc2.evaluateList(evaluator); addDistinctTuples(result, result2, emitted); } } evaluator.restore(savepoint); return result; } private static void addDistinctTuples( TupleList result, TupleList result2, Set> emitted) { for (List row : result2) { // wrap array for correct distinctness test if (emitted.add(row)) { result.add(row); } } } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } } private static class GenerateStringCalcImpl extends AbstractStringCalc { private final IterCalc iterCalc; private final StringCalc stringCalc; private final StringCalc sepCalc; public GenerateStringCalcImpl( ResolvedFunCall call, IterCalc iterCalc, StringCalc stringCalc, StringCalc sepCalc) { super(call, new Calc[]{iterCalc, stringCalc}); this.iterCalc = iterCalc; this.stringCalc = stringCalc; this.sepCalc = sepCalc; } public String evaluateString(Evaluator evaluator) { StringBuilder buf = new StringBuilder(); int k = 0; final TupleIterable iter11 = iterCalc.evaluateIterable(evaluator); final int savepoint = evaluator.savepoint(); final TupleCursor cursor = iter11.tupleCursor(); while (cursor.forward()) { cursor.setContext(evaluator); if (k++ > 0) { String sep = sepCalc.evaluateString(evaluator); buf.append(sep); } final String result2 = stringCalc.evaluateString(evaluator); buf.append(result2); } evaluator.restore(savepoint); return buf.toString(); } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } } } // End GenerateFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/FunUtil.java0000644000175000017500000033652711735330606022544 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.DelegatingTupleList; import mondrian.calc.impl.UnaryTupleList; import mondrian.mdx.*; import mondrian.olap.*; import mondrian.olap.type.*; import mondrian.resource.MondrianResource; import mondrian.rolap.RolapHierarchy; import mondrian.util.*; import org.apache.commons.collections.ComparatorUtils; import org.apache.commons.collections.comparators.ComparatorChain; import org.apache.log4j.Logger; import java.util.*; /** * {@code FunUtil} contains a set of methods useful within the {@code * mondrian.olap.fun} package. * * @author jhyde * @since 1.0 */ public class FunUtil extends Util { private static final String SORT_TIMING_NAME = "Sort"; private static final String SORT_EVAL_TIMING_NAME = "EvalForSort"; static final String[] emptyStringArray = new String[0]; private static final boolean debug = false; public static final NullMember NullMember = new NullMember(); /** * Special value which indicates that a {@code double} computation * has returned the MDX null value. See {@link DoubleCalc}. */ public static final double DoubleNull = 0.000000012345; /** * Special value which indicates that a {@code double} computation * has returned the MDX EMPTY value. See {@link DoubleCalc}. */ public static final double DoubleEmpty = -0.000000012345; /** * Special value which indicates that an {@code int} computation * has returned the MDX null value. See {@link mondrian.calc.IntegerCalc}. */ public static final int IntegerNull = Integer.MIN_VALUE + 1; /** * Null value in three-valued boolean logic. * Actually, a placeholder until we actually implement 3VL. */ public static final boolean BooleanNull = false; /** * Creates an exception which indicates that an error has occurred while * executing a given function. * * @param funDef Function being executed * @param message Explanatory message * @return Exception that can be used as a cell result */ public static RuntimeException newEvalException( FunDef funDef, String message) { Util.discard(funDef); // TODO: use this return new MondrianEvaluationException(message); } /** * Creates an exception which indicates that an error has occurred while * executing a given function. * * @param throwable Exception * @return Exception that can be used as a cell result */ public static RuntimeException newEvalException(Throwable throwable) { return new MondrianEvaluationException( throwable.getClass().getName() + ": " + throwable.getMessage()); } /** * Creates an exception which indicates that an error has occurred while * executing a given function. * * @param message Explanatory message * @param throwable Exception * @return Exception that can be used as a cell result */ public static RuntimeException newEvalException( String message, Throwable throwable) { return new MondrianEvaluationException( message + ": " + Util.getErrorMessage(throwable)); } public static void checkIterListResultStyles(Calc calc) { switch (calc.getResultStyle()) { case ITERABLE: case LIST: case MUTABLE_LIST: break; default: throw ResultStyleException.generateBadType( ResultStyle.ITERABLE_LIST_MUTABLELIST, calc.getResultStyle()); } } public static void checkListResultStyles(Calc calc) { switch (calc.getResultStyle()) { case LIST: case MUTABLE_LIST: break; default: throw ResultStyleException.generateBadType( ResultStyle.LIST_MUTABLELIST, calc.getResultStyle()); } } /** * Returns an argument whose value is a literal. */ static String getLiteralArg( ResolvedFunCall call, int i, String defaultValue, String[] allowedValues) { if (i >= call.getArgCount()) { if (defaultValue == null) { throw newEvalException( call.getFunDef(), "Required argument is missing"); } else { return defaultValue; } } Exp arg = call.getArg(i); if (!(arg instanceof Literal) || arg.getCategory() != Category.Symbol) { throw newEvalException( call.getFunDef(), "Expected a symbol, found '" + arg + "'"); } String s = (String) ((Literal) arg).getValue(); StringBuilder sb = new StringBuilder(64); for (int j = 0; j < allowedValues.length; j++) { String allowedValue = allowedValues[j]; if (allowedValue.equalsIgnoreCase(s)) { return allowedValue; } if (j > 0) { sb.append(", "); } sb.append(allowedValue); } throw newEvalException( call.getFunDef(), "Allowed values are: {" + sb + "}"); } /** * Returns the ordinal of a literal argument. If the argument does not * belong to the supplied enumeration, returns -1. */ static > E getLiteralArg( ResolvedFunCall call, int i, E defaultValue, Class allowedValues) { if (i >= call.getArgCount()) { if (defaultValue == null) { throw newEvalException( call.getFunDef(), "Required argument is missing"); } else { return defaultValue; } } Exp arg = call.getArg(i); if (!(arg instanceof Literal) || arg.getCategory() != Category.Symbol) { throw newEvalException( call.getFunDef(), "Expected a symbol, found '" + arg + "'"); } String s = (String) ((Literal) arg).getValue(); for (E e : allowedValues.getEnumConstants()) { if (e.name().equalsIgnoreCase(s)) { return e; } } StringBuilder buf = new StringBuilder(64); int k = 0; for (E e : allowedValues.getEnumConstants()) { if (k++ > 0) { buf.append(", "); } buf.append(e.name()); } throw newEvalException( call.getFunDef(), "Allowed values are: {" + buf + "}"); } /** * Throws an error if the expressions don't have the same hierarchy. * @param left * @param right * @throws MondrianEvaluationException if expressions don't have the same * hierarchy */ static void checkCompatible(Exp left, Exp right, FunDef funDef) { final Type leftType = TypeUtil.stripSetType(left.getType()); final Type rightType = TypeUtil.stripSetType(right.getType()); if (!TypeUtil.isUnionCompatible(leftType, rightType)) { throw newEvalException( funDef, "Expressions must have the same hierarchy"); } } /** * Adds every element of {@code right} which is not in {@code set} * to both {@code set} and {@code left}. */ static void addUnique( TupleList left, TupleList right, Set> set) { assert left != null; assert right != null; if (right.isEmpty()) { return; } for (int i = 0, n = right.size(); i < n; i++) { List o = right.get(i); if (set.add(o)) { left.add(o); } } } /** * Returns the default hierarchy of a dimension, or null if there is no * default. * * @see MondrianResource#CannotImplicitlyConvertDimensionToHierarchy * * @param dimension Dimension * @return Default hierarchy, or null */ public static Hierarchy getDimensionDefaultHierarchy(Dimension dimension) { final Hierarchy[] hierarchies = dimension.getHierarchies(); if (hierarchies.length == 1) { return hierarchies[0]; } if (MondrianProperties.instance().SsasCompatibleNaming.get()) { // In SSAS 2005, dimensions with more than one hierarchy do not have // a default hierarchy. return null; } for (Hierarchy hierarchy : hierarchies) { if (hierarchy.getName() == null || hierarchy.getUniqueName().equals(dimension.getUniqueName())) { return hierarchy; } } return null; } static List addMembers( final SchemaReader schemaReader, final List members, final Hierarchy hierarchy) { // only add accessible levels for (Level level : schemaReader.getHierarchyLevels(hierarchy)) { addMembers(schemaReader, members, level); } return members; } static List addMembers( SchemaReader schemaReader, List members, Level level) { List levelMembers = schemaReader.getLevelMembers(level, true); members.addAll(levelMembers); return members; } /** * Removes every member from a list which is calculated. * The list must not be null, and must consist only of members. * * @param memberList Member list * @return List of non-calculated members */ static List removeCalculatedMembers(List memberList) { List clone = new ArrayList(); for (Member member : memberList) { if (member.isCalculated()) { continue; } clone.add(member); } return clone; } /** * Removes every tuple from a list which is calculated. * The list must not be null, and must consist only of members. * * @param memberList Member list * @return List of non-calculated members */ static TupleList removeCalculatedMembers(TupleList memberList) { if (memberList.getArity() == 1) { return new UnaryTupleList( removeCalculatedMembers( memberList.slice(0))); } else { final TupleList clone = memberList.cloneList(memberList.size()); outer: for (List members : memberList) { for (Member member : members) { if (member.isCalculated()) { continue outer; } } clone.add(members); } return clone; } } /** * Returns whether {@code m0} is an ancestor of {@code m1}. * * @param strict if true, a member is not an ancestor of itself */ static boolean isAncestorOf(Member m0, Member m1, boolean strict) { if (strict) { if (m1 == null) { return false; } m1 = m1.getParentMember(); } while (m1 != null) { if (m1.equals(m0)) { return true; } m1 = m1.getParentMember(); } return false; } /** * For each member in a list, evaluates an expression and creates a map * from members to values. * *

    If the list contains tuples, use * {@link #evaluateTuples(mondrian.olap.Evaluator, mondrian.calc.Calc, mondrian.calc.TupleList)}. * * @param evaluator Evaluation context * @param exp Expression to evaluate * @param memberIter Iterable over the collection of members * @param memberList List to be populated with members, or null * @param parentsToo If true, evaluate the expression for all ancestors * of the members as well * * @pre exp != null * @pre exp.getType() instanceof ScalarType */ static Map evaluateMembers( Evaluator evaluator, Calc exp, Iterable memberIter, List memberList, boolean parentsToo) { // REVIEW: is this necessary? final int savepoint = evaluator.savepoint(); assert exp.getType() instanceof ScalarType; Map mapMemberToValue = new HashMap(); for (Member member : memberIter) { if (memberList != null) { memberList.add(member); } while (true) { evaluator.setContext(member); Object result = exp.evaluate(evaluator); if (result == null) { result = Util.nullValue; } mapMemberToValue.put(member, result); if (!parentsToo) { break; } member = member.getParentMember(); if (member == null) { break; } if (mapMemberToValue.containsKey(member)) { break; } } } evaluator.restore(savepoint); return mapMemberToValue; } /** * For each tuple in a list, evaluates an expression and creates a map * from tuples to values. * * @param evaluator Evaluation context * @param exp Expression to evaluate * @param tuples List of tuples * * @pre exp != null * @pre exp.getType() instanceof ScalarType */ static Map, Object> evaluateTuples( Evaluator evaluator, Calc exp, TupleList tuples) { final int savepoint = evaluator.savepoint(); assert exp.getType() instanceof ScalarType; final Map, Object> mapMemberToValue = new HashMap, Object>(); for (int i = 0, count = tuples.size(); i < count; i++) { List tuple = tuples.get(i); evaluator.setContext(tuple); Object result = exp.evaluate(evaluator); if (result == null) { result = Util.nullValue; } mapMemberToValue.put(tuple, result); } evaluator.restore(savepoint); return mapMemberToValue; } /** * Helper function to sort a list of members according to an expression. * *

    NOTE: This function does not preserve the contents of the validator. * *

    If you do not specify {@code memberList}, the method * will build its own member list as it iterates over {@code memberIter}. * It is acceptable if {@code memberList} and {@code memberIter} are the * same list object. * *

    If you specify {@code memberList}, the list is sorted in place, and * memberList is returned. * * @param evaluator Evaluator * @param memberIter Iterable over members * @param memberList List of members * @param exp Expression to sort on * @param desc Whether to sort descending * @param brk Whether to break * @return sorted list (never null) */ static List sortMembers( Evaluator evaluator, Iterable memberIter, List memberList, Calc exp, boolean desc, boolean brk) { if ((memberList != null) && (memberList.size() <= 1)) { return memberList; } evaluator.getTiming().markStart(SORT_EVAL_TIMING_NAME); boolean timingEval = true; boolean timingSort = false; try { // REVIEW mberkowitz 1/09: test whether precomputing // values saves time. Map mapMemberToValue; final boolean parentsToo = !brk; if (memberList == null) { memberList = new ArrayList(); mapMemberToValue = evaluateMembers( evaluator, exp, memberIter, memberList, parentsToo); } else { mapMemberToValue = evaluateMembers( evaluator, exp, memberIter, null, parentsToo); } MemberComparator comp; if (brk) { comp = new BreakMemberComparator(evaluator, exp, desc); } else { comp = new HierarchicalMemberComparator(evaluator, exp, desc); } comp.preloadValues(mapMemberToValue); evaluator.getTiming().markEnd(SORT_EVAL_TIMING_NAME); timingEval = false; evaluator.getTiming().markStart(SORT_TIMING_NAME); timingSort = true; Collections.sort(memberList, comp.wrap()); return memberList; } finally { if (timingEval) { evaluator.getTiming().markEnd(SORT_EVAL_TIMING_NAME); } else if (timingSort) { evaluator.getTiming().markEnd(SORT_TIMING_NAME); } } } /** * Sorts a list of members according to a list of SortKeySpecs. * An in-place, Stable sort. * Helper function for MDX OrderSet function. * *

    NOTE: Does not preserve the contents of the validator. */ static List sortMembers( Evaluator evaluator, Iterable memberIter, List memberList, List keySpecList) { if ((memberList != null) && (memberList.size() <= 1)) { return memberList; } if (memberList == null) { memberList = new ArrayList(); for (Member member : memberIter) { memberList.add(member); } if (memberList.size() <= 1) { return memberList; } } ComparatorChain chain = new ComparatorChain(); for (SortKeySpec key : keySpecList) { boolean brk = key.direction.brk; MemberComparator comp; if (brk) { comp = new BreakMemberComparator( evaluator, key.key, key.direction.descending); } else { comp = new HierarchicalMemberComparator( evaluator, key.key, key.direction.descending); } comp.preloadValues(memberList); chain.addComparator(comp.wrap(), false); } Collections.sort(memberList, chain); return memberList; } /** * Sorts a list of Tuples by the value of an applied expression. Stable * sort. * *

    Helper function for MDX functions TopCount, TopSum, TopPercent, * BottomCount, BottomSum, BottomPercent, but not the MDX function Order. * *

    NOTE: This function does not preserve the contents of the validator. * *

    If you specify {@code tupleList}, the list is sorted in place, and * tupleList is returned. * * @param evaluator Evaluator * @param tupleIterable Iterator over tuples * @param tupleList List of tuples, if known, otherwise null * @param exp Expression to sort on * @param desc Whether to sort descending * @param brk Whether to break * @param arity Number of members in each tuple * @return sorted list (never null) */ public static TupleList sortTuples( Evaluator evaluator, TupleIterable tupleIterable, TupleList tupleList, Calc exp, boolean desc, boolean brk, int arity) { // NOTE: This method does not implement the iterable/list concept // as fully as sortMembers. This is because sortMembers evaluates all // sort expressions up front. There, it is efficient to unravel the // iterator and evaluate the sort expressions at the same time. List> tupleArrayList; if (tupleList == null) { tupleArrayList = new ArrayList>(); final TupleCursor cursor = tupleIterable.tupleCursor(); while (cursor.forward()) { tupleArrayList.add(cursor.current()); } if (tupleArrayList.size() <= 1) { return new DelegatingTupleList( tupleIterable.getArity(), tupleArrayList); } } else { if (tupleList.size() <= 1) { return tupleList; } tupleArrayList = tupleList; } @SuppressWarnings({"unchecked"}) List[] tuples = tupleArrayList.toArray(new List[tupleArrayList.size()]); final DelegatingTupleList result = new DelegatingTupleList( tupleIterable.getArity(), Arrays.asList(tuples)); Comparator> comparator; if (brk) { BreakTupleComparator c = new BreakTupleComparator(evaluator, exp, arity); c.preloadValues(result); comparator = c.wrap(); if (desc) { comparator = Collections.reverseOrder(comparator); } } else { comparator = new HierarchicalTupleComparator(evaluator, exp, arity, desc) .wrap(); } Arrays.sort(tuples, comparator); return result; } /** * Partially sorts a list of Members by the value of an applied expression. * *

    Avoids sorting the whole list, finds only the ntop (or bottom) * valued Members, and returns them as a new List. Helper function for MDX * functions TopCount and BottomCount. * * @param list a list of members * @param exp a Calc applied to each member to find its sort-key * @param evaluator Evaluator * @param limit maximum count of members to return. * @param desc true to sort descending (and find TopCount), false to sort * ascending (and find BottomCount). * @return the top or bottom members, as a new list. *

    NOTE: Does not preserve the contents of the validator. */ public static List partiallySortMembers( Evaluator evaluator, List list, Calc exp, int limit, boolean desc) { evaluator.getTiming().markStart(SORT_EVAL_TIMING_NAME); boolean timingEval = true; boolean timingSort = false; try { MemberComparator comp = new BreakMemberComparator(evaluator, exp, desc); Map valueMap = evaluateMembers(evaluator, exp, list, null, false); evaluator.getTiming().markEnd(SORT_EVAL_TIMING_NAME); timingEval = false; evaluator.getTiming().markStart(SORT_TIMING_NAME); timingSort = true; comp.preloadValues(valueMap); return stablePartialSort(list, comp.wrap(), limit); } finally { if (timingEval) { evaluator.getTiming().markEnd(SORT_EVAL_TIMING_NAME); } else if (timingSort) { evaluator.getTiming().markEnd(SORT_TIMING_NAME); } } } /** * Helper function to sort a list of tuples according to a list * of expressions and a list of sorting flags. * *

    NOTE: This function does not preserve the contents of the validator. */ static TupleList sortTuples( Evaluator evaluator, TupleIterable tupleIter, TupleList tupleList, List keySpecList, int arity) { if (tupleList == null) { tupleList = TupleCollections.createList(arity); TupleCursor cursor = tupleIter.tupleCursor(); while (cursor.forward()) { tupleList.addCurrent(cursor); } } if (tupleList.size() <= 1) { return tupleList; } ComparatorChain chain = new ComparatorChain(); for (SortKeySpec key : keySpecList) { boolean brk = key.direction.brk; boolean orderByKey = key.key instanceof MemberOrderKeyFunDef.CalcImpl; if (brk) { TupleExpMemoComparator comp = new BreakTupleComparator(evaluator, key.key, arity); comp.preloadValues(tupleList); chain.addComparator(comp.wrap(), key.direction.descending); } else if (orderByKey) { TupleExpMemoComparator comp = new HierarchicalTupleKeyComparator( evaluator, key.key, arity); comp.preloadValues(tupleList); chain.addComparator(comp.wrap(), key.direction.descending); } else { TupleExpComparator comp = new HierarchicalTupleComparator( evaluator, key.key, arity, key.direction.descending); chain.addComparator(comp.wrap(), false); } } Collections.sort(tupleList, chain); return tupleList; } /** * Partially sorts a list of Tuples by the value of an applied expression. * *

    Avoids sorting the whole list, finds only the n top (or bottom) * valued Tuples, and returns them as a new List. Helper function for MDX * functions TopCount and BottomCount. * *

    NOTE: Does not preserve the contents of the validator. The returned * list is immutable. * * @param evaluator Evaluator * @param list a list of tuples * @param exp a Calc applied to each tple to find its sort-key * @param limit maximum count of tuples to return. * @param desc true to sort descending (and find TopCount), * false to sort ascending (and find BottomCount). * @return the top or bottom tuples, as a new list. */ public static List> partiallySortTuples( Evaluator evaluator, TupleList list, Calc exp, int limit, boolean desc) { Comparator> comp = new BreakTupleComparator(evaluator, exp, list.getArity()).wrap(); if (desc) { comp = Collections.reverseOrder(comp); } return stablePartialSort(list, comp, limit); } /** * Sorts a list of members into hierarchical order. The members must belong * to the same dimension. * * @param memberList List of members * @param post Whether to sort in post order; if false, sorts in pre order * * @see #hierarchizeTupleList(mondrian.calc.TupleList, boolean) */ public static void hierarchizeMemberList( List memberList, boolean post) { if (memberList.size() <= 1) { return; } if (memberList.get(0).getDimension().isHighCardinality()) { return; } Comparator comparator = new HierarchizeComparator(post); Collections.sort(memberList, comparator); } /** * Sorts a list of tuples into hierarchical order. * * @param tupleList List of tuples * @param post Whether to sort in post order; if false, sorts in pre order * * @see #hierarchizeMemberList(java.util.List, boolean) */ public static TupleList hierarchizeTupleList( TupleList tupleList, boolean post) { if (tupleList.isEmpty()) { TupleCollections.emptyList(tupleList.getArity()); } final TupleList fixedList = tupleList.fix(); if (tupleList.getArity() == 1) { hierarchizeMemberList(fixedList.slice(0), post); return fixedList; } Comparator> comparator = new HierarchizeTupleComparator(fixedList.getArity(), post).wrap(); Collections.sort(fixedList, comparator); return fixedList; } /** * Compares double-precision values according to MDX semantics. * *

    MDX requires a total order: *

    * -inf < NULL < ... < -1 < ... < 0 < ... < NaN < * +inf *
    * but this is different than Java semantics, specifically with regard * to {@link Double#NaN}. */ public static int compareValues(double d1, double d2) { if (Double.isNaN(d1)) { if (d2 == Double.POSITIVE_INFINITY) { return -1; } else if (Double.isNaN(d2)) { return 0; } else { return 1; } } else if (Double.isNaN(d2)) { if (d1 == Double.POSITIVE_INFINITY) { return 1; } else { return -1; } } else if (d1 == d2) { return 0; } else if (d1 == FunUtil.DoubleNull) { if (d2 == Double.NEGATIVE_INFINITY) { return 1; } else { return -1; } } else if (d2 == FunUtil.DoubleNull) { if (d1 == Double.NEGATIVE_INFINITY) { return -1; } else { return 1; } } else if (d1 < d2) { return -1; } else { return 1; } } /** * Compares two cell values. * *

    Nulls compare last, exceptions (including the * object which indicates the the cell is not in the cache yet) next, * then numbers and strings are compared by value. * * @param value0 First cell value * @param value1 Second cell value * @return -1, 0, or 1, depending upon whether first cell value is less * than, equal to, or greater than the second */ public static int compareValues(Object value0, Object value1) { if (value0 == value1) { return 0; } // null is less than anything else if (value0 == null) { return -1; } if (value1 == null) { return 1; } if (value0 instanceof RuntimeException || value1 instanceof RuntimeException) { // one of the values is not in cache; continue as best as we can return 0; } else if (value0 == Util.nullValue) { return -1; // null == -infinity } else if (value1 == Util.nullValue) { return 1; // null == -infinity } else if (value0 instanceof String) { return ((String) value0).compareToIgnoreCase((String) value1); } else if (value0 instanceof Number) { return FunUtil.compareValues( ((Number) value0).doubleValue(), ((Number) value1).doubleValue()); } else if (value0 instanceof Date) { return ((Date) value0).compareTo((Date) value1); } else if (value0 instanceof OrderKey) { return ((OrderKey) value0).compareTo(value1); } else { throw Util.newInternal("cannot compare " + value0); } } /** * Turns the mapped values into relative values (percentages) for easy * use by the general topOrBottom function. This might also be a useful * function in itself. */ static void toPercent( TupleList members, Map, Object> mapMemberToValue) { double total = 0; int memberCount = members.size(); for (int i = 0; i < memberCount; i++) { final List key = members.get(i); final Object o = mapMemberToValue.get(key); if (o instanceof Number) { total += ((Number) o).doubleValue(); } } for (int i = 0; i < memberCount; i++) { final List key = members.get(i); final Object o = mapMemberToValue.get(key); if (o instanceof Number) { double d = ((Number) o).doubleValue(); mapMemberToValue.put( key, d / total * (double) 100); } } } /** * Decodes the syntactic type of an operator. * * @param flags A encoded string which represents an operator signature, * as used by the {@code flags} parameter used to construct a * {@link FunDefBase}. * * @return A {@link Syntax} */ public static Syntax decodeSyntacticType(String flags) { char c = flags.charAt(0); switch (c) { case 'p': return Syntax.Property; case 'f': return Syntax.Function; case 'm': return Syntax.Method; case 'i': return Syntax.Infix; case 'P': return Syntax.Prefix; case 'Q': return Syntax.Postfix; case 'I': return Syntax.Internal; default: throw newInternal( "unknown syntax code '" + c + "' in string '" + flags + "'"); } } /** * Decodes the signature of a function into a category code which describes * the return type of the operator. * *

    For example, decodeReturnType("fnx") returns * {@link Category#Numeric}, indicating this function has a * numeric return value. * * @param flags The signature of an operator, * as used by the {@code flags} parameter used to construct a * {@link FunDefBase}. * * @return An array {@link Category} codes. */ public static int decodeReturnCategory(String flags) { final int returnCategory = decodeCategory(flags, 1); if ((returnCategory & Category.Mask) != returnCategory) { throw newInternal("bad return code flag in flags '" + flags + "'"); } return returnCategory; } /** * Decodes the {@code offset}th character of an encoded method * signature into a type category. * *

    The codes are: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    a{@link Category#Array}
    d{@link Category#Dimension}
    h{@link Category#Hierarchy}
    l{@link Category#Level}
    b{@link Category#Logical}
    m{@link Category#Member}
    NConstant {@link Category#Numeric}
    n{@link Category#Numeric}
    x{@link Category#Set}
    #Constant {@link Category#String}
    S{@link Category#String}
    t{@link Category#Tuple}
    v{@link Category#Value}
    y{@link Category#Symbol}
    * * @param flags Encoded signature string * @param offset 0-based offset of character within string * @return A {@link Category} */ public static int decodeCategory(String flags, int offset) { char c = flags.charAt(offset); switch (c) { case 'a': return Category.Array; case 'd': return Category.Dimension; case 'h': return Category.Hierarchy; case 'l': return Category.Level; case 'b': return Category.Logical; case 'm': return Category.Member; case 'N': return Category.Numeric | Category.Constant; case 'n': return Category.Numeric; case 'I': return Category.Numeric | Category.Integer | Category.Constant; case 'i': return Category.Numeric | Category.Integer; case 'x': return Category.Set; case '#': return Category.String | Category.Constant; case 'S': return Category.String; case 't': return Category.Tuple; case 'v': return Category.Value; case 'y': return Category.Symbol; case 'U': return Category.Null; case 'e': return Category.Empty; case 'D': return Category.DateTime; default: throw newInternal( "unknown type code '" + c + "' in string '" + flags + "'"); } } /** * Decodes a string of parameter types into an array of type codes. * *

    Each character is decoded using {@link #decodeCategory(String, int)}. * For example, decodeParameterTypes("nx") returns * {{@link Category#Numeric}, {@link Category#Set}}. * * @param flags The signature of an operator, * as used by the {@code flags} parameter used to construct a * {@link FunDefBase}. * * @return An array {@link Category} codes. */ public static int[] decodeParameterCategories(String flags) { int[] parameterCategories = new int[flags.length() - 2]; for (int i = 0; i < parameterCategories.length; i++) { parameterCategories[i] = decodeCategory(flags, i + 2); } return parameterCategories; } /** * Converts a double (primitive) value to a Double. {@link #DoubleNull} * becomes null. */ public static Double box(double d) { return d == DoubleNull ? null : d; } /** * Converts an int (primitive) value to an Integer. {@link #IntegerNull} * becomes null. */ public static Integer box(int n) { return n == IntegerNull ? null : n; } static double percentile( Evaluator evaluator, TupleList members, Calc exp, double p) { SetWrapper sw = evaluateSet(evaluator, members, exp); if (sw.errorCount > 0) { return Double.NaN; } else if (sw.v.size() == 0) { return FunUtil.DoubleNull; } double[] asArray = new double[sw.v.size()]; for (int i = 0; i < asArray.length; i++) { asArray[i] = (Double) sw.v.get(i); } Arrays.sort(asArray); /* * The median is defined as the value that has exactly the same * number of entries before it in the sorted list as after. * So, if the number of entries in the list is odd, the * median is the entry at (length-1)/2 (using zero-based indexes). * If the number of entries is even, the median is defined as the * arithmetic mean of the two numbers in the middle of the list, or * (entries[length/2 - 1] + entries[length/2]) / 2. */ int length = asArray.length; if (p == 0.5) { // Special case for median. if ((length & 1) == 1) { // The length is odd. Note that length/2 is an integer // expression, and it's positive so we save ourselves a divide. return asArray[length >> 1]; } else { return (asArray[(length >> 1) - 1] + asArray[length >> 1]) / 2.0; } } else if (p <= 0.0) { return asArray[0]; } else if (p >= 1.0) { return asArray[length - 1]; } else { final double jD = Math.floor(length * p); int j = (int) jD; double alpha = (p - jD) * length; assert alpha >= 0; assert alpha <= 1; return asArray[j] * (1.0 - alpha) + asArray[j + 1] * alpha; } } /** * Returns the member which lies upon a particular quartile according to a * given expression. * * @param evaluator Evaluator * @param members List of members * @param exp Expression to rank members * @param range Quartile (1, 2 or 3) * * @pre range >= 1 && range <= 3 */ protected static double quartile( Evaluator evaluator, TupleList members, Calc exp, int range) { assert range >= 1 && range <= 3; SetWrapper sw = evaluateSet(evaluator, members, exp); if (sw.errorCount > 0) { return Double.NaN; } else if (sw.v.size() == 0) { return DoubleNull; } double[] asArray = new double[sw.v.size()]; for (int i = 0; i < asArray.length; i++) { asArray[i] = ((Double) sw.v.get(i)).doubleValue(); } Arrays.sort(asArray); // get a quartile, median is a second q double dm = 0.25 * asArray.length * range; int median = (int) Math.floor(dm); return dm == median && median < asArray.length - 1 ? (asArray[median] + asArray[median + 1]) / 2 : asArray[median]; } public static Object min( Evaluator evaluator, TupleList members, Calc calc) { SetWrapper sw = evaluateSet(evaluator, members, calc); if (sw.errorCount > 0) { return Double.NaN; } else { final int size = sw.v.size(); if (size == 0) { return Util.nullValue; } else { Double min = (Double) sw.v.get(0); for (int i = 1; i < size; i++) { Double iValue = (Double) sw.v.get(i); if (iValue < min) { min = iValue; } } return min; } } } public static Object max( Evaluator evaluator, TupleList members, Calc exp) { SetWrapper sw = evaluateSet(evaluator, members, exp); if (sw.errorCount > 0) { return Double.NaN; } else { final int size = sw.v.size(); if (size == 0) { return Util.nullValue; } else { Double max = (Double) sw.v.get(0); for (int i = 1; i < size; i++) { Double iValue = (Double) sw.v.get(i); if (iValue > max) { max = iValue; } } return max; } } } static Object var( Evaluator evaluator, TupleList members, Calc exp, boolean biased) { SetWrapper sw = evaluateSet(evaluator, members, exp); return _var(sw, biased); } private static Object _var(SetWrapper sw, boolean biased) { if (sw.errorCount > 0) { return new Double(Double.NaN); } else if (sw.v.size() == 0) { return Util.nullValue; } else { double stdev = 0.0; double avg = _avg(sw); for (int i = 0; i < sw.v.size(); i++) { stdev += Math.pow((((Double) sw.v.get(i)).doubleValue() - avg), 2); } int n = sw.v.size(); if (!biased) { n--; } return new Double(stdev / (double) n); } } static double correlation( Evaluator evaluator, TupleList memberList, Calc exp1, Calc exp2) { SetWrapper sw1 = evaluateSet(evaluator, memberList, exp1); SetWrapper sw2 = evaluateSet(evaluator, memberList, exp2); Object covar = _covariance(sw1, sw2, false); Object var1 = _var(sw1, false); //this should be false, yes? Object var2 = _var(sw2, false); if ((covar instanceof Double) && (var1 instanceof Double) && (var2 instanceof Double)) { return ((Double) covar).doubleValue() / Math.sqrt( ((Double) var1).doubleValue() * ((Double) var2).doubleValue()); } else { return DoubleNull; } } static Object covariance( Evaluator evaluator, TupleList members, Calc exp1, Calc exp2, boolean biased) { final int savepoint = evaluator.savepoint(); SetWrapper sw1 = evaluateSet(evaluator, members, exp1); evaluator.restore(savepoint); SetWrapper sw2 = evaluateSet(evaluator, members, exp2); evaluator.restore(savepoint); // todo: because evaluateSet does not add nulls to the SetWrapper, this // solution may lead to mismatched lists and is therefore not robust return _covariance(sw1, sw2, biased); } private static Object _covariance( SetWrapper sw1, SetWrapper sw2, boolean biased) { if (sw1.v.size() != sw2.v.size()) { return Util.nullValue; } double avg1 = _avg(sw1); double avg2 = _avg(sw2); double covar = 0.0; for (int i = 0; i < sw1.v.size(); i++) { // all of this casting seems inefficient - can we make SetWrapper // contain an array of double instead? double diff1 = (((Double) sw1.v.get(i)).doubleValue() - avg1); double diff2 = (((Double) sw2.v.get(i)).doubleValue() - avg2); covar += (diff1 * diff2); } int n = sw1.v.size(); if (!biased) { n--; } return new Double(covar / (double) n); } static Object stdev( Evaluator evaluator, TupleList members, Calc exp, boolean biased) { Object o = var(evaluator, members, exp, biased); return (o instanceof Double) ? new Double(Math.sqrt(((Double) o).doubleValue())) : o; } public static Object avg( Evaluator evaluator, TupleList members, Calc calc) { SetWrapper sw = evaluateSet(evaluator, members, calc); return (sw.errorCount > 0) ? new Double(Double.NaN) : (sw.v.size() == 0) ? Util.nullValue : new Double(_avg(sw)); } // TODO: parameterize inclusion of nulls; also, maybe make _avg a method of // setwrapper, so we can cache the result (i.e. for correl) private static double _avg(SetWrapper sw) { double sum = 0.0; for (int i = 0; i < sw.v.size(); i++) { sum += ((Double) sw.v.get(i)).doubleValue(); } //todo: should look at context and optionally include nulls return sum / (double) sw.v.size(); } public static Object sum( Evaluator evaluator, TupleList members, Calc exp) { double d = sumDouble(evaluator, members, exp); return d == DoubleNull ? Util.nullValue : new Double(d); } public static double sumDouble( Evaluator evaluator, TupleList members, Calc exp) { SetWrapper sw = evaluateSet(evaluator, members, exp); if (sw.errorCount > 0) { return Double.NaN; } else if (sw.v.size() == 0) { return DoubleNull; } else { double sum = 0.0; for (int i = 0; i < sw.v.size(); i++) { sum += ((Double) sw.v.get(i)).doubleValue(); } return sum; } } public static double sumDouble( Evaluator evaluator, TupleIterable iterable, Calc exp) { SetWrapper sw = evaluateSet(evaluator, iterable, exp); if (sw.errorCount > 0) { return Double.NaN; } else if (sw.v.size() == 0) { return DoubleNull; } else { double sum = 0.0; for (int i = 0; i < sw.v.size(); i++) { sum += ((Double) sw.v.get(i)).doubleValue(); } return sum; } } public static int count( Evaluator evaluator, TupleIterable iterable, boolean includeEmpty) { if (iterable == null) { return 0; } if (includeEmpty) { if (iterable instanceof TupleList) { return ((TupleList) iterable).size(); } else { int retval = 0; TupleCursor cursor = iterable.tupleCursor(); while (cursor.forward()) { retval++; } return retval; } } else { int retval = 0; TupleCursor cursor = iterable.tupleCursor(); while (cursor.forward()) { cursor.setContext(evaluator); if (!evaluator.currentIsEmpty()) { retval++; } } return retval; } } /** * Evaluates {@code exp} (if defined) over {@code members} to * generate a {@link List} of {@link SetWrapper} objects, which contains * a {@link Double} value and meta information, unlike * {@link #evaluateMembers}, which only produces values. * * @pre exp != null */ static SetWrapper evaluateSet( Evaluator evaluator, TupleIterable members, Calc calc) { assert members != null; assert calc != null; assert calc.getType() instanceof ScalarType; // todo: treat constant exps as evaluateMembers() does SetWrapper retval = new SetWrapper(); final TupleCursor cursor = members.tupleCursor(); while (cursor.forward()) { cursor.setContext(evaluator); Object o = calc.evaluate(evaluator); if (o == null || o == Util.nullValue) { retval.nullCount++; } else if (o instanceof Throwable) { // Carry on summing, so that if we are running in a // BatchingCellReader, we find out all the dependent cells we // need retval.errorCount++; } else if (o instanceof Double) { retval.v.add(o); } else if (o instanceof Number) { retval.v.add(((Number) o).doubleValue()); } else { retval.v.add(o); } } return retval; } /** * Evaluates one or more expressions against the member list returning * a SetWrapper array. Where this differs very significantly from the * above evaluateSet methods is how it count null values and Throwables; * this method adds nulls to the SetWrapper Vector rather than not adding * anything - as the above method does. The impact of this is that if, for * example, one was creating a list of x,y values then each list will have * the same number of values (though some might be null) - this allows * higher level code to determine how to handle the lack of data rather than * having a non-equal number (if one is plotting x,y values it helps to * have the same number and know where a potential gap is the data is. */ static SetWrapper[] evaluateSet( Evaluator evaluator, TupleList list, DoubleCalc[] calcs) { Util.assertPrecondition(calcs != null, "calcs != null"); // todo: treat constant exps as evaluateMembers() does SetWrapper[] retvals = new SetWrapper[calcs.length]; for (int i = 0; i < calcs.length; i++) { retvals[i] = new SetWrapper(); } final TupleCursor cursor = list.tupleCursor(); while (cursor.forward()) { cursor.setContext(evaluator); for (int i = 0; i < calcs.length; i++) { DoubleCalc calc = calcs[i]; SetWrapper retval = retvals[i]; double o = calc.evaluateDouble(evaluator); if (o == FunUtil.DoubleNull) { retval.nullCount++; retval.v.add(null); } else { retval.v.add(o); } // TODO: If the expression yielded an error, carry on // summing, so that if we are running in a // BatchingCellReader, we find out all the dependent cells // we need } } return retvals; } static List periodsToDate( Evaluator evaluator, Level level, Member member) { if (member == null) { member = evaluator.getContext(level.getHierarchy()); } Member m = member; while (m != null) { if (m.getLevel() == level) { break; } m = m.getParentMember(); } // If m == null, then "level" was lower than member's level. // periodsToDate([Time].[Quarter], [Time].[1997] is valid, // but will return an empty List List members = new ArrayList(); if (m != null) { // e.g. m is [Time].[1997] and member is [Time].[1997].[Q1].[3] // we now have to make m to be the first member of the range, // so m becomes [Time].[1997].[Q1].[1] SchemaReader reader = evaluator.getSchemaReader(); m = Util.getFirstDescendantOnLevel(reader, m, member.getLevel()); reader.getMemberRange(level, m, member, members); } return members; } static List memberRange( Evaluator evaluator, Member startMember, Member endMember) { final Level level = startMember.getLevel(); assertTrue(level == endMember.getLevel()); List members = new ArrayList(); evaluator.getSchemaReader().getMemberRange( level, startMember, endMember, members); if (members.isEmpty()) { // The result is empty, so maybe the members are reversed. This is // cheaper than comparing the members before we call getMemberRange. evaluator.getSchemaReader().getMemberRange( level, endMember, startMember, members); } return members; } /** * Returns the member under ancestorMember having the same relative position * under member's parent. *

    For exmaple, cousin([Feb 2001], [Q3 2001]) is [August 2001]. * @param schemaReader The reader to use * @param member The member for which we'll find the cousin. * @param ancestorMember The cousin's ancestor. * * @return The child of {@code ancestorMember} in the same position * under {@code ancestorMember} as {@code member} is under its * parent. */ static Member cousin( SchemaReader schemaReader, Member member, Member ancestorMember) { if (ancestorMember.isNull()) { return ancestorMember; } if (member.getHierarchy() != ancestorMember.getHierarchy()) { throw MondrianResource.instance().CousinHierarchyMismatch.ex( member.getUniqueName(), ancestorMember.getUniqueName()); } if (member.getLevel().getDepth() < ancestorMember.getLevel().getDepth()) { return member.getHierarchy().getNullMember(); } Member cousin = cousin2(schemaReader, member, ancestorMember); if (cousin == null) { cousin = member.getHierarchy().getNullMember(); } return cousin; } private static Member cousin2( SchemaReader schemaReader, Member member1, Member member2) { if (member1.getLevel() == member2.getLevel()) { return member2; } Member uncle = cousin2(schemaReader, member1.getParentMember(), member2); if (uncle == null) { return null; } int ordinal = Util.getMemberOrdinalInParent(schemaReader, member1); List cousins = schemaReader.getMemberChildren(uncle); if (cousins.size() <= ordinal) { return null; } return cousins.get(ordinal); } /** * Returns the ancestor of {@code member} at the given level * or distance. It is assumed that any error checking required * has been done prior to calling this function. * *

    This method takes into consideration the fact that there * may be intervening hidden members between {@code member} * and the ancestor. If {@code targetLevel} is not null, then * the method will only return a member if the level at * {@code distance} from the member is actually the * {@code targetLevel} specified. * * @param evaluator The evaluation context * @param member The member for which the ancestor is to be found * @param distance The distance up the chain the ancestor is to * be found. * * @param targetLevel The desired targetLevel of the ancestor. If * {@code null}, then the distance completely determines the desired * ancestor. * * @return The ancestor member, or {@code null} if no such * ancestor exists. */ static Member ancestor( Evaluator evaluator, Member member, int distance, Level targetLevel) { if ((targetLevel != null) && (member.getHierarchy() != targetLevel.getHierarchy())) { throw MondrianResource.instance().MemberNotInLevelHierarchy.ex( member.getUniqueName(), targetLevel.getUniqueName()); } if (distance == 0) { /* * Shortcut if there's nowhere to go. */ return member; } else if (distance < 0) { /* * Can't go backwards. */ return member.getHierarchy().getNullMember(); } final List ancestors = new ArrayList(); final SchemaReader schemaReader = evaluator.getSchemaReader(); schemaReader.getMemberAncestors(member, ancestors); Member result = member.getHierarchy().getNullMember(); searchLoop: for (int i = 0; i < ancestors.size(); i++) { final Member ancestorMember = ancestors.get(i); if (targetLevel != null) { if (ancestorMember.getLevel() == targetLevel) { if (schemaReader.isVisible(ancestorMember)) { result = ancestorMember; break; } else { result = member.getHierarchy().getNullMember(); break; } } } else { if (schemaReader.isVisible(ancestorMember)) { distance--; // Make sure that this ancestor is really on the right // targetLevel. If a targetLevel was specified and at least // one of the ancestors was hidden, this this algorithm goes // too far up the ancestor list. It's not a problem, except // that we need to check if it's happened and return the // hierarchy's null member instead. // // For example, consider what happens with // Ancestor([Store].[Israel].[Haifa], [Store].[Store // State]). The distance from [Haifa] to [Store State] is // 1, but that lands us at the country targetLevel, which is // clearly wrong. if (distance == 0) { result = ancestorMember; break; } } } } return result; } /** * Compares a pair of members according to their positions in a * prefix-order (or postfix-order, if {@code post} is true) walk * over a hierarchy. * * @param m1 First member * @param m2 Second member * * @param post Whether to sortMembers in postfix order. If true, a parent * will sortMembers immediately after its last child. If false, a parent * will sortMembers immediately before its first child. * * @return -1 if m1 collates before m2, * 0 if m1 equals m2, * 1 if m1 collates after m2 */ public static int compareHierarchically( Member m1, Member m2, boolean post) { // Strip away the LimitedRollupMember wrapper, if it exists. The // wrapper does not implement equals and comparisons correctly. This // is safe this method has no side-effects: it just returns an int. if (m1 instanceof RolapHierarchy.LimitedRollupMember) { m1 = ((RolapHierarchy.LimitedRollupMember) m1).member; } if (m2 instanceof RolapHierarchy.LimitedRollupMember) { m2 = ((RolapHierarchy.LimitedRollupMember) m2).member; } if (equals(m1, m2)) { return 0; } while (true) { int depth1 = m1.getDepth(); int depth2 = m2.getDepth(); if (depth1 < depth2) { m2 = m2.getParentMember(); if (equals(m1, m2)) { return post ? 1 : -1; } } else if (depth1 > depth2) { m1 = m1.getParentMember(); if (equals(m1, m2)) { return post ? -1 : 1; } } else { Member prev1 = m1; Member prev2 = m2; m1 = m1.getParentMember(); m2 = m2.getParentMember(); if (equals(m1, m2)) { final int c = compareSiblingMembers(prev1, prev2); // compareHierarchically needs to impose a total order; // cannot return 0 for non-equal members assert c != 0 : "Members " + prev1 + ", " + prev2 + " are not equal, but compare returned 0."; return c; } } } } /** * Compares two members which are known to have the same parent. * * First, compare by ordinal. * This is only valid now we know they're siblings, because * ordinals are only unique within a parent. * If the dimension does not use ordinals, both ordinals * will be -1. * *

    If the ordinals do not differ, compare using regular member * comparison. * * @param m1 First member * @param m2 Second member * @return -1 if m1 collates less than m2, * 1 if m1 collates after m2, * 0 if m1 == m2. */ public static int compareSiblingMembers(Member m1, Member m2) { // calculated members collate after non-calculated final boolean calculated1 = m1.isCalculatedInQuery(); final boolean calculated2 = m2.isCalculatedInQuery(); if (calculated1) { if (!calculated2) { return 1; } } else { if (calculated2) { return -1; } } final Comparable k1 = m1.getOrderKey(); final Comparable k2 = m2.getOrderKey(); if ((k1 != null) && (k2 != null)) { return k1.compareTo(k2); } else { final int ordinal1 = m1.getOrdinal(); final int ordinal2 = m2.getOrdinal(); return (ordinal1 == ordinal2) ? m1.compareTo(m2) : (ordinal1 < ordinal2) ? -1 : 1; } } /** * Returns whether one of the members in a tuple is null. */ public static boolean tupleContainsNullMember(Member[] tuple) { for (Member member : tuple) { if (member.isNull()) { return true; } } return false; } /** * Returns whether one of the members in a tuple is null. */ public static boolean tupleContainsNullMember(List tuple) { for (Member member : tuple) { if (member.isNull()) { return true; } } return false; } public static Member[] makeNullTuple(final TupleType tupleType) { final Type[] elementTypes = tupleType.elementTypes; Member[] members = new Member[elementTypes.length]; for (int i = 0; i < elementTypes.length; i++) { MemberType type = (MemberType) elementTypes[i]; members[i] = makeNullMember(type); } return members; } static Member makeNullMember(MemberType memberType) { Hierarchy hierarchy = memberType.getHierarchy(); if (hierarchy == null) { return NullMember; } return hierarchy.getNullMember(); } /** * Validates the arguments to a function and resolves the function. * * @param validator Validator used to validate function arguments and * resolve the function * @param funDef Function definition, or null to deduce definition from * name, syntax and argument types * @param args Arguments to the function * @param newArgs Output parameter for the resolved arguments * @param name Function name * @param syntax Syntax style used to invoke function * @return resolved function definition */ public static FunDef resolveFunArgs( Validator validator, FunDef funDef, Exp[] args, Exp[] newArgs, String name, Syntax syntax) { for (int i = 0; i < args.length; i++) { newArgs[i] = validator.validate(args[i], false); } if (funDef == null || validator.alwaysResolveFunDef()) { funDef = validator.getDef(newArgs, name, syntax); } checkNativeCompatible(validator, funDef, newArgs); return funDef; } /** * Functions that dynamically return one or more members of the measures * dimension prevent us from using native evaluation. * * @param validator Validator used to validate function arguments and * resolve the function * @param funDef Function definition, or null to deduce definition from * name, syntax and argument types * @param args Arguments to the function */ private static void checkNativeCompatible( Validator validator, FunDef funDef, Exp[] args) { // If the first argument to a function is either: // 1) the measures dimension or // 2) a measures member where the function returns another member or // a set, // then these are functions that dynamically return one or more // members of the measures dimension. In that case, we cannot use // native cross joins because the functions need to be executed to // determine the resultant measures. // // As a result, we disallow functions like AllMembers applied on the // Measures dimension as well as functions like the range operator, // siblings, and lag, when the argument is a measure member. // However, we do allow functions like isEmpty, rank, and topPercent. // // Also, the Set and Parentheses functions are ok since they're // essentially just containers. Query query = validator.getQuery(); if (!(funDef instanceof SetFunDef) && !(funDef instanceof ParenthesesFunDef) && query != null && query.nativeCrossJoinVirtualCube()) { int[] paramCategories = funDef.getParameterCategories(); if (paramCategories.length > 0) { final int cat0 = paramCategories[0]; final Exp arg0 = args[0]; switch (cat0) { case Category.Dimension: case Category.Hierarchy: if (arg0 instanceof DimensionExpr && ((DimensionExpr) arg0).getDimension().isMeasures() && !(funDef instanceof HierarchyCurrentMemberFunDef)) { query.setVirtualCubeNonNativeCrossJoin(); } break; case Category.Member: if (arg0 instanceof MemberExpr && ((MemberExpr) arg0).getMember().isMeasure() && isMemberOrSet(funDef.getReturnCategory())) { query.setVirtualCubeNonNativeCrossJoin(); } break; } } } } private static boolean isMemberOrSet(int category) { return category == Category.Member || category == Category.Set; } static void appendTuple(StringBuilder buf, Member[] members) { buf.append("("); for (int j = 0; j < members.length; j++) { if (j > 0) { buf.append(", "); } Member member = members[j]; buf.append(member.getUniqueName()); } buf.append(")"); } /** * Returns whether two tuples are equal. * *

    The members are allowed to be in different positions. For example, * ([Gender].[M], [Store].[USA]) IS ([Store].[USA], * [Gender].[M]) returns {@code true}. */ static boolean equalTuple(Member[] members0, Member[] members1) { final int count = members0.length; if (count != members1.length) { return false; } outer: for (int i = 0; i < count; i++) { // First check the member at the corresponding ordinal. It is more // likely to be there. final Member member0 = members0[i]; if (member0.equals(members1[i])) { continue; } // Look for this member in other positions. // We can assume that the members in members0 are distinct (because // they belong to different dimensions), so this test is valid. for (int j = 0; j < count; j++) { if (i != j && member0.equals(members1[j])) { continue outer; } } // This member of members0 does not occur in any position of // members1. The tuples are not equal. return false; } return true; } static FunDef createDummyFunDef( Resolver resolver, int returnCategory, Exp[] args) { final int[] argCategories = ExpBase.getTypes(args); return new FunDefBase(resolver, returnCategory, argCategories) { }; } public static List getNonEmptyMemberChildren( Evaluator evaluator, Member member) { SchemaReader sr = evaluator.getSchemaReader(); if (evaluator.isNonEmpty()) { return sr.getMemberChildren(member, evaluator); } else { return sr.getMemberChildren(member); } } /** * Returns members of a level which are not empty (according to the * criteria expressed by the evaluator). * * @param evaluator Evaluator, determines non-empty criteria * @param level Level * @param includeCalcMembers Whether to include calculated members */ static List getNonEmptyLevelMembers( final Evaluator evaluator, final Level level, final boolean includeCalcMembers) { SchemaReader sr = evaluator.getSchemaReader(); if (evaluator.isNonEmpty()) { List members = sr.getLevelMembers(level, evaluator); if (includeCalcMembers) { return addLevelCalculatedMembers(sr, level, members); } return members; } return sr.getLevelMembers(level, includeCalcMembers); } static TupleList levelMembers( final Level level, final Evaluator evaluator, final boolean includeCalcMembers) { List memberList = getNonEmptyLevelMembers(evaluator, level, includeCalcMembers); TupleList tupleList; if (!includeCalcMembers) { memberList = removeCalculatedMembers(memberList); } final List memberListClone = new ArrayList(memberList); tupleList = new UnaryTupleList(memberListClone); return hierarchizeTupleList(tupleList, false); } static TupleList hierarchyMembers( Hierarchy hierarchy, Evaluator evaluator, final boolean includeCalcMembers) { TupleList tupleList = new UnaryTupleList(); final List memberList = tupleList.slice(0); if (evaluator.isNonEmpty()) { // Allow the SQL generator to generate optimized SQL since we know // we're only interested in non-empty members of this level. for (Level level : hierarchy.getLevels()) { List members = getNonEmptyLevelMembers( evaluator, level, includeCalcMembers); memberList.addAll(members); } } else { final List memberList1 = addMembers( evaluator.getSchemaReader(), new ConcatenableList(), hierarchy); if (includeCalcMembers) { memberList.addAll(memberList1); } else { // Same effect as calling removeCalculatedMembers(tupleList) // but one fewer copy of the list. for (Member member1 : memberList1) { if (!member1.isCalculated()) { memberList.add(member1); } } } } return hierarchizeTupleList(tupleList, false); } /** * Partial Sort: sorts in place an array of Objects using a given Comparator, * but only enough so that the N biggest (or smallest) items are at the start * of the array. Not a stable sort, unless the Comparator is so contrived. * * @param items will be partially-sorted in place * @param comp a Comparator; null means use natural comparison * @param limit */ static void partialSort(T[] items, Comparator comp, int limit) { if (comp == null) { //noinspection unchecked comp = (Comparator) ComparatorUtils.naturalComparator(); } new Quicksorter(items, comp).partialSort(limit); } /** * Stable partial sort of a list. Returns the desired head of the list. */ static List stablePartialSort( final List list, final Comparator comp, int limit) { assert limit >= 0; // Load an array of pairs {list-item, list-index}. // List-index is a secondary sort key, to give a stable sort. // REVIEW Can we use a simple T[], with the index implied? // REVIEW When limit is big relative to list size, faster to // mergesort. Test for this. int n = list.size(); // O(n) to scan list @SuppressWarnings({"unchecked"}) final ObjIntPair[] pairs = new ObjIntPair[n]; int i = 0; for (T item : list) { // O(n) to scan list pairs[i] = new ObjIntPair(item, i); ++i; } Comparator> pairComp = new Comparator>() { public int compare(ObjIntPair x, ObjIntPair y) { int val = comp.compare(x.t, y.t); if (val == 0) { val = x.i - y.i; } return val; } }; final int length = Math.min(limit, n); // O(n + limit * log(limit)) to quicksort partialSort(pairs, pairComp, length); // Use an abstract list to avoid doing a copy. The result is immutable. return new AbstractList() { @Override public T get(int index) { return pairs[index].t; } @Override public int size() { return length; } }; } static TupleList parseTupleList( Evaluator evaluator, String string, List hierarchies) { final IdentifierParser.TupleListBuilder builder = new IdentifierParser.TupleListBuilder( evaluator.getSchemaReader(), evaluator.getCube(), hierarchies); IdentifierParser.parseTupleList(builder, string); return builder.tupleList; } /** * Parses a tuple, of the form '(member, member, ...)'. * There must be precisely one member for each hierarchy. * * * @param evaluator Evaluator, provides a {@link mondrian.olap.SchemaReader} * and {@link mondrian.olap.Cube} * @param string String to parse * @param i Position to start parsing in string * @param members Output array of members * @param hierarchies Hierarchies of the members * @return Position where parsing ended in string */ private static int parseTuple( final Evaluator evaluator, String string, int i, final Member[] members, List hierarchies) { final IdentifierParser.Builder builder = new IdentifierParser.TupleBuilder( evaluator.getSchemaReader(), evaluator.getCube(), hierarchies) { public void tupleComplete() { super.tupleComplete(); memberList.toArray(members); } }; return IdentifierParser.parseTuple(builder, string, i); } /** * Parses a tuple, such as "([Gender].[M], [Marital Status].[S])". * * @param evaluator Evaluator, provides a {@link mondrian.olap.SchemaReader} * and {@link mondrian.olap.Cube} * @param string String to parse * @param hierarchies Hierarchies of the members * @return Tuple represented as array of members */ static Member[] parseTuple( Evaluator evaluator, String string, List hierarchies) { final Member[] members = new Member[hierarchies.size()]; int i = parseTuple(evaluator, string, 0, members, hierarchies); // todo: check for garbage at end of string if (FunUtil.tupleContainsNullMember(members)) { return null; } return members; } static List parseMemberList( Evaluator evaluator, String string, Hierarchy hierarchy) { IdentifierParser.MemberListBuilder builder = new IdentifierParser.MemberListBuilder( evaluator.getSchemaReader(), evaluator.getCube(), hierarchy); IdentifierParser.parseMemberList(builder, string); return builder.memberList; } private static int parseMember( Evaluator evaluator, String string, int i, final Member[] members, Hierarchy hierarchy) { IdentifierParser.MemberListBuilder builder = new IdentifierParser.MemberListBuilder( evaluator.getSchemaReader(), evaluator.getCube(), hierarchy) { @Override public void memberComplete() { members[0] = resolveMember(hierarchyList.get(0)); segmentList.clear(); } }; return IdentifierParser.parseMember(builder, string, i); } static Member parseMember( Evaluator evaluator, String string, Hierarchy hierarchy) { Member[] members = {null}; int i = parseMember(evaluator, string, 0, members, hierarchy); // todo: check for garbage at end of string final Member member = members[0]; if (member == null) { throw MondrianResource.instance().MdxChildObjectNotFound.ex( string, evaluator.getCube().getQualifiedName()); } return member; } /** * Returns whether an expression is worth wrapping in "Cache( ... )". * * @param exp Expression * @return Whether worth caching */ public static boolean worthCaching(Exp exp) { // Literal is not worth caching. if (exp instanceof Literal) { return false; } // Member, hierarchy, level, or dimension expression is not worth // caching. if (exp instanceof MemberExpr || exp instanceof LevelExpr || exp instanceof HierarchyExpr || exp instanceof DimensionExpr) { return false; } if (exp instanceof ResolvedFunCall) { ResolvedFunCall call = (ResolvedFunCall) exp; // A set of literals is not worth caching. if (call.getFunDef() instanceof SetFunDef) { for (Exp setArg : call.getArgs()) { if (worthCaching(setArg)) { return true; } } return false; } } return true; } // ~ Inner classes --------------------------------------------------------- /** * A functional for {@link FunUtil#partialSort}. * Sorts or partially sorts an array in ascending order, using a Comparator. * *

    Algorithm: quicksort, or partial quicksort (alias * "quickselect"), Hoare/Singleton. Partial quicksort is * quicksort that recurs only on one side, which is thus * tail-recursion. Picks pivot as median of three; falls back on * insertion sort for small "subfiles". Partial quicksort is O(n * + m log m), instead of O(n log n), where n is the input size, * and m is the desired output size. * *

    See D Knuth, Art of Computer Programming, 5.2.2 (Algorithm * Q); R. Sedgewick, Algorithms in C, ch 5. Good summary in * http://en.wikipedia.org/wiki/Selection_algorithm * *

    TODO: What is the time-cost of this functor and of the nested * Comparators? */ static class Quicksorter { // size of smallest set worth a quicksort public final int TOO_SMALL = 8; private static final Logger LOGGER = Logger.getLogger(Quicksorter.class); private final T[] vec; private final Comparator comp; private final boolean traced; private long partitions, comparisons, exchanges; // stats public Quicksorter(T[] vec, Comparator comp) { this.vec = vec; this.comp = comp; partitions = comparisons = exchanges = 0; traced = LOGGER.isDebugEnabled(); } private void traceStats(String prefix) { StringBuilder sb = new StringBuilder(prefix); sb.append(": "); sb.append(partitions).append(" partitions, "); sb.append(comparisons).append(" comparisons, "); sb.append(exchanges).append(" exchanges."); LOGGER.debug(sb.toString()); } // equivalent to operator < private boolean less(T x, T y) { comparisons++; return comp.compare(x, y) < 0; } // equivalent to operator > private boolean more(T x, T y) { comparisons++; return comp.compare(x, y) > 0; } // equivalent to operator > private boolean equal(T x, T y) { comparisons++; return comp.compare(x, y) == 0; } // swaps two items (identified by index in vec[]) private void swap(int i, int j) { exchanges++; T temp = vec[i]; vec[i] = vec[j]; vec[j] = temp; } // puts into ascending order three items // (identified by index in vec[]) // REVIEW: use only 2 comparisons?? private void order3(int i, int j, int k) { if (more(vec[i], vec[j])) { swap(i, j); } if (more(vec[i], vec[k])) { swap(i, k); } if (more(vec[j], vec[k])) { swap(j, k); } } // runs a selection sort on the array segment VEC[START .. END] private void selectionSort(int start, int end) { for (int i = start; i < end; ++i) { // pick the min of vec[i, end] int pmin = i; for (int j = i + 1; j <= end; ++j) { if (less(vec[j], vec[pmin])) { pmin = j; } } if (pmin != i) { swap(i, pmin); } } } /** * Runs one pass of quicksort on array segment VEC[START .. END], * dividing it into two parts, the left side VEC[START .. P] none * greater than the pivot value VEC[P], and the right side VEC[P+1 * .. END] none less than the pivot value. Returns P, the index of the * pivot element in VEC[]. */ private int partition(int start, int end) { partitions++; assert start <= end; // Find median of three (both ends and the middle). // TODO: use pseudo-median of nine when array segment is big enough. int mid = (start + end) / 2; order3(start, mid, end); if (end - start <= 2) { return mid; // sorted! } // Now the left and right ends are in place (ie in the correct // partition), and will serve as sentinels for scanning. Pick middle // as pivot and set it aside, in penultimate position. final T pivot = vec[mid]; swap(mid, end - 1); // Scan inward from both ends, swapping misplaced items. int left = start + 1; // vec[start] is in place int right = end - 2; // vec[end - 1] is pivot while (left < right) { // scan past items in correct place, but stop at a pivot value // (Sedgewick's idea). while (less(vec[left], pivot)) { ++left; } while (less(pivot, vec[right])) { --right; } if (debug) { assert (left <= end) && (right >= start); } if (left < right) { // found a misplaced pair swap(left, right); ++left; --right; } } if ((left == right) && less(vec[left], pivot)) { ++left; } // All scanned. Restore pivot to its rightful place. swap(left, end - 1); if (debug) { for (int i = start; i < left; i++) { assert !more(vec[i], pivot); } assert equal(vec[left], pivot); for (int i = left + 1; i <= end; i++) { assert !less(vec[i], pivot); } } return left; } // Runs quicksort on VEC[START, END]. Recursive version, // TODO: exploit tail recursion private void sort(int start, int end) { if (end - start < TOO_SMALL) { selectionSort(start, end); return; } // Split data, so that left side dominates the right side // (but neither is sorted): int mid = partition(start, end); sort(start, mid - 1); sort(mid + 1, end); } // Runs quickselect(LIMIT) on VEC[START, END]. Recursive version, // TODO: exploit tail recursion, unfold. private void select(int limit, int start, int end) { if (limit == 0) { return; } if (end - start < TOO_SMALL) { selectionSort(start, end); return; } int mid = partition(start, end); int leftSize = mid - start + 1; if (limit < leftSize) { // work on the left side, and ignore the right side select(limit, start, mid); } else { limit -= leftSize; // work on the right side, but keep the left side select(limit, mid + 1, end); } } public void sort() { int n = vec.length - 1; sort(0, n); if (traced) { traceStats("quicksort on " + n + "items"); } } /** puts the LIMIT biggest items at the head, not sorted */ public void select(int limit) { int n = vec.length - 1; select(limit, 0, n); if (traced) { traceStats("quickselect for " + limit + " from" + n + "items"); } } public void partialSort(int limit) { int n = vec.length - 1; select(limit, 0, n); if (traced) { traceStats( "partial sort: quickselect phase for " + limit + "from " + n + "items"); } sort(0, limit - 1); if (traced) { traceStats("partial sort: quicksort phase on " + n + "items"); } } } /** * Comparator for members. * *

    Could genericize this to class<T> MemorizingComparator * implements Comparator<T>, but not if it adds a run time * cost, since the comparator is at the heart of the sort algorithms. */ private static abstract class MemberComparator implements Comparator { private static final Logger LOGGER = Logger.getLogger(MemberComparator.class); final Evaluator evaluator; final Calc exp; private final int descMask; private final Map valueMap; MemberComparator(Evaluator evaluator, Calc exp, boolean desc) { this.evaluator = evaluator; this.exp = exp; this.descMask = desc ? -1 : 1; this.valueMap = new HashMap(); } private int maybeNegate(int c) { return descMask * c; } // applies the Calc to a member, memorizing results protected Object eval(Member m) { Object val = valueMap.get(m); if (val == null) { evaluator.setContext(m); val = exp.evaluate(evaluator); if (val == null) { val = Util.nullValue; } valueMap.put(m, val); } return val; } // wraps comparison with tracing Comparator wrap() { final MemberComparator comparator = this; if (LOGGER.isDebugEnabled()) { return new Comparator() { public int compare(Member m1, Member m2) { final int c = comparator.compare(m1, m2); // here guaranteed that eval(m) finds a memorized value LOGGER.debug( "compare " + m1.getUniqueName() + "(" + eval(m1) + "), " + m2.getUniqueName() + "(" + eval(m2) + ")" + " yields " + c); return c; } }; } else { return this; } } // Preloads the value map with precomputed members (supplied as a map). void preloadValues(Map map) { valueMap.putAll(map); } // Preloads the value map by applying the expression to a Collection of // members. void preloadValues(Collection members) { for (Member m : members) { eval(m); } } protected final int compareByValue(Member m1, Member m2) { final int c = FunUtil.compareValues(eval(m1), eval(m2)); return maybeNegate(c); } protected final int compareHierarchicallyButSiblingsByValue( Member m1, Member m2) { if (FunUtil.equals(m1, m2)) { return 0; } while (true) { int depth1 = m1.getDepth(), depth2 = m2.getDepth(); if (depth1 < depth2) { m2 = m2.getParentMember(); if (Util.equals(m1, m2)) { return -1; } } else if (depth1 > depth2) { m1 = m1.getParentMember(); if (Util.equals(m1, m2)) { return 1; } } else { Member prev1 = m1, prev2 = m2; m1 = m1.getParentMember(); m2 = m2.getParentMember(); if (Util.equals(m1, m2)) { // including case where both parents are null int c = compareByValue(prev1, prev2); if (c != 0) { return c; } // prev1 and prev2 are siblings. Order according to // hierarchy, if the values do not differ. Needed to // have a consistent sortMembers if members with equal // (null!) values are compared. c = FunUtil.compareSiblingMembers(prev1, prev2); // Do not negate c, even if we are sorting descending. // This comparison is to achieve the 'natural order'. return c; } } } } } private static class HierarchicalMemberComparator extends MemberComparator { HierarchicalMemberComparator( Evaluator evaluator, Calc exp, boolean desc) { super(evaluator, exp, desc); } public int compare(Member m1, Member m2) { return compareHierarchicallyButSiblingsByValue(m1, m2); } } private static class BreakMemberComparator extends MemberComparator { BreakMemberComparator(Evaluator evaluator, Calc exp, boolean desc) { super(evaluator, exp, desc); } public final int compare(Member m1, Member m2) { return compareByValue(m1, m2); } } /** * Compares tuples, which are represented as lists of {@link Member}s. */ private static abstract class TupleComparator implements Comparator> { private static final Logger LOGGER = Logger.getLogger(TupleComparator.class); final int arity; TupleComparator(int arity) { this.arity = arity; } Comparator> wrap() { final TupleComparator base = this; if (LOGGER.isDebugEnabled()) { return new Comparator>() { public int compare(List a1, List a2) { int c = base.compare(a1, a2); LOGGER.debug( "compare {" + a1 + "}, {" + a2 + "} yields " + c); return c; } }; } else { return this; } } } /** * Extension to {@link TupleComparator} which compares tuples by evaluating * an expression. */ private static abstract class TupleExpComparator extends TupleComparator { Evaluator evaluator; final Calc calc; TupleExpComparator(Evaluator evaluator, Calc calc, int arity) { super(arity); this.evaluator = evaluator; this.calc = calc; } } private static class HierarchicalTupleComparator extends TupleExpComparator { private final boolean desc; HierarchicalTupleComparator( Evaluator evaluator, Calc calc, int arity, boolean desc) { super(evaluator, calc, arity); this.desc = desc; } public int compare(List a1, List a2) { int c = 0; final int savepoint = evaluator.savepoint(); for (int i = 0; i < arity; i++) { Member m1 = a1.get(i), m2 = a2.get(i); c = compareHierarchicallyButSiblingsByValue(m1, m2); if (c != 0) { break; } // compareHierarchicallyButSiblingsByValue imposes a total order assert m1.equals(m2); evaluator.setContext(m1); } evaluator.restore(savepoint); return c; } protected int compareHierarchicallyButSiblingsByValue( Member m1, Member m2) { if (FunUtil.equals(m1, m2)) { return 0; } while (true) { int depth1 = m1.getDepth(), depth2 = m2.getDepth(); if (depth1 < depth2) { m2 = m2.getParentMember(); if (FunUtil.equals(m1, m2)) { return -1; } } else if (depth1 > depth2) { m1 = m1.getParentMember(); if (FunUtil.equals(m1, m2)) { return 1; } } else { Member prev1 = m1, prev2 = m2; m1 = m1.getParentMember(); m2 = m2.getParentMember(); if (FunUtil.equals(m1, m2)) { // including case where both parents are null int c = compareByValue(prev1, prev2); if (c == 0) { c = FunUtil.compareSiblingMembers(prev1, prev2); } return desc ? -c : c; } } } } private int compareByValue(Member m1, Member m2) { int c; final int savepoint = evaluator.savepoint(); evaluator.setContext(m1); Object v1 = calc.evaluate(evaluator); evaluator.setContext(m2); Object v2 = calc.evaluate(evaluator); // important to restore the evaluator state evaluator.restore(savepoint); c = FunUtil.compareValues(v1, v2); return c; } } // almost the same as MemberComparator static abstract class TupleExpMemoComparator extends TupleExpComparator { private final Map, Object> valueMap = new HashMap, Object>(); TupleExpMemoComparator(Evaluator e, Calc calc, int arity) { super(e, calc, arity); } // applies the Calc to a tuple, memorizing results protected Object eval(List t) { Object val = valueMap.get(t); if (val != null) { return val; } return compute(t); } private Object compute(List key) { evaluator.setContext(key); Object val = calc.evaluate(evaluator); if (val == null) { val = Util.nullValue; } valueMap.put(key, val); return val; } // Preloads the value map by applying the expression to a Collection of // members. void preloadValues(TupleList tuples) { for (List t : tuples) { compute(t); } } } private static class BreakTupleComparator extends TupleExpMemoComparator { BreakTupleComparator(Evaluator e, Calc calc, int arity) { super(e, calc, arity); } public int compare(List a1, List a2) { return FunUtil.compareValues(eval(a1), eval(a2)); } } private static class HierarchicalTupleKeyComparator extends TupleExpMemoComparator { HierarchicalTupleKeyComparator(Evaluator e, Calc calc, int arity) { super(e, calc, arity); } public int compare(List a1, List a2) { OrderKey k1 = (OrderKey) eval(a1); OrderKey k2 = (OrderKey) eval(a2); return compareMemberOrderKeysHierarchically(k1, k2); } private int compareMemberOrderKeysHierarchically( OrderKey k1, OrderKey k2) { // null is less than anything else if (k1 == Util.nullValue) { return -1; } if (k2 == Util.nullValue) { return 1; } Member m1 = k1.member; Member m2 = k2.member; if (FunUtil.equals(m1, m2)) { return 0; } while (true) { int depth1 = m1.getDepth(), depth2 = m2.getDepth(); if (depth1 < depth2) { m2 = m2.getParentMember(); if (FunUtil.equals(m1, m2)) { return -1; } } else if (depth1 > depth2) { m1 = m1.getParentMember(); if (FunUtil.equals(m1, m2)) { return 1; } } else { Member prev1 = m1, prev2 = m2; m1 = m1.getParentMember(); m2 = m2.getParentMember(); if (FunUtil.equals(m1, m2)) { OrderKey pk1 = new OrderKey(prev1); OrderKey pk2 = new OrderKey(prev2); return FunUtil.compareValues(pk1, pk2); } } } } } /** * Compares lists of {@link Member}s so as to convert them into hierarchical * order. Applies lexicographic order to the array. */ private static class HierarchizeTupleComparator extends TupleComparator { private final boolean post; HierarchizeTupleComparator(int arity, boolean post) { super(arity); this.post = post; } public int compare(List a1, List a2) { for (int i = 0; i < arity; i++) { Member m1 = a1.get(i), m2 = a2.get(i); int c = FunUtil.compareHierarchically(m1, m2, post); if (c != 0) { return c; } } return 0; } } /** * Compares {@link Member}s so as to arrage them in prefix or postfix * hierarchical order. */ private static class HierarchizeComparator implements Comparator { private final boolean post; HierarchizeComparator(boolean post) { this.post = post; } public int compare(Member m1, Member m2) { return FunUtil.compareHierarchically(m1, m2, post); } } static class SetWrapper { List v = new ArrayList(); public int errorCount = 0, nullCount = 0; //private double avg = Double.NaN; //todo: parameterize inclusion of nulls //by making this a method of the SetWrapper, we can cache the result //this allows its reuse in Correlation // public double getAverage() { // if (avg == Double.NaN) { // double sum = 0.0; // for (int i = 0; i < resolvers.size(); i++) { // sum += ((Double) resolvers.elementAt(i)).doubleValue(); // } // //todo: should look at context and optionally include nulls // avg = sum / (double) resolvers.size(); // } // return avg; // } } /** * Compares cell values, so that larger values compare first. * *

    Nulls compare last, exceptions (including the * object which indicates the the cell is not in the cache yet) next, * then numbers and strings are compared by value. */ public static class DescendingValueComparator implements Comparator { /** * The singleton. */ static final DescendingValueComparator instance = new DescendingValueComparator(); public int compare(Object o1, Object o2) { return - compareValues(o1, o2); } } /** * Null member of unknown hierarchy. */ private static class NullMember implements Member { public Member getParentMember() { throw new UnsupportedOperationException(); } public Level getLevel() { throw new UnsupportedOperationException(); } public Hierarchy getHierarchy() { throw new UnsupportedOperationException(); } public String getParentUniqueName() { throw new UnsupportedOperationException(); } public MemberType getMemberType() { throw new UnsupportedOperationException(); } public boolean isParentChildLeaf() { return false; } public void setName(String name) { throw new UnsupportedOperationException(); } public boolean isAll() { return false; } public boolean isMeasure() { throw new UnsupportedOperationException(); } public boolean isNull() { return true; } public boolean isChildOrEqualTo(Member member) { throw new UnsupportedOperationException(); } public boolean isCalculated() { throw new UnsupportedOperationException(); } public boolean isEvaluated() { throw new UnsupportedOperationException(); } public int getSolveOrder() { throw new UnsupportedOperationException(); } public Exp getExpression() { throw new UnsupportedOperationException(); } public List getAncestorMembers() { throw new UnsupportedOperationException(); } public boolean isCalculatedInQuery() { throw new UnsupportedOperationException(); } public Object getPropertyValue(String propertyName) { throw new UnsupportedOperationException(); } public Object getPropertyValue(String propertyName, boolean matchCase) { throw new UnsupportedOperationException(); } public String getPropertyFormattedValue(String propertyName) { throw new UnsupportedOperationException(); } public void setProperty(String name, Object value) { throw new UnsupportedOperationException(); } public Property[] getProperties() { throw new UnsupportedOperationException(); } public int getOrdinal() { throw new UnsupportedOperationException(); } public Comparable getOrderKey() { throw new UnsupportedOperationException(); } public boolean isHidden() { throw new UnsupportedOperationException(); } public int getDepth() { throw new UnsupportedOperationException(); } public Member getDataMember() { throw new UnsupportedOperationException(); } public String getUniqueName() { throw new UnsupportedOperationException(); } public String getName() { throw new UnsupportedOperationException(); } public String getDescription() { throw new UnsupportedOperationException(); } public OlapElement lookupChild( SchemaReader schemaReader, Id.Segment s, MatchType matchType) { throw new UnsupportedOperationException(); } public String getQualifiedName() { throw new UnsupportedOperationException(); } public String getCaption() { throw new UnsupportedOperationException(); } public String getLocalized(LocalizedProperty prop, Locale locale) { throw new UnsupportedOperationException(); } public boolean isVisible() { throw new UnsupportedOperationException(); } public Dimension getDimension() { throw new UnsupportedOperationException(); } public Map getAnnotationMap() { throw new UnsupportedOperationException(); } public int compareTo(Object o) { throw new UnsupportedOperationException(); } public boolean equals(Object obj) { throw new UnsupportedOperationException(); } public int hashCode() { throw new UnsupportedOperationException(); } } /** * Enumeration of the flags allowed to the {@code ORDER} MDX function. */ enum Flag { ASC(false, false), DESC(true, false), BASC(false, true), BDESC(true, true); final boolean descending; final boolean brk; Flag(boolean descending, boolean brk) { this.descending = descending; this.brk = brk; } public static String[] getNames() { List names = new ArrayList(); for (Flag flags : Flag.class.getEnumConstants()) { names.add(flags.name()); } return names.toArray(new String[names.size()]); } } static class SortKeySpec { private final Calc key; private final Flag direction; SortKeySpec(Calc key, Flag dir) { this.key = key; this.direction = dir; } Calc getKey() { return this.key; } Flag getDirection() { return this.direction; } } public static class OrderKey implements Comparable { private final Member member; public OrderKey(Member member) { super(); this.member = member; } public int compareTo(Object o) { assert o instanceof OrderKey; Member otherMember = ((OrderKey) o).member; final boolean thisCalculated = this.member.isCalculatedInQuery(); final boolean otherCalculated = otherMember.isCalculatedInQuery(); if (thisCalculated) { if (!otherCalculated) { return 1; } } else { if (otherCalculated) { return -1; } } final Comparable thisKey = this.member.getOrderKey(); final Comparable otherKey = otherMember.getOrderKey(); if ((thisKey != null) && (otherKey != null)) { return thisKey.compareTo(otherKey); } else { return this.member.compareTo(otherMember); } } } /** * Tuple consisting of an object and an integer. * *

    Similar to {@link Pair}, but saves boxing overhead of converting * {@code int} to {@link Integer}. */ public static class ObjIntPair { T t; int i; public ObjIntPair(T t, int i) { this.t = t; this.i = i; } public int hashCode() { return Util.hash(i, t); } public boolean equals(Object obj) { return obj instanceof ObjIntPair && this.i == ((ObjIntPair) obj).i && Util.equals(this.t, ((ObjIntPair) obj).t); } public String toString() { return "<" + t + ", " + i + ">"; } } } // End FunUtil.java mondrian-3.4.1/src/main/mondrian/olap/fun/CaseMatchFunDef.java0000644000175000017500000001120311735330606024053 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.Calc; import mondrian.calc.ExpCompiler; import mondrian.calc.impl.ConstantCalc; import mondrian.calc.impl.GenericCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.util.ArrayList; import java.util.List; /** * Definition of the matched CASE MDX operator. * * Syntax is: *

    Case <Expression>
     * When <Expression> Then <Expression>
     * [...]
     * [Else <Expression>]
     * End
    . * * @see CaseTestFunDef * @author jhyde * @since Mar 23, 2006 */ class CaseMatchFunDef extends FunDefBase { static final ResolverImpl Resolver = new ResolverImpl(); private CaseMatchFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Exp[] args = call.getArgs(); final List calcList = new ArrayList(); final Calc valueCalc = compiler.compileScalar(args[0], true); calcList.add(valueCalc); final int matchCount = (args.length - 1) / 2; final Calc[] matchCalcs = new Calc[matchCount]; final Calc[] exprCalcs = new Calc[matchCount]; for (int i = 0, j = 1; i < exprCalcs.length; i++) { matchCalcs[i] = compiler.compileScalar(args[j++], true); calcList.add(matchCalcs[i]); exprCalcs[i] = compiler.compile(args[j++]); calcList.add(exprCalcs[i]); } final Calc defaultCalc = args.length % 2 == 0 ? compiler.compile(args[args.length - 1]) : ConstantCalc.constantNull(call.getType()); calcList.add(defaultCalc); final Calc[] calcs = calcList.toArray(new Calc[calcList.size()]); return new GenericCalc(call) { public Object evaluate(Evaluator evaluator) { Object value = valueCalc.evaluate(evaluator); for (int i = 0; i < matchCalcs.length; i++) { Object match = matchCalcs[i].evaluate(evaluator); if (match.equals(value)) { return exprCalcs[i].evaluate(evaluator); } } return defaultCalc.evaluate(evaluator); } public Calc[] getCalcs() { return calcs; } }; } private static class ResolverImpl extends ResolverBase { private ResolverImpl() { super( "_CaseMatch", "Case When Then [...] [Else ] End", "Evaluates various expressions, and returns the corresponding expression for the first which matches a particular value.", Syntax.Case); } public FunDef resolve( Exp[] args, Validator validator, List conversions) { if (args.length < 3) { return null; } int valueType = args[0].getCategory(); int returnType = args[2].getCategory(); int j = 0; int clauseCount = (args.length - 1) / 2; int mismatchingArgs = 0; if (!validator.canConvert(j, args[j++], valueType, conversions)) { mismatchingArgs++; } for (int i = 0; i < clauseCount; i++) { if (!validator.canConvert(j, args[j++], valueType, conversions)) { mismatchingArgs++; } if (!validator.canConvert( j, args[j++], returnType, conversions)) { mismatchingArgs++; } } if (j < args.length) { if (!validator.canConvert( j, args[j++], returnType, conversions)) { mismatchingArgs++; } } Util.assertTrue(j == args.length); if (mismatchingArgs != 0) { return null; } FunDef dummy = createDummyFunDef(this, returnType, args); return new CaseMatchFunDef(dummy); } public boolean requiresExpression(int k) { return true; } } } // End CaseMatchFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/ToggleDrillStateFunDef.java0000644000175000017500000001027511735330606025444 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.resource.MondrianResource; import java.util.*; /** * Definition of the ToggleDrillState MDX function. * * @author jhyde * @since Mar 23, 2006 */ class ToggleDrillStateFunDef extends FunDefBase { static final String[] ReservedWords = new String[] {"RECURSIVE"}; static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "ToggleDrillState", "ToggleDrillState(, [, RECURSIVE])", "Toggles the drill state of members. This function is a combination of DrillupMember and DrilldownMember.", new String[]{"fxxx", "fxxxy"}, ToggleDrillStateFunDef.class, ReservedWords); public ToggleDrillStateFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { if (call.getArgCount() > 2) { throw MondrianResource.instance() .ToggleDrillStateRecursiveNotSupported.ex(); } final ListCalc listCalc0 = compiler.compileList(call.getArg(0)); final ListCalc listCalc1 = compiler.compileList(call.getArg(1)); return new AbstractListCalc(call, new Calc[]{listCalc0, listCalc1}) { public TupleList evaluateList(Evaluator evaluator) { final TupleList list0 = listCalc0.evaluateList(evaluator); final TupleList list1 = listCalc1.evaluateList(evaluator); return toggleDrillStateTuples(evaluator, list0, list1); } }; } TupleList toggleDrillStateTuples( Evaluator evaluator, TupleList v0, TupleList list1) { assert list1.getArity() == 1; if (list1.isEmpty()) { return v0; } if (v0.isEmpty()) { return v0; } final Member[] members = new Member[v0.getArity()]; // tuple workspace final Set set = new HashSet(list1.slice(0)); TupleList result = v0.cloneList((v0.size() * 3) / 2 + 1); // allow 50% int i = 0, n = v0.size(); while (i < n) { List o = v0.get(i++); result.add(o); Member m = null; int k = -1; for (int j = 0; j < o.size(); j++) { Member member = o.get(j); if (set.contains(member)) { k = j; m = member; break; } } if (k == -1) { continue; } boolean isDrilledDown = false; if (i < n) { List next = v0.get(i); Member nextMember = next.get(k); boolean strict = true; if (FunUtil.isAncestorOf(m, nextMember, strict)) { isDrilledDown = true; } } if (isDrilledDown) { // skip descendants of this member do { List next = v0.get(i); Member nextMember = next.get(k); boolean strict = true; if (FunUtil.isAncestorOf(m, nextMember, strict)) { i++; } else { break; } } while (i < n); } else { List children = evaluator.getSchemaReader().getMemberChildren(m); for (Member child : children) { o.toArray(members); members[k] = child; result.addTuple(members); } } } return result; } } // End ToggleDrillStateFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/TupleToStrFunDef.java0000644000175000017500000000464111735330606024320 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractStringCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.Evaluator; import mondrian.olap.Member; import mondrian.olap.type.TypeUtil; /** * Definition of the TupleToStr MDX function. * *

    Syntax: *

    * TupleToStr(<Tuple>) *
    * * @author jhyde * @since Aug 3, 2006 */ class TupleToStrFunDef extends FunDefBase { static final TupleToStrFunDef instance = new TupleToStrFunDef(); private TupleToStrFunDef() { super("TupleToStr", "Constructs a string from a tuple.", "fSt"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { if (TypeUtil.couldBeMember(call.getArg(0).getType())) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractStringCalc(call, new Calc[] {memberCalc}) { public String evaluateString(Evaluator evaluator) { final Member member = memberCalc.evaluateMember(evaluator); if (member.isNull()) { return ""; } StringBuilder buf = new StringBuilder(); buf.append(member.getUniqueName()); return buf.toString(); } }; } else { final TupleCalc tupleCalc = compiler.compileTuple(call.getArg(0)); return new AbstractStringCalc(call, new Calc[] {tupleCalc}) { public String evaluateString(Evaluator evaluator) { final Member[] members = tupleCalc.evaluateTuple(evaluator); if (members == null) { return ""; } StringBuilder buf = new StringBuilder(); SetToStrFunDef.appendTuple(buf, members); return buf.toString(); } }; } } } // End TupleToStrFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/CovarianceFunDef.java0000644000175000017500000000547211735330606024310 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractDoubleCalc; import mondrian.calc.impl.ValueCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the Covariance and * CovarianceN MDX functions. * * @author jhyde * @since Mar 23, 2006 */ class CovarianceFunDef extends FunDefBase { static final ReflectiveMultiResolver CovarianceResolver = new ReflectiveMultiResolver( "Covariance", "Covariance(, [, ])", "Returns the covariance of two series evaluated over a set (biased).", new String[]{"fnxn", "fnxnn"}, CovarianceFunDef.class); static final MultiResolver CovarianceNResolver = new ReflectiveMultiResolver( "CovarianceN", "CovarianceN(, [, ])", "Returns the covariance of two series evaluated over a set (unbiased).", new String[]{"fnxn", "fnxnn"}, CovarianceFunDef.class); private final boolean biased; public CovarianceFunDef(FunDef dummyFunDef) { super(dummyFunDef); this.biased = dummyFunDef.getName().equals("Covariance"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final Calc calc1 = compiler.compileScalar(call.getArg(1), true); final Calc calc2 = call.getArgCount() > 2 ? compiler.compileScalar(call.getArg(2), true) : new ValueCalc(call); return new AbstractDoubleCalc(call, new Calc[] {listCalc, calc1, calc2}) { public double evaluateDouble(Evaluator evaluator) { TupleList memberList = listCalc.evaluateList(evaluator); final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final double covariance = (Double) covariance( evaluator, memberList, calc1, calc2, biased); evaluator.restore(savepoint); return covariance; } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } }; } } // End CovarianceFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/UnionFunDef.java0000644000175000017500000000535111735330606023322 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.util.*; /** * Definition of the Union MDX function. * * @author jhyde * @since Mar 23, 2006 */ class UnionFunDef extends FunDefBase { static final String[] ReservedWords = new String[] {"ALL", "DISTINCT"}; static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "Union", "Union(, [, ALL])", "Returns the union of two sets, optionally retaining duplicates.", new String[] {"fxxx", "fxxxy"}, UnionFunDef.class, ReservedWords); public UnionFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { String allString = getLiteralArg(call, 2, "DISTINCT", ReservedWords); final boolean all = allString.equalsIgnoreCase("ALL"); // todo: do at validate time checkCompatible(call.getArg(0), call.getArg(1), null); final ListCalc listCalc0 = compiler.compileList(call.getArg(0)); final ListCalc listCalc1 = compiler.compileList(call.getArg(1)); return new AbstractListCalc(call, new Calc[] {listCalc0, listCalc1}) { public TupleList evaluateList(Evaluator evaluator) { TupleList list0 = listCalc0.evaluateList(evaluator); TupleList list1 = listCalc1.evaluateList(evaluator); return union(list0, list1, all); } }; } TupleList union(TupleList list0, TupleList list1, final boolean all) { assert list0 != null; assert list1 != null; if (all) { if (list0.isEmpty()) { return list1; } if (list1.isEmpty()) { return list0; } TupleList result = TupleCollections.createList(list0.getArity()); result.addAll(list0); result.addAll(list1); return result; } else { Set> added = new HashSet>(); TupleList result = TupleCollections.createList(list0.getArity()); FunUtil.addUnique(result, list0, added); FunUtil.addUnique(result, list1, added); return result; } } } // End UnionFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/VarFunDef.java0000644000175000017500000000452311735330606022762 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractDoubleCalc; import mondrian.calc.impl.ValueCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the Var MDX builtin function * (and its synonym Variance). * * @author jhyde * @since Mar 23, 2006 */ class VarFunDef extends AbstractAggregateFunDef { static final Resolver VarResolver = new ReflectiveMultiResolver( "Var", "Var([, ])", "Returns the variance of a numeric expression evaluated over a set (unbiased).", new String[]{"fnx", "fnxn"}, VarFunDef.class); static final Resolver VarianceResolver = new ReflectiveMultiResolver( "Variance", "Variance([, ])", "Alias for Var.", new String[]{"fnx", "fnxn"}, VarFunDef.class); public VarFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final Calc calc = call.getArgCount() > 1 ? compiler.compileScalar(call.getArg(1), true) : new ValueCalc(call); return new AbstractDoubleCalc(call, new Calc[] {listCalc, calc}) { public double evaluateDouble(Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); TupleList list = evaluateCurrentList(listCalc, evaluator); final double var = (Double) var( evaluator, list, calc, false); evaluator.restore(savepoint); return var; } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } }; } } // End VarFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/VarPFunDef.java0000644000175000017500000000451511735330606023103 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractDoubleCalc; import mondrian.calc.impl.ValueCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the VarP MDX builtin function * (and its synonym VarianceP). * * @author jhyde * @since Mar 23, 2006 */ class VarPFunDef extends AbstractAggregateFunDef { static final Resolver VariancePResolver = new ReflectiveMultiResolver( "VarianceP", "VarianceP([, ])", "Alias for VarP.", new String[]{"fnx", "fnxn"}, VarPFunDef.class); static final Resolver VarPResolver = new ReflectiveMultiResolver( "VarP", "VarP([, ])", "Returns the variance of a numeric expression evaluated over a set (biased).", new String[]{"fnx", "fnxn"}, VarPFunDef.class); public VarPFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final Calc calc = call.getArgCount() > 1 ? compiler.compileScalar(call.getArg(1), true) : new ValueCalc(call); return new AbstractDoubleCalc(call, new Calc[] {listCalc, calc}) { public double evaluateDouble(Evaluator evaluator) { TupleList memberList = evaluateCurrentList(listCalc, evaluator); final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final double d = (Double) var(evaluator, memberList, calc, true); evaluator.restore(savepoint); return d; } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } }; } } // End VarPFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/StrToTupleFunDef.java0000644000175000017500000001406311735330606024317 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractMemberCalc; import mondrian.calc.impl.AbstractTupleCalc; import mondrian.mdx.*; import mondrian.olap.*; import mondrian.olap.type.*; import mondrian.resource.MondrianResource; import java.util.ArrayList; import java.util.List; /** * Definition of the StrToTuple MDX function. * * @author jhyde * @since Mar 23, 2006 */ class StrToTupleFunDef extends FunDefBase { static final ResolverImpl Resolver = new ResolverImpl(); private StrToTupleFunDef(int[] parameterTypes) { super( "StrToTuple", null, "Constructs a tuple from a string.", Syntax.Function, Category.Tuple, parameterTypes); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final StringCalc stringCalc = compiler.compileString(call.getArg(0)); Type elementType = call.getType(); if (elementType instanceof MemberType) { final Hierarchy hierarchy = elementType.getHierarchy(); return new AbstractMemberCalc(call, new Calc[] {stringCalc}) { public Member evaluateMember(Evaluator evaluator) { String string = stringCalc.evaluateString(evaluator); if (string == null) { throw newEvalException( MondrianResource.instance().NullValue.ex()); } return parseMember(evaluator, string, hierarchy); } }; } else { TupleType tupleType = (TupleType) elementType; final List hierarchies = tupleType.getHierarchies(); return new AbstractTupleCalc(call, new Calc[] {stringCalc}) { public Member[] evaluateTuple(Evaluator evaluator) { String string = stringCalc.evaluateString(evaluator); if (string == null) { throw newEvalException( MondrianResource.instance().NullValue.ex()); } return parseTuple(evaluator, string, hierarchies); } }; } } public Exp createCall(Validator validator, Exp[] args) { final int argCount = args.length; if (argCount <= 1) { throw MondrianResource.instance().MdxFuncArgumentsNum.ex(getName()); } for (int i = 1; i < argCount; i++) { final Exp arg = args[i]; if (arg instanceof DimensionExpr) { // if arg is a dimension, switch to dimension's default // hierarchy DimensionExpr dimensionExpr = (DimensionExpr) arg; Dimension dimension = dimensionExpr.getDimension(); args[i] = new HierarchyExpr(dimension.getHierarchy()); } else if (arg instanceof HierarchyExpr) { // nothing } else { throw MondrianResource.instance().MdxFuncNotHier.ex( i + 1, getName()); } } return super.createCall(validator, args); } public Type getResultType(Validator validator, Exp[] args) { switch (args.length) { case 1: // This is a call to the standard version of StrToTuple, // which doesn't give us any hints about type. return new TupleType(null); case 2: final Type argType = args[1].getType(); return new MemberType( argType.getDimension(), argType.getHierarchy(), argType.getLevel(), null); default: { // This is a call to Mondrian's extended version of // StrToTuple, of the form // StrToTuple(s, , ... , ) // // The result is a tuple // (, ... , ) final List list = new ArrayList(); for (int i = 1; i < args.length; i++) { Exp arg = args[i]; final Type type = arg.getType(); list.add(TypeUtil.toMemberType(type)); } final MemberType[] types = list.toArray(new MemberType[list.size()]); TupleType.checkHierarchies(types); return new TupleType(types); } } } private static class ResolverImpl extends ResolverBase { ResolverImpl() { super( "StrToTuple", "StrToTuple()", "Constructs a tuple from a string.", Syntax.Function); } public FunDef resolve( Exp[] args, Validator validator, List conversions) { if (args.length < 1) { return null; } Type type = args[0].getType(); if (!(type instanceof StringType) && !(type instanceof NullType)) { return null; } for (int i = 1; i < args.length; i++) { Exp exp = args[i]; if (!(exp instanceof DimensionExpr || exp instanceof HierarchyExpr)) { return null; } } int[] argTypes = new int[args.length]; argTypes[0] = Category.String; for (int i = 1; i < argTypes.length; i++) { argTypes[i] = Category.Hierarchy; } return new StrToTupleFunDef(argTypes); } public FunDef getFunDef() { return new StrToTupleFunDef(new int[] {Category.String}); } } } // End StrToTupleFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/VisualTotalsFunDef.java0000644000175000017500000003044111735330606024662 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.calc.impl.UnaryTupleList; import mondrian.mdx.*; import mondrian.olap.*; import mondrian.olap.type.*; import mondrian.resource.MondrianResource; import mondrian.rolap.*; import java.util.ArrayList; import java.util.List; /** * Definition of the VisualTotals MDX function. * * @author jhyde * @since Jan 16, 2006 */ public class VisualTotalsFunDef extends FunDefBase { static final Resolver Resolver = new ReflectiveMultiResolver( "VisualTotals", "VisualTotals([, ])", "Dynamically totals child members specified in a set using a pattern for the total label in the result set.", new String[] {"fxx", "fxxS"}, VisualTotalsFunDef.class); public VisualTotalsFunDef(FunDef dummyFunDef) { super(dummyFunDef); } protected Exp validateArg( Validator validator, Exp[] args, int i, int category) { final Exp validatedArg = super.validateArg(validator, args, i, category); if (i == 0) { // The function signature guarantees that we have a set of members // or a set of tuples. final SetType setType = (SetType) validatedArg.getType(); final Type elementType = setType.getElementType(); if (!(elementType instanceof MemberType)) { throw MondrianResource.instance().VisualTotalsAppliedToTuples .ex(); } } return validatedArg; } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final StringCalc stringCalc = call.getArgCount() > 1 ? compiler.compileString(call.getArg(1)) : null; return new CalcImpl(call, listCalc, stringCalc); } /** * Calc implementation of the VisualTotals function. */ private static class CalcImpl extends AbstractListCalc { private final ListCalc listCalc; private final StringCalc stringCalc; public CalcImpl( ResolvedFunCall call, ListCalc listCalc, StringCalc stringCalc) { super(call, new Calc[] {listCalc, stringCalc}); this.listCalc = listCalc; this.stringCalc = stringCalc; } public TupleList evaluateList(Evaluator evaluator) { final List list = listCalc.evaluateList(evaluator).slice(0); final List resultList = new ArrayList(list); final int memberCount = list.size(); for (int i = memberCount - 1; i >= 0; --i) { Member member = list.get(i); if (i + 1 < memberCount) { Member nextMember = resultList.get(i + 1); if (nextMember != member && nextMember.isChildOrEqualTo(member)) { resultList.set( i, createMember(member, i, resultList, evaluator)); } } } return new UnaryTupleList(resultList); } private VisualTotalMember createMember( Member member, int i, final List list, Evaluator evaluator) { final String name; if (stringCalc != null) { final String namePattern = stringCalc.evaluateString(evaluator); name = substitute(namePattern, member.getName()); } else { name = member.getName(); } final List childMemberList = followingDescendants(member, i + 1, list); final Exp exp = makeExpr(childMemberList); final Validator validator = evaluator.getQuery().createValidator(); final Exp validatedExp = exp.accept(validator); return new VisualTotalMember(member, name, validatedExp); } private List followingDescendants( Member member, int i, final List list) { List childMemberList = new ArrayList(); while (i < list.size()) { Member descendant = list.get(i); if (descendant.equals(member)) { // strict descendants only break; } if (!descendant.isChildOrEqualTo(member)) { break; } if (descendant instanceof VisualTotalMember) { // Add the visual total member, but skip over its children. VisualTotalMember visualTotalMember = (VisualTotalMember) descendant; childMemberList.add(visualTotalMember); i = lastChildIndex(visualTotalMember.member, i, list); continue; } childMemberList.add(descendant); ++i; } return childMemberList; } private int lastChildIndex(Member member, int start, List list) { int i = start; while (true) { ++i; if (i >= list.size()) { break; } Member descendant = (Member) list.get(i); if (descendant.equals(member)) { // strict descendants only break; } if (!descendant.isChildOrEqualTo(member)) { break; } } return i; } private Exp makeExpr(final List childMemberList) { Exp[] memberExprs = new Exp[childMemberList.size()]; for (int i = 0; i < childMemberList.size(); i++) { final Member childMember = (Member) childMemberList.get(i); memberExprs[i] = new MemberExpr(childMember); } return new UnresolvedFunCall( "Aggregate", new Exp[] { new UnresolvedFunCall( "{}", Syntax.Braces, memberExprs) }); } } /** * Calculated member for VisualTotals function. * *

    It corresponds to a real member, and most of its properties are * similar. The main differences are:

      *
    • its name is derived from the VisualTotals pattern, e.g. * "*Subtotal - Dairy" as opposed to "Dairy" *
    • its value is a calculation computed by aggregating all of the * members which occur following it in the list

    */ public static class VisualTotalMember extends RolapMemberBase { private final Member member; private Exp exp; VisualTotalMember( Member member, String name, final Exp exp) { super( (RolapMember) member.getParentMember(), (RolapLevel) member.getLevel(), null, name, MemberType.FORMULA); this.member = member; this.exp = exp; } @Override public boolean equals(Object o) { // A visual total member must compare equal to the member it wraps // (for purposes of the MDX Intersect function, for instance). return o instanceof VisualTotalMember && this.member.equals(((VisualTotalMember) o).member) && this.exp.equals(((VisualTotalMember) o).exp) || o instanceof Member && this.member.equals(o); } @Override public int hashCode() { return member.hashCode(); } @Override public String getCaption() { return member.getCaption(); } protected boolean computeCalculated(final MemberType memberType) { return true; } public int getSolveOrder() { // high solve order, so it is expanded after other calculations return 99; } public Exp getExpression() { return exp; } public void setExpression(Exp exp) { this.exp = exp; } public void setExpression( Evaluator evaluator, List childMembers) { final Exp exp = makeExpr(childMembers); final Validator validator = evaluator.getQuery().createValidator(); final Exp validatedExp = exp.accept(validator); setExpression(validatedExp); } private Exp makeExpr(final List childMemberList) { Exp[] memberExprs = new Exp[childMemberList.size()]; for (int i = 0; i < childMemberList.size(); i++) { final Member childMember = (Member) childMemberList.get(i); memberExprs[i] = new MemberExpr(childMember); } return new UnresolvedFunCall( "Aggregate", new Exp[] { new UnresolvedFunCall( "{}", Syntax.Braces, memberExprs) }); } public int getOrdinal() { return member.getOrdinal(); } public Member getDataMember() { return member; } public String getQualifiedName() { throw new UnsupportedOperationException(); } public Member getMember() { return member; } public Object getPropertyValue(String propertyName, boolean matchCase) { Property property = Property.lookup(propertyName, matchCase); if (property == null) { return null; } switch (property.ordinal) { case Property.CHILDREN_CARDINALITY_ORDINAL: return member.getPropertyValue(propertyName, matchCase); default: return super.getPropertyValue(propertyName, matchCase); } } } /** * Substitutes a name into a pattern.

    * * Asterisks are replaced with the name, * double-asterisks are replaced with a single asterisk. * For example, * *

    substitute("** Subtotal - *", * "Dairy")
    * * returns * *
    "* Subtotal - Dairy"
    * * @param namePattern Pattern * @param name Name to substitute into pattern * @return Substituted pattern */ static String substitute(String namePattern, String name) { final StringBuilder buf = new StringBuilder(256); final int namePatternLen = namePattern.length(); int startIndex = 0; while (true) { int endIndex = namePattern.indexOf('*', startIndex); if (endIndex == -1) { // No '*' left // append the rest of namePattern from startIndex onwards buf.append(namePattern.substring(startIndex)); break; } // endIndex now points to the '*'; check for '**' ++endIndex; if (endIndex < namePatternLen && namePattern.charAt(endIndex) == '*') { // Found '**', replace with '*' // Include first '*'. buf.append(namePattern.substring(startIndex, endIndex)); // Skip over 2nd '*' ++endIndex; } else { // Found single '*' - substitute (omitting the '*') // Exclude '*' buf.append(namePattern.substring(startIndex, endIndex - 1)); buf.append(name); } startIndex = endIndex; } return buf.toString(); } } // End VisualTotalsFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/SetItemFunDef.java0000644000175000017500000002150011735330606023576 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractMemberCalc; import mondrian.calc.impl.AbstractTupleCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.*; import java.util.ArrayList; import java.util.List; /** * Definition of the <Set>.Item MDX function. * *

    Syntax: *

    * <Set>.Item(<Index>)
    * <Set>.Item(<String Expression> [, ...]) *
    * * @author jhyde * @since Mar 23, 2006 */ class SetItemFunDef extends FunDefBase { static final Resolver intResolver = new ReflectiveMultiResolver( "Item", ".Item()", "Returns a tuple from the set specified in . The tuple to be returned is specified by the zero-based position of the tuple in the set in .", new String[] {"mmxn"}, SetItemFunDef.class); static final Resolver stringResolver = new ResolverBase( "Item", ".Item( [, ...])", "Returns a tuple from the set specified in . The tuple to be returned is specified by the member name (or names) in .", Syntax.Method) { public FunDef resolve( Exp[] args, Validator validator, List conversions) { if (args.length < 1) { return null; } final Exp setExp = args[0]; if (!(setExp.getType() instanceof SetType)) { return null; } final SetType setType = (SetType) setExp.getType(); final int arity = setType.getArity(); // All args must be strings. for (int i = 1; i < args.length; i++) { if (!validator.canConvert( i, args[i], Category.String, conversions)) { return null; } } if (args.length - 1 != arity) { throw Util.newError( "Argument count does not match set's cardinality " + arity); } final int category = arity == 1 ? Category.Member : Category.Tuple; FunDef dummy = createDummyFunDef(this, category, args); return new SetItemFunDef(dummy); } }; public SetItemFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Type getResultType(Validator validator, Exp[] args) { SetType setType = (SetType) args[0].getType(); return setType.getElementType(); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final Type elementType = ((SetType) listCalc.getType()).getElementType(); final boolean isString = call.getArgCount() < 2 || call.getArg(1).getType() instanceof StringType; final IntegerCalc indexCalc; final StringCalc[] stringCalcs; List calcList = new ArrayList(); calcList.add(listCalc); if (isString) { indexCalc = null; stringCalcs = new StringCalc[call.getArgCount() - 1]; for (int i = 0; i < stringCalcs.length; i++) { stringCalcs[i] = compiler.compileString(call.getArg(i + 1)); calcList.add(stringCalcs[i]); } } else { stringCalcs = null; indexCalc = compiler.compileInteger(call.getArg(1)); calcList.add(indexCalc); } Calc[] calcs = calcList.toArray(new Calc[calcList.size()]); if (elementType instanceof TupleType) { final TupleType tupleType = (TupleType) elementType; final Member[] nullTuple = makeNullTuple(tupleType); if (isString) { return new AbstractTupleCalc(call, calcs) { public Member[] evaluateTuple(Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final TupleList list = listCalc.evaluateList(evaluator); assert list != null; evaluator.restore(savepoint); String[] results = new String[stringCalcs.length]; for (int i = 0; i < stringCalcs.length; i++) { results[i] = stringCalcs[i].evaluateString(evaluator); } listLoop: for (List members : list) { for (int j = 0; j < results.length; j++) { String result = results[j]; final Member member = members.get(j); if (!matchMember(member, result)) { continue listLoop; } } // All members match. Return the current one. return members.toArray(new Member[members.size()]); } // We use 'null' to represent the null tuple. Don't // know why. return null; } }; } else { return new AbstractTupleCalc(call, calcs) { public Member[] evaluateTuple(Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final TupleList list = listCalc.evaluateList(evaluator); assert list != null; evaluator.restore(savepoint); final int index = indexCalc.evaluateInteger(evaluator); int listSize = list.size(); if (index >= listSize || index < 0) { return nullTuple; } else { final List members = list.get(index); return members.toArray(new Member[members.size()]); } } }; } } else { final MemberType memberType = (MemberType) elementType; final Member nullMember = makeNullMember(memberType); if (isString) { return new AbstractMemberCalc(call, calcs) { public Member evaluateMember(Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final List list = listCalc.evaluateList(evaluator).slice(0); assert list != null; evaluator.restore(savepoint); final String result = stringCalcs[0].evaluateString(evaluator); for (Member member : list) { if (matchMember(member, result)) { return member; } } return nullMember; } }; } else { return new AbstractMemberCalc(call, calcs) { public Member evaluateMember(Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final List list = listCalc.evaluateList(evaluator).slice(0); assert list != null; evaluator.restore(savepoint); final int index = indexCalc.evaluateInteger(evaluator); int listSize = list.size(); if (index >= listSize || index < 0) { return nullMember; } else { return list.get(index); } } }; } } } private static boolean matchMember(final Member member, String name) { return member.getName().equals(name); } } // End SetItemFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/StrToMemberFunDef.java0000644000175000017500000000321111735330606024426 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2010 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractMemberCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.resource.MondrianResource; /** * Definition of the StrToMember MDX function. * *

    Syntax: *

    StrToMember(<String Expression>) *
    */ class StrToMemberFunDef extends FunDefBase { public static final FunDef INSTANCE = new StrToMemberFunDef(); private StrToMemberFunDef() { super( "StrToMember", "Returns a member from a unique name String in MDX format.", "fmS"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final StringCalc memberNameCalc = compiler.compileString(call.getArg(0)); return new AbstractMemberCalc(call, new Calc[] {memberNameCalc}) { public Member evaluateMember(Evaluator evaluator) { String memberName = memberNameCalc.evaluateString(evaluator); if (memberName == null) { throw newEvalException( MondrianResource.instance().NullValue.ex()); } return parseMember(evaluator, memberName, null); } }; } } // End StrToMemberFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/PercentileFunDef.java0000644000175000017500000000425011735330606024321 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractDoubleCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the Percentile MDX function. * * @author jhyde * @since Jan 16, 2008 */ class PercentileFunDef extends AbstractAggregateFunDef { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "Percentile", "Percentile(, , )", "Returns the value of the tuple that is at a given percentile of a set.", new String[] {"fnxnn"}, PercentileFunDef.class); public PercentileFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final Calc calc = compiler.compileScalar(call.getArg(1), true); final DoubleCalc percentCalc = compiler.compileDouble(call.getArg(2)); return new AbstractDoubleCalc( call, new Calc[] {listCalc, calc, percentCalc}) { public double evaluateDouble(Evaluator evaluator) { TupleList list = evaluateCurrentList(listCalc, evaluator); double percent = percentCalc.evaluateDouble(evaluator) * 0.01; final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final double percentile = percentile(evaluator, list, calc, percent); evaluator.restore(savepoint); return percentile; } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } }; } } // End PercentileFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/ParallelPeriodFunDef.java0000644000175000017500000001377011735330606025135 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractMemberCalc; import mondrian.calc.impl.ConstantCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.MemberType; import mondrian.olap.type.Type; import mondrian.resource.MondrianResource; import mondrian.rolap.RolapCube; import mondrian.rolap.RolapHierarchy; /** * Definition of the ParallelPeriod MDX function. * * @author jhyde * @since Mar 23, 2006 */ class ParallelPeriodFunDef extends FunDefBase { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "ParallelPeriod", "ParallelPeriod([[, [, ]]])", "Returns a member from a prior period in the same relative position as a specified member.", new String[] {"fm", "fml", "fmln", "fmlnm"}, ParallelPeriodFunDef.class); public ParallelPeriodFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Type getResultType(Validator validator, Exp[] args) { if (args.length == 0) { // With no args, the default implementation cannot // guess the hierarchy, so we supply the Time // dimension. RolapHierarchy defaultTimeHierarchy = ((RolapCube) validator.getQuery().getCube()).getTimeHierarchy( getName()); return MemberType.forHierarchy(defaultTimeHierarchy); } return super.getResultType(validator, args); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { // Member defaults to [Time].currentmember Exp[] args = call.getArgs(); // Numeric Expression defaults to 1. final IntegerCalc lagValueCalc = (args.length >= 2) ? compiler.compileInteger(args[1]) : ConstantCalc.constantInteger(1); // If level is not specified, we compute it from // member at runtime. final LevelCalc ancestorLevelCalc = args.length >= 1 ? compiler.compileLevel(args[0]) : null; final MemberCalc memberCalc; switch (args.length) { case 3: memberCalc = compiler.compileMember(args[2]); break; case 1: final Hierarchy hierarchy = args[0].getType().getHierarchy(); if (hierarchy != null) { // For some functions, such as Levels(), // the dimension cannot be determined at compile time. memberCalc = new HierarchyCurrentMemberFunDef.FixedCalcImpl( call, hierarchy); } else { memberCalc = null; } break; default: final RolapHierarchy timeHierarchy = ((RolapCube) compiler.getEvaluator().getCube()) .getTimeHierarchy(getName()); memberCalc = new HierarchyCurrentMemberFunDef.FixedCalcImpl( call, timeHierarchy); break; } return new AbstractMemberCalc( call, new Calc[] {memberCalc, lagValueCalc, ancestorLevelCalc}) { public Member evaluateMember(Evaluator evaluator) { Member member; int lagValue = lagValueCalc.evaluateInteger(evaluator); Level ancestorLevel; if (ancestorLevelCalc != null) { ancestorLevel = ancestorLevelCalc.evaluateLevel(evaluator); if (memberCalc == null) { member = evaluator.getContext(ancestorLevel.getHierarchy()); } else { member = memberCalc.evaluateMember(evaluator); } } else { member = memberCalc.evaluateMember(evaluator); Member parent = member.getParentMember(); if (parent == null) { // This is a root member, // so there is no parallelperiod. return member.getHierarchy().getNullMember(); } ancestorLevel = parent.getLevel(); } return parallelPeriod( member, ancestorLevel, evaluator, lagValue); } }; } Member parallelPeriod( Member member, Level ancestorLevel, Evaluator evaluator, int lagValue) { // Now do some error checking. // The ancestorLevel and the member must be from the // same hierarchy. if (member.getHierarchy() != ancestorLevel.getHierarchy()) { MondrianResource.instance().FunctionMbrAndLevelHierarchyMismatch.ex( "ParallelPeriod", ancestorLevel.getHierarchy().getUniqueName(), member.getHierarchy().getUniqueName()); } if (lagValue == Integer.MIN_VALUE) { // Bump up lagValue by one; otherwise -lagValue (used in // the getleadMember call below) is out of range because // Integer.MAX_VALUE == -(Integer.MIN_VALUE + 1) lagValue += 1; } int distance = member.getLevel().getDepth() - ancestorLevel.getDepth(); Member ancestor = FunUtil.ancestor( evaluator, member, distance, ancestorLevel); Member inLaw = evaluator.getSchemaReader() .getLeadMember(ancestor, -lagValue); return FunUtil.cousin( evaluator.getSchemaReader(), member, inLaw); } } // End ParallelPeriodFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/SimpleResolver.java0000644000175000017500000000361611735330606024117 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.olap.*; import java.util.List; /** * A SimpleResolver resolves a single, non-overloaded function. * * @author jhyde * @since 3 March, 2002 */ class SimpleResolver implements Resolver { private final FunDef funDef; SimpleResolver(FunDef funDef) { this.funDef = funDef; } public FunDef getFunDef() { return funDef; } public String getName() { return funDef.getName(); } public String getDescription() { return funDef.getDescription(); } public String getSignature() { return funDef.getSignature(); } public Syntax getSyntax() { return funDef.getSyntax(); } public String[] getReservedWords() { return FunUtil.emptyStringArray; } public FunDef resolve( Exp[] args, Validator validator, List conversions) { int[] parameterTypes = funDef.getParameterCategories(); if (parameterTypes.length != args.length) { return null; } for (int i = 0; i < args.length; i++) { if (!validator.canConvert( i, args[i], parameterTypes[i], conversions)) { return null; } } return funDef; } public boolean requiresExpression(int k) { int[] parameterTypes = funDef.getParameterCategories(); return (k >= parameterTypes.length) || (parameterTypes[k] != Category.Set); } } // End SimpleResolver.java mondrian-3.4.1/src/main/mondrian/olap/fun/CacheFunDef.java0000644000175000017500000000706711735330606023243 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.GenericCalc; import mondrian.calc.impl.GenericIterCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.SetType; import mondrian.olap.type.Type; import java.io.PrintWriter; import java.util.List; /** * Definition of the Cache system function, which is smart enough * to evaluate its argument only once. * * @author jhyde * @since 2005/8/14 */ public class CacheFunDef extends FunDefBase { static final String NAME = "Cache"; private static final String SIGNATURE = "Cache(<>)"; private static final String DESCRIPTION = "Evaluates and returns its sole argument, applying statement-level caching"; private static final Syntax SYNTAX = Syntax.Function; static final CacheFunResolver Resolver = new CacheFunResolver(); CacheFunDef( String name, String signature, String description, Syntax syntax, int category, Type type) { super( name, signature, description, syntax, category, new int[] {category}); Util.discard(type); } public void unparse(Exp[] args, PrintWriter pw) { args[0].unparse(pw); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Exp exp = call.getArg(0); final ExpCacheDescriptor cacheDescriptor = new ExpCacheDescriptor(exp, compiler); if (call.getType() instanceof SetType) { return new GenericIterCalc(call) { public Object evaluate(Evaluator evaluator) { return evaluator.getCachedResult(cacheDescriptor); } public Calc[] getCalcs() { return new Calc[] {cacheDescriptor.getCalc()}; } public ResultStyle getResultStyle() { // cached lists are immutable return ResultStyle.LIST; } }; } else { return new GenericCalc(call) { public Object evaluate(Evaluator evaluator) { return evaluator.getCachedResult(cacheDescriptor); } public Calc[] getCalcs() { return new Calc[] {cacheDescriptor.getCalc()}; } public ResultStyle getResultStyle() { return ResultStyle.VALUE; } }; } } public static class CacheFunResolver extends ResolverBase { CacheFunResolver() { super(NAME, SIGNATURE, DESCRIPTION, SYNTAX); } public FunDef resolve( Exp[] args, Validator validator, List conversions) { if (args.length != 1) { return null; } final Exp exp = args[0]; final int category = exp.getCategory(); final Type type = exp.getType(); return new CacheFunDef( NAME, SIGNATURE, DESCRIPTION, SYNTAX, category, type); } public boolean requiresExpression(int k) { return false; } } } // End CacheFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/HierarchyCurrentMemberFunDef.java0000644000175000017500000000653111735330606026644 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractMemberCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.rolap.RolapHierarchy; import java.util.Map; /** * Definition of the <Hierarchy>.CurrentMember MDX * builtin function. * * @author jhyde * @since Mar 23, 2006 */ public class HierarchyCurrentMemberFunDef extends FunDefBase { static final HierarchyCurrentMemberFunDef instance = new HierarchyCurrentMemberFunDef(); private HierarchyCurrentMemberFunDef() { super( "CurrentMember", "Returns the current member along a hierarchy during an iteration.", "pmh"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final HierarchyCalc hierarchyCalc = compiler.compileHierarchy(call.getArg(0)); final Hierarchy hierarchy = hierarchyCalc.getType().getHierarchy(); if (hierarchy != null) { return new FixedCalcImpl(call, hierarchy); } else { return new CalcImpl(call, hierarchyCalc); } } /** * Compiled implementation of the Hierarchy.CurrentMember function that * evaluates the hierarchy expression first. */ public static class CalcImpl extends AbstractMemberCalc { private final HierarchyCalc hierarchyCalc; public CalcImpl(Exp exp, HierarchyCalc hierarchyCalc) { super(exp, new Calc[] {hierarchyCalc}); this.hierarchyCalc = hierarchyCalc; } protected String getName() { return "CurrentMember"; } public Member evaluateMember(Evaluator evaluator) { Hierarchy hierarchy = hierarchyCalc.evaluateHierarchy(evaluator); return evaluator.getContext(hierarchy); } public boolean dependsOn(Hierarchy hierarchy) { return hierarchyCalc.getType().usesHierarchy(hierarchy, false); } } /** * Compiled implementation of the Hierarchy.CurrentMember function that * uses a fixed hierarchy. */ public static class FixedCalcImpl extends AbstractMemberCalc { // getContext works faster if we give RolapHierarchy rather than // Hierarchy private final RolapHierarchy hierarchy; public FixedCalcImpl(Exp exp, Hierarchy hierarchy) { super(exp, new Calc[] {}); assert hierarchy != null; this.hierarchy = (RolapHierarchy) hierarchy; } protected String getName() { return "CurrentMemberFixed"; } public Member evaluateMember(Evaluator evaluator) { return evaluator.getContext(hierarchy); } public boolean dependsOn(Hierarchy hierarchy) { return this.hierarchy == hierarchy; } public void collectArguments(Map arguments) { arguments.put("hierarchy", hierarchy); super.collectArguments(arguments); } } } // End HierarchyCurrentMemberFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/CoalesceEmptyFunDef.java0000644000175000017500000000625511735330606024773 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.Calc; import mondrian.calc.ExpCompiler; import mondrian.calc.impl.GenericCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.util.List; /** * Definition of the CoalesceEmpty MDX function. * *

    It evaluates each of the arguments to the function, returning the * first such argument that does not return a null value. * * @author gjohnson */ public class CoalesceEmptyFunDef extends FunDefBase { static final ResolverBase Resolver = new ResolverImpl(); public CoalesceEmptyFunDef(ResolverBase resolverBase, int type, int[] types) { super(resolverBase, type, types); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Exp[] args = call.getArgs(); final Calc[] calcs = new Calc[args.length]; for (int i = 0; i < args.length; i++) { calcs[i] = compiler.compileScalar(args[i], true); } return new GenericCalc(call) { public Object evaluate(Evaluator evaluator) { for (Calc calc : calcs) { final Object o = calc.evaluate(evaluator); if (o != null) { return o; } } return null; } public Calc[] getCalcs() { return calcs; } }; } private static class ResolverImpl extends ResolverBase { public ResolverImpl() { super( "CoalesceEmpty", "CoalesceEmpty([, ...])", "Coalesces an empty cell value to a different value. All of the expressions must be of the same type (number or string).", Syntax.Function); } public FunDef resolve( Exp[] args, Validator validator, List conversions) { if (args.length < 1) { return null; } final int[] types = {Category.Numeric, Category.String}; final int[] argTypes = new int[args.length]; for (int type : types) { int matchingArgs = 0; conversions.clear(); for (int i = 0; i < args.length; i++) { if (validator.canConvert(i, args[i], type, conversions)) { matchingArgs++; } argTypes[i] = type; } if (matchingArgs == args.length) { return new CoalesceEmptyFunDef(this, type, argTypes); } } return null; } public boolean requiresExpression(int k) { return true; } } } // End CoalesceEmptyFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/LastPeriodsFunDef.java0000644000175000017500000001172411735330606024464 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.calc.impl.UnaryTupleList; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.*; import mondrian.rolap.RolapCube; import mondrian.rolap.RolapHierarchy; import java.util.*; /** * Definition of the LastPeriods MDX function. * * @author jhyde * @since Mar 23, 2006 */ class LastPeriodsFunDef extends FunDefBase { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "LastPeriods", "LastPeriods( [, ])", "Returns a set of members prior to and including a specified member.", new String[] {"fxn", "fxnm"}, LastPeriodsFunDef.class); public LastPeriodsFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Type getResultType(Validator validator, Exp[] args) { if (args.length == 1) { // If Member is not specified, // it is Time.CurrentMember. RolapHierarchy defaultTimeHierarchy = ((RolapCube) validator.getQuery().getCube()).getTimeHierarchy( getName()); return new SetType(MemberType.forHierarchy(defaultTimeHierarchy)); } else { Type type = args[1].getType(); Type memberType = TypeUtil.toMemberOrTupleType(type); return new SetType(memberType); } } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { // Member defaults to [Time].currentmember Exp[] args = call.getArgs(); final MemberCalc memberCalc; if (args.length == 1) { final RolapHierarchy timeHierarchy = ((RolapCube) compiler.getEvaluator().getCube()) .getTimeHierarchy(getName()); memberCalc = new HierarchyCurrentMemberFunDef.FixedCalcImpl( call, timeHierarchy); } else { memberCalc = compiler.compileMember(args[1]); } // Numeric Expression. final IntegerCalc indexValueCalc = compiler.compileInteger(args[0]); return new AbstractListCalc( call, new Calc[] {memberCalc, indexValueCalc}) { public TupleList evaluateList(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); int indexValue = indexValueCalc.evaluateInteger(evaluator); return new UnaryTupleList( lastPeriods(member, evaluator, indexValue)); } }; } /** * If Index is positive, returns the set of Index * members ending with Member and starting with the * member lagging Index - 1 from Member. * *

    If Index is negative, returns the set of (- Index) * members starting with Member and ending with the * member leading (- Index - 1) from Member. * *

    If Index is zero, the empty set is returned. */ List lastPeriods( Member member, Evaluator evaluator, int indexValue) { // empty set if ((indexValue == 0) || member.isNull()) { return Collections.emptyList(); } List list = new ArrayList(); // set with just member if ((indexValue == 1) || (indexValue == -1)) { list.add(member); return list; } // When null is found, getting the first/last // member at a given level is not particularly // fast. Member startMember; Member endMember; if (indexValue > 0) { startMember = evaluator.getSchemaReader() .getLeadMember(member, - (indexValue - 1)); endMember = member; if (startMember.isNull()) { List members = evaluator.getSchemaReader() .getLevelMembers(member.getLevel(), false); startMember = members.get(0); } } else { startMember = member; endMember = evaluator.getSchemaReader() .getLeadMember(member, -(indexValue + 1)); if (endMember.isNull()) { List members = evaluator.getSchemaReader() .getLevelMembers(member.getLevel(), false); endMember = members.get(members.size() - 1); } } evaluator.getSchemaReader().getMemberRange( member.getLevel(), startMember, endMember, list); return list; } } // End LastPeriodsFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/DistinctFunDef.java0000644000175000017500000000362111735330606024011 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.Evaluator; import mondrian.olap.Member; import java.util.*; /** * Definition of the Distinct MDX function. * *

    Syntax: *

    Distinct(<Set>)
    * * @author jhyde * @since Jun 10, 2007 */ class DistinctFunDef extends FunDefBase { public static final DistinctFunDef instance = new DistinctFunDef(); private DistinctFunDef() { super( "Distinct", "Eliminates duplicate tuples from a set.", "fxx"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); return new CalcImpl(call, listCalc); } static class CalcImpl extends AbstractListCalc { private final ListCalc listCalc; public CalcImpl(ResolvedFunCall call, ListCalc listCalc) { super(call, new Calc[]{listCalc}); this.listCalc = listCalc; } public TupleList evaluateList(Evaluator evaluator) { TupleList list = listCalc.evaluateList(evaluator); Set> set = new HashSet>(list.size()); TupleList result = list.cloneList(list.size()); for (List element : list) { if (set.add(element)) { result.add(element); } } return result; } } } // End DistinctFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/StdevFunDef.java0000644000175000017500000000462611735330606023323 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractDoubleCalc; import mondrian.calc.impl.ValueCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the Stdev builtin MDX function, and its alias * Stddev. * * @author jhyde * @since Mar 23, 2006 */ class StdevFunDef extends AbstractAggregateFunDef { static final ReflectiveMultiResolver StdevResolver = new ReflectiveMultiResolver( "Stdev", "Stdev([, ])", "Returns the standard deviation of a numeric expression evaluated over a set (unbiased).", new String[]{"fnx", "fnxn"}, StdevFunDef.class); static final ReflectiveMultiResolver StddevResolver = new ReflectiveMultiResolver( "Stddev", "Stddev([, ])", "Alias for Stdev.", new String[]{"fnx", "fnxn"}, StdevFunDef.class); public StdevFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final Calc calc = call.getArgCount() > 1 ? compiler.compileScalar(call.getArg(1), true) : new ValueCalc(call); return new AbstractDoubleCalc(call, new Calc[] {listCalc, calc}) { public double evaluateDouble(Evaluator evaluator) { TupleList memberList = evaluateCurrentList(listCalc, evaluator); final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final double stdev = (Double) stdev( evaluator, memberList, calc, false); evaluator.restore(savepoint); return stdev; } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } }; } } // End StdevFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/FunTableImpl.java0000644000175000017500000001471511735330606023470 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.olap.*; import mondrian.util.Pair; import java.util.*; /** * Abstract implementation of {@link FunTable}. * *

    The derived class must implement * {@link #defineFunctions(mondrian.olap.FunTable.Builder)} to define * each function which will be recognized by this table. This method is called * from the constructor, after which point, no further functions can be added. */ public abstract class FunTableImpl implements FunTable { /** * Maps the upper-case name of a function plus its * {@link mondrian.olap.Syntax} to an array of * {@link Resolver} objects for that name. */ private Map, List> mapNameToResolvers; private Set reservedWordSet; private List reservedWordList; private Set propertyWords; private List funInfoList; /** * Creates a FunTableImpl. */ protected FunTableImpl() { } /** * Initializes the function table. */ public final void init() { final BuilderImpl builder = new BuilderImpl(); defineFunctions(builder); builder.organizeFunctions(); // Copy information out of builder into this. this.funInfoList = Collections.unmodifiableList(builder.funInfoList); this.mapNameToResolvers = Collections.unmodifiableMap(builder.mapNameToResolvers); this.reservedWordSet = builder.reservedWords; final String[] reservedWords = builder.reservedWords.toArray( new String[builder.reservedWords.size()]); Arrays.sort(reservedWords); this.reservedWordList = Collections.unmodifiableList(Arrays.asList(reservedWords)); this.propertyWords = Collections.unmodifiableSet(builder.propertyWords); } /** * Creates a key to look up an operator in the resolver map. The key * consists of the uppercase function name and the syntax. * * @param name Function/operator name * @param syntax Syntax * @return Key */ private static Pair makeResolverKey( String name, Syntax syntax) { return new Pair(name.toUpperCase(), syntax); } public List getReservedWords() { return reservedWordList; } public boolean isReserved(String s) { return reservedWordSet.contains(s.toUpperCase()); } public List getResolvers() { final List list = new ArrayList(); for (List resolvers : mapNameToResolvers.values()) { list.addAll(resolvers); } return list; } public boolean isProperty(String s) { return propertyWords.contains(s.toUpperCase()); } public List getFunInfoList() { return funInfoList; } public List getResolvers(String name, Syntax syntax) { Pair key = makeResolverKey(name, syntax); List resolvers = mapNameToResolvers.get(key); if (resolvers == null) { resolvers = Collections.emptyList(); } return resolvers; } /** * Implementation of {@link mondrian.olap.FunTable.Builder}. * Functions are added to lists each time {@link #define(Resolver)} is * called, then {@link #organizeFunctions()} sorts and indexes the map. */ private class BuilderImpl implements Builder { private final List resolverList = new ArrayList(); private final List funInfoList = new ArrayList(); private final Map, List> mapNameToResolvers = new HashMap, List>(); private final Set reservedWords = new HashSet(); private final Set propertyWords = new HashSet(); public void define(FunDef funDef) { define(new SimpleResolver(funDef)); } public void define(Resolver resolver) { funInfoList.add(FunInfo.make(resolver)); if (resolver.getSyntax() == Syntax.Property) { propertyWords.add(resolver.getName().toUpperCase()); } resolverList.add(resolver); final String[] reservedWords = resolver.getReservedWords(); for (String reservedWord : reservedWords) { defineReserved(reservedWord); } } public void define(FunInfo funInfo) { funInfoList.add(funInfo); } public void defineReserved(String s) { reservedWords.add(s.toUpperCase()); } /** * Indexes the collection of functions. */ protected void organizeFunctions() { Collections.sort(funInfoList); // Map upper-case function names to resolvers. final List> nonSingletonResolverLists = new ArrayList>(); for (Resolver resolver : resolverList) { Pair key = makeResolverKey( resolver.getName(), resolver.getSyntax()); List list = mapNameToResolvers.get(key); if (list == null) { list = new ArrayList(); mapNameToResolvers.put(key, list); } list.add(resolver); if (list.size() == 2) { nonSingletonResolverLists.add(list); } } // Sort lists by signature (skipping singleton lists) final Comparator comparator = new Comparator() { public int compare(Resolver o1, Resolver o2) { return o1.getSignature().compareTo(o2.getSignature()); } }; for (List resolverList : nonSingletonResolverLists) { Collections.sort(resolverList, comparator); } } } } // End FunTableImpl.java mondrian-3.4.1/src/main/mondrian/olap/fun/AddCalculatedMembersFunDef.java0000644000175000017500000001013211735330606026210 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.calc.impl.UnaryTupleList; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.*; import java.util.*; /** * Definition of the AddCalculatedMembers MDX function. * *

    AddCalculatedMembers adds calculated members that are siblings * of the members in the set. The set is limited to one dimension. * *

    Syntax: * *

    AddCalculatedMembers(<Set>)
    * @author jhyde * @since Mar 23, 2006 */ class AddCalculatedMembersFunDef extends FunDefBase { private static final AddCalculatedMembersFunDef instance = new AddCalculatedMembersFunDef(); public static final Resolver resolver = new ResolverImpl(); private static final String FLAG = "fxx"; private AddCalculatedMembersFunDef() { super( "AddCalculatedMembers", "Adds calculated members to a set.", FLAG); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); return new AbstractListCalc(call, new Calc[] {listCalc}) { public TupleList evaluateList(Evaluator evaluator) { final TupleList list = listCalc.evaluateList(evaluator); return new UnaryTupleList( addCalculatedMembers(list.slice(0), evaluator)); } }; } private List addCalculatedMembers( List memberList, Evaluator evaluator) { // Determine unique levels in the set final Set levels = new LinkedHashSet(); Hierarchy hierarchy = null; for (Member member : memberList) { if (hierarchy == null) { hierarchy = member.getHierarchy(); } else if (hierarchy != member.getHierarchy()) { throw newEvalException( this, "Only members from the same hierarchy are allowed in the " + "AddCalculatedMembers set: " + hierarchy + " vs " + member.getHierarchy()); } levels.add(member.getLevel()); } // For each level, add the calculated members from both // the schema and the query List workingList = new ArrayList(memberList); final SchemaReader schemaReader = evaluator.getQuery().getSchemaReader(true); for (Level level : levels) { List calcMemberList = schemaReader.getCalculatedMembers(level); workingList.addAll(calcMemberList); } return workingList; } private static class ResolverImpl extends MultiResolver { public ResolverImpl() { super( instance.getName(), instance.getSignature(), instance.getDescription(), new String[] {FLAG}); } protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) { if (args.length == 1) { Exp arg = args[0]; final Type type1 = arg.getType(); if (type1 instanceof SetType) { SetType type = (SetType) type1; if (type.getElementType() instanceof MemberType) { return instance; } else { throw newEvalException( instance, "Only single dimension members allowed in set for AddCalculatedMembers"); } } } return null; } } } // End AddCalculatedMembersFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/ParenthesesFunDef.java0000644000175000017500000000351711735330606024515 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.Calc; import mondrian.calc.ExpCompiler; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.Type; import java.io.PrintWriter; /** * ParenthesesFunDef implements the parentheses operator as if it * were a function. * * @author jhyde * @since 3 March, 2002 */ public class ParenthesesFunDef extends FunDefBase { private final int argType; public ParenthesesFunDef(int argType) { super( "()", "()", "Parenthesis enclose an expression and indicate precedence.", Syntax.Parentheses, argType, new int[] {argType}); this.argType = argType; } public void unparse(Exp[] args, PrintWriter pw) { if (args.length != 1) { ExpBase.unparseList(pw, args, "(", ",", ")"); } else { // Don't use parentheses unless necessary. We add parentheses around // expressions because we're not sure of operator precedence, so if // we're not careful, the parentheses tend to multiply ad infinitum. args[0].unparse(pw); } } public Type getResultType(Validator validator, Exp[] args) { Util.assertTrue(args.length == 1); return args[0].getType(); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { return compiler.compile(call.getArg(0)); } } // End ParenthesesFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/DrilldownLevelFunDef.java0000644000175000017500000001315711735330606025163 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.calc.impl.UnaryTupleList; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.util.ArrayList; import java.util.List; /** * Definition of the DrilldownLevel MDX function. * *

    Syntax: * *

     * DrilldownLevel(Set_Expression[, Level_Expression])
     * DrilldownLevel(Set_Expression, , Numeric_Expression)
     * 
    * * @author jhyde * @since Mar 23, 2006 */ class DrilldownLevelFunDef extends FunDefBase { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "DrilldownLevel", "DrilldownLevel([, ]) or DrilldownLevel(, , )", "Drills down the members of a set, at a specified level, to one level below. Alternatively, drills down on a specified dimension in the set.", new String[]{"fxx", "fxxl", "fxxen"}, DrilldownLevelFunDef.class); public DrilldownLevelFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final LevelCalc levelCalc = call.getArgCount() > 1 && call.getArg(1).getType() instanceof mondrian.olap.type.LevelType ? compiler.compileLevel(call.getArg(1)) : null; final IntegerCalc indexCalc = call.getArgCount() > 2 ? compiler.compileInteger(call.getArg(2)) : null; final int arity = listCalc.getType().getArity(); if (indexCalc == null) { return new AbstractListCalc(call, new Calc[] {listCalc, levelCalc}) { public TupleList evaluateList(Evaluator evaluator) { TupleList list = listCalc.evaluateList(evaluator); if (list.size() == 0) { return list; } int searchDepth = -1; if (levelCalc != null) { Level level = levelCalc.evaluateLevel(evaluator); searchDepth = level.getDepth(); } return new UnaryTupleList( drill(searchDepth, list.slice(0), evaluator)); } }; } else { return new AbstractListCalc(call, new Calc[] {listCalc, indexCalc}) { public TupleList evaluateList(Evaluator evaluator) { TupleList list = listCalc.evaluateList(evaluator); if (list.isEmpty()) { return list; } final int index = indexCalc.evaluateInteger(evaluator); if (index < 0 || index >= arity) { return list; } TupleList result = TupleCollections.createList(arity); final SchemaReader schemaReader = evaluator.getSchemaReader(); final Member[] tupleClone = new Member[arity]; for (List tuple : list) { result.add(tuple); final List children = schemaReader.getMemberChildren(tuple.get(index)); for (Member child : children) { tuple.toArray(tupleClone); tupleClone[index] = child; result.addTuple(tupleClone); } } return result; } }; } } List drill(int searchDepth, List list, Evaluator evaluator) { if (searchDepth == -1) { searchDepth = list.get(0).getLevel().getDepth(); for (int i = 1, m = list.size(); i < m; i++) { Member member = list.get(i); int memberDepth = member.getLevel().getDepth(); if (memberDepth > searchDepth) { searchDepth = memberDepth; } } } List drilledSet = new ArrayList(); for (int i = 0, m = list.size(); i < m; i++) { Member member = list.get(i); drilledSet.add(member); Member nextMember = i == (m - 1) ? null : list.get(i + 1); // // This member is drilled if it's at the correct depth // and if it isn't drilled yet. A member is considered // to be "drilled" if it is immediately followed by // at least one descendant // if (member.getLevel().getDepth() == searchDepth && !FunUtil.isAncestorOf(member, nextMember, true)) { final List childMembers = evaluator.getSchemaReader().getMemberChildren(member); for (Member childMember : childMembers) { drilledSet.add(childMember); } } } return drilledSet; } } // End DrilldownLevelFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/XtdFunDef.java0000644000175000017500000001247511735330606022776 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.calc.impl.UnaryTupleList; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.DimensionType; import mondrian.olap.*; import mondrian.olap.LevelType; import mondrian.olap.type.*; import mondrian.resource.MondrianResource; import mondrian.rolap.RolapCube; import mondrian.rolap.RolapHierarchy; /** * Definition of Ytd, Qtd, Mtd, * and Wtd MDX builtin functions. * * @author jhyde * @since Mar 23, 2006 */ class XtdFunDef extends FunDefBase { private final LevelType levelType; static final ResolverImpl MtdResolver = new ResolverImpl( "Mtd", "Mtd([])", "A shortcut function for the PeriodsToDate function that specifies the level to be Month.", new String[]{"fx", "fxm"}, LevelType.TimeMonths); static final ResolverImpl QtdResolver = new ResolverImpl( "Qtd", "Qtd([])", "A shortcut function for the PeriodsToDate function that specifies the level to be Quarter.", new String[]{"fx", "fxm"}, LevelType.TimeQuarters); static final ResolverImpl WtdResolver = new ResolverImpl( "Wtd", "Wtd([])", "A shortcut function for the PeriodsToDate function that specifies the level to be Week.", new String[]{"fx", "fxm"}, LevelType.TimeWeeks); static final ResolverImpl YtdResolver = new ResolverImpl( "Ytd", "Ytd([])", "A shortcut function for the PeriodsToDate function that specifies the level to be Year.", new String[]{"fx", "fxm"}, LevelType.TimeYears); public XtdFunDef(FunDef dummyFunDef, LevelType levelType) { super(dummyFunDef); this.levelType = levelType; } public Type getResultType(Validator validator, Exp[] args) { if (args.length == 0) { // With no args, the default implementation cannot // guess the hierarchy. RolapHierarchy defaultTimeHierarchy = ((RolapCube) validator.getQuery().getCube()).getTimeHierarchy( getName()); return new SetType(MemberType.forHierarchy(defaultTimeHierarchy)); } final Type type = args[0].getType(); if (type.getDimension().getDimensionType() != DimensionType.TimeDimension) { throw MondrianResource.instance().TimeArgNeeded.ex(getName()); } return super.getResultType(validator, args); } private Level getLevel(Evaluator evaluator) { switch (levelType) { case TimeYears: return evaluator.getCube().getYearLevel(); case TimeQuarters: return evaluator.getCube().getQuarterLevel(); case TimeMonths: return evaluator.getCube().getMonthLevel(); case TimeWeeks: return evaluator.getCube().getWeekLevel(); case TimeDays: return evaluator.getCube().getWeekLevel(); default: throw Util.badValue(levelType); } } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Level level = getLevel(compiler.getEvaluator()); switch (call.getArgCount()) { case 0: return new AbstractListCalc(call, new Calc[0]) { public TupleList evaluateList(Evaluator evaluator) { return new UnaryTupleList( periodsToDate(evaluator, level, null)); } public boolean dependsOn(Hierarchy hierarchy) { return hierarchy.getDimension().getDimensionType() == mondrian.olap.DimensionType.TimeDimension; } }; default: final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractListCalc(call, new Calc[] {memberCalc}) { public TupleList evaluateList(Evaluator evaluator) { return new UnaryTupleList( periodsToDate( evaluator, level, memberCalc.evaluateMember(evaluator))); } }; } } private static class ResolverImpl extends MultiResolver { private final LevelType levelType; public ResolverImpl( String name, String signature, String description, String[] signatures, LevelType levelType) { super(name, signature, description, signatures); this.levelType = levelType; } protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) { return new XtdFunDef(dummyFunDef, levelType); } } } // End XtdFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/JavaFunDef.java0000644000175000017500000003421311735330606023112 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractCalc; import mondrian.calc.impl.GenericCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.lang.annotation.*; import java.lang.reflect.*; import java.util.*; /** * MDX function which is implemented by a Java method. When the function is * executed, the method is invoked via reflection. * * @author wgorman, jhyde * @since Jan 5, 2008 */ public class JavaFunDef extends FunDefBase { private static final Map mapClazzToCategory = new HashMap(); private static final String className = JavaFunDef.class.getName(); static { mapClazzToCategory.put(String.class, Category.String); mapClazzToCategory.put(Double.class, Category.Numeric); mapClazzToCategory.put(double.class, Category.Numeric); mapClazzToCategory.put(Integer.class, Category.Integer); mapClazzToCategory.put(int.class, Category.Integer); mapClazzToCategory.put(boolean.class, Category.Logical); mapClazzToCategory.put(Object.class, Category.Value); mapClazzToCategory.put(Date.class, Category.DateTime); mapClazzToCategory.put(float.class, Category.Numeric); mapClazzToCategory.put(long.class, Category.Numeric); mapClazzToCategory.put(double[].class, Category.Array); mapClazzToCategory.put(char.class, Category.String); mapClazzToCategory.put(byte.class, Category.Integer); } private final Method method; /** * Creates a JavaFunDef. * * @param name Name * @param desc Description * @param syntax Syntax * @param returnCategory Return type * @param paramCategories Parameter types * @param method Java method which implements this function */ public JavaFunDef( String name, String desc, Syntax syntax, int returnCategory, int[] paramCategories, Method method) { super(name, null, desc, syntax, returnCategory, paramCategories); this.method = method; } public Calc compileCall( ResolvedFunCall call, ExpCompiler compiler) { final Calc[] calcs = new Calc[parameterCategories.length]; final Class[] parameterTypes = method.getParameterTypes(); for (int i = 0; i < calcs.length;i++) { calcs[i] = compileTo( compiler, call.getArgs()[i], parameterTypes[i]); } return new JavaMethodCalc(call, calcs, method); } private static int getCategory(Class clazz) { return mapClazzToCategory.get(clazz); } private static int getReturnCategory(Method m) { return getCategory(m.getReturnType()); } private static int[] getParameterCategories(Method m) { int arr[] = new int[m.getParameterTypes().length]; for (int i = 0; i < m.getParameterTypes().length; i++) { arr[i] = getCategory(m.getParameterTypes()[i]); } return arr; } private static FunDef generateFunDef(final Method method) { String name = getAnnotation( method, className + "$FunctionName", method.getName()); String desc = getAnnotation( method, className + "$Description", ""); Syntax syntax = getAnnotation( method, className + "$SyntaxDef", Syntax.Function); // In JDK 1.4 we don't have annotations, so the function name will be // precisely the method name. In particular, we went the // Vba.int_(Object) method to become the 'Int' function. if (name.endsWith("_") && Util.PreJdk15) { name = name.substring(0, name.length() - 1); } int returnCategory = getReturnCategory(method); int paramCategories[] = getParameterCategories(method); return new JavaFunDef( name, desc, syntax, returnCategory, paramCategories, method); } /** * Scans a java class and returns a list of function definitions, one for * each static method which is suitable to become an MDX function. * * @param clazz Class * @return List of function definitions */ public static List scan(Class clazz) { List list = new ArrayList(); Method[] methods = clazz.getMethods(); for (Method method : methods) { if (Modifier.isStatic(method.getModifiers()) && !method.getName().equals("main")) { list.add(generateFunDef(method)); } } return list; } /** * Compiles an expression to a calc of the required result type. * *

    Since the result of evaluating the calc will be passed to the method * using reflection, it is important that the calc returns * precisely the correct type: if a method requires an * int, you can pass an {@link Integer} but not a {@link Long} * or {@link Float}. * *

    If it can be determined that the underlying calc will never return * null, generates an optimal form with one fewer object instantiation. * * @param compiler Compiler * @param exp Expression to compile * @param clazz Desired class * @return compiled expression */ private static Calc compileTo(ExpCompiler compiler, Exp exp, Class clazz) { if (clazz == String.class) { return compiler.compileString(exp); } else if (clazz == Date.class) { return compiler.compileDateTime(exp); } else if (clazz == boolean.class) { return compiler.compileBoolean(exp); } else if (clazz == byte.class) { final IntegerCalc integerCalc = compiler.compileInteger(exp); if (integerCalc.getResultStyle() == ResultStyle.VALUE_NOT_NULL) { // We know that the calculation will never return a null value, // so generate optimized code. return new AbstractCalc2(exp, integerCalc) { public Object evaluate(Evaluator evaluator) { return (byte) integerCalc.evaluateInteger(evaluator); } }; } else { return new AbstractCalc2(exp, integerCalc) { public Object evaluate(Evaluator evaluator) { Integer i = (Integer) integerCalc.evaluate(evaluator); return i == null ? null : (byte) i.intValue(); } }; } } else if (clazz == char.class) { final StringCalc stringCalc = compiler.compileString(exp); return new AbstractCalc2(exp, stringCalc) { public Object evaluate(Evaluator evaluator) { final String string = stringCalc.evaluateString(evaluator); return Character.valueOf( string == null || string.length() < 1 ? (char) 0 : string.charAt(0)); } }; } else if (clazz == short.class) { final IntegerCalc integerCalc = compiler.compileInteger(exp); if (integerCalc.getResultStyle() == ResultStyle.VALUE_NOT_NULL) { return new AbstractCalc2(exp, integerCalc) { public Object evaluate(Evaluator evaluator) { return (short) integerCalc.evaluateInteger(evaluator); } }; } else { return new AbstractCalc2(exp, integerCalc) { public Object evaluate(Evaluator evaluator) { Integer i = (Integer) integerCalc.evaluate(evaluator); return i == null ? null : (short) i.intValue(); } }; } } else if (clazz == int.class) { return compiler.compileInteger(exp); } else if (clazz == long.class) { final IntegerCalc integerCalc = compiler.compileInteger(exp); if (integerCalc.getResultStyle() == ResultStyle.VALUE_NOT_NULL) { return new AbstractCalc2(exp, integerCalc) { public Object evaluate(Evaluator evaluator) { return (long) integerCalc.evaluateInteger(evaluator); } }; } else { return new AbstractCalc2(exp, integerCalc) { public Object evaluate(Evaluator evaluator) { Integer i = (Integer) integerCalc.evaluate(evaluator); return i == null ? null : (long) i.intValue(); } }; } } else if (clazz == float.class) { final DoubleCalc doubleCalc = compiler.compileDouble(exp); if (doubleCalc.getResultStyle() == ResultStyle.VALUE_NOT_NULL) { return new AbstractCalc2(exp, doubleCalc) { public Object evaluate(Evaluator evaluator) { Double v = (Double) doubleCalc.evaluate(evaluator); return v == null ? null : v.floatValue(); } }; } else { return new AbstractCalc2(exp, doubleCalc) { public Object evaluate(Evaluator evaluator) { return (float) doubleCalc.evaluateDouble(evaluator); } }; } } else if (clazz == double.class) { return compiler.compileDouble(exp); } else if (clazz == Object.class) { return compiler.compileScalar(exp, false); } else { throw newInternal("expected primitive type, got " + clazz); } } /** * Annotation which allows you to tag a Java method with the name of the * MDX function it implements. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface FunctionName { public abstract String value(); } /** * Annotation which allows you to tag a Java method with the description * of the MDX function it implements. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Description { public abstract String value(); } /** * Annotation which allows you to tag a Java method with the signature of * the MDX function it implements. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Signature { public abstract String value(); } /** * Annotation which allows you to tag a Java method with the syntax of the * MDX function it implements. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface SyntaxDef { public abstract Syntax value(); } /** * Base class for adapter calcs that convert arguments into the precise * type needed. */ private static abstract class AbstractCalc2 extends AbstractCalc { /** * Creates an AbstractCalc2. * * @param exp Source expression * @param calc Child compiled expression */ protected AbstractCalc2(Exp exp, Calc calc) { super(exp, new Calc[] {calc}); } } /** * Calc which calls a Java method. */ private static class JavaMethodCalc extends GenericCalc { private final Method method; private final Object[] args; /** * Creates a JavaMethodCalc. * * @param call Function call being implemented * @param calcs Calcs for arguments of function call * @param method Method to call */ public JavaMethodCalc( ResolvedFunCall call, Calc[] calcs, Method method) { super(call, calcs); this.method = method; this.args = new Object[calcs.length]; } public Object evaluate(Evaluator evaluator) { final Calc[] calcs = getCalcs(); for (int i = 0; i < args.length; i++) { args[i] = calcs[i].evaluate(evaluator); if (args[i] == null) { return nullValue; } } try { return method.invoke(null, args); } catch (IllegalAccessException e) { throw newEvalException(e); } catch (InvocationTargetException e) { throw newEvalException(e.getCause()); } catch (IllegalArgumentException e) { if (e.getMessage().equals("argument type mismatch")) { StringBuilder buf = new StringBuilder( "argument type mismatch: parameters ("); int k = 0; for (Class parameterType : method.getParameterTypes()) { if (k++ > 0) { buf.append(", "); } buf.append(parameterType.getName()); } buf.append("), actual ("); k = 0; for (Object arg : args) { if (k++ > 0) { buf.append(", "); } buf.append( arg == null ? "null" : arg.getClass().getName()); } buf.append(")"); throw newInternal(buf.toString()); } else { throw e; } } } } } // End JavaFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/StdevPFunDef.java0000644000175000017500000000457011735330606023441 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractDoubleCalc; import mondrian.calc.impl.ValueCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the StdevP builtin MDX function, and its alias * StddevP. * * @author jhyde * @since Mar 23, 2006 */ class StdevPFunDef extends AbstractAggregateFunDef { static final Resolver StdevpResolver = new ReflectiveMultiResolver( "StdevP", "StdevP([, ])", "Returns the standard deviation of a numeric expression evaluated over a set (biased).", new String[]{"fnx", "fnxn"}, StdevPFunDef.class); static final Resolver StddevpResolver = new ReflectiveMultiResolver( "StddevP", "StddevP([, ])", "Alias for StdevP.", new String[]{"fnx", "fnxn"}, StdevPFunDef.class); public StdevPFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final Calc calc = call.getArgCount() > 1 ? compiler.compileScalar(call.getArg(1), true) : new ValueCalc(call); return new AbstractDoubleCalc(call, new Calc[] {listCalc, calc}) { public double evaluateDouble(Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); TupleList list = evaluateCurrentList(listCalc, evaluator); final double stdev = (Double) stdev( evaluator, list, calc, true); evaluator.restore(savepoint); return stdev; } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } }; } } // End StdevPFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/ValueFunDef.java0000644000175000017500000000256211735330606023307 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2006 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.olap.*; import mondrian.olap.type.Type; import java.io.PrintWriter; /** * A ValueFunDef is a pseudo-function to evaluate a member or * a tuple. Similar to {@link TupleFunDef}. * * @author jhyde * @since Jun 14, 2002 */ class ValueFunDef extends FunDefBase { private final int[] argTypes; ValueFunDef(int[] argTypes) { super( "_Value()", "_Value([, ...])", "Pseudo-function which evaluates a tuple.", Syntax.Parentheses, Category.Numeric, argTypes); this.argTypes = argTypes; } public int getReturnCategory() { return Category.Tuple; } public int[] getParameterCategories() { return argTypes; } public void unparse(Exp[] args, PrintWriter pw) { ExpBase.unparseList(pw, args, "(", ", ", ")"); } public Type getResultType(Validator validator, Exp[] args) { return null; } } // End ValueFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/SubsetFunDef.java0000644000175000017500000000524111735330606023475 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.Evaluator; import mondrian.olap.FunDef; /** * Definition of the Subset MDX function. * * @author jhyde * @since Mar 23, 2006 */ class SubsetFunDef extends FunDefBase { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "Subset", "Subset(, [, ])", "Returns a subset of elements from a set.", new String[] {"fxxn", "fxxnn"}, SubsetFunDef.class); public SubsetFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final IntegerCalc startCalc = compiler.compileInteger(call.getArg(1)); final IntegerCalc countCalc = call.getArgCount() > 2 ? compiler.compileInteger(call.getArg(2)) : null; return new AbstractListCalc( call, new Calc[] {listCalc, startCalc, countCalc}) { public TupleList evaluateList(Evaluator evaluator) { final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final TupleList list = listCalc.evaluateList(evaluator); final int start = startCalc.evaluateInteger(evaluator); int end; if (countCalc != null) { final int count = countCalc.evaluateInteger(evaluator); end = start + count; } else { end = list.size(); } if (end > list.size()) { end = list.size(); } evaluator.restore(savepoint); if (start >= end || start < 0) { return TupleCollections.emptyList(list.getArity()); } if (start == 0 && end == list.size()) { return list; } assert 0 <= start; assert start < end; assert end <= list.size(); return list.subList(start, end); } }; } } // End SubsetFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/GlobalFunTable.java0000644000175000017500000001034411735330606023761 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.olap.*; import mondrian.olap.type.Type; import mondrian.spi.UserDefinedFunction; import mondrian.util.ServiceDiscovery; import java.util.List; /** * Global function table contains builtin functions and global user-defined * functions. * * @author Gang Chen */ public class GlobalFunTable extends FunTableImpl { private static GlobalFunTable instance = new GlobalFunTable(); static { instance.init(); } public static GlobalFunTable instance() { return instance; } private GlobalFunTable() { } public void defineFunctions(Builder builder) { final FunTable builtinFunTable = BuiltinFunTable.instance(); final List reservedWords = builtinFunTable.getReservedWords(); for (String reservedWord : reservedWords) { builder.defineReserved(reservedWord); } final List resolvers = builtinFunTable.getResolvers(); for (Resolver resolver : resolvers) { builder.define(resolver); } for (Class udfClass : lookupUdfImplClasses()) { defineUdf( builder, new UdfResolver.ClassUdfFactory(udfClass, null)); } } private List> lookupUdfImplClasses() { final ServiceDiscovery serviceDiscovery = ServiceDiscovery.forClass(UserDefinedFunction.class); return serviceDiscovery.getImplementor(); } /** * Defines a user-defined function in this table. * *

    If the function is not valid, throws an error. * * @param builder Builder * @param udfFactory Factory for UDF */ private void defineUdf( Builder builder, UdfResolver.UdfFactory udfFactory) { // Instantiate class with default constructor. final UserDefinedFunction udf = udfFactory.create(); // Validate function. validateFunction(udf); // Define function. builder.define(new UdfResolver(udfFactory)); } /** * Throws an error if a user-defined function does not adhere to the * API. * * @param udf User defined function */ private void validateFunction(final UserDefinedFunction udf) { // Check that the name is not null or empty. final String udfName = udf.getName(); if (udfName == null || udfName.equals("")) { throw Util.newInternal( "User-defined function defined by class '" + udf.getClass() + "' has empty name"); } // It's OK for the description to be null. //final String description = udf.getDescription(); final Type[] parameterTypes = udf.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { Type parameterType = parameterTypes[i]; if (parameterType == null) { throw Util.newInternal( "Invalid user-defined function '" + udfName + "': parameter type #" + i + " is null"); } } // It's OK for the reserved words to be null or empty. //final String[] reservedWords = udf.getReservedWords(); // Test that the function returns a sensible type when given the FORMAL // types. It may still fail when we give it the ACTUAL types, but it's // impossible to check that now. final Type returnType = udf.getReturnType(parameterTypes); if (returnType == null) { throw Util.newInternal( "Invalid user-defined function '" + udfName + "': return type is null"); } final Syntax syntax = udf.getSyntax(); if (syntax == null) { throw Util.newInternal( "Invalid user-defined function '" + udfName + "': syntax is null"); } } } // End GlobalFunTable.java mondrian-3.4.1/src/main/mondrian/olap/fun/FunDefBase.java0000644000175000017500000003334711735330606023112 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.Calc; import mondrian.calc.ExpCompiler; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.*; import mondrian.olap.type.DimensionType; import mondrian.olap.type.LevelType; import java.io.PrintWriter; /** * FunDefBase is the default implementation of {@link FunDef}. * *

    Signatures

    * *

    A function is defined by the following:

    * * * * * * * * * * * * * * * * * * * * *
    ParameterMeaningExample
    nameName of the function"Members"
    signatureSignature of the function"<Dimension>.Members"
    descriptionDescription of the function"Returns the set of all members in a dimension."
    flagsEncoding of the syntactic type, return type, and parameter * types of this operator. The encoding is described below."pxd"
    * * The flags field is an string which encodes * the syntactic type, return type, and parameter types of this operator. *
      *
    • The first character determines the syntactic type, as described by * {@link FunUtil#decodeSyntacticType(String)}. *
    • The second character determines the return type, as described by * {@link FunUtil#decodeReturnCategory(String)}. *
    • The third and subsequence characters determine the types of the * arguments arguments, as described by * {@link FunUtil#decodeParameterCategories(String)}. *

    * * For example, "pxd" means "an operator with * {@link Syntax#Property property} syntax (p) which returns a set * (x) and takes a dimension (d) as its argument".

    * * The arguments are always read from left to right, regardless of the * syntactic type of the operator. For example, the * "<Set>.Item(<Index>)" operator * (signature "mmxn") has the * syntax of a method-call, and takes two parameters: * a set (x) and a numeric (n).

    * * @author jhyde * @since 26 February, 2002 */ public abstract class FunDefBase extends FunUtil implements FunDef { protected final int flags; private final String name; final String signature; private final String description; protected final int returnCategory; protected final int[] parameterCategories; /** * Creates an operator. * * @param name Name of the function, for example "Members". * @param signature Signature of the function, for example * "<Dimension>.Members". * @param description Description of the function, for example * "Returns the set of all members in a dimension." * @param syntax Syntactic type of the operator (for * example, function, method, infix operator) * @param returnCategory The {@link Category} of the value returned by this * operator. * @param parameterCategories An array of {@link Category} codes, one for * each parameter. */ FunDefBase( String name, String signature, String description, Syntax syntax, int returnCategory, int[] parameterCategories) { assert name != null; assert syntax != null; this.name = name; this.signature = signature; this.description = description; this.flags = syntax.ordinal(); this.returnCategory = returnCategory; this.parameterCategories = parameterCategories; } /** * Creates an operator. * * @param name Name of the function, for example "Members". * @param description Description of the function, for example * "Returns the set of all members in a dimension." * @param flags Encoding of the syntactic type, return type, * and parameter types of this operator. The * "Members" operator has a syntactic type * "pxd" which means "an operator with * {@link Syntax#Property property} syntax (p) which * returns a set (x) and takes a dimension (d) as its * argument". * See {@link FunUtil#decodeSyntacticType(String)}, * {@link FunUtil#decodeReturnCategory(String)}, * {@link FunUtil#decodeParameterCategories(String)}. */ protected FunDefBase( String name, String description, String flags) { this( name, null, description, decodeSyntacticType(flags), decodeReturnCategory(flags), decodeParameterCategories(flags)); } /** * Creates an operator with an explicit signature. * *

    In most cases, the signature can be generated automatically, and * you should use the constructor which creates an implicit signature, * {@link #FunDefBase(String, String, String, String)} * instead. * * @param name Name of the function, for example "Members". * @param signature Signature of the function, for example * "<Dimension>.Members". * @param description Description of the function, for example * "Returns the set of all members in a dimension." * @param flags Encoding of the syntactic type, return type, and * parameter types of this operator. The "Members" * operator has a syntactic type "pxd" which means "an * operator with {@link Syntax#Property property} syntax * (p) which returns a set (x) and takes a dimension (d) * as its argument". See * {@link FunUtil#decodeSyntacticType(String)}, * {@link FunUtil#decodeReturnCategory(String)}, * {@link FunUtil#decodeParameterCategories(String)}. */ protected FunDefBase( String name, String signature, String description, String flags) { this( name, signature, description, decodeSyntacticType(flags), decodeReturnCategory(flags), decodeParameterCategories(flags)); } /** * Convenience constructor when we are created by a {@link Resolver}. * * @param resolver Resolver * @param returnType Return type * @param parameterTypes Parameter types */ FunDefBase(Resolver resolver, int returnType, int[] parameterTypes) { this( resolver.getName(), null, null, resolver.getSyntax(), returnType, parameterTypes); } /** * Copy constructor. * * @param funDef Function definition to copy */ FunDefBase(FunDef funDef) { this( funDef.getName(), funDef.getSignature(), funDef.getDescription(), funDef.getSyntax(), funDef.getReturnCategory(), funDef.getParameterCategories()); } public String getName() { return name; } public String getDescription() { return description; } public Syntax getSyntax() { return Syntax.class.getEnumConstants()[flags]; } public int getReturnCategory() { return returnCategory; } public int[] getParameterCategories() { return parameterCategories; } public Exp createCall(Validator validator, Exp[] args) { int[] categories = getParameterCategories(); Util.assertTrue(categories.length == args.length); for (int i = 0; i < args.length; i++) { args[i] = validateArg(validator, args, i, categories[i]); } final Type type = getResultType(validator, args); if (type == null) { throw Util.newInternal("could not derive type"); } return new ResolvedFunCall(this, args, type); } /** * Validates an argument to a call to this function. * *

    The default implementation of this method adds an implicit * conversion to the correct type. Derived classes may override. * * @param validator Validator * @param args Arguments to this function * @param i Ordinal of argument * @param category Expected {@link Category category} of argument * @return Validated argument */ protected Exp validateArg( Validator validator, Exp[] args, int i, int category) { return args[i]; } /** * Converts a type to a different category, maintaining as much type * information as possible. * * For example, given LevelType(dimension=Time, hierarchy=unknown, * level=unkown) and category=Hierarchy, returns * HierarchyType(dimension=Time). * * @param type Type * @param category Desired category * @return Type after conversion to desired category */ static Type castType(Type type, int category) { switch (category) { case Category.Logical: return new BooleanType(); case Category.Numeric: return new NumericType(); case Category.Numeric | Category.Integer: return new DecimalType(Integer.MAX_VALUE, 0); case Category.String: return new StringType(); case Category.DateTime: return new DateTimeType(); case Category.Symbol: return new SymbolType(); case Category.Value: return new ScalarType(); case Category.Cube: if (type instanceof Cube) { return new CubeType((Cube) type); } return null; case Category.Dimension: if (type != null) { return DimensionType.forType(type); } return null; case Category.Hierarchy: if (type != null) { return HierarchyType.forType(type); } return null; case Category.Level: if (type != null) { return LevelType.forType(type); } return null; case Category.Member: if (type != null) { final MemberType memberType = TypeUtil.toMemberType(type); if (memberType != null) { return memberType; } } // Take a wild guess. return MemberType.Unknown; case Category.Tuple: if (type != null) { final Type memberType = TypeUtil.toMemberOrTupleType(type); if (memberType != null) { return memberType; } } return null; case Category.Set: if (type != null) { final Type memberType = TypeUtil.toMemberOrTupleType(type); if (memberType != null) { return new SetType(memberType); } } return null; case Category.Empty: return new EmptyType(); default: throw Category.instance.badValue(category); } } /** * Returns the type of a call to this function with a given set of * arguments.

    * * The default implementation makes the coarse assumption that the return * type is in some way related to the type of the first argument. * Operators whose arguments don't follow the requirements of this * implementation should override this method.

    * * If the function definition says it returns a literal type (numeric, * string, symbol) then it's a fair guess that the function call * returns the same kind of value.

    * * If the function definition says it returns an object type (cube, * dimension, hierarchy, level, member) then we check the first * argument of the function. Suppose that the function definition says * that it returns a hierarchy, and the first argument of the function * happens to be a member. Then it's reasonable to assume that this * function returns a member. * * @param validator Validator * @param args Arguments to the call to this operator * @return result type of a call this function */ public Type getResultType(Validator validator, Exp[] args) { Type firstArgType = args.length > 0 ? args[0].getType() : null; Type type = castType(firstArgType, getReturnCategory()); if (type != null) { return type; } throw Util.newInternal( "Cannot deduce type of call to function '" + this.name + "'"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { throw Util.newInternal( "function '" + getSignature() + "' has not been implemented"); } public String getSignature() { return getSyntax().getSignature( getName(), getReturnCategory(), getParameterCategories()); } public void unparse(Exp[] args, PrintWriter pw) { getSyntax().unparse(getName(), args, pw); } } // End FunDefBase.java mondrian-3.4.1/src/main/mondrian/olap/fun/IntersectFunDef.java0000644000175000017500000001511511735330606024171 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.util.*; /** * Definition of the INTERSECT MDX function. * * @author jhyde * @since Mar 23, 2006 */ class IntersectFunDef extends FunDefBase { private static final String[] ReservedWords = new String[] {"ALL"}; static final Resolver resolver = new ReflectiveMultiResolver( "Intersect", "Intersect(, [, ALL])", "Returns the intersection of two input sets, optionally retaining duplicates.", new String[] {"fxxxy", "fxxx"}, IntersectFunDef.class, ReservedWords); public IntersectFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final String literalArg = getLiteralArg(call, 2, "", ReservedWords); final boolean all = literalArg.equalsIgnoreCase("ALL"); final int arity = call.getType().getArity(); final ListCalc listCalc1 = compiler.compileList(call.getArg(0)); final ListCalc listCalc2 = compiler.compileList(call.getArg(1)); return new AbstractListCalc( call, new Calc[] {listCalc1, listCalc2}) { public TupleList evaluateList(Evaluator evaluator) { TupleList leftList = listCalc1.evaluateList(evaluator); if (leftList.isEmpty()) { return leftList; } final TupleList rightList = listCalc2.evaluateList(evaluator); if (rightList.isEmpty()) { return rightList; } // Set of members from the right side of the intersect. // We use a RetrievableSet because distinct keys // (regular members and visual totals members) compare // identical using hashCode and equals, we want to retrieve // the actual key, and java.util.Set only has containsKey. RetrievableSet> rightSet = new RetrievableHashSet>( rightList.size() * 3 / 2); for (List tuple : rightList) { rightSet.add(tuple); } final TupleList result = TupleCollections.createList( arity, Math.min(leftList.size(), rightList.size())); final Set> resultSet = all ? null : new HashSet>(); for (List leftTuple : leftList) { List rightKey = rightSet.getKey(leftTuple); if (rightKey == null) { continue; } if (resultSet != null && !resultSet.add(leftTuple)) { continue; } result.add( copyTupleWithVisualTotalsMembersOverriding( leftTuple, rightKey)); } return result; } /** * Constructs a tuple consisting of members from * {@code leftTuple}, but overridden by any corresponding * members from {@code rightKey} that happen to be visual totals * members. * *

    Returns the original tuple if there are no visual totals * members on the RHS. * * @param leftTuple Original tuple * @param rightKey Right tuple * @return Copy of original tuple, with any VisualTotalMembers * from right tuple overriding */ private List copyTupleWithVisualTotalsMembersOverriding( List leftTuple, List rightKey) { List tuple = leftTuple; for (int i = 0; i < rightKey.size(); i++) { Member member = rightKey.get(i); if (!(tuple.get(i) instanceof VisualTotalsFunDef.VisualTotalMember) && member instanceof VisualTotalsFunDef.VisualTotalMember) { if (tuple == leftTuple) { // clone on first VisualTotalMember -- to avoid // alloc/copy in the common case where there are // no VisualTotalMembers tuple = new ArrayList(leftTuple); } tuple.set(i, member); } } return tuple; } }; } /** * Interface similar to the Set interface that allows key values to be * returned. * *

    Useful if multiple objects can compare equal (using * {@link Object#equals(Object)} and {@link Object#hashCode()}, per the * set contract) and you wish to distinguish them after they have been added * to the set. * * @param element type */ private interface RetrievableSet { /** * Returns the key in this set that compares equal to a given object, * or null if there is no such key. * * @param e Key value * @return Key in the set equal to given key value */ E getKey(E e); /** * Analogous to {@link Set#add(Object)}. * * @param e element to be added to this set * @return true if this set did not already contain the * specified element */ boolean add(E e); } private static class RetrievableHashSet extends HashMap implements RetrievableSet { public RetrievableHashSet(int initialCapacity) { super(initialCapacity); } public E getKey(E e) { return super.get(e); } public boolean add(E e) { return super.put(e, e) == null; } } } // End IntersectFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/RankFunDef.java0000644000175000017500000006056611735330606023136 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.TupleType; import mondrian.olap.type.Type; import mondrian.rolap.RolapUtil; import java.io.PrintWriter; import java.util.*; /** * Definition of the RANK MDX function. * * @author Richard Emberson * @since 17 January, 2005 */ public class RankFunDef extends FunDefBase { static final boolean debug = false; static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "Rank", "Rank(, [, ])", "Returns the one-based rank of a tuple in a set.", new String[]{"fitx", "fitxn", "fimx", "fimxn"}, RankFunDef.class); public RankFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { switch (call.getArgCount()) { case 2: return compileCall2(call, compiler); case 3: return compileCall3(call, compiler); default: throw Util.newInternal("invalid arg count " + call.getArgCount()); } } public Calc compileCall3(ResolvedFunCall call, ExpCompiler compiler) { final Type type0 = call.getArg(0).getType(); final ListCalc listCalc = compiler.compileList(call.getArg(1)); final Calc keyCalc = compiler.compileScalar(call.getArg(2), true); Calc sortedListCalc = new SortedListCalc(call, listCalc, keyCalc); final ExpCacheDescriptor cacheDescriptor = new ExpCacheDescriptor( call, sortedListCalc, compiler.getEvaluator()); if (type0 instanceof TupleType) { final TupleCalc tupleCalc = compiler.compileTuple(call.getArg(0)); return new Rank3TupleCalc( call, tupleCalc, keyCalc, cacheDescriptor); } else { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new Rank3MemberCalc( call, memberCalc, keyCalc, cacheDescriptor); } } public Calc compileCall2(ResolvedFunCall call, ExpCompiler compiler) { final boolean tuple = call.getArg(0).getType() instanceof TupleType; final Exp listExp = call.getArg(1); final ListCalc listCalc0 = compiler.compileList(listExp); Calc listCalc1 = new RankedListCalc(listCalc0, tuple); final Calc listCalc; if (MondrianProperties.instance().EnableExpCache.get()) { final ExpCacheDescriptor key = new ExpCacheDescriptor( listExp, listCalc1, compiler.getEvaluator()); listCalc = new CacheCalc(listExp, key); } else { listCalc = listCalc1; } if (tuple) { final TupleCalc tupleCalc = compiler.compileTuple(call.getArg(0)); return new Rank2TupleCalc(call, tupleCalc, listCalc); } else { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new Rank2MemberCalc(call, memberCalc, listCalc); } } private static class Rank2TupleCalc extends AbstractIntegerCalc { private final TupleCalc tupleCalc; private final Calc listCalc; public Rank2TupleCalc( ResolvedFunCall call, TupleCalc tupleCalc, Calc listCalc) { super(call, new Calc[] {tupleCalc, listCalc}); this.tupleCalc = tupleCalc; this.listCalc = listCalc; } public int evaluateInteger(Evaluator evaluator) { // Get member or tuple. // If the member is null (or the tuple contains a null member) // the result is null (even if the list is null). final Member[] members = tupleCalc.evaluateTuple(evaluator); if (members == null) { return IntegerNull; } assert !tupleContainsNullMember(members); // Get the set of members/tuples. // If the list is empty, MSAS cannot figure out the type of the // list, so returns an error "Formula error - dimension count is // not valid - in the Rank function". We will naturally return 0, // which I think is better. final RankedTupleList rankedTupleList = (RankedTupleList) listCalc.evaluate(evaluator); if (rankedTupleList == null) { return 0; } // Find position of member in list. -1 signifies not found. final List memberList = Arrays.asList(members); final int i = rankedTupleList.indexOf(memberList); // Return 1-based rank. 0 signifies not found. return i + 1; } } private static class Rank2MemberCalc extends AbstractIntegerCalc { private final MemberCalc memberCalc; private final Calc listCalc; public Rank2MemberCalc( ResolvedFunCall call, MemberCalc memberCalc, Calc listCalc) { super(call, new Calc[] {memberCalc, listCalc}); this.memberCalc = memberCalc; this.listCalc = listCalc; } public int evaluateInteger(Evaluator evaluator) { // Get member or tuple. // If the member is null (or the tuple contains a null member) // the result is null (even if the list is null). final Member member = memberCalc.evaluateMember(evaluator); if (member == null || member.isNull()) { return IntegerNull; } // Get the set of members/tuples. // If the list is empty, MSAS cannot figure out the type of the // list, so returns an error "Formula error - dimension count is // not valid - in the Rank function". We will naturally return 0, // which I think is better. RankedMemberList rankedMemberList = (RankedMemberList) listCalc.evaluate(evaluator); if (rankedMemberList == null) { return 0; } // Find position of member in list. -1 signifies not found. final int i = rankedMemberList.indexOf(member); // Return 1-based rank. 0 signifies not found. return i + 1; } } private static class Rank3TupleCalc extends AbstractIntegerCalc { private final TupleCalc tupleCalc; private final Calc sortCalc; private final ExpCacheDescriptor cacheDescriptor; public Rank3TupleCalc( ResolvedFunCall call, TupleCalc tupleCalc, Calc sortCalc, ExpCacheDescriptor cacheDescriptor) { super(call, new Calc[] {tupleCalc, sortCalc}); this.tupleCalc = tupleCalc; this.sortCalc = sortCalc; this.cacheDescriptor = cacheDescriptor; } public int evaluateInteger(Evaluator evaluator) { Member[] members = tupleCalc.evaluateTuple(evaluator); if (members == null) { return IntegerNull; } assert !tupleContainsNullMember(members); // Evaluate the list (or retrieve from cache). // If there is an exception while calculating the // list, propagate it up. final TupleSortResult sortResult = (TupleSortResult) evaluator.getCachedResult(cacheDescriptor); if (debug) { sortResult.print(new PrintWriter(System.out)); } if (sortResult.isEmpty()) { // If list is empty, the rank is null. return IntegerNull; } // First try to find the member in the cached SortResult Integer rank = sortResult.rankOf(members); if (rank != null) { return rank; } // member is not seen before, now compute the value of the tuple. final int savepoint = evaluator.savepoint(); evaluator.setContext(members); Object value = sortCalc.evaluate(evaluator); evaluator.restore(savepoint); if (value == RolapUtil.valueNotReadyException) { // The value wasn't ready, so quit now... we'll be back. return 0; } // If value is null, it won't be in the values array. if (value == Util.nullValue || value == null) { return sortResult.values.length + 1; } // Look for the ranked value in the array. int j = Arrays.binarySearch( sortResult.values, value, Collections.reverseOrder()); if (j < 0) { // Value not found. Flip the result to find the // insertion point. j = -(j + 1); } return j + 1; // 1-based } } private static class Rank3MemberCalc extends AbstractIntegerCalc { private final MemberCalc memberCalc; private final Calc sortCalc; private final ExpCacheDescriptor cacheDescriptor; public Rank3MemberCalc( ResolvedFunCall call, MemberCalc memberCalc, Calc sortCalc, ExpCacheDescriptor cacheDescriptor) { super(call, new Calc[] {memberCalc, sortCalc}); this.memberCalc = memberCalc; this.sortCalc = sortCalc; this.cacheDescriptor = cacheDescriptor; } public int evaluateInteger(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); if (member == null || member.isNull()) { return IntegerNull; } // Evaluate the list (or retrieve from cache). // If there was an exception while calculating the // list, propagate it up. final MemberSortResult sortResult = (MemberSortResult) evaluator.getCachedResult(cacheDescriptor); if (debug) { sortResult.print(new PrintWriter(System.out)); } if (sortResult.isEmpty()) { // If list is empty, the rank is null. return IntegerNull; } // First try to find the member in the cached SortResult Integer rank = sortResult.rankOf(member); if (rank != null) { return rank; } // member is not seen before, now compute the value of the tuple. final int savepoint = evaluator.savepoint(); evaluator.setContext(member); Object value = sortCalc.evaluate(evaluator); evaluator.restore(savepoint); if (value == RolapUtil.valueNotReadyException) { // The value wasn't ready, so quit now... we'll be back. return 0; } // If value is null, it won't be in the values array. if (value == Util.nullValue || value == null) { return sortResult.values.length + 1; } // Look for the ranked value in the array. int j = Arrays.binarySearch( sortResult.values, value, Collections.reverseOrder()); if (j < 0) { // Value not found. Flip the result to find the // insertion point. j = -(j + 1); } return j + 1; // 1-based } } /** * Calc which evaluates an expression to form a list of tuples, * evaluates a scalar expression at each tuple, then sorts the list of * values. The result is a value of type {@link SortResult}, and can be * used to implement the Rank function efficiently. */ private static class SortedListCalc extends AbstractCalc { private final ListCalc listCalc; private final Calc keyCalc; private static final Integer ONE = 1; /** * Creates a SortCalc. * * @param exp Source expression * @param listCalc Compiled expression to compute the list * @param keyCalc Compiled expression to compute the sort key */ public SortedListCalc( Exp exp, ListCalc listCalc, Calc keyCalc) { super(exp, new Calc[] {listCalc, keyCalc}); this.listCalc = listCalc; this.keyCalc = keyCalc; } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } public Object evaluate(Evaluator evaluator) { // Save the state of the evaluator. final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); // Construct an array containing the value of the expression // for each member. TupleList list = listCalc.evaluateList(evaluator); assert list != null; if (list.isEmpty()) { return list.getArity() == 1 ? new MemberSortResult( new Object[0], Collections.emptyMap()) : new TupleSortResult( new Object[0], Collections., Integer>emptyMap()); } RuntimeException exception = null; //noinspection unchecked final Map uniqueValueCounterMap = new TreeMap( FunUtil.DescendingValueComparator.instance); final Map memberValueMap; final Map, Object> tupleValueMap; final int numValues; if (list.getArity() == 1) { memberValueMap = new HashMap(); tupleValueMap = null; for (Member member : list.slice(0)) { evaluator.setContext(member); final Object keyValue = keyCalc.evaluate(evaluator); if (keyValue instanceof RuntimeException) { if (exception == null) { exception = (RuntimeException) keyValue; } } else if (Util.isNull(keyValue)) { // nothing to do } else { // Assume it's the first time seeing this keyValue. Integer valueCounter = uniqueValueCounterMap.put( keyValue, ONE); if (valueCounter != null) { // Update the counter on how many times this // keyValue has been seen. uniqueValueCounterMap.put( keyValue, valueCounter + 1); } memberValueMap.put(member, keyValue); } } numValues = memberValueMap.keySet().size(); } else { tupleValueMap = new HashMap, Object>(); memberValueMap = null; for (List tuple : list) { evaluator.setContext(tuple); final Object keyValue = keyCalc.evaluate(evaluator); if (keyValue instanceof RuntimeException) { if (exception == null) { exception = (RuntimeException) keyValue; } } else if (Util.isNull(keyValue)) { // nothing to do } else { // Assume it's the first time seeing this keyValue. Integer valueCounter = uniqueValueCounterMap.put( keyValue, ONE); if (valueCounter != null) { // Update the counter on how many times this // keyValue has been seen. uniqueValueCounterMap.put( keyValue, valueCounter + 1); } tupleValueMap.put(tuple, keyValue); } } numValues = tupleValueMap.keySet().size(); } evaluator.restore(savepoint); // If there were exceptions, quit now... we'll be back. if (exception != null) { return exception; } final Object[] allValuesSorted = new Object[numValues]; // Now build the sorted array containing all keyValues // And update the counter to the rank int currentOrdinal = 0; //noinspection unchecked final Map uniqueValueRankMap = new TreeMap( Collections.reverseOrder()); for (Map.Entry entry : uniqueValueCounterMap.entrySet()) { Object keyValue = entry.getKey(); Integer valueCount = entry.getValue(); // Because uniqueValueCounterMap is already sorted, so the // reconstructed allValuesSorted is guaranteed to be sorted. for (int i = 0; i < valueCount; i ++) { allValuesSorted[currentOrdinal + i] = keyValue; } uniqueValueRankMap.put(keyValue, currentOrdinal + 1); currentOrdinal += valueCount; } // Build a member/tuple to rank map if (list.getArity() == 1) { final Map rankMap = new HashMap(); for (Map.Entry entry : memberValueMap.entrySet()) { int oneBasedRank = uniqueValueRankMap.get(entry.getValue()); rankMap.put(entry.getKey(), oneBasedRank); } return new MemberSortResult(allValuesSorted, rankMap); } else { final Map, Integer> rankMap = new HashMap, Integer>(); for (Map.Entry, Object> entry : tupleValueMap.entrySet()) { int oneBasedRank = uniqueValueRankMap.get(entry.getValue()); rankMap.put(entry.getKey(), oneBasedRank); } return new TupleSortResult(allValuesSorted, rankMap); } } } /** * Holder for the result of sorting a set of values. * It provides simple interface to look up the rank for a member or a tuple. */ private static abstract class SortResult { /** * All values in sorted order; Duplicates are not removed. * E.g. Set (15,15,5,0) * 10 should be ranked 3. * *

    Null values are not present: they would be at the end, anyway. */ final Object[] values; public SortResult(Object[] values) { this.values = values; } public boolean isEmpty() { return values == null; } public void print(PrintWriter pw) { if (values == null) { pw.println("SortResult: empty"); } else { pw.println("SortResult {"); for (int i = 0; i < values.length; i++) { if (i > 0) { pw.println(","); } Object value = values[i]; pw.print(value); } pw.println("}"); } pw.flush(); } } private static class MemberSortResult extends SortResult { /** * The precomputed rank associated with all members */ final Map rankMap; public MemberSortResult( Object[] values, Map rankMap) { super(values); this.rankMap = rankMap; } public Integer rankOf(Member member) { return rankMap.get(member); } } private static class TupleSortResult extends SortResult { /** * The precomputed rank associated with all tuples */ final Map, Integer> rankMap; public TupleSortResult( Object[] values, Map, Integer> rankMap) { super(values); this.rankMap = rankMap; } public Integer rankOf(Member[] tuple) { return rankMap.get(Arrays.asList(tuple)); } } /** * Expression which evaluates an expression to form a list of tuples. * *

    The result is a value of type * {@link mondrian.olap.fun.RankFunDef.RankedMemberList} or * {@link mondrian.olap.fun.RankFunDef.RankedTupleList}, or * null if the list is empty. */ private static class RankedListCalc extends AbstractCalc { private final ListCalc listCalc; private final boolean tuple; /** * Creates a RankedListCalc. * * @param listCalc Compiled expression to compute the list * @param tuple Whether elements of the list are tuples (as opposed to * members) */ public RankedListCalc(ListCalc listCalc, boolean tuple) { super(new DummyExp(listCalc.getType()), new Calc[] {listCalc}); this.listCalc = listCalc; this.tuple = tuple; } public Object evaluate(Evaluator evaluator) { // Construct an array containing the value of the expression // for each member. TupleList tupleList = listCalc.evaluateList(evaluator); assert tupleList != null; if (tuple) { return new RankedTupleList(tupleList); } else { return new RankedMemberList(tupleList.slice(0)); } } } /** * Data structure which contains a list and can return the position of an * element in the list in O(log N). */ static class RankedMemberList { Map map = new HashMap(); RankedMemberList(List members) { int i = -1; for (final Member member : members) { ++i; final Integer value = map.put(member, i); if (value != null) { // The list already contained a value for this key -- put // it back. map.put(member, value); } } } int indexOf(Member m) { Integer integer = map.get(m); if (integer == null) { return -1; } else { return integer; } } } /** * Data structure which contains a list and can return the position of an * element in the list in O(log N). */ static class RankedTupleList { final Map, Integer> map = new HashMap, Integer>(); RankedTupleList(TupleList tupleList) { int i = -1; for (final List tupleMembers : tupleList.fix()) { ++i; final Integer value = map.put(tupleMembers, i); if (value != null) { // The list already contained a value for this key -- put // it back. map.put(tupleMembers, value); } } } int indexOf(List tupleMembers) { Integer integer = map.get(tupleMembers); if (integer == null) { return -1; } else { return integer; } } } } // End RankFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/IsEmptyFunDef.java0000644000175000017500000000344511735330606023626 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.Calc; import mondrian.calc.ExpCompiler; import mondrian.calc.impl.AbstractBooleanCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.Evaluator; import mondrian.olap.FunDef; /** * Definition of the IsEmpty MDX function. * * @author jhyde * @since Mar 23, 2006 */ class IsEmptyFunDef extends FunDefBase { static final ReflectiveMultiResolver FunctionResolver = new ReflectiveMultiResolver( "IsEmpty", "IsEmpty()", "Determines if an expression evaluates to the empty cell value.", new String[] {"fbS", "fbn"}, IsEmptyFunDef.class); static final ReflectiveMultiResolver PostfixResolver = new ReflectiveMultiResolver( "IS EMPTY", " IS EMPTY", "Determines if an expression evaluates to the empty cell value.", new String[] {"Qbm", "Qbt"}, IsEmptyFunDef.class); public IsEmptyFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Calc calc = compiler.compileScalar(call.getArg(0), true); return new AbstractBooleanCalc(call, new Calc[] {calc}) { public boolean evaluateBoolean(Evaluator evaluator) { Object o = calc.evaluate(evaluator); return o == null; } }; } } // End IsEmptyFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/LeadLagFunDef.java0000644000175000017500000000476511735330606023533 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractMemberCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the Lead and Lag MDX functions. * * @author jhyde * @since Mar 23, 2006 */ class LeadLagFunDef extends FunDefBase { static final ReflectiveMultiResolver LagResolver = new ReflectiveMultiResolver( "Lag", ".Lag()", "Returns a member further along the specified member's dimension.", new String[]{"mmmn"}, LeadLagFunDef.class); static final ReflectiveMultiResolver LeadResolver = new ReflectiveMultiResolver( "Lead", ".Lead()", "Returns a member further along the specified member's dimension.", new String[]{"mmmn"}, LeadLagFunDef.class); public LeadLagFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); final IntegerCalc integerCalc = compiler.compileInteger(call.getArg(1)); final boolean lag = call.getFunName().equals("Lag"); return new AbstractMemberCalc( call, new Calc[] {memberCalc, integerCalc}) { public Member evaluateMember(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); int n = integerCalc.evaluateInteger(evaluator); if (lag) { if (n == Integer.MIN_VALUE) { // Bump up lagValue by one, otherwise -n (used // in the getLeadMember call below) is out of // range because Integer.MAX_VALUE == // -(Integer.MIN_VALUE + 1). n += 1; } n = -n; } return evaluator.getSchemaReader().getLeadMember(member, n); } }; } } // End LeadLagFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/MultiResolver.java0000644000175000017500000000775511735330606023770 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde and others // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.olap.*; import java.util.List; /** * A MultiResolver considers several overloadings of the same * function. If one of these overloadings matches the actual arguments, it * calls the factory method {@link #createFunDef}. * * @author jhyde * @since Feb 12, 2003 */ public abstract class MultiResolver extends FunUtil implements Resolver { private final String name; private final String signature; private final String description; private final String[] signatures; private final Syntax syntax; /** * Creates a MultiResolver. * * @param name Name of function or operator * @param signature Signature of function or operator * @param description Description of function or operator * @param signatures Array of possible signatures, each of which is an * encoding of the syntactic type, return type, and parameter * types of this operator. The "Members" operator has a syntactic * type "pxd" which means "an operator with * {@link Syntax#Property property} syntax (p) which returns a set * (x) and takes a dimension (d) as its argument". * See {@link FunUtil#decodeSyntacticType(String)}, * {@link FunUtil#decodeReturnCategory(String)}, * {@link FunUtil#decodeParameterCategories(String)}. */ protected MultiResolver( String name, String signature, String description, String[] signatures) { this.name = name; this.signature = signature; this.description = description; this.signatures = signatures; Util.assertTrue(signatures.length > 0); this.syntax = decodeSyntacticType(signatures[0]); for (int i = 1; i < signatures.length; i++) { Util.assertTrue(decodeSyntacticType(signatures[i]) == syntax); } } public String getName() { return name; } public String getDescription() { return description; } public String getSignature() { return signature; } public Syntax getSyntax() { return syntax; } public String[] getReservedWords() { return emptyStringArray; } public String[] getSignatures() { return signatures; } public FunDef getFunDef() { return null; } public FunDef resolve( Exp[] args, Validator validator, List conversions) { outer: for (String signature : signatures) { int[] parameterTypes = decodeParameterCategories(signature); if (parameterTypes.length != args.length) { continue; } conversions.clear(); for (int i = 0; i < args.length; i++) { if (!validator.canConvert( i, args[i], parameterTypes[i], conversions)) { continue outer; } } int returnType = decodeReturnCategory(signature); FunDef dummy = createDummyFunDef(this, returnType, args); return createFunDef(args, dummy); } return null; } public boolean requiresExpression(int k) { for (String signature : signatures) { int[] parameterTypes = decodeParameterCategories(signature); if ((k < parameterTypes.length) && parameterTypes[k] == Category.Set) { return false; } } return true; } protected abstract FunDef createFunDef(Exp[] args, FunDef dummyFunDef); } // End MultiResolver.java mondrian-3.4.1/src/main/mondrian/olap/fun/DimensionsNumericFunDef.java0000644000175000017500000000432111735330606025661 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractHierarchyCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.HierarchyType; import mondrian.olap.type.Type; import mondrian.rolap.RolapCube; import mondrian.rolap.RolapHierarchy; import java.util.List; /** * Definition of the Dimensions(<Numeric Expression>) * MDX builtin function. * *

    NOTE: Actually returns a hierarchy. This is consistent with Analysis * Services. * * @author jhyde * @since Jul 20, 2009 */ class DimensionsNumericFunDef extends FunDefBase { public static final FunDefBase INSTANCE = new DimensionsNumericFunDef(); private DimensionsNumericFunDef() { super( "Dimensions", "Returns the hierarchy whose zero-based position within the cube " + "is specified by a numeric expression.", "fhn"); } public Type getResultType(Validator validator, Exp[] args) { return HierarchyType.Unknown; } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final IntegerCalc integerCalc = compiler.compileInteger(call.getArg(0)); return new AbstractHierarchyCalc(call, new Calc[] {integerCalc}) { public Hierarchy evaluateHierarchy(Evaluator evaluator) { int n = integerCalc.evaluateInteger(evaluator); return nthHierarchy(evaluator, n); } }; } RolapHierarchy nthHierarchy(Evaluator evaluator, int n) { RolapCube cube = (RolapCube) evaluator.getCube(); List hierarchies = cube.getHierarchies(); if (n >= hierarchies.size() || n < 0) { throw newEvalException( this, "Index '" + n + "' out of bounds"); } return hierarchies.get(n); } } // End DimensionsNumericFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/ValidMeasureFunDef.java0000644000175000017500000001535011735330606024613 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.GenericCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.TypeUtil; import mondrian.rolap.RolapCube; import mondrian.rolap.RolapMember; import java.util.*; /** * Definition of the ValidMeasure MDX function. * *

    Returns a valid measure in a virtual cube by forcing inapplicable * dimensions to their top level. * *

    Syntax: *

    * ValidMeasure(<Tuple>) *
    * * @author kwalker, mpflug */ public class ValidMeasureFunDef extends FunDefBase { static final ValidMeasureFunDef instance = new ValidMeasureFunDef(); private ValidMeasureFunDef() { super( "ValidMeasure", "Returns a valid measure in a virtual cube by forcing inapplicable dimensions to their top level.", "fnt"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Calc calc; final Exp arg = call.getArg(0); if (TypeUtil.couldBeMember(arg.getType())) { calc = compiler.compileMember(arg); } else { calc = compiler.compileTuple(arg); } return new CalcImpl(call, calc); } private static class CalcImpl extends GenericCalc { private final Calc calc; public CalcImpl(ResolvedFunCall call, Calc calc) { super(call); this.calc = calc; } public Object evaluate(Evaluator evaluator) { final List memberList; if (calc.isWrapperFor(MemberCalc.class)) { memberList = new ArrayList(1); memberList.add( calc.unwrap(MemberCalc.class).evaluateMember(evaluator)); } else { final Member[] tupleMembers = calc.unwrap((TupleCalc.class)).evaluateTuple(evaluator); memberList = Arrays.asList(tupleMembers); } RolapCube baseCube = null; RolapCube virtualCube = (RolapCube) evaluator.getCube(); // find the measure in the tuple int measurePosition = -1; for (int i = 0; i < memberList.size(); i++) { if (memberList.get(i).getDimension().isMeasures()) { measurePosition = i; break; } } // problem: if measure is in two base cubes baseCube = getBaseCubeofMeasure( evaluator, memberList.get(measurePosition), baseCube); List vMinusBDimensions = getDimensionsToForceToAllLevel( virtualCube, baseCube, memberList); // declare members array and fill in with all needed members final List validMeasureMembers = new ArrayList(memberList); // start adding to validMeasureMembers at right place for (Dimension vMinusBDimension : vMinusBDimensions) { final Hierarchy hierarchy = vMinusBDimension.getHierarchy(); if (hierarchy.hasAll()) { validMeasureMembers.add(hierarchy.getAllMember()); } else { validMeasureMembers.add(hierarchy.getDefaultMember()); } } // this needs to be done before validmeasuremembers are set on the // context since calculated members defined on a non joining // dimension might have been pulled to default member List calculatedMembers = getCalculatedMembersFromContext(evaluator); evaluator.setContext(validMeasureMembers); evaluator.setContext(calculatedMembers); return evaluator.evaluateCurrent(); } private List getCalculatedMembersFromContext( Evaluator evaluator) { Member[] currentMembers = evaluator.getMembers(); List calculatedMembers = new ArrayList(); for (Member currentMember : currentMembers) { if (currentMember.isCalculated()) { calculatedMembers.add(currentMember); } } return calculatedMembers; } public Calc[] getCalcs() { return new Calc[]{calc}; } private RolapCube getBaseCubeofMeasure( Evaluator evaluator, Member member, RolapCube baseCube) { final Cube[] cubes = evaluator.getSchemaReader().getCubes(); for (Cube cube1 : cubes) { RolapCube cube = (RolapCube) cube1; if (!cube.isVirtual()) { for (RolapMember measure : cube.getMeasuresMembers()) { if (measure.getName().equals(member.getName())) { baseCube = cube; } } } if (baseCube != null) { break; } } return baseCube; } private List getDimensionsToForceToAllLevel( RolapCube virtualCube, RolapCube baseCube, List memberList) { List vMinusBDimensions = new ArrayList(); Set virtualCubeDims = new HashSet(); virtualCubeDims.addAll(Arrays.asList(virtualCube.getDimensions())); Set nonJoiningDims = baseCube.nonJoiningDimensions(virtualCubeDims); for (Dimension nonJoiningDim : nonJoiningDims) { if (!isDimInMembersList(memberList, nonJoiningDim)) { vMinusBDimensions.add(nonJoiningDim); } } return vMinusBDimensions; } private boolean isDimInMembersList( List members, Dimension dimension) { for (Member member : members) { if (member.getName().equalsIgnoreCase(dimension.getName())) { return true; } } return false; } public boolean dependsOn(Hierarchy hierarchy) { // depends on all hierarchies return butDepends(getCalcs(), hierarchy); } } } // End ValidMeasureFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/SetFunDef.java0000644000175000017500000004172211735330606022767 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.*; import mondrian.resource.MondrianResource; import java.io.PrintWriter; import java.util.*; /** * SetFunDef implements the 'set' function (whose syntax is the * brace operator, { ... }). * * @author jhyde * @since 3 March, 2002 */ public class SetFunDef extends FunDefBase { static final ResolverImpl Resolver = new ResolverImpl(); SetFunDef(Resolver resolver, int[] argTypes) { super(resolver, Category.Set, argTypes); } public void unparse(Exp[] args, PrintWriter pw) { ExpBase.unparseList(pw, args, "{", ", ", "}"); } public Type getResultType(Validator validator, Exp[] args) { // All of the members in {[,]...} must have the same // Hierarchy. But if there are no members, we can't derive a // hierarchy. Type type0 = null; if (args.length == 0) { // No members to go on, so we can't guess the hierarchy. type0 = MemberType.Unknown; } else { for (int i = 0; i < args.length; i++) { Exp arg = args[i]; Type type = arg.getType(); type = TypeUtil.toMemberOrTupleType(type); if (i == 0) { type0 = type; } else { if (!TypeUtil.isUnionCompatible(type0, type)) { throw MondrianResource.instance() .ArgsMustHaveSameHierarchy.ex(getName()); } } } } return new SetType(type0); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Exp[] args = call.getArgs(); if (args.length == 0) { // Special treatment for empty set, because we don't know whether it // is a set of members or tuples, and so we need it to implement // both MemberListCalc and ListCalc. return new EmptyListCalc(call); } if (args.length == 1 && args[0].getType() instanceof SetType) { // Optimized case when there is only one argument. This occurs quite // often, because people write '{Foo.Children} on 1' when they could // write 'Foo.Children on 1'. return args[0].accept(compiler); } return new SetListCalc( call, args, compiler, ResultStyle.LIST_MUTABLELIST); } /** * Compiled expression to implement the MDX set function, { ... * }, applied to a set of tuples, as a list. * *

    The set function can contain expressions which yield sets together * with expressions which yield individual tuples, provided that * they all have the same type. It automatically removes null * or partially-null tuples from the list. * *

    Also, does not process high-cardinality dimensions specially. */ public static class SetListCalc extends AbstractListCalc { private TupleList result; private final VoidCalc[] voidCalcs; public SetListCalc( Exp exp, Exp[] args, ExpCompiler compiler, List resultStyles) { super(exp, null); voidCalcs = compileSelf(args, compiler, resultStyles); result = TupleCollections.createList(getType().getArity()); } public Calc[] getCalcs() { return voidCalcs; } private VoidCalc[] compileSelf( Exp[] args, ExpCompiler compiler, List resultStyles) { VoidCalc[] voidCalcs = new VoidCalc[args.length]; for (int i = 0; i < args.length; i++) { voidCalcs[i] = createCalc(args[i], compiler, resultStyles); } return voidCalcs; } private VoidCalc createCalc( Exp arg, ExpCompiler compiler, List resultStyles) { final Type type = arg.getType(); if (type instanceof SetType) { // TODO use resultStyles final ListCalc listCalc = compiler.compileList(arg); return new AbstractVoidCalc(arg, new Calc[] {listCalc}) { public void evaluateVoid(Evaluator evaluator) { TupleList list = listCalc.evaluateList(evaluator); // Add only tuples which are not null. Tuples with // any null members are considered null. outer: for (List members : list) { for (Member member : members) { if (member == null || member.isNull()) { continue outer; } } result.add(members); } } protected String getName() { return "Sublist"; } }; } else if (type.getArity() == 1) { final MemberCalc memberCalc = compiler.compileMember(arg); return new AbstractVoidCalc(arg, new Calc[]{memberCalc}) { final Member[] members = {null}; public void evaluateVoid(Evaluator evaluator) { // Don't add null or partially null tuple to result. Member member = memberCalc.evaluateMember(evaluator); if (member == null || member.isNull()) { return; } members[0] = member; result.addTuple(members); } }; } else { final TupleCalc tupleCalc = compiler.compileTuple(arg); return new AbstractVoidCalc(arg, new Calc[]{tupleCalc}) { public void evaluateVoid(Evaluator evaluator) { // Don't add null or partially null tuple to result. Member[] members = tupleCalc.evaluateTuple(evaluator); if (members == null || tupleContainsNullMember(members)) { return; } result.addTuple(members); } }; } } public TupleList evaluateList(final Evaluator evaluator) { result.clear(); for (VoidCalc voidCalc : voidCalcs) { voidCalc.evaluateVoid(evaluator); } return result.cloneList(-1); } } private static List compileSelf( Exp[] args, ExpCompiler compiler, List resultStyles) { List calcs = new ArrayList(args.length); for (Exp arg : args) { calcs.add(createCalc(arg, compiler, resultStyles)); } return calcs; } private static IterCalc createCalc( Exp arg, ExpCompiler compiler, List resultStyles) { final Type type = arg.getType(); if (type instanceof SetType) { final Calc calc = compiler.compileAs(arg, null, resultStyles); switch (calc.getResultStyle()) { case ITERABLE: final IterCalc iterCalc = (IterCalc) calc; return new AbstractIterCalc(arg, new Calc[]{calc}) { public TupleIterable evaluateIterable( Evaluator evaluator) { return iterCalc.evaluateIterable(evaluator); } protected String getName() { return "Sublist"; } }; case LIST: case MUTABLE_LIST: final ListCalc listCalc = (ListCalc) calc; return new AbstractIterCalc(arg, new Calc[]{calc}) { public TupleIterable evaluateIterable( Evaluator evaluator) { TupleList list = listCalc.evaluateList( evaluator); TupleList result = list.cloneList(list.size()); // Add only tuples which are not null. Tuples with // any null members are considered null. list: for (List members : list) { for (Member member : members) { if (member == null || member.isNull()) { continue list; } } result.add(members); } return result; } protected String getName() { return "Sublist"; } }; } throw ResultStyleException.generateBadType( ResultStyle.ITERABLE_LIST_MUTABLELIST, calc.getResultStyle()); } else if (TypeUtil.couldBeMember(type)) { final MemberCalc memberCalc = compiler.compileMember(arg); final ResolvedFunCall call = wrapAsSet(arg); return new AbstractIterCalc(call, new Calc[] {memberCalc}) { public TupleIterable evaluateIterable( Evaluator evaluator) { final Member member = memberCalc.evaluateMember(evaluator); return member == null ? TupleCollections.createList(1) : new UnaryTupleList(Collections.singletonList(member)); } protected String getName() { return "Sublist"; } }; } else { final TupleCalc tupleCalc = compiler.compileTuple(arg); final ResolvedFunCall call = wrapAsSet(arg); return new AbstractIterCalc(call, new Calc[] {tupleCalc}) { public TupleIterable evaluateIterable( Evaluator evaluator) { final Member[] members = tupleCalc.evaluateTuple(evaluator); return new ListTupleList( tupleCalc.getType().getArity(), Arrays.asList(members)); } protected String getName() { return "Sublist"; } }; } } /** * Creates a call to the set operator with a given collection of * expressions. * *

    There must be at least one expression. Each expression may be a set of * members/tuples, or may be a member/tuple, but method assumes that * expressions have compatible types. * * @param args Expressions * @return Call to set operator */ public static ResolvedFunCall wrapAsSet(Exp... args) { assert args.length > 0; final int[] categories = new int[args.length]; Type type = null; for (int i = 0; i < args.length; i++) { final Exp arg = args[i]; categories[i] = arg.getCategory(); final Type argType = arg.getType(); if (argType instanceof SetType) { type = ((SetType) argType).getElementType(); } else { type = argType; } } return new ResolvedFunCall( new SetFunDef(Resolver, categories), args, new SetType(type)); } /** * Compiled expression that evaluates one or more expressions, each of which * yields a tuple or a set of tuples, and returns the result as a tuple * iterator. */ public static class ExprIterCalc extends AbstractIterCalc { private final IterCalc[] iterCalcs; public ExprIterCalc( Exp exp, Exp[] args, ExpCompiler compiler, List resultStyles) { super(exp, null); final List calcList = compileSelf(args, compiler, resultStyles); iterCalcs = calcList.toArray(new IterCalc[calcList.size()]); } // override return type public IterCalc[] getCalcs() { return iterCalcs; } public TupleIterable evaluateIterable( final Evaluator evaluator) { return new AbstractTupleIterable(getType().getArity()) { public TupleCursor tupleCursor() { return new AbstractTupleCursor(arity) { Iterator calcIterator = Arrays.asList(iterCalcs).iterator(); TupleCursor currentCursor = TupleCollections.emptyList(1).tupleCursor(); public boolean forward() { while (true) { if (currentCursor.forward()) { return true; } if (!calcIterator.hasNext()) { return false; } currentCursor = calcIterator.next() .evaluateIterable(evaluator) .tupleCursor(); } } public List current() { return currentCursor.current(); } @Override public void setContext(Evaluator evaluator) { currentCursor.setContext(evaluator); } @Override public void currentToArray( Member[] members, int offset) { currentCursor.currentToArray(members, offset); } @Override public Member member(int column) { return currentCursor.member(column); } }; } }; } } private static class ResolverImpl extends ResolverBase { public ResolverImpl() { super( "{}", "{ [, ...]}", "Brace operator constructs a set.", Syntax.Braces); } public FunDef resolve( Exp[] args, Validator validator, List conversions) { int[] parameterTypes = new int[args.length]; for (int i = 0; i < args.length; i++) { if (validator.canConvert( i, args[i], Category.Member, conversions)) { parameterTypes[i] = Category.Member; continue; } if (validator.canConvert( i, args[i], Category.Tuple, conversions)) { parameterTypes[i] = Category.Tuple; continue; } if (validator.canConvert( i, args[i], Category.Set, conversions)) { parameterTypes[i] = Category.Set; continue; } return null; } return new SetFunDef(this, parameterTypes); } } /** * Compiled expression that returns an empty list of members or tuples. */ private static class EmptyListCalc extends AbstractListCalc { private final TupleList list; /** * Creates an EmptyListCalc. * * @param call Expression which was compiled */ EmptyListCalc(ResolvedFunCall call) { super(call, new Calc[0]); list = TupleCollections.emptyList(call.getType().getArity()); } public TupleList evaluateList(Evaluator evaluator) { return list; } } } // End SetFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/SetToStrFunDef.java0000644000175000017500000000445111735330606023761 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractStringCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.util.List; /** * Definition of the SetToStr MDX function. * * @author jhyde * @since Aug 3, 2006 */ class SetToStrFunDef extends FunDefBase { public static final FunDefBase instance = new SetToStrFunDef(); private SetToStrFunDef() { super("SetToStr", "Constructs a string from a set.", "fSx"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { Exp arg = call.getArg(0); final ListCalc listCalc = compiler.compileList(arg); return new AbstractStringCalc(call, new Calc[]{listCalc}) { public String evaluateString(Evaluator evaluator) { final TupleList list = listCalc.evaluateList(evaluator); if (list.getArity() == 1) { return memberSetToStr(list.slice(0)); } else { return tupleSetToStr(list); } } }; } static String memberSetToStr(List list) { StringBuilder buf = new StringBuilder(); buf.append("{"); int k = 0; for (Member member : list) { if (k++ > 0) { buf.append(", "); } buf.append(member.getUniqueName()); } buf.append("}"); return buf.toString(); } static String tupleSetToStr(TupleList list) { StringBuilder buf = new StringBuilder(); buf.append("{"); int k = 0; Member[] members = new Member[list.getArity()]; final TupleCursor cursor = list.tupleCursor(); while (cursor.forward()) { if (k++ > 0) { buf.append(", "); } cursor.currentToArray(members, 0); appendTuple(buf, members); } buf.append("}"); return buf.toString(); } } // End SetToStrFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/TupleItemFunDef.java0000644000175000017500000000671311735330606024145 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractMemberCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.*; /** * Definition of the <Tuple>.Item MDX function. * *

    Syntax: *

    * <Tuple>.Item(<Index>)
    *
    * * @author jhyde * @since Mar 23, 2006 */ class TupleItemFunDef extends FunDefBase { static final TupleItemFunDef instance = new TupleItemFunDef(); private TupleItemFunDef() { super( "Item", "Returns a member from the tuple specified in . The member to be returned is specified by the zero-based position of the member in the set in .", "mmtn"); } public Type getResultType(Validator validator, Exp[] args) { // Suppose we are called as follows: // ([Gender].CurrentMember, [Store].CurrentMember).Item(n) // // We know that our result is a member type, but we don't // know which dimension. return MemberType.Unknown; } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Type type = call.getArg(0).getType(); if (type instanceof MemberType) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); final IntegerCalc indexCalc = compiler.compileInteger(call.getArg(1)); return new AbstractMemberCalc( call, new Calc[] {memberCalc, indexCalc}) { public Member evaluateMember(Evaluator evaluator) { final Member member = memberCalc.evaluateMember(evaluator); final int index = indexCalc.evaluateInteger(evaluator); if (index != 0) { return null; } return member; } }; } else { final TupleCalc tupleCalc = compiler.compileTuple(call.getArg(0)); final IntegerCalc indexCalc = compiler.compileInteger(call.getArg(1)); return new AbstractMemberCalc( call, new Calc[] {tupleCalc, indexCalc}) { final Member[] nullTupleMembers = makeNullTuple((TupleType) tupleCalc.getType()); public Member evaluateMember(Evaluator evaluator) { final Member[] members = tupleCalc.evaluateTuple(evaluator); assert members == null || members.length == nullTupleMembers.length; final int index = indexCalc.evaluateInteger(evaluator); if (members == null) { return nullTupleMembers[index]; } if (index >= members.length || index < 0) { return null; } return members[index]; } }; } } } // End TupleItemFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/BuiltinFunTable.java0000644000175000017500000024022411735330606024171 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.fun.extra.CalculatedChildFunDef; import mondrian.olap.fun.extra.NthQuartileFunDef; import mondrian.olap.fun.vba.Excel; import mondrian.olap.fun.vba.Vba; import mondrian.olap.type.LevelType; import mondrian.olap.type.Type; import java.io.PrintWriter; import java.util.*; /** * BuiltinFunTable contains a list of all built-in MDX functions. * *

    Note: Boolean expressions return {@link Boolean#TRUE}, * {@link Boolean#FALSE} or null. null is returned if the expression can not be * evaluated because some values have not been loaded from database yet.

    * * @author jhyde * @since 26 February, 2002 */ public class BuiltinFunTable extends FunTableImpl { /** the singleton */ private static BuiltinFunTable instance; /** * Creates a function table containing all of the builtin MDX functions. * This method should only be called from {@link BuiltinFunTable#instance}. */ protected BuiltinFunTable() { super(); } public void defineFunctions(Builder builder) { builder.defineReserved("NULL"); // Empty expression builder.define( new FunDefBase( "", "", "Dummy function representing the empty expression", Syntax.Empty, Category.Empty, new int[0]) { } ); // first char: p=Property, m=Method, i=Infix, P=Prefix // 2nd: // ARRAY FUNCTIONS // "SetToArray([, ]...[, ])" if (false) builder.define(new FunDefBase( "SetToArray", "SetToArray([, ]...[, ])", "Converts one or more sets to an array for use in a user-defined function.", "fa*") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { throw new UnsupportedOperationException(); } }); // // DIMENSION FUNCTIONS builder.define(HierarchyDimensionFunDef.instance); // ".Dimension" builder.define(DimensionDimensionFunDef.INSTANCE); // ".Dimension" builder.define(LevelDimensionFunDef.INSTANCE); // ".Dimension" builder.define(MemberDimensionFunDef.INSTANCE); // "Dimensions()" builder.define(DimensionsNumericFunDef.INSTANCE); // "Dimensions()" builder.define(DimensionsStringFunDef.INSTANCE); // // HIERARCHY FUNCTIONS builder.define(LevelHierarchyFunDef.instance); builder.define(MemberHierarchyFunDef.instance); // // LEVEL FUNCTIONS builder.define(MemberLevelFunDef.instance); // ".Levels()" builder.define( new FunDefBase( "Levels", "Returns the level whose position in a hierarchy is specified by a numeric expression.", "mlhn") { public Type getResultType(Validator validator, Exp[] args) { final Type argType = args[0].getType(); return LevelType.forType(argType); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final HierarchyCalc hierarchyCalc = compiler.compileHierarchy(call.getArg(0)); final IntegerCalc ordinalCalc = compiler.compileInteger(call.getArg(1)); return new AbstractLevelCalc( call, new Calc[] {hierarchyCalc, ordinalCalc}) { public Level evaluateLevel(Evaluator evaluator) { Hierarchy hierarchy = hierarchyCalc.evaluateHierarchy(evaluator); int ordinal = ordinalCalc.evaluateInteger(evaluator); return nthLevel(hierarchy, ordinal); } }; } Level nthLevel(Hierarchy hierarchy, int n) { Level[] levels = hierarchy.getLevels(); if (n >= levels.length || n < 0) { throw newEvalException( this, "Index '" + n + "' out of bounds"); } return levels[n]; } }); // ".Levels()" builder.define( new FunDefBase( "Levels", "Returns the level whose name is specified by a string expression.", "mlhS") { public Type getResultType(Validator validator, Exp[] args) { final Type argType = args[0].getType(); return LevelType.forType(argType); } public Calc compileCall( final ResolvedFunCall call, ExpCompiler compiler) { final HierarchyCalc hierarchyCalc = compiler.compileHierarchy(call.getArg(0)); final StringCalc nameCalc = compiler.compileString(call.getArg(1)); return new AbstractLevelCalc( call, new Calc[] {hierarchyCalc, nameCalc}) { public Level evaluateLevel(Evaluator evaluator) { Hierarchy hierarchy = hierarchyCalc.evaluateHierarchy(evaluator); String name = nameCalc.evaluateString(evaluator); for (Level level : hierarchy.getLevels()) { if (level.getName().equals(name)) { return level; } } throw newEvalException( call.getFunDef(), "Level '" + name + "' not found in hierarchy '" + hierarchy + "'"); } }; } }); // "Levels()" builder.define( new FunDefBase( "Levels", "Returns the level whose name is specified by a string expression.", "flS") { public Type getResultType(Validator validator, Exp[] args) { final Type argType = args[0].getType(); return LevelType.forType(argType); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final StringCalc stringCalc = compiler.compileString(call.getArg(0)); return new AbstractLevelCalc(call, new Calc[] {stringCalc}) { public Level evaluateLevel(Evaluator evaluator) { String levelName = stringCalc.evaluateString(evaluator); return findLevel(evaluator, levelName); } }; } Level findLevel(Evaluator evaluator, String s) { Cube cube = evaluator.getCube(); OlapElement o = (s.startsWith("[")) ? evaluator.getSchemaReader().lookupCompound( cube, parseIdentifier(s), false, Category.Level) // lookupCompound barfs if "s" doesn't have matching // brackets, so don't even try : null; if (o instanceof Level) { return (Level) o; } else if (o == null) { throw newEvalException(this, "Level '" + s + "' not found"); } else { throw newEvalException( this, "Levels('" + s + "') found " + o); } } }); // // LOGICAL FUNCTIONS builder.define(IsEmptyFunDef.FunctionResolver); builder.define(IsEmptyFunDef.PostfixResolver); builder.define(IsNullFunDef.Resolver); builder.define(IsFunDef.Resolver); builder.define(AsFunDef.RESOLVER); // // MEMBER FUNCTIONS builder.define(AncestorFunDef.Resolver); builder.define( new FunDefBase( "Cousin", " Cousin(, )", "Returns the member with the same relative position under as the member specified.", "fmmm") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); final MemberCalc ancestorMemberCalc = compiler.compileMember(call.getArg(1)); return new AbstractMemberCalc( call, new Calc[] {memberCalc, ancestorMemberCalc}) { public Member evaluateMember(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); Member ancestorMember = ancestorMemberCalc.evaluateMember(evaluator); return cousin( evaluator.getSchemaReader(), member, ancestorMember); } }; } }); builder.define(HierarchyCurrentMemberFunDef.instance); builder.define(NamedSetCurrentFunDef.instance); builder.define(NamedSetCurrentOrdinalFunDef.instance); // ".DataMember" builder.define( new FunDefBase( "DataMember", "Returns the system-generated data member that is associated with a nonleaf member of a dimension.", "pmm") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractMemberCalc(call, new Calc[] {memberCalc}) { public Member evaluateMember(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); return member.getDataMember(); } }; } }); // ".DefaultMember". The function is implemented using an // implicit cast to hierarchy, and we create a FunInfo for // documentation & backwards compatibility. builder.define( new FunInfo( "DefaultMember", "Returns the default member of a dimension.", "pmd")); // ".DefaultMember" builder.define( new FunDefBase( "DefaultMember", "Returns the default member of a hierarchy.", "pmh") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final HierarchyCalc hierarchyCalc = compiler.compileHierarchy(call.getArg(0)); return new AbstractMemberCalc( call, new Calc[] {hierarchyCalc}) { public Member evaluateMember(Evaluator evaluator) { Hierarchy hierarchy = hierarchyCalc.evaluateHierarchy(evaluator); return evaluator.getSchemaReader() .getHierarchyDefaultMember(hierarchy); } }; } }); // ".FirstChild" builder.define( new FunDefBase( "FirstChild", "Returns the first child of a member.", "pmm") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractMemberCalc(call, new Calc[] {memberCalc}) { public Member evaluateMember(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); return firstChild(evaluator, member); } }; } Member firstChild(Evaluator evaluator, Member member) { List children = evaluator.getSchemaReader() .getMemberChildren(member); return (children.size() == 0) ? member.getHierarchy().getNullMember() : children.get(0); } }); // .FirstSibling builder.define( new FunDefBase( "FirstSibling", "Returns the first child of the parent of a member.", "pmm") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractMemberCalc(call, new Calc[] {memberCalc}) { public Member evaluateMember(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); return firstSibling(member, evaluator); } }; } Member firstSibling(Member member, Evaluator evaluator) { Member parent = member.getParentMember(); List children; final SchemaReader schemaReader = evaluator.getSchemaReader(); if (parent == null) { if (member.isNull()) { return member; } children = schemaReader.getHierarchyRootMembers( member.getHierarchy()); } else { children = schemaReader.getMemberChildren(parent); } return children.get(0); } }); builder.define(LeadLagFunDef.LagResolver); // .LastChild builder.define( new FunDefBase( "LastChild", "Returns the last child of a member.", "pmm") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractMemberCalc(call, new Calc[] {memberCalc}) { public Member evaluateMember(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); return lastChild(evaluator, member); } }; } Member lastChild(Evaluator evaluator, Member member) { List children = evaluator.getSchemaReader().getMemberChildren(member); return (children.size() == 0) ? member.getHierarchy().getNullMember() : children.get(children.size() - 1); } }); // .LastSibling builder.define( new FunDefBase( "LastSibling", "Returns the last child of the parent of a member.", "pmm") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractMemberCalc(call, new Calc[] {memberCalc}) { public Member evaluateMember(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); return firstSibling(member, evaluator); } }; } Member firstSibling(Member member, Evaluator evaluator) { Member parent = member.getParentMember(); List children; final SchemaReader schemaReader = evaluator.getSchemaReader(); if (parent == null) { if (member.isNull()) { return member; } children = schemaReader.getHierarchyRootMembers( member.getHierarchy()); } else { children = schemaReader.getMemberChildren(parent); } return children.get(children.size() - 1); } }); builder.define(LeadLagFunDef.LeadResolver); // Members() builder.define( new FunDefBase( "Members", "Returns the member whose name is specified by a string expression.", "fmS") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { throw new UnsupportedOperationException(); } }); // .NextMember builder.define( new FunDefBase( "NextMember", "Returns the next member in the level that contains a specified member.", "pmm") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractMemberCalc(call, new Calc[] {memberCalc}) { public Member evaluateMember(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); return evaluator.getSchemaReader().getLeadMember( member, 1); } }; } }); builder.define(OpeningClosingPeriodFunDef.OpeningPeriodResolver); builder.define(OpeningClosingPeriodFunDef.ClosingPeriodResolver); builder.define(MemberOrderKeyFunDef.instance); builder.define(ParallelPeriodFunDef.Resolver); // .Parent builder.define( new FunDefBase( "Parent", "Returns the parent of a member.", "pmm") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractMemberCalc(call, new Calc[] {memberCalc}) { public Member evaluateMember(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); return memberParent(evaluator, member); } }; } Member memberParent(Evaluator evaluator, Member member) { Member parent = evaluator.getSchemaReader().getMemberParent(member); if (parent == null) { parent = member.getHierarchy().getNullMember(); } return parent; } }); // .PrevMember builder.define( new FunDefBase( "PrevMember", "Returns the previous member in the level that contains a specified member.", "pmm") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractMemberCalc(call, new Calc[] {memberCalc}) { public Member evaluateMember(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); return evaluator.getSchemaReader().getLeadMember( member, -1); } }; } }); builder.define(StrToMemberFunDef.INSTANCE); builder.define(ValidMeasureFunDef.instance); // // NUMERIC FUNCTIONS builder.define(AggregateFunDef.resolver); // Obsolete?? builder.define( new MultiResolver( "$AggregateChildren", "$AggregateChildren()", "Equivalent to 'Aggregate(.CurrentMember.Children); for internal use.", new String[] {"Inh"}) { protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) { return new FunDefBase(dummyFunDef) { public void unparse(Exp[] args, PrintWriter pw) { pw.print(getName()); pw.print("("); args[0].unparse(pw); pw.print(")"); } public Calc compileCall( ResolvedFunCall call, ExpCompiler compiler) { final HierarchyCalc hierarchyCalc = compiler.compileHierarchy(call.getArg(0)); final Calc valueCalc = new ValueCalc(call); return new GenericCalc(call) { public Object evaluate(Evaluator evaluator) { Hierarchy hierarchy = hierarchyCalc.evaluateHierarchy(evaluator); return aggregateChildren( evaluator, hierarchy, valueCalc); } public Calc[] getCalcs() { return new Calc[] {hierarchyCalc, valueCalc}; } }; } Object aggregateChildren( Evaluator evaluator, Hierarchy hierarchy, final Calc valueFunCall) { Member member = evaluator.getPreviousContext(hierarchy); List members = new ArrayList(); evaluator.getSchemaReader() .getParentChildContributingChildren( member.getDataMember(), hierarchy, members); Aggregator aggregator = (Aggregator) evaluator.getProperty( Property.AGGREGATION_TYPE.name, null); if (aggregator == null) { throw FunUtil.newEvalException( null, "Could not find an aggregator in the current " + "evaluation context"); } Aggregator rollup = aggregator.getRollup(); if (rollup == null) { throw FunUtil.newEvalException( null, "Don't know how to rollup aggregator '" + aggregator + "'"); } final int savepoint = evaluator.savepoint(); final Object o = rollup.aggregate( evaluator, new UnaryTupleList(members), valueFunCall); evaluator.restore(savepoint); return o; } }; } }); builder.define(AvgFunDef.Resolver); builder.define(CorrelationFunDef.Resolver); builder.define(CountFunDef.Resolver); // .Count builder.define( new FunDefBase( "Count", "Returns the number of tuples in a set including empty cells.", "pnx") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); return new AbstractIntegerCalc(call, new Calc[] {listCalc}) { public int evaluateInteger(Evaluator evaluator) { TupleList list = listCalc.evaluateList(evaluator); return count(evaluator, list, true); } }; } }); builder.define(CovarianceFunDef.CovarianceResolver); builder.define(CovarianceFunDef.CovarianceNResolver); builder.define(IifFunDef.STRING_INSTANCE); builder.define(IifFunDef.NUMERIC_INSTANCE); builder.define(IifFunDef.TUPLE_INSTANCE); builder.define(IifFunDef.BOOLEAN_INSTANCE); builder.define(IifFunDef.MEMBER_INSTANCE); builder.define(IifFunDef.LEVEL_INSTANCE); builder.define(IifFunDef.HIERARCHY_INSTANCE); builder.define(IifFunDef.DIMENSION_INSTANCE); builder.define(IifFunDef.SET_INSTANCE); builder.define(LinReg.InterceptResolver); builder.define(LinReg.PointResolver); builder.define(LinReg.R2Resolver); builder.define(LinReg.SlopeResolver); builder.define(LinReg.VarianceResolver); builder.define(MinMaxFunDef.MaxResolver); builder.define(MinMaxFunDef.MinResolver); builder.define(MedianFunDef.Resolver); builder.define(PercentileFunDef.Resolver); // .Ordinal builder.define( new FunDefBase( "Ordinal", "Returns the zero-based ordinal value associated with a level.", "pnl") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final LevelCalc levelCalc = compiler.compileLevel(call.getArg(0)); return new AbstractIntegerCalc(call, new Calc[] {levelCalc}) { public int evaluateInteger(Evaluator evaluator) { final Level level = levelCalc.evaluateLevel(evaluator); return level.getDepth(); } }; } }); builder.define(RankFunDef.Resolver); builder.define(CacheFunDef.Resolver); builder.define(StdevFunDef.StdevResolver); builder.define(StdevFunDef.StddevResolver); builder.define(StdevPFunDef.StdevpResolver); builder.define(StdevPFunDef.StddevpResolver); builder.define(SumFunDef.Resolver); // .Value builder.define( new FunDefBase( "Value", "Returns the value of a measure.", "pnm") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new GenericCalc(call) { public Object evaluate(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); final int savepoint = evaluator.savepoint(); evaluator.setContext(member); Object value = evaluator.evaluateCurrent(); evaluator.restore(savepoint); return value; } public boolean dependsOn(Hierarchy hierarchy) { if (super.dependsOn(hierarchy)) { return true; } if (memberCalc.getType().usesHierarchy( hierarchy, true)) { return false; } return true; } public Calc[] getCalcs() { return new Calc[] {memberCalc}; } }; } }); builder.define(VarFunDef.VarResolver); builder.define(VarFunDef.VarianceResolver); builder.define(VarPFunDef.VariancePResolver); builder.define(VarPFunDef.VarPResolver); // // SET FUNCTIONS builder.define(AddCalculatedMembersFunDef.resolver); // Ascendants() builder.define( new FunDefBase( "Ascendants", "Returns the set of the ascendants of a specified member.", "fxm") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractListCalc(call, new Calc[] {memberCalc}) { public TupleList evaluateList(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); return new UnaryTupleList( ascendants(evaluator.getSchemaReader(), member)); } }; } List ascendants(SchemaReader schemaReader, Member member) { if (member.isNull()) { return Collections.emptyList(); } final List result = new ArrayList(); result.add(member); schemaReader.getMemberAncestors(member, result); return result; } }); builder.define(TopBottomCountFunDef.BottomCountResolver); builder.define(TopBottomPercentSumFunDef.BottomPercentResolver); builder.define(TopBottomPercentSumFunDef.BottomSumResolver); builder.define(TopBottomCountFunDef.TopCountResolver); builder.define(TopBottomPercentSumFunDef.TopPercentResolver); builder.define(TopBottomPercentSumFunDef.TopSumResolver); // .Children builder.define( new FunDefBase( "Children", "Returns the children of a member.", "pxm") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractListCalc( call, new Calc[] {memberCalc}, false) { public TupleList evaluateList(Evaluator evaluator) { // Return the list of children. The list is immutable, // hence 'false' above. Member member = memberCalc.evaluateMember(evaluator); return new UnaryTupleList( getNonEmptyMemberChildren(evaluator, member)); } }; } }); builder.define(CrossJoinFunDef.Resolver); builder.define(NonEmptyCrossJoinFunDef.Resolver); builder.define(CrossJoinFunDef.StarResolver); builder.define(DescendantsFunDef.Resolver); builder.define(DescendantsFunDef.Resolver2); builder.define(DistinctFunDef.instance); builder.define(DrilldownLevelFunDef.Resolver); builder.define(DrilldownLevelTopBottomFunDef.DrilldownLevelTopResolver); builder.define( DrilldownLevelTopBottomFunDef.DrilldownLevelBottomResolver); builder.define(DrilldownMemberFunDef.Resolver); if (false) builder.define( new FunDefBase( "DrilldownMemberBottom", "DrilldownMemberBottom(, , [, [][, RECURSIVE]])", "Like DrilldownMember except that it includes only the bottom N children.", "fx*") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { throw new UnsupportedOperationException(); } }); if (false) builder.define( new FunDefBase( "DrilldownMemberTop", "DrilldownMemberTop(, , [, [][, RECURSIVE]])", "Like DrilldownMember except that it includes only the top N children.", "fx*") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { throw new UnsupportedOperationException(); } }); if (false) builder.define( new FunDefBase( "DrillupLevel", "DrillupLevel([, ])", "Drills up the members of a set that are below a specified level.", "fx*") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { throw new UnsupportedOperationException(); } }); if (false) builder.define( new FunDefBase( "DrillupMember", "DrillupMember(, )", "Drills up the members in a set that are present in a second specified set.", "fx*") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { throw new UnsupportedOperationException(); } }); builder.define(ExceptFunDef.Resolver); builder.define(ExistsFunDef.resolver); builder.define(ExtractFunDef.Resolver); builder.define(FilterFunDef.instance); builder.define(GenerateFunDef.ListResolver); builder.define(GenerateFunDef.StringResolver); builder.define(HeadTailFunDef.HeadResolver); builder.define(HierarchizeFunDef.Resolver); builder.define(IntersectFunDef.resolver); builder.define(LastPeriodsFunDef.Resolver); // .Members is really just shorthand for .Members builder.define( new FunInfo( "Members", "Returns the set of members in a dimension.", "pxd")); // .Members builder.define( new FunDefBase( "Members", "Returns the set of members in a hierarchy.", "pxh") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final HierarchyCalc hierarchyCalc = compiler.compileHierarchy(call.getArg(0)); return new AbstractListCalc( call, new Calc[] {hierarchyCalc}) { public TupleList evaluateList(Evaluator evaluator) { Hierarchy hierarchy = hierarchyCalc.evaluateHierarchy(evaluator); return hierarchyMembers(hierarchy, evaluator, false); } }; } }); // .AllMembers builder.define( new FunDefBase( "AllMembers", "Returns a set that contains all members, including calculated members, of the specified hierarchy.", "pxh") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final HierarchyCalc hierarchyCalc = compiler.compileHierarchy(call.getArg(0)); return new AbstractListCalc( call, new Calc[] {hierarchyCalc}) { public TupleList evaluateList(Evaluator evaluator) { Hierarchy hierarchy = hierarchyCalc.evaluateHierarchy(evaluator); return hierarchyMembers(hierarchy, evaluator, true); } }; } }); // .Members builder.define(LevelMembersFunDef.INSTANCE); // .AllMembers builder.define( new FunDefBase( "AllMembers", "Returns a set that contains all members, including calculated members, of the specified level.", "pxl") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final LevelCalc levelCalc = compiler.compileLevel(call.getArg(0)); return new AbstractListCalc(call, new Calc[] {levelCalc}) { public TupleList evaluateList(Evaluator evaluator) { Level level = levelCalc.evaluateLevel(evaluator); return levelMembers(level, evaluator, true); } }; } }); builder.define(XtdFunDef.MtdResolver); builder.define(OrderFunDef.Resolver); builder.define(UnorderFunDef.Resolver); builder.define(PeriodsToDateFunDef.Resolver); builder.define(XtdFunDef.QtdResolver); // StripCalculatedMembers() builder.define( new FunDefBase( "StripCalculatedMembers", "Removes calculated members from a set.", "fxx") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); return new AbstractListCalc(call, new Calc[] {listCalc}) { public TupleList evaluateList(Evaluator evaluator) { TupleList list = listCalc.evaluateList(evaluator); return removeCalculatedMembers(list); } }; } }); // .Siblings builder.define( new FunDefBase( "Siblings", "Returns the siblings of a specified member, including the member itself.", "pxm") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractListCalc(call, new Calc[] {memberCalc}) { public TupleList evaluateList(Evaluator evaluator) { final Member member = memberCalc.evaluateMember(evaluator); return new UnaryTupleList( memberSiblings(member, evaluator)); } }; } List memberSiblings(Member member, Evaluator evaluator) { if (member.isNull()) { // the null member has no siblings -- not even itself return Collections.emptyList(); } Member parent = member.getParentMember(); final SchemaReader schemaReader = evaluator.getSchemaReader(); if (parent == null) { return schemaReader.getHierarchyRootMembers( member.getHierarchy()); } else { return schemaReader.getMemberChildren(parent); } } }); builder.define(StrToSetFunDef.Resolver); builder.define(SubsetFunDef.Resolver); builder.define(HeadTailFunDef.TailResolver); builder.define(ToggleDrillStateFunDef.Resolver); builder.define(UnionFunDef.Resolver); builder.define(VisualTotalsFunDef.Resolver); builder.define(XtdFunDef.WtdResolver); builder.define(XtdFunDef.YtdResolver); builder.define(RangeFunDef.instance); // " : " operator builder.define(SetFunDef.Resolver); // "{ [,...] }" operator builder.define(NativizeSetFunDef.Resolver); // // STRING FUNCTIONS builder.define(FormatFunDef.Resolver); // .Caption builder.define( new FunDefBase( "Caption", "Returns the caption of a dimension.", "pSd") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final DimensionCalc dimensionCalc = compiler.compileDimension(call.getArg(0)); return new AbstractStringCalc(call, new Calc[] {dimensionCalc}) { public String evaluateString(Evaluator evaluator) { final Dimension dimension = dimensionCalc.evaluateDimension(evaluator); return dimension.getCaption(); } }; } }); // .Caption builder.define( new FunDefBase( "Caption", "Returns the caption of a hierarchy.", "pSh") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final HierarchyCalc hierarchyCalc = compiler.compileHierarchy(call.getArg(0)); return new AbstractStringCalc(call, new Calc[] {hierarchyCalc}) { public String evaluateString(Evaluator evaluator) { final Hierarchy hierarchy = hierarchyCalc.evaluateHierarchy(evaluator); return hierarchy.getCaption(); } }; } }); // .Caption builder.define( new FunDefBase( "Caption", "Returns the caption of a level.", "pSl") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final LevelCalc levelCalc = compiler.compileLevel(call.getArg(0)); return new AbstractStringCalc(call, new Calc[] {levelCalc}) { public String evaluateString(Evaluator evaluator) { final Level level = levelCalc.evaluateLevel(evaluator); return level.getCaption(); } }; } }); // .Caption builder.define( new FunDefBase( "Caption", "Returns the caption of a member.", "pSm") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractStringCalc(call, new Calc[] {memberCalc}) { public String evaluateString(Evaluator evaluator) { final Member member = memberCalc.evaluateMember(evaluator); return member.getCaption(); } }; } }); // .Name builder.define( new FunDefBase( "Name", "Returns the name of a dimension.", "pSd") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final DimensionCalc dimensionCalc = compiler.compileDimension(call.getArg(0)); return new AbstractStringCalc(call, new Calc[] {dimensionCalc}) { public String evaluateString(Evaluator evaluator) { final Dimension dimension = dimensionCalc.evaluateDimension(evaluator); return dimension.getName(); } }; } }); // .Name builder.define( new FunDefBase( "Name", "Returns the name of a hierarchy.", "pSh") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final HierarchyCalc hierarchyCalc = compiler.compileHierarchy(call.getArg(0)); return new AbstractStringCalc(call, new Calc[] {hierarchyCalc}) { public String evaluateString(Evaluator evaluator) { final Hierarchy hierarchy = hierarchyCalc.evaluateHierarchy(evaluator); return hierarchy.getName(); } }; } }); // .Name builder.define( new FunDefBase( "Name", "Returns the name of a level.", "pSl") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final LevelCalc levelCalc = compiler.compileLevel(call.getArg(0)); return new AbstractStringCalc(call, new Calc[] {levelCalc}) { public String evaluateString(Evaluator evaluator) { final Level level = levelCalc.evaluateLevel(evaluator); return level.getName(); } }; } }); // .Name builder.define( new FunDefBase( "Name", "Returns the name of a member.", "pSm") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractStringCalc(call, new Calc[] {memberCalc}) { public String evaluateString(Evaluator evaluator) { final Member member = memberCalc.evaluateMember(evaluator); return member.getName(); } }; } }); builder.define(SetToStrFunDef.instance); builder.define(TupleToStrFunDef.instance); // .UniqueName builder.define( new FunDefBase( "UniqueName", "Returns the unique name of a dimension.", "pSd") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final DimensionCalc dimensionCalc = compiler.compileDimension(call.getArg(0)); return new AbstractStringCalc(call, new Calc[] {dimensionCalc}) { public String evaluateString(Evaluator evaluator) { final Dimension dimension = dimensionCalc.evaluateDimension(evaluator); return dimension.getUniqueName(); } }; } }); // .UniqueName builder.define( new FunDefBase( "UniqueName", "Returns the unique name of a hierarchy.", "pSh") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final HierarchyCalc hierarchyCalc = compiler.compileHierarchy(call.getArg(0)); return new AbstractStringCalc(call, new Calc[] {hierarchyCalc}) { public String evaluateString(Evaluator evaluator) { final Hierarchy hierarchy = hierarchyCalc.evaluateHierarchy(evaluator); return hierarchy.getUniqueName(); } }; } }); // .UniqueName builder.define( new FunDefBase( "UniqueName", "Returns the unique name of a level.", "pSl") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final LevelCalc levelCalc = compiler.compileLevel(call.getArg(0)); return new AbstractStringCalc(call, new Calc[] {levelCalc}) { public String evaluateString(Evaluator evaluator) { final Level level = levelCalc.evaluateLevel(evaluator); return level.getUniqueName(); } }; } }); // .UniqueName builder.define( new FunDefBase( "UniqueName", "Returns the unique name of a member.", "pSm") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new AbstractStringCalc(call, new Calc[] {memberCalc}) { public String evaluateString(Evaluator evaluator) { final Member member = memberCalc.evaluateMember(evaluator); return member.getUniqueName(); } }; } }); // // TUPLE FUNCTIONS // .Current if (false) builder.define( new FunDefBase( "Current", "Returns the current tuple from a set during an iteration.", "ptx") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { throw new UnsupportedOperationException(); } }); builder.define(SetItemFunDef.intResolver); builder.define(SetItemFunDef.stringResolver); builder.define(TupleItemFunDef.instance); builder.define(StrToTupleFunDef.Resolver); // special resolver for "()" builder.define(TupleFunDef.Resolver); // // GENERIC VALUE FUNCTIONS builder.define(CoalesceEmptyFunDef.Resolver); builder.define(CaseTestFunDef.Resolver); builder.define(CaseMatchFunDef.Resolver); builder.define(PropertiesFunDef.Resolver); // // PARAMETER FUNCTIONS builder.define(new ParameterFunDef.ParameterResolver()); builder.define(new ParameterFunDef.ParamRefResolver()); // // OPERATORS // + builder.define( new FunDefBase( "+", "Adds two numbers.", "innn") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final DoubleCalc calc0 = compiler.compileDouble(call.getArg(0)); final DoubleCalc calc1 = compiler.compileDouble(call.getArg(1)); return new AbstractDoubleCalc(call, new Calc[] {calc0, calc1}) { public double evaluateDouble(Evaluator evaluator) { final double v0 = calc0.evaluateDouble(evaluator); final double v1 = calc1.evaluateDouble(evaluator); if (v0 == DoubleNull) { if (v1 == DoubleNull) { return DoubleNull; } else { return v1; } } else { if (v1 == DoubleNull) { return v0; } else { return v0 + v1; } } } }; } }); // - builder.define( new FunDefBase( "-", "Subtracts two numbers.", "innn") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final DoubleCalc calc0 = compiler.compileDouble(call.getArg(0)); final DoubleCalc calc1 = compiler.compileDouble(call.getArg(1)); return new AbstractDoubleCalc(call, new Calc[] {calc0, calc1}) { public double evaluateDouble(Evaluator evaluator) { final double v0 = calc0.evaluateDouble(evaluator); final double v1 = calc1.evaluateDouble(evaluator); if (v0 == DoubleNull) { if (v1 == DoubleNull) { return DoubleNull; } else { return - v1; } } else { if (v1 == DoubleNull) { return v0; } else { return v0 - v1; } } } }; } }); // * builder.define( new FunDefBase( "*", "Multiplies two numbers.", "innn") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final DoubleCalc calc0 = compiler.compileDouble(call.getArg(0)); final DoubleCalc calc1 = compiler.compileDouble(call.getArg(1)); return new AbstractDoubleCalc(call, new Calc[] {calc0, calc1}) { public double evaluateDouble(Evaluator evaluator) { final double v0 = calc0.evaluateDouble(evaluator); final double v1 = calc1.evaluateDouble(evaluator); // Multiply and divide return null if EITHER arg is // null. if (v0 == DoubleNull || v1 == DoubleNull) { return DoubleNull; } else { return v0 * v1; } } }; } }); // / builder.define( new FunDefBase( "/", "Divides two numbers.", "innn") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final DoubleCalc calc0 = compiler.compileDouble(call.getArg(0)); final DoubleCalc calc1 = compiler.compileDouble(call.getArg(1)); final boolean isNullDenominatorProducesNull = MondrianProperties.instance().NullDenominatorProducesNull .get(); // If the mondrian property // mondrian.olap.NullOrZeroDenominatorProducesNull // is false(default), Null in denominator with numeric numerator // returns infinity. This is consistent with MSAS. // // If this property is true, Null or zero in denominator returns // Null. This is only used by certain applications and does not // conform to MSAS behavior. if (!isNullDenominatorProducesNull) { return new AbstractDoubleCalc( call, new Calc[] {calc0, calc1}) { public double evaluateDouble(Evaluator evaluator) { final double v0 = calc0.evaluateDouble(evaluator); final double v1 = calc1.evaluateDouble(evaluator); // Null in numerator always returns DoubleNull. // if (v0 == DoubleNull) { return DoubleNull; } else if (v1 == DoubleNull) { // Null only in denominator returns Infinity. return Double.POSITIVE_INFINITY; } else { return v0 / v1; } } }; } else { return new AbstractDoubleCalc( call, new Calc[] {calc0, calc1}) { public double evaluateDouble(Evaluator evaluator) { final double v0 = calc0.evaluateDouble(evaluator); final double v1 = calc1.evaluateDouble(evaluator); // Null in numerator or denominator returns // DoubleNull. if (v0 == DoubleNull || v1 == DoubleNull) { return DoubleNull; } else { return v0 / v1; } } }; } } }); // - builder.define( new FunDefBase( "-", "Returns the negative of a number.", "Pnn") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final DoubleCalc calc = compiler.compileDouble(call.getArg(0)); return new AbstractDoubleCalc(call, new Calc[] {calc}) { public double evaluateDouble(Evaluator evaluator) { final double v = calc.evaluateDouble(evaluator); if (v == DoubleNull) { return DoubleNull; } else { return - v; } } }; } }); // || builder.define( new FunDefBase( "||", "Concatenates two strings.", "iSSS") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final StringCalc calc0 = compiler.compileString(call.getArg(0)); final StringCalc calc1 = compiler.compileString(call.getArg(1)); return new AbstractStringCalc(call, new Calc[] {calc0, calc1}) { public String evaluateString(Evaluator evaluator) { final String s0 = calc0.evaluateString(evaluator); final String s1 = calc1.evaluateString(evaluator); return s0 + s1; } }; } }); // AND builder.define( new FunDefBase( "AND", "Returns the conjunction of two conditions.", "ibbb") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final BooleanCalc calc0 = compiler.compileBoolean(call.getArg(0)); final BooleanCalc calc1 = compiler.compileBoolean(call.getArg(1)); return new AbstractBooleanCalc(call, new Calc[] {calc0, calc1}) { public boolean evaluateBoolean(Evaluator evaluator) { boolean b0 = calc0.evaluateBoolean(evaluator); // don't short-circuit evaluation if we're evaluating // the axes; that way, we can combine all measures // referenced in the AND expression in a single query if (!evaluator.isEvalAxes() && !b0) { return false; } boolean b1 = calc1.evaluateBoolean(evaluator); return b0 && b1; } }; } }); // OR builder.define( new FunDefBase( "OR", "Returns the disjunction of two conditions.", "ibbb") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final BooleanCalc calc0 = compiler.compileBoolean(call.getArg(0)); final BooleanCalc calc1 = compiler.compileBoolean(call.getArg(1)); return new AbstractBooleanCalc(call, new Calc[] {calc0, calc1}) { public boolean evaluateBoolean(Evaluator evaluator) { boolean b0 = calc0.evaluateBoolean(evaluator); // don't short-circuit evaluation if we're evaluating // the axes; that way, we can combine all measures // referenced in the OR expression in a single query if (!evaluator.isEvalAxes() && b0) { return true; } boolean b1 = calc1.evaluateBoolean(evaluator); return b0 || b1; } }; } }); // XOR builder.define( new FunDefBase( "XOR", "Returns whether two conditions are mutually exclusive.", "ibbb") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final BooleanCalc calc0 = compiler.compileBoolean(call.getArg(0)); final BooleanCalc calc1 = compiler.compileBoolean(call.getArg(1)); return new AbstractBooleanCalc(call, new Calc[] {calc0, calc1}) { public boolean evaluateBoolean(Evaluator evaluator) { final boolean b0 = calc0.evaluateBoolean(evaluator); final boolean b1 = calc1.evaluateBoolean(evaluator); return b0 != b1; } }; } }); // NOT builder.define( new FunDefBase( "NOT", "Returns the negation of a condition.", "Pbb") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final BooleanCalc calc = compiler.compileBoolean(call.getArg(0)); return new AbstractBooleanCalc(call, new Calc[] {calc}) { public boolean evaluateBoolean(Evaluator evaluator) { return !calc.evaluateBoolean(evaluator); } }; } }); // = builder.define( new FunDefBase( "=", "Returns whether two expressions are equal.", "ibSS") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final StringCalc calc0 = compiler.compileString(call.getArg(0)); final StringCalc calc1 = compiler.compileString(call.getArg(1)); return new AbstractBooleanCalc(call, new Calc[] {calc0, calc1}) { public boolean evaluateBoolean(Evaluator evaluator) { final String b0 = calc0.evaluateString(evaluator); final String b1 = calc1.evaluateString(evaluator); if (b0 == null || b1 == null) { return BooleanNull; } return b0.equals(b1); } }; } }); // = builder.define( new FunDefBase( "=", "Returns whether two expressions are equal.", "ibnn") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final DoubleCalc calc0 = compiler.compileDouble(call.getArg(0)); final DoubleCalc calc1 = compiler.compileDouble(call.getArg(1)); return new AbstractBooleanCalc(call, new Calc[] {calc0, calc1}) { public boolean evaluateBoolean(Evaluator evaluator) { final double v0 = calc0.evaluateDouble(evaluator); final double v1 = calc1.evaluateDouble(evaluator); if (Double.isNaN(v0) || Double.isNaN(v1) || v0 == DoubleNull || v1 == DoubleNull) { return BooleanNull; } return v0 == v1; } }; } }); // <> builder.define( new FunDefBase( "<>", "Returns whether two expressions are not equal.", "ibSS") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final StringCalc calc0 = compiler.compileString(call.getArg(0)); final StringCalc calc1 = compiler.compileString(call.getArg(1)); return new AbstractBooleanCalc(call, new Calc[] {calc0, calc1}) { public boolean evaluateBoolean(Evaluator evaluator) { final String b0 = calc0.evaluateString(evaluator); final String b1 = calc1.evaluateString(evaluator); if (b0 == null || b1 == null) { return BooleanNull; } return !b0.equals(b1); } }; } }); // <> builder.define( new FunDefBase( "<>", "Returns whether two expressions are not equal.", "ibnn") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final DoubleCalc calc0 = compiler.compileDouble(call.getArg(0)); final DoubleCalc calc1 = compiler.compileDouble(call.getArg(1)); return new AbstractBooleanCalc(call, new Calc[] {calc0, calc1}) { public boolean evaluateBoolean(Evaluator evaluator) { final double v0 = calc0.evaluateDouble(evaluator); final double v1 = calc1.evaluateDouble(evaluator); if (Double.isNaN(v0) || Double.isNaN(v1) || v0 == DoubleNull || v1 == DoubleNull) { return BooleanNull; } return v0 != v1; } }; } }); // < builder.define( new FunDefBase( "<", "Returns whether an expression is less than another.", "ibnn") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final DoubleCalc calc0 = compiler.compileDouble(call.getArg(0)); final DoubleCalc calc1 = compiler.compileDouble(call.getArg(1)); return new AbstractBooleanCalc(call, new Calc[] {calc0, calc1}) { public boolean evaluateBoolean(Evaluator evaluator) { final double v0 = calc0.evaluateDouble(evaluator); final double v1 = calc1.evaluateDouble(evaluator); if (Double.isNaN(v0) || Double.isNaN(v1) || v0 == DoubleNull || v1 == DoubleNull) { return BooleanNull; } return v0 < v1; } }; } }); // < builder.define( new FunDefBase( "<", "Returns whether an expression is less than another.", "ibSS") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final StringCalc calc0 = compiler.compileString(call.getArg(0)); final StringCalc calc1 = compiler.compileString(call.getArg(1)); return new AbstractBooleanCalc(call, new Calc[] {calc0, calc1}) { public boolean evaluateBoolean(Evaluator evaluator) { final String b0 = calc0.evaluateString(evaluator); final String b1 = calc1.evaluateString(evaluator); if (b0 == null || b1 == null) { return BooleanNull; } return b0.compareTo(b1) < 0; } }; } }); // <= builder.define( new FunDefBase( "<=", "Returns whether an expression is less than or equal to another.", "ibnn") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final DoubleCalc calc0 = compiler.compileDouble(call.getArg(0)); final DoubleCalc calc1 = compiler.compileDouble(call.getArg(1)); return new AbstractBooleanCalc(call, new Calc[] {calc0, calc1}) { public boolean evaluateBoolean(Evaluator evaluator) { final double v0 = calc0.evaluateDouble(evaluator); final double v1 = calc1.evaluateDouble(evaluator); if (Double.isNaN(v0) || Double.isNaN(v1) || v0 == DoubleNull || v1 == DoubleNull) { return BooleanNull; } return v0 <= v1; } }; } }); // <= builder.define( new FunDefBase( "<=", "Returns whether an expression is less than or equal to another.", "ibSS") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final StringCalc calc0 = compiler.compileString(call.getArg(0)); final StringCalc calc1 = compiler.compileString(call.getArg(1)); return new AbstractBooleanCalc(call, new Calc[] {calc0, calc1}) { public boolean evaluateBoolean(Evaluator evaluator) { final String b0 = calc0.evaluateString(evaluator); final String b1 = calc1.evaluateString(evaluator); if (b0 == null || b1 == null) { return BooleanNull; } return b0.compareTo(b1) <= 0; } }; } }); // > builder.define( new FunDefBase( ">", "Returns whether an expression is greater than another.", "ibnn") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final DoubleCalc calc0 = compiler.compileDouble(call.getArg(0)); final DoubleCalc calc1 = compiler.compileDouble(call.getArg(1)); return new AbstractBooleanCalc(call, new Calc[] {calc0, calc1}) { public boolean evaluateBoolean(Evaluator evaluator) { final double v0 = calc0.evaluateDouble(evaluator); final double v1 = calc1.evaluateDouble(evaluator); if (Double.isNaN(v0) || Double.isNaN(v1) || v0 == DoubleNull || v1 == DoubleNull) { return BooleanNull; } return v0 > v1; } }; } }); // > builder.define( new FunDefBase( ">", "Returns whether an expression is greater than another.", "ibSS") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final StringCalc calc0 = compiler.compileString(call.getArg(0)); final StringCalc calc1 = compiler.compileString(call.getArg(1)); return new AbstractBooleanCalc(call, new Calc[] {calc0, calc1}) { public boolean evaluateBoolean(Evaluator evaluator) { final String b0 = calc0.evaluateString(evaluator); final String b1 = calc1.evaluateString(evaluator); if (b0 == null || b1 == null) { return BooleanNull; } return b0.compareTo(b1) > 0; } }; } }); // >= builder.define( new FunDefBase( ">=", "Returns whether an expression is greater than or equal to another.", "ibnn") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final DoubleCalc calc0 = compiler.compileDouble(call.getArg(0)); final DoubleCalc calc1 = compiler.compileDouble(call.getArg(1)); return new AbstractBooleanCalc(call, new Calc[] {calc0, calc1}) { public boolean evaluateBoolean(Evaluator evaluator) { final double v0 = calc0.evaluateDouble(evaluator); final double v1 = calc1.evaluateDouble(evaluator); if (Double.isNaN(v0) || Double.isNaN(v1) || v0 == DoubleNull || v1 == DoubleNull) { return BooleanNull; } return v0 >= v1; } }; } }); // >= builder.define( new FunDefBase( ">=", "Returns whether an expression is greater than or equal to another.", "ibSS") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final StringCalc calc0 = compiler.compileString(call.getArg(0)); final StringCalc calc1 = compiler.compileString(call.getArg(1)); return new AbstractBooleanCalc(call, new Calc[] {calc0, calc1}) { public boolean evaluateBoolean(Evaluator evaluator) { final String b0 = calc0.evaluateString(evaluator); final String b1 = calc1.evaluateString(evaluator); if (b0 == null || b1 == null) { return BooleanNull; } return b0.compareTo(b1) >= 0; } }; } }); // NON-STANDARD FUNCTIONS builder.define(NthQuartileFunDef.FirstQResolver); builder.define(NthQuartileFunDef.ThirdQResolver); builder.define(CalculatedChildFunDef.instance); builder.define(CastFunDef.Resolver); // UCase() builder.define( new FunDefBase( "UCase", "Returns a string that has been converted to uppercase", "fSS") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Locale locale = compiler.getEvaluator().getConnectionLocale(); final StringCalc stringCalc = compiler.compileString(call.getArg(0)); return new AbstractStringCalc(call, new Calc[]{stringCalc}) { public String evaluateString(Evaluator evaluator) { String value = stringCalc.evaluateString(evaluator); return value.toUpperCase(locale); } }; } }); // Len() builder.define( new FunDefBase( "Len", "Returns the number of characters in a string", "fnS") { public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final StringCalc stringCalc = compiler.compileString(call.getArg(0)); return new AbstractIntegerCalc(call, new Calc[] {stringCalc}) { public int evaluateInteger(Evaluator evaluator) { String value = stringCalc.evaluateString(evaluator); if (value == null) { return 0; } return value.length(); } }; } }); // Define VBA functions. for (FunDef funDef : JavaFunDef.scan(Vba.class)) { builder.define(funDef); } // Define Excel functions. for (FunDef funDef : JavaFunDef.scan(Excel.class)) { builder.define(funDef); } } /** * Returns the singleton, creating if necessary. * * @return the singleton */ public static BuiltinFunTable instance() { if (instance == null) { instance = new BuiltinFunTable(); instance.init(); } return instance; } } // End BuiltinFunTable.java mondrian-3.4.1/src/main/mondrian/olap/fun/PropertiesFunDef.java0000644000175000017500000001215111735330606024362 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.GenericCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.util.List; /** * Definition of the Properties MDX function. * * @author jhyde * @since Mar 23, 2006 */ class PropertiesFunDef extends FunDefBase { static final ResolverImpl Resolver = new ResolverImpl(); public PropertiesFunDef( String name, String signature, String description, Syntax syntax, int returnType, int[] parameterTypes) { super(name, signature, description, syntax, returnType, parameterTypes); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); final StringCalc stringCalc = compiler.compileString(call.getArg(1)); return new GenericCalc(call) { public Object evaluate(Evaluator evaluator) { return properties( memberCalc.evaluateMember(evaluator), stringCalc.evaluateString(evaluator)); } public Calc[] getCalcs() { return new Calc[] {memberCalc, stringCalc}; } }; } static Object properties(Member member, String s) { boolean matchCase = MondrianProperties.instance().CaseSensitive.get(); Object o = member.getPropertyValue(s, matchCase); if (o == null) { if (!Util.isValidProperty(s, member.getLevel())) { throw new MondrianEvaluationException( "Property '" + s + "' is not valid for member '" + member + "'"); } } return o; } /** * Resolves calls to the PROPERTIES MDX function. */ private static class ResolverImpl extends ResolverBase { private ResolverImpl() { super( "Properties", ".Properties()", "Returns the value of a member property.", Syntax.Method); } public FunDef resolve( Exp[] args, Validator validator, List conversions) { final int[] argTypes = new int[]{Category.Member, Category.String}; final Exp propertyNameExp = args[1]; final Exp memberExp = args[0]; if ((args.length != 2) || (memberExp.getCategory() != Category.Member) || (propertyNameExp.getCategory() != Category.String)) { return null; } 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 { switch (property.getType()) { case TYPE_BOOLEAN: return Category.Logical; case TYPE_NUMERIC: return Category.Numeric; case TYPE_STRING: return Category.String; case TYPE_DATE: case TYPE_TIME: case TYPE_TIMESTAMP: return Category.DateTime; default: throw Util.badValue(property.getType()); } } } public boolean requiresExpression(int k) { return true; } } } // End PropertiesFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/AvgFunDef.java0000644000175000017500000000377711735330606022761 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractDoubleCalc; import mondrian.calc.impl.ValueCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the Avg MDX function. * * @author jhyde * @since Mar 23, 2006 */ class AvgFunDef extends AbstractAggregateFunDef { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "Avg", "Avg([, ])", "Returns the average value of a numeric expression evaluated over a set.", new String[]{"fnx", "fnxn"}, AvgFunDef.class); public AvgFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0)); final Calc calc = call.getArgCount() > 1 ? compiler.compileScalar(call.getArg(1), true) : new ValueCalc(call); return new AbstractDoubleCalc(call, new Calc[]{listCalc, calc}) { public double evaluateDouble(Evaluator evaluator) { TupleList memberList = evaluateCurrentList(listCalc, evaluator); final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final double avg = (Double) avg( evaluator, memberList, calc); evaluator.restore(savepoint); return avg; } public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } }; } } // End AvgFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/PeriodsToDateFunDef.java0000644000175000017500000001040411735330606024733 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.calc.impl.UnaryTupleList; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.*; import mondrian.rolap.RolapCube; import mondrian.rolap.RolapHierarchy; /** * Definition of the PeriodsToDate MDX function. * * @author jhyde * @since Mar 23, 2006 */ class PeriodsToDateFunDef extends FunDefBase { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "PeriodsToDate", "PeriodsToDate([[, ]])", "Returns a set of periods (members) from a specified level starting with the first period and ending with a specified member.", new String[]{"fx", "fxl", "fxlm"}, PeriodsToDateFunDef.class); public PeriodsToDateFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Type getResultType(Validator validator, Exp[] args) { if (args.length == 0) { // With no args, the default implementation cannot // guess the hierarchy. RolapHierarchy defaultTimeHierarchy = ((RolapCube) validator.getQuery().getCube()).getTimeHierarchy( getName()); return new SetType(MemberType.forHierarchy(defaultTimeHierarchy)); } if (args.length >= 2) { Type hierarchyType = args[0].getType(); MemberType memberType = (MemberType) args[1].getType(); if (memberType.getHierarchy() != null && hierarchyType.getHierarchy() != null && memberType.getHierarchy() != hierarchyType.getHierarchy()) { throw Util.newError( "Type mismatch: member must belong to hierarchy " + hierarchyType.getHierarchy().getUniqueName()); } } // If we have at least one arg, it's a level which will // tell us the type. return super.getResultType(validator, args); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final LevelCalc levelCalc = call.getArgCount() > 0 ? compiler.compileLevel(call.getArg(0)) : null; final MemberCalc memberCalc = call.getArgCount() > 1 ? compiler.compileMember(call.getArg(1)) : null; final RolapHierarchy timeHierarchy = levelCalc == null ? ((RolapCube) compiler.getEvaluator().getCube()).getTimeHierarchy( getName()) : null; return new AbstractListCalc(call, new Calc[] {levelCalc, memberCalc}) { public TupleList evaluateList(Evaluator evaluator) { final Member member; final Level level; if (levelCalc == null) { member = evaluator.getContext(timeHierarchy); level = member.getLevel().getParentLevel(); } else { level = levelCalc.evaluateLevel(evaluator); if (memberCalc == null) { member = evaluator.getContext(level.getHierarchy()); } else { member = memberCalc.evaluateMember(evaluator); } } return new UnaryTupleList( periodsToDate(evaluator, level, member)); } public boolean dependsOn(Hierarchy hierarchy) { if (super.dependsOn(hierarchy)) { return true; } if (memberCalc != null) { return false; } else if (levelCalc != null) { return levelCalc.getType().usesHierarchy(hierarchy, true); } else { return hierarchy == timeHierarchy; } } }; } } // End PeriodsToDateFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/StrToSetFunDef.java0000644000175000017500000001445211735330606023763 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.calc.impl.UnaryTupleList; import mondrian.mdx.*; import mondrian.olap.*; import mondrian.olap.type.*; import mondrian.resource.MondrianResource; import java.util.ArrayList; import java.util.List; /** * Definition of the StrToSet MDX builtin function. * * @author jhyde * @since Mar 23, 2006 */ class StrToSetFunDef extends FunDefBase { static final ResolverImpl Resolver = new ResolverImpl(); private StrToSetFunDef(int[] parameterTypes) { super( "StrToSet", " StrToSet([, ...])", "Constructs a set from a string expression.", Syntax.Function, Category.Set, parameterTypes); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final StringCalc stringCalc = compiler.compileString(call.getArg(0)); SetType type = (SetType) call.getType(); Type elementType = type.getElementType(); if (elementType instanceof MemberType) { final Hierarchy hierarchy = elementType.getHierarchy(); return new AbstractListCalc(call, new Calc[] {stringCalc}) { public TupleList evaluateList(Evaluator evaluator) { String string = stringCalc.evaluateString(evaluator); if (string == null) { throw newEvalException( MondrianResource.instance().NullValue.ex()); } return new UnaryTupleList( parseMemberList(evaluator, string, hierarchy)); } }; } else { TupleType tupleType = (TupleType) elementType; final List hierarchyList = tupleType.getHierarchies(); return new AbstractListCalc(call, new Calc[] {stringCalc}) { public TupleList evaluateList(Evaluator evaluator) { String string = stringCalc.evaluateString(evaluator); if (string == null) { throw newEvalException( MondrianResource.instance().NullValue.ex()); } return parseTupleList(evaluator, string, hierarchyList); } }; } } public Exp createCall(Validator validator, Exp[] args) { final int argCount = args.length; if (argCount <= 1) { throw MondrianResource.instance().MdxFuncArgumentsNum.ex(getName()); } for (int i = 1; i < argCount; i++) { final Exp arg = args[i]; if (arg instanceof DimensionExpr) { // if arg is a dimension, switch to dimension's default // hierarchy DimensionExpr dimensionExpr = (DimensionExpr) arg; Dimension dimension = dimensionExpr.getDimension(); args[i] = new HierarchyExpr(dimension.getHierarchy()); } else if (arg instanceof HierarchyExpr) { // nothing } else { throw MondrianResource.instance().MdxFuncNotHier.ex( i + 1, getName()); } } return super.createCall(validator, args); } public Type getResultType(Validator validator, Exp[] args) { switch (args.length) { case 1: // This is a call to the standard version of StrToSet, // which doesn't give us any hints about type. return new SetType(null); case 2: { final Type argType = args[1].getType(); return new SetType( new MemberType( argType.getDimension(), argType.getHierarchy(), argType.getLevel(), null)); } default: { // This is a call to Mondrian's extended version of // StrToSet, of the form // StrToSet(s, , ... , ) // // The result is a set of tuples // (, ... , ) final List list = new ArrayList(); for (int i = 1; i < args.length; i++) { Exp arg = args[i]; final Type argType = arg.getType(); list.add(TypeUtil.toMemberType(argType)); } final MemberType[] types = list.toArray(new MemberType[list.size()]); TupleType.checkHierarchies(types); return new SetType(new TupleType(types)); } } } private static class ResolverImpl extends ResolverBase { ResolverImpl() { super( "StrToSet", "StrToSet()", "Constructs a set from a string expression.", Syntax.Function); } public FunDef resolve( Exp[] args, Validator validator, List conversions) { if (args.length < 1) { return null; } Type type = args[0].getType(); if (!(type instanceof StringType) && !(type instanceof NullType)) { return null; } for (int i = 1; i < args.length; i++) { Exp exp = args[i]; if (!(exp instanceof DimensionExpr || exp instanceof HierarchyExpr)) { return null; } } int[] argTypes = new int[args.length]; argTypes[0] = Category.String; for (int i = 1; i < argTypes.length; i++) { argTypes[i] = Category.Hierarchy; } return new StrToSetFunDef(argTypes); } public FunDef getFunDef() { return new StrToSetFunDef(new int[] {Category.String}); } } } // End StrToSetFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/HierarchizeFunDef.java0000644000175000017500000000326011735330606024464 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.Evaluator; import mondrian.olap.FunDef; /** * Definition of the Hierarchize MDX function. * * @author jhyde * @since Mar 23, 2006 */ class HierarchizeFunDef extends FunDefBase { static final String[] prePost = {"PRE", "POST"}; static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "Hierarchize", "Hierarchize([, POST])", "Orders the members of a set in a hierarchy.", new String[] {"fxx", "fxxy"}, HierarchizeFunDef.class, prePost); public HierarchizeFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc = compiler.compileList(call.getArg(0), true); String order = getLiteralArg(call, 1, "PRE", prePost); final boolean post = order.equals("POST"); return new AbstractListCalc(call, new Calc[] {listCalc}) { public TupleList evaluateList(Evaluator evaluator) { TupleList list = listCalc.evaluateList(evaluator); return hierarchizeTupleList(list, post); } }; } } // End HierarchizeFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/MemberOrderKeyFunDef.java0000644000175000017500000000365211735330606025110 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.Evaluator; import mondrian.olap.Exp; /** * Definition of the <Member>.OrderKey MDX builtin * function. * *

    Syntax: *

    <Member>.OrderKey
    * * @author kvu * @since Nov 10, 2008 */ public final class MemberOrderKeyFunDef extends FunDefBase { static final MemberOrderKeyFunDef instance = new MemberOrderKeyFunDef(); /** * Creates the singleton MemberOrderKeyFunDef. */ private MemberOrderKeyFunDef() { super( "OrderKey", "Returns the member order key.", "pvm"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new CalcImpl(call, memberCalc); } public static class CalcImpl extends AbstractCalc { private final MemberCalc memberCalc; /** * Creates a CalcImpl. * * @param exp Source expression * @param memberCalc Compiled expression to calculate member */ public CalcImpl(Exp exp, MemberCalc memberCalc) { super(exp, new Calc[] {memberCalc}); this.memberCalc = memberCalc; } public OrderKey evaluate(Evaluator evaluator) { return new OrderKey(memberCalc.evaluateMember(evaluator)); } protected String getName() { return "OrderKey"; } } } // End MemberOrderKeyFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/ExtractFunDef.java0000644000175000017500000001715311735330606023647 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractListCalc; import mondrian.mdx.*; import mondrian.olap.*; import mondrian.olap.type.*; import java.util.*; /** * Definition of the Extract MDX function. * *

    Syntax: *

    Extract(<Set>, <Hierarchy>[, * <Hierarchy>...])
    * * @author jhyde * @since Jun 10, 2007 */ class ExtractFunDef extends FunDefBase { static final ResolverBase Resolver = new ResolverBase( "Extract", "Extract(, [, ...])", "Returns a set of tuples from extracted hierarchy elements. The opposite of Crossjoin.", Syntax.Function) { public FunDef resolve( Exp[] args, Validator validator, List conversions) { if (args.length < 2) { return null; } if (!validator.canConvert(0, args[0], Category.Set, conversions)) { return null; } for (int i = 1; i < args.length; ++i) { if (!validator.canConvert( 0, args[i], Category.Hierarchy, conversions)) { return null; } } // Find the dimensionality of the set expression. // Form a list of ordinals of the hierarchies being extracted. // For example, in // Extract(X.Members * Y.Members * Z.Members, Z, X) // the hierarchy ordinals are X=0, Y=1, Z=2, and the extracted // ordinals are {2, 0}. // // Each hierarchy extracted must exist in the LHS, // and no hierarchy may be extracted more than once. List extractedOrdinals = new ArrayList(); final List extractedHierarchies = new ArrayList(); findExtractedHierarchies( args, extractedHierarchies, extractedOrdinals); int[] parameterTypes = new int[args.length]; parameterTypes[0] = Category.Set; Arrays.fill( parameterTypes, 1, parameterTypes.length, Category.Hierarchy); return new ExtractFunDef(this, Category.Set, parameterTypes); } }; private ExtractFunDef( Resolver resolver, int returnType, int[] parameterTypes) { super(resolver, returnType, parameterTypes); } public Type getResultType(Validator validator, Exp[] args) { final List extractedHierarchies = new ArrayList(); final List extractedOrdinals = new ArrayList(); findExtractedHierarchies(args, extractedHierarchies, extractedOrdinals); if (extractedHierarchies.size() == 1) { return new SetType( MemberType.forHierarchy( extractedHierarchies.get(0))); } else { List typeList = new ArrayList(); for (Hierarchy extractedHierarchy : extractedHierarchies) { typeList.add( MemberType.forHierarchy( extractedHierarchy)); } return new SetType( new TupleType( typeList.toArray(new Type[typeList.size()]))); } } private static void findExtractedHierarchies( Exp[] args, List extractedHierarchies, List extractedOrdinals) { SetType type = (SetType) args[0].getType(); final List hierarchies; if (type.getElementType() instanceof TupleType) { hierarchies = ((TupleType) type.getElementType()).getHierarchies(); } else { hierarchies = Collections.singletonList(type.getHierarchy()); } for (Hierarchy hierarchy : hierarchies) { if (hierarchy == null) { throw new RuntimeException( "hierarchy of argument not known"); } } for (int i = 1; i < args.length; i++) { Exp arg = args[i]; Hierarchy extractedHierarchy = null; if (arg instanceof HierarchyExpr) { HierarchyExpr hierarchyExpr = (HierarchyExpr) arg; extractedHierarchy = hierarchyExpr.getHierarchy(); } else if (arg instanceof DimensionExpr) { DimensionExpr dimensionExpr = (DimensionExpr) arg; extractedHierarchy = dimensionExpr.getDimension().getHierarchy(); } if (extractedHierarchy == null) { throw new RuntimeException("not a constant hierarchy: " + arg); } int ordinal = hierarchies.indexOf(extractedHierarchy); if (ordinal == -1) { throw new RuntimeException( "hierarchy " + extractedHierarchy.getUniqueName() + " is not a hierarchy of the expression " + args[0]); } if (extractedOrdinals.indexOf(ordinal) >= 0) { throw new RuntimeException( "hierarchy " + extractedHierarchy.getUniqueName() + " is extracted more than once"); } extractedOrdinals.add(ordinal); extractedHierarchies.add(extractedHierarchy); } } private static int[] toIntArray(List integerList) { final int[] ints = new int[integerList.size()]; for (int i = 0; i < ints.length; i++) { ints[i] = integerList.get(i); } return ints; } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { List extractedHierarchyList = new ArrayList(); List extractedOrdinalList = new ArrayList(); findExtractedHierarchies( call.getArgs(), extractedHierarchyList, extractedOrdinalList); Util.assertTrue( extractedOrdinalList.size() == extractedHierarchyList.size()); Exp arg = call.getArg(0); final ListCalc listCalc = compiler.compileList(arg, false); int inArity = arg.getType().getArity(); final int outArity = extractedOrdinalList.size(); if (inArity == 1) { // LHS is a set of members, RHS is the same hierarchy. Extract boils // down to eliminating duplicate members. Util.assertTrue(outArity == 1); return new DistinctFunDef.CalcImpl(call, listCalc); } final int[] extractedOrdinals = toIntArray(extractedOrdinalList); return new AbstractListCalc(call, new Calc[]{listCalc}) { public TupleList evaluateList(Evaluator evaluator) { TupleList result = TupleCollections.createList(outArity); TupleList list = listCalc.evaluateList(evaluator); Set> emittedTuples = new HashSet>(); for (List members : list.project(extractedOrdinals)) { if (emittedTuples.add(members)) { result.add(members); } } return result; } }; } } // End ExtractFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/FormatFunDef.java0000644000175000017500000000504011735330606023455 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractStringCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.util.Format; import java.util.Locale; /** * Definition of the Format MDX function. * * @author jhyde * @since Mar 23, 2006 */ class FormatFunDef extends FunDefBase { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "Format", "Format(, )", "Formats a number or date to a string.", new String[] { "fSmS", "fSnS", "fSDS" }, FormatFunDef.class); public FormatFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Exp[] args = call.getArgs(); final Calc calc = compiler.compileScalar(call.getArg(0), true); final Locale locale = compiler.getEvaluator().getConnectionLocale(); if (args[1] instanceof Literal) { // Constant string expression: optimize by // compiling format string. String formatString = (String) ((Literal) args[1]).getValue(); final Format format = new Format(formatString, locale); return new AbstractStringCalc(call, new Calc[] {calc}) { public String evaluateString(Evaluator evaluator) { final Object o = calc.evaluate(evaluator); return format.format(o); } }; } else { // Variable string expression final StringCalc stringCalc = compiler.compileString(call.getArg(1)); return new AbstractStringCalc(call, new Calc[] {calc, stringCalc}) { public String evaluateString(Evaluator evaluator) { final Object o = calc.evaluate(evaluator); final String formatString = stringCalc.evaluateString(evaluator); final Format format = new Format(formatString, locale); return format.format(o); } }; } } } // End FormatFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/IsFunDef.java0000644000175000017500000000446211735330606022607 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractBooleanCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the IS MDX function. * * @see IsNullFunDef * @author jhyde * @since Mar 23, 2006 */ class IsFunDef extends FunDefBase { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "IS", " IS ", "Returns whether two objects are the same", new String[] {"ibmm", "ibll", "ibhh", "ibdd", "ibtt"}, IsFunDef.class); public IsFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final int category = call.getArg(0).getCategory(); switch (category) { case Category.Tuple: final TupleCalc tupleCalc0 = compiler.compileTuple(call.getArg(0)); final TupleCalc tupleCalc1 = compiler.compileTuple(call.getArg(1)); return new AbstractBooleanCalc( call, new Calc[] {tupleCalc0, tupleCalc1}) { public boolean evaluateBoolean(Evaluator evaluator) { Member[] o0 = tupleCalc0.evaluateTuple(evaluator); Member[] o1 = tupleCalc1.evaluateTuple(evaluator); return equalTuple(o0, o1); } }; default: assert category == call.getArg(1).getCategory(); final Calc calc0 = compiler.compile(call.getArg(0)); final Calc calc1 = compiler.compile(call.getArg(1)); return new AbstractBooleanCalc(call, new Calc[] {calc0, calc1}) { public boolean evaluateBoolean(Evaluator evaluator) { Object o0 = calc0.evaluate(evaluator); Object o1 = calc1.evaluate(evaluator); return o0.equals(o1); } }; } } } // End IsFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/CrossJoinFunDef.java0000644000175000017500000011705511735330606024150 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.mdx.*; import mondrian.olap.*; import mondrian.olap.type.*; import mondrian.rolap.RolapEvaluator; import mondrian.util.CartesianProductList; import java.util.*; /** * Definition of the CrossJoin MDX function. * * @author jhyde * @since Mar 23, 2006 */ public class CrossJoinFunDef extends FunDefBase { static final ReflectiveMultiResolver Resolver = new ReflectiveMultiResolver( "Crossjoin", "Crossjoin(, )", "Returns the cross product of two sets.", new String[]{"fxxx"}, CrossJoinFunDef.class); static final StarCrossJoinResolver StarResolver = new StarCrossJoinResolver(); private static int counterTag = 0; // used to tell the difference between crossjoin expressions. private final int ctag = counterTag++; public CrossJoinFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Type getResultType(Validator validator, Exp[] args) { // CROSSJOIN(,) has type [Hie1] x [Hie2]. List list = new ArrayList(); for (Exp arg : args) { final Type type = arg.getType(); if (type instanceof SetType) { addTypes(type, list); } else if (getName().equals("*")) { // The "*" form of CrossJoin is lenient: args can be either // members/tuples or sets. addTypes(type, list); } else { throw Util.newInternal("arg to crossjoin must be a set"); } } final MemberType[] types = list.toArray(new MemberType[list.size()]); TupleType.checkHierarchies(types); final TupleType tupleType = new TupleType(types); return new SetType(tupleType); } /** * Adds a type to a list of types. If type is a {@link TupleType}, does so * recursively. * * @param type Type to add to list * @param list List of types to add to */ private static void addTypes(final Type type, List list) { if (type instanceof SetType) { SetType setType = (SetType) type; addTypes(setType.getElementType(), list); } else if (type instanceof TupleType) { TupleType tupleType = (TupleType) type; for (Type elementType : tupleType.elementTypes) { addTypes(elementType, list); } } else if (type instanceof MemberType) { list.add((MemberType) type); } else { throw Util.newInternal("Unexpected type: " + type); } } public Calc compileCall(final ResolvedFunCall call, ExpCompiler compiler) { // What is the desired return type? for (ResultStyle r : compiler.getAcceptableResultStyles()) { switch (r) { case ITERABLE: case ANY: // Consumer wants ITERABLE or ANY return compileCallIterable(call, compiler); case LIST: // Consumer wants (immutable) LIST return compileCallImmutableList(call, compiler); case MUTABLE_LIST: // Consumer MUTABLE_LIST return compileCallMutableList(call, compiler); } } throw ResultStyleException.generate( ResultStyle.ITERABLE_LIST_MUTABLELIST_ANY, compiler.getAcceptableResultStyles()); } /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Iterable /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// protected IterCalc compileCallIterable( final ResolvedFunCall call, ExpCompiler compiler) { final Calc calc1 = toIter(compiler, call.getArg(0)); final Calc calc2 = toIter(compiler, call.getArg(1)); Calc[] calcs = new Calc[] {calc1, calc2}; // The Calcs, 1 and 2, can be of type: Member or Member[] and // of ResultStyle: ITERABLE, LIST or MUTABLE_LIST, but // LIST and MUTABLE_LIST are treated the same; so // there are 16 possible combinations - sweet. // Check returned calc ResultStyles checkIterListResultStyles(calc1); checkIterListResultStyles(calc2); return new CrossJoinIterCalc(call, calcs); } private Calc toIter(ExpCompiler compiler, final Exp exp) { // Want iterable, immutable list or mutable list in that order // It is assumed that an immutable list is easier to get than // a mutable list. final Type type = exp.getType(); if (type instanceof SetType) { // this can return an IterCalc or ListCalc return compiler.compileAs( exp, null, ResultStyle.ITERABLE_LIST_MUTABLELIST); } else { // this always returns an IterCalc return new SetFunDef.ExprIterCalc( new DummyExp(new SetType(type)), new Exp[] {exp}, compiler, ResultStyle.ITERABLE_LIST_MUTABLELIST); } } class CrossJoinIterCalc extends AbstractIterCalc { CrossJoinIterCalc(ResolvedFunCall call, Calc[] calcs) { super(call, calcs); } public TupleIterable evaluateIterable(Evaluator evaluator) { ResolvedFunCall call = (ResolvedFunCall) exp; // Use a native evaluator, if more efficient. // TODO: Figure this out at compile time. SchemaReader schemaReader = evaluator.getSchemaReader(); NativeEvaluator nativeEvaluator = schemaReader.getNativeSetEvaluator( call.getFunDef(), call.getArgs(), evaluator, this); if (nativeEvaluator != null) { return (TupleIterable) nativeEvaluator.execute(ResultStyle.ITERABLE); } Calc[] calcs = getCalcs(); IterCalc calc1 = (IterCalc) calcs[0]; IterCalc calc2 = (IterCalc) calcs[1]; TupleIterable o1 = calc1.evaluateIterable(evaluator); if (o1 instanceof TupleList) { TupleList l1 = (TupleList) o1; l1 = nonEmptyOptimizeList(evaluator, l1, call); if (l1.isEmpty()) { return TupleCollections.emptyList(getType().getArity()); } o1 = l1; } TupleIterable o2 = calc2.evaluateIterable(evaluator); if (o2 instanceof TupleList) { TupleList l2 = (TupleList) o2; l2 = nonEmptyOptimizeList(evaluator, l2, call); if (l2.isEmpty()) { return TupleCollections.emptyList(getType().getArity()); } o2 = l2; } return makeIterable(o1, o2); } protected TupleIterable makeIterable( final TupleIterable it1, final TupleIterable it2) { // There is no knowledge about how large either it1 ore it2 // are or how many null members they might have, so all // one can do is iterate across them: // iterate across it1 and for each member iterate across it2 return new AbstractTupleIterable(it1.getArity() + it2.getArity()) { public TupleCursor tupleCursor() { return new AbstractTupleCursor(getArity()) { final TupleCursor i1 = it1.tupleCursor(); final int arity1 = i1.getArity(); TupleCursor i2 = TupleCollections.emptyList(1).tupleCursor(); final Member[] members = new Member[arity]; public boolean forward() { if (i2.forward()) { return true; } while (i1.forward()) { i2 = it2.tupleCursor(); if (i2.forward()) { return true; } } return false; } public List current() { i1.currentToArray(members, 0); i2.currentToArray(members, arity1); return Util.flatList(members); } @Override public Member member(int column) { if (column < arity1) { return i1.member(column); } else { return i2.member(column - arity1); } } @Override public void setContext(Evaluator evaluator) { i1.setContext(evaluator); i2.setContext(evaluator); } @Override public void currentToArray( Member[] members, int offset) { i1.currentToArray(members, offset); i2.currentToArray(members, offset + arity1); } }; } }; } } /////////////////////////////////////////////////////////////////////////// // Immutable List /////////////////////////////////////////////////////////////////////////// protected ListCalc compileCallImmutableList( final ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc1 = toList(compiler, call.getArg(0)); final ListCalc listCalc2 = toList(compiler, call.getArg(1)); Calc[] calcs = new Calc[] {listCalc1, listCalc2}; // The Calcs, 1 and 2, can be of type: Member or Member[] and // of ResultStyle: LIST or MUTABLE_LIST. // Since we want an immutable list as the result, it does not // matter whether the Calc list are of type // LIST and MUTABLE_LIST - they are treated the same; so // there are 4 possible combinations - even sweeter. // Check returned calc ResultStyles checkListResultStyles(listCalc1); checkListResultStyles(listCalc2); return new ImmutableListCalc(call, calcs); } /** * Compiles an expression to list (or mutable list) format. Never returns * null. * * @param compiler Compiler * @param exp Expression * @return Compiled expression that yields a list or mutable list */ private ListCalc toList(ExpCompiler compiler, final Exp exp) { // Want immutable list or mutable list in that order // It is assumed that an immutable list is easier to get than // a mutable list. final Type type = exp.getType(); if (type instanceof SetType) { final Calc calc = compiler.compileAs( exp, null, ResultStyle.LIST_MUTABLELIST); if (calc == null) { return compiler.compileList(exp, false); } return (ListCalc) calc; } else { return new SetFunDef.SetListCalc( new DummyExp(new SetType(type)), new Exp[] {exp}, compiler, ResultStyle.LIST_MUTABLELIST); } } abstract class BaseListCalc extends AbstractListCalc { protected BaseListCalc( ResolvedFunCall call, Calc[] calcs, boolean mutable) { super(call, calcs, mutable); } public TupleList evaluateList(Evaluator evaluator) { ResolvedFunCall call = (ResolvedFunCall) exp; // Use a native evaluator, if more efficient. // TODO: Figure this out at compile time. SchemaReader schemaReader = evaluator.getSchemaReader(); NativeEvaluator nativeEvaluator = schemaReader.getNativeSetEvaluator( call.getFunDef(), call.getArgs(), evaluator, this); if (nativeEvaluator != null) { return (TupleList) nativeEvaluator.execute(ResultStyle.LIST); } Calc[] calcs = getCalcs(); ListCalc listCalc1 = (ListCalc) calcs[0]; ListCalc listCalc2 = (ListCalc) calcs[1]; TupleList l1 = listCalc1.evaluateList(evaluator); TupleList l2 = listCalc2.evaluateList(evaluator); l1 = nonEmptyOptimizeList(evaluator, l1, call); if (l1.isEmpty()) { return TupleCollections.emptyList( l1.getArity() + l2.getArity()); } l2 = nonEmptyOptimizeList(evaluator, l2, call); if (l2.isEmpty()) { return TupleCollections.emptyList( l1.getArity() + l2.getArity()); } return makeList(l1, l2); } protected abstract TupleList makeList(TupleList l1, TupleList l2); } class ImmutableListCalc extends BaseListCalc { ImmutableListCalc( ResolvedFunCall call, Calc[] calcs) { super(call, calcs, false); } protected TupleList makeList(final TupleList l1, final TupleList l2) { final int arity = l1.getArity() + l2.getArity(); return new DelegatingTupleList( arity, new AbstractList>() { final List>> lists = Arrays.>>asList( l1, l2); final Member[] members = new Member[arity]; final CartesianProductList cartesianProductList = new CartesianProductList>( lists); @Override public List get(int index) { cartesianProductList.getIntoArray(index, members); return Util.flatList(members); } @Override public int size() { return cartesianProductList.size(); } }); } } protected ListCalc compileCallMutableList( final ResolvedFunCall call, ExpCompiler compiler) { final ListCalc listCalc1 = toList(compiler, call.getArg(0)); final ListCalc listCalc2 = toList(compiler, call.getArg(1)); Calc[] calcs = new Calc[] {listCalc1, listCalc2}; // The Calcs, 1 and 2, can be of type: Member or Member[] and // of ResultStyle: LIST or MUTABLE_LIST. // Since we want an mutable list as the result, it does not // matter whether the Calc list are of type // LIST and MUTABLE_LIST - they are treated the same, // regardless of type, one must materialize the result list; so // there are 4 possible combinations - even sweeter. // Check returned calc ResultStyles checkListResultStyles(listCalc1); checkListResultStyles(listCalc2); return new MutableListCalc(call, calcs); } class MutableListCalc extends BaseListCalc { MutableListCalc(ResolvedFunCall call, Calc[] calcs) { super(call, calcs, true); } @SuppressWarnings({"unchecked"}) protected TupleList makeList(final TupleList l1, final TupleList l2) { final int arity = l1.getArity() + l2.getArity(); final List members = new ArrayList(arity * l1.size() * l2.size()); for (List ma1 : l1) { for (List ma2 : l2) { members.addAll(ma1); members.addAll(ma2); } } return new ListTupleList(arity, members); } } protected TupleList nonEmptyOptimizeList( Evaluator evaluator, TupleList list, ResolvedFunCall call) { int opSize = MondrianProperties.instance().CrossJoinOptimizerSize.get(); if (list.isEmpty()) { return list; } try { final Object o = list.get(0); if (o instanceof Member) { // Cannot optimize high cardinality dimensions if (((Member)o).getDimension().isHighCardinality()) { return list; } } } catch (IndexOutOfBoundsException ioobe) { return TupleCollections.emptyList(list.getArity()); } int size = list.size(); if (size > opSize && evaluator.isNonEmpty()) { // instead of overflow exception try to further // optimize nonempty(crossjoin(a,b)) == // nonempty(crossjoin(nonempty(a),nonempty(b)) final int missCount = evaluator.getMissCount(); list = nonEmptyList(evaluator, list, call); size = list.size(); // list may be empty after nonEmpty optimization if (size == 0) { return TupleCollections.emptyList(list.getArity()); } final int missCount2 = evaluator.getMissCount(); final int puntMissCountListSize = 1000; if (missCount2 > missCount && size > puntMissCountListSize) { // We've hit some cells which are not in the cache. They // registered as non-empty, but we won't really know until // we've populated the cache. The cartesian product is still // huge, so let's quit now, and try again after the cache // has been loaded. // Return an empty list short circuits higher level // evaluation poping one all the way to the top. return TupleCollections.emptyList(list.getArity()); } } return list; } public static TupleList mutableCrossJoin( TupleList list1, TupleList list2) { return mutableCrossJoin(Arrays.asList(list1, list2)); } public static TupleList mutableCrossJoin( List lists) { long size = 1; int arity = 0; for (TupleList list : lists) { size *= (long) list.size(); arity += list.getArity(); } if (size == 0L) { return TupleCollections.emptyList(arity); } // Optimize nonempty(crossjoin(a,b)) == // nonempty(crossjoin(nonempty(a),nonempty(b)) // FIXME: If we're going to apply a NON EMPTY constraint later, it's // possible that the ultimate result will be much smaller. Util.checkCJResultLimit(size); // Now we can safely cast size to an integer. It still might be very // large - which means we're allocating a huge array which we might // pare down later by applying NON EMPTY constraints - which is a // concern. List result = new ArrayList((int) size * arity); final Member[] partialArray = new Member[arity]; final List partial = Arrays.asList(partialArray); cartesianProductRecurse(0, lists, partial, partialArray, 0, result); return new ListTupleList(arity, result); } private static void cartesianProductRecurse( int i, List lists, List partial, Member[] partialArray, int partialSize, List result) { final TupleList tupleList = lists.get(i); final int partialSizeNext = partialSize + tupleList.getArity(); final int iNext = i + 1; final TupleCursor cursor = tupleList.tupleCursor(); while (cursor.forward()) { cursor.currentToArray(partialArray, partialSize); if (i == lists.size() - 1) { result.addAll(partial); } else { cartesianProductRecurse( iNext, lists, partial, partialArray, partialSizeNext, result); } } } /** * Visitor class used to locate a resolved function call within an * expression */ private static class ResolvedFunCallFinder extends MdxVisitorImpl { private final ResolvedFunCall call; public boolean found; private final Set activeMembers = new HashSet(); public ResolvedFunCallFinder(ResolvedFunCall call) { this.call = call; found = false; } public Object visit(ResolvedFunCall funCall) { if (funCall == call) { found = true; } return null; } public Object visit(MemberExpr memberExpr) { Member member = memberExpr.getMember(); if (member.isCalculated()) { if (activeMembers.add(member)) { Exp memberExp = member.getExpression(); memberExp.accept(this); activeMembers.remove(member); } } return null; } } /** * Traverses the function call tree of * the non empty crossjoin function and populates the queryMeasureSet * with base measures */ private static class MeasureVisitor extends MdxVisitorImpl { private final Set queryMeasureSet; private final ResolvedFunCallFinder finder; private final Set activeMeasures = new HashSet(); /** * Creates a MeasureVisitor. * * @param queryMeasureSet Set of measures in query * * @param crossJoinCall Measures referencing this call should be * excluded from the list of measures found */ MeasureVisitor( Set queryMeasureSet, ResolvedFunCall crossJoinCall) { this.queryMeasureSet = queryMeasureSet; this.finder = new ResolvedFunCallFinder(crossJoinCall); } public Object visit(ParameterExpr parameterExpr) { final Parameter parameter = parameterExpr.getParameter(); final Type type = parameter.getType(); if (type instanceof mondrian.olap.type.MemberType) { final Object value = parameter.getValue(); if (value instanceof Member) { final Member member = (Member) value; process(member); } } return null; } public Object visit(MemberExpr memberExpr) { Member member = memberExpr.getMember(); process(member); return null; } private void process(final Member member) { if (member.isMeasure()) { if (member.isCalculated()) { if (activeMeasures.add(member)) { Exp exp = member.getExpression(); finder.found = false; exp.accept(finder); if (! finder.found) { exp.accept(this); } activeMeasures.remove(member); } } else { queryMeasureSet.add(member); } } } } /** * This is the entry point to the crossjoin non-empty optimizer code. * *

    What one wants to determine is for each individual Member of the input * parameter list, a 'List-Member', whether across a slice there is any * data. * *

    But what data? * *

    For Members other than those in the list, the 'non-List-Members', * one wants to consider * all data across the scope of these other Members. For instance, if * Time is not a List-Member, then one wants to consider data * across All Time. Or, if Customer is not a List-Member, then * look at data across All Customers. The theory here, is if there * is no data for a particular Member of the list where all other * Members not part of the list are span their complete hierarchy, then * there is certainly no data for Members of that Hierarchy at a * more specific Level (more on this below). * *

    When a Member that is a non-List-Member is part of a Hierarchy * that has an * All Member (hasAll="true"), then its very easy to make sure that * the All Member is used during the optimization. * If a non-List-Member is part of a Hierarchy that does not have * an All Member, then one must, in fact, iterate over all top-level * Members of the Hierarchy!!! - otherwise a List-Member might * be excluded because the optimization code was not looking everywhere. * *

    Concerning default Members for those Hierarchies for the * non-List-Members, ignore them. What is wanted is either the * All Member or one must iterate across all top-level Members, what * happens to be the default Member of the Hierarchy is of no relevant. * *

    The Measures Hierarchy has special considerations. First, there is * no All Measure. But, certainly one need only involve Measures * that are actually in the query... yes and no. For Calculated Measures * one must also get all of the non-Calculated Measures that make up * each Calculated Measure. Thus, one ends up iterating across all * Calculated and non-Calculated Measures that are explicitly * mentioned in the query as well as all Calculated and non-Calculated * Measures that are used to define the Calculated Measures in * the query. Why all of these? because this represents the total * scope of possible Measures that might yield a non-null value * for the List-Members and that is what we what to find. It might * be a super set, but thats ok; we just do not want to miss anything. * *

    For other Members, the default Member is used, but for Measures one * should look for that data for all Measures associated with the query, not * just one Measure. For a dense dataset this may not be a problem or even * apparent, but for a sparse dataset, the first Measure may, in fact, have * not data but other Measures associated with the query might. * Hence, the solution here is to identify all Measures associated with the * query and then for each Member of the list, determine if there is any * data iterating across all Measures until non-null data is found or the * end of the Measures is reached. * *

    This is a non-optimistic implementation. This means that an * element of the input parameter List is only not included in the * returned result List if for no combination of Measures, non-All * Members (for Hierarchies that have no All Members) and evaluator * default Members did the element evaluate to non-null. * * @param evaluator Evaluator * * @param list List of members or tuples * * @param call Calling ResolvedFunCall used to determine what Measures * to use * * @return List of elements from the input parameter list that have * evaluated to non-null. */ protected TupleList nonEmptyList( Evaluator evaluator, TupleList list, ResolvedFunCall call) { if (list.isEmpty()) { return list; } TupleList result = TupleCollections.createList( list.getArity(), (list.size() + 2) >> 1); // Get all of the Measures final Query query = evaluator.getQuery(); final String measureSetKey = "MEASURE_SET-" + ctag; Set measureSet = Util.cast((Set) query.getEvalCache(measureSetKey)); // If not in query cache, then create and place into cache. // This information is used for each iteration so it makes // sense to create and cache it. if (measureSet == null) { measureSet = new HashSet(); Set queryMeasureSet = query.getMeasuresMembers(); MeasureVisitor visitor = new MeasureVisitor(measureSet, call); for (Member m : queryMeasureSet) { if (m.isCalculated()) { Exp exp = m.getExpression(); exp.accept(visitor); } else { measureSet.add(m); } } Formula[] formula = query.getFormulas(); if (formula != null) { for (Formula f : formula) { f.accept(visitor); } } query.putEvalCache(measureSetKey, measureSet); } final String allMemberListKey = "ALL_MEMBER_LIST-" + ctag; List allMemberList = Util.cast((List) query.getEvalCache(allMemberListKey)); final String nonAllMembersKey = "NON_ALL_MEMBERS-" + ctag; Member[][] nonAllMembers = (Member[][]) query.getEvalCache(nonAllMembersKey); if (nonAllMembers == null) { // // Get all of the All Members and those Hierarchies that // do not have All Members. // Member[] evalMembers = evaluator.getMembers().clone(); List listMembers = list.get(0); // Remove listMembers from evalMembers and independentSlicerMembers for (Member lm : listMembers) { Hierarchy h = lm.getHierarchy(); for (int i = 0; i < evalMembers.length; i++) { Member em = evalMembers[i]; if ((em != null) && h.equals(em.getHierarchy())) { evalMembers[i] = null; } } } List slicerMembers = null; if (evaluator instanceof RolapEvaluator) { RolapEvaluator rev = (RolapEvaluator) evaluator; slicerMembers = rev.getSlicerMembers(); } // Iterate the list of slicer members, grouping them by hierarchy Map> mapOfSlicerMembers = new HashMap>(); if (slicerMembers != null) { for (Member slicerMember : slicerMembers) { Hierarchy hierarchy = slicerMember.getHierarchy(); if (!mapOfSlicerMembers.containsKey(hierarchy)) { mapOfSlicerMembers.put( hierarchy, new HashSet()); } mapOfSlicerMembers.get(hierarchy).add(slicerMember); } } // Now we have the non-List-Members, but some of them may not be // All Members (default Member need not be the All Member) and // for some Hierarchies there may not be an All Member. // So we create an array of Objects some elements of which are // All Members and others elements will be an array of all top-level // Members when there is not an All Member. SchemaReader schemaReader = evaluator.getSchemaReader(); allMemberList = new ArrayList(); List nonAllMemberList = new ArrayList(); Member em; boolean isSlicerMember; for (Member evalMember : evalMembers) { em = evalMember; isSlicerMember = slicerMembers != null && slicerMembers.contains(em); if (em == null) { // Above we might have removed some by setting them // to null. These are the CrossJoin axes. continue; } if (em.isMeasure()) { continue; } // // The unconstrained members need to be replaced by the "All" // member based on its usage and property. This is currently // also the behavior of native cross join evaluation. See // SqlConstraintUtils.addContextConstraint() // // on slicer? | calculated? | replace with All? // ----------------------------------------------- // Y | Y | Y always // Y | N | N // N | Y | N // N | N | Y if not "All" // ----------------------------------------------- // if ((isSlicerMember && !em.isCalculated()) || (!isSlicerMember && em.isCalculated())) { // If the slicer contains multiple members from this one's // hierarchy, add them to nonAllMemberList if (isSlicerMember) { Set hierarchySlicerMembers = mapOfSlicerMembers.get(em.getHierarchy()); if (hierarchySlicerMembers.size() > 1) { nonAllMemberList.add( hierarchySlicerMembers.toArray( new Member[hierarchySlicerMembers.size()])); } } continue; } // If the member is not the All member; // or if it is a slicer member, // replace with the "all" member. if (isSlicerMember || !em.isAll()) { Hierarchy h = em.getHierarchy(); final List rootMemberList = schemaReader.getHierarchyRootMembers(h); if (h.hasAll()) { // The Hierarchy has an All member boolean found = false; for (Member m : rootMemberList) { if (m.isAll()) { allMemberList.add(m); found = true; break; } } if (!found) { System.out.println( "CrossJoinFunDef.nonEmptyListNEW: ERROR"); } } else { // The Hierarchy does NOT have an All member Member[] rootMembers = rootMemberList.toArray( new Member[rootMemberList.size()]); nonAllMemberList.add(rootMembers); } } } nonAllMembers = nonAllMemberList.toArray( new Member[nonAllMemberList.size()][]); query.putEvalCache(allMemberListKey, allMemberList); query.putEvalCache(nonAllMembersKey, nonAllMembers); } // // Determine if there is any data. // // Put all of the All Members into Evaluator final int savepoint = evaluator.savepoint(); evaluator.setContext(allMemberList); // Iterate over elements of the input list. If for any combination of // Measure and non-All Members evaluation is non-null, then // add it to the result List. final TupleCursor cursor = list.tupleCursor(); while (cursor.forward()) { cursor.setContext(evaluator); if (checkData( nonAllMembers, nonAllMembers.length - 1, measureSet, evaluator)) { result.addCurrent(cursor); } } evaluator.restore(savepoint); return result; } /** * Return true if for some combination of Members * from the nonAllMembers array of Member arrays and Measures from * the Set of Measures evaluate to a non-null value. Even if a * particular combination is non-null, all combinations are tested * just to make sure that the data is loaded. * * @param nonAllMembers array of Member arrays of top-level Members * for Hierarchies that have no All Member. * @param cnt which Member array is to be processed. * @param measureSet Set of all that should be tested against. * @param evaluator the Evaluator. * @return True if at least one combination evaluated to non-null. */ private static boolean checkData( Member[][] nonAllMembers, int cnt, Set measureSet, Evaluator evaluator) { if (cnt < 0) { // no measures found, use standard algorithm if (measureSet.isEmpty()) { Object value = evaluator.evaluateCurrent(); if (value != null && !(value instanceof Throwable)) { return true; } } else { // Here we evaluate across all measures just to // make sure that the data is all loaded boolean found = false; for (Member measure : measureSet) { evaluator.setContext(measure); Object value = evaluator.evaluateCurrent(); if (value != null && !(value instanceof Throwable)) { found = true; } } return found; } } else { boolean found = false; for (Member m : nonAllMembers[cnt]) { evaluator.setContext(m); if (checkData(nonAllMembers, cnt - 1, measureSet, evaluator)) { found = true; } } return found; } return false; } private static class StarCrossJoinResolver extends MultiResolver { public StarCrossJoinResolver() { super( "*", " * ", "Returns the cross product of two sets.", new String[]{"ixxx", "ixmx", "ixxm", "ixmm"}); } public FunDef resolve( Exp[] args, Validator validator, List conversions) { // This function only applies in contexts which require a set. // Elsewhere, "*" is the multiplication operator. // This means that [Measures].[Unit Sales] * [Gender].[M] is // well-defined. if (validator.requiresExpression()) { return null; } return super.resolve(args, validator, conversions); } protected FunDef createFunDef(Exp[] args, FunDef dummyFunDef) { return new CrossJoinFunDef(dummyFunDef); } } } // End CrossJoinFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/FunInfo.java0000644000175000017500000001520211735330606022502 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.olap.*; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.List; /** * Support class for the {@link mondrian.tui.CmdRunner} allowing one to view * available functions and their syntax. * * @author Richard M. Emberson */ public class FunInfo implements Comparable { private final Syntax syntax; private final String name; private final String description; private final int[] returnTypes; private final int[][] parameterTypes; private String[] sigs; static FunInfo make(Resolver resolver) { FunDef funDef = resolver.getFunDef(); if (funDef != null) { return new FunInfo(funDef); } else if (resolver instanceof MultiResolver) { return new FunInfo((MultiResolver) resolver); } else { return new FunInfo(resolver); } } FunInfo(FunDef funDef) { this.syntax = funDef.getSyntax(); this.name = funDef.getName(); assert name != null; assert syntax != null; this.returnTypes = new int[] { funDef.getReturnCategory() }; this.parameterTypes = new int[][] { funDef.getParameterCategories() }; // use explicit signature if it has one, otherwise generate a set this.sigs = funDef instanceof FunDefBase && ((FunDefBase) funDef).signature != null ? new String[] {((FunDefBase) funDef).signature} : makeSigs(syntax, name, returnTypes, parameterTypes); this.description = funDef.getDescription(); } FunInfo(MultiResolver multiResolver) { this.syntax = multiResolver.getSyntax(); this.name = multiResolver.getName(); assert name != null; assert syntax != null; this.description = multiResolver.getDescription(); String[] signatures = multiResolver.getSignatures(); this.returnTypes = new int[signatures.length]; this.parameterTypes = new int[signatures.length][]; for (int i = 0; i < signatures.length; i++) { returnTypes[i] = FunUtil.decodeReturnCategory(signatures[i]); parameterTypes[i] = FunUtil.decodeParameterCategories(signatures[i]); } this.sigs = makeSigs(syntax, name, returnTypes, parameterTypes); } FunInfo(Resolver resolver) { this.syntax = resolver.getSyntax(); this.name = resolver.getName(); assert name != null; assert syntax != null; this.description = resolver.getDescription(); this.returnTypes = null; this.parameterTypes = null; final String signature = resolver.getSignature(); this.sigs = signature == null ? new String[0] : new String[] {signature}; } FunInfo( String name, String description, String flags) { this.name = name; this.description = description; this.syntax = FunUtil.decodeSyntacticType(flags); this.returnTypes = new int[] {FunUtil.decodeReturnCategory(flags)}; this.parameterTypes = new int[][] {FunUtil.decodeParameterCategories(flags)}; } public String[] getSignatures() { return sigs; } private static String[] makeSigs( Syntax syntax, String name, int[] returnTypes, int[][] parameterTypes) { if (parameterTypes == null) { return null; } String[] sigs = new String[parameterTypes.length]; for (int i = 0; i < sigs.length; i++) { sigs[i] = syntax.getSignature( name, returnTypes[i], parameterTypes[i]); } return sigs; } /** * Returns the syntactic type of the function. */ public Syntax getSyntax() { return this.syntax; } /** * Returns the name of this function. */ public String getName() { return this.name; } /** * Returns the description of this function. */ public String getDescription() { return this.description; } /** * Returns the type of value returned by this function. Values are the same * as those returned by {@link mondrian.olap.Exp#getCategory()}. */ public int[] getReturnCategories() { return this.returnTypes; } /** * Returns the types of the arguments of this function. Values are the same * as those returned by {@link mondrian.olap.Exp#getCategory()}. The * 0th argument of methods and properties are the object they * are applied to. Infix operators have two arguments, and prefix operators * have one argument. */ public int[][] getParameterCategories() { return this.parameterTypes; } public int compareTo(FunInfo fi) { int c = this.name.compareTo(fi.name); if (c != 0) { return c; } final List pcList = toList(this.getParameterCategories()); final String pc = pcList.toString(); final List otherPcList = toList(fi.getParameterCategories()); final String otherPc = otherPcList.toString(); return pc.compareTo(otherPc); } public boolean equals(Object obj) { if (obj instanceof FunInfo) { final FunInfo that = (FunInfo) obj; if (!name.equals(that.name)) { return false; } final List pcList = toList(this.getParameterCategories()); final List pcList2 = toList(that.getParameterCategories()); return pcList.equals(pcList2); } else { return false; } } public int hashCode() { int h = name.hashCode(); final List pcList = toList(this.getParameterCategories()); return Util.hash(h, pcList); } private static List toList(Object a) { final List list = new ArrayList(); if (a == null) { return list; } final int length = Array.getLength(a); for (int i = 0; i < length; i++) { final Object o = Array.get(a, i); if (o.getClass().isArray()) { list.add(toList(o)); } else { list.add(o); } } return list; } } // End FunInfo.java mondrian-3.4.1/src/main/mondrian/olap/fun/RangeFunDef.java0000644000175000017500000000724011735330606023265 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.NullType; import mondrian.resource.MondrianResource; import mondrian.rolap.RolapMember; /** * Definition of the MDX <Member> : <Member> operator, * which returns the set of members between a given pair of members. * * @author jhyde * @since 3 March, 2002 */ class RangeFunDef extends FunDefBase { static final RangeFunDef instance = new RangeFunDef(); private RangeFunDef() { super( ":", " : ", "Infix colon operator returns the set of members between a given pair of members.", "ixmm"); } /** * Returns two membercalc objects, substituting nulls with the hierarchy * null member of the other expression. * * @param exp0 first expression * @param exp1 second expression * * @return two member calcs */ private MemberCalc[] compileMembers( Exp exp0, Exp exp1, ExpCompiler compiler) { MemberCalc[] members = new MemberCalc[2]; if (exp0.getType() instanceof NullType) { members[0] = null; } else { members[0] = compiler.compileMember(exp0); } if (exp1.getType() instanceof NullType) { members[1] = null; } else { members[1] = compiler.compileMember(exp1); } // replace any null types with hierachy null member // if both objects are null, throw exception if (members[0] == null && members[1] == null) { throw MondrianResource.instance().TwoNullsNotSupported.ex(); } else if (members[0] == null) { Member nullMember = ((RolapMember) members[1].evaluate(null)).getHierarchy() .getNullMember(); members[0] = (MemberCalc)ConstantCalc.constantMember(nullMember); } else if (members[1] == null) { Member nullMember = ((RolapMember) members[0].evaluate(null)).getHierarchy() .getNullMember(); members[1] = (MemberCalc)ConstantCalc.constantMember(nullMember); } return members; } public Calc compileCall(final ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc[] memberCalcs = compileMembers(call.getArg(0), call.getArg(1), compiler); return new AbstractListCalc( call, new Calc[] {memberCalcs[0], memberCalcs[1]}) { public TupleList evaluateList(Evaluator evaluator) { final Member member0 = memberCalcs[0].evaluateMember(evaluator); final Member member1 = memberCalcs[1].evaluateMember(evaluator); if (member0.isNull() || member1.isNull()) { return TupleCollections.emptyList(1); } if (member0.getLevel() != member1.getLevel()) { throw evaluator.newEvalException( call.getFunDef(), "Members must belong to the same level"); } return new UnaryTupleList( FunUtil.memberRange(evaluator, member0, member1)); } }; } } // End RangeFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/CaseTestFunDef.java0000644000175000017500000001036111735330606023742 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.ConstantCalc; import mondrian.calc.impl.GenericCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import java.util.ArrayList; import java.util.List; /** * Definition of the tested CASE MDX operator. * * Syntax is: *
    Case
     * When <Logical Expression> Then <Expression>
     * [...]
     * [Else <Expression>]
     * End
    . * * @see CaseMatchFunDef * * @author jhyde * @since Mar 23, 2006 */ class CaseTestFunDef extends FunDefBase { static final ResolverImpl Resolver = new ResolverImpl(); public CaseTestFunDef(FunDef dummyFunDef) { super(dummyFunDef); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Exp[] args = call.getArgs(); final BooleanCalc[] conditionCalcs = new BooleanCalc[args.length / 2]; final Calc[] exprCalcs = new Calc[args.length / 2]; final List calcList = new ArrayList(); for (int i = 0, j = 0; i < exprCalcs.length; i++) { conditionCalcs[i] = compiler.compileBoolean(args[j++]); calcList.add(conditionCalcs[i]); exprCalcs[i] = compiler.compile(args[j++]); calcList.add(exprCalcs[i]); } final Calc defaultCalc = args.length % 2 == 1 ? compiler.compileScalar(args[args.length - 1], true) : ConstantCalc.constantNull(call.getType()); calcList.add(defaultCalc); final Calc[] calcs = calcList.toArray(new Calc[calcList.size()]); return new GenericCalc(call) { public Object evaluate(Evaluator evaluator) { for (int i = 0; i < conditionCalcs.length; i++) { if (conditionCalcs[i].evaluateBoolean(evaluator)) { return exprCalcs[i].evaluate(evaluator); } } return defaultCalc.evaluate(evaluator); } public Calc[] getCalcs() { return calcs; } }; } private static class ResolverImpl extends ResolverBase { public ResolverImpl() { super( "_CaseTest", "Case When Then [...] [Else ] End", "Evaluates various conditions, and returns the corresponding expression for the first which evaluates to true.", Syntax.Case); } public FunDef resolve( Exp[] args, Validator validator, List conversions) { if (args.length < 1) { return null; } int j = 0; int clauseCount = args.length / 2; int mismatchingArgs = 0; int returnType = args[1].getCategory(); for (int i = 0; i < clauseCount; i++) { if (!validator.canConvert( j, args[j++], Category.Logical, conversions)) { mismatchingArgs++; } if (!validator.canConvert( j, args[j++], returnType, conversions)) { mismatchingArgs++; } } if (j < args.length) { if (!validator.canConvert( j, args[j++], returnType, conversions)) { mismatchingArgs++; } } Util.assertTrue(j == args.length); if (mismatchingArgs != 0) { return null; } FunDef dummy = createDummyFunDef(this, returnType, args); return new CaseTestFunDef(dummy); } public boolean requiresExpression(int k) { return true; } } } // End CaseTestFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/vba/0000755000175000017500000000000011735330606021043 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/olap/fun/vba/Vba.java0000644000175000017500000025021411735330606022422 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun.vba; import mondrian.olap.InvalidArgumentException; import mondrian.olap.Util; import java.text.*; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import static mondrian.olap.fun.JavaFunDef.*; /** * Implementations of functions in the Visual Basic for Applications (VBA) * specification. * *

    The functions are defined in * MSDN * . * * @author jhyde * @since Dec 31, 2007 */ public class Vba { private static final long MILLIS_IN_A_DAY = 24 * 60 * 60 * 1000; private static final DateFormatSymbols DATE_FORMAT_SYMBOLS = new DateFormatSymbols(Locale.getDefault()); // Conversion @FunctionName("CBool") @Signature("CBool(expression)") @Description( "Returns an expression that has been converted to a Variant of subtype " + "Boolean.") public static boolean cBool(Object expression) { if (expression instanceof Boolean) { return (Boolean) expression; } else { int i = cInt(expression); return i != 0; } } // Conversion functions @FunctionName("CByte") @Signature("CByte(expression)") @Description( "Returns an expression that has been converted to a Variant of subtype " + "Byte.") public static byte cByte(Object expression) { if (expression instanceof Byte) { return (Byte) expression; } else { int i = cInt(expression); return (byte) i; } } // public Currency cCur(Object expression) @FunctionName("CDate") @Signature("CDate(date)") @Description( "Returns an expression that has been converted to a Variant of subtype " + "Date.") public static Date cDate(Object expression) { String str = String.valueOf(expression); if (expression instanceof Date) { return (Date) expression; } else if (expression == null) { return null; } else { // note that this currently only supports a limited set of dates and // times // "October 19, 1962" // "4:35:47 PM" try { return DateFormat.getTimeInstance().parse(str); } catch (ParseException ex0) { try { return DateFormat.getDateTimeInstance().parse(str); } catch (ParseException ex1) { try { return DateFormat.getDateInstance().parse(str); } catch (ParseException ex2) { throw new InvalidArgumentException( "Invalid parameter. " + "expression parameter of CDate function must be " + "formatted correctly (" + String.valueOf(expression) + ")"); } } } } } @FunctionName("CDbl") @Signature("CDbl(expression)") @Description( "Returns an expression that has been converted to a Variant of subtype " + "Double.") public static double cDbl(Object expression) { if (expression instanceof Number) { Number number = (Number) expression; return number.doubleValue(); } else { final String s = String.valueOf(expression); return new Double(s).intValue(); } } @FunctionName("CInt") @Signature("CInt(expression)") @Description( "Returns an expression that has been converted to a Variant of subtype " + "Integer.") public static int cInt(Object expression) { if (expression instanceof Number) { Number number = (Number) expression; final int intValue = number.intValue(); if (number instanceof Float || number instanceof Double) { final double doubleValue = number.doubleValue(); if (doubleValue == (double) intValue) { // Number is already an integer return intValue; } final double doubleDouble = doubleValue * 2d; if (doubleDouble == Math.floor(doubleDouble)) { // Number ends in .5 - round towards even required return (int) Math.round(doubleValue / 2d) * 2; } return (int) Math.round(doubleValue); } return intValue; } else { // Try to parse as integer before parsing as double. More // efficient, and avoids loss of precision. final String s = String.valueOf(expression); try { return Integer.parseInt(s); } catch (NumberFormatException e) { return new Double(s).intValue(); } } } // public int cLng(Object expression) // public float cSng(Object expression) // public String cStr(Object expression) // public Object cVDate(Object expression) // public Object cVErr(Object expression) // public Object cVar(Object expression) // public String error$(Object errorNumber) // public Object error(Object errorNumber) @FunctionName("Fix") @Signature("Fix(number)") @Description( "Returns the integer portion of a number. If negative, returns the " + "negative number greater than or equal to the number.") public static int fix(Object number) { if (number instanceof Number) { int v = ((Number) number).intValue(); double dv = ((Number) number).doubleValue(); if (v < 0 && v < dv) { v++; } return v; } else { throw new InvalidArgumentException( "Invalid parameter. " + "number parameter " + number + " of Int function must be " + "of type number"); } } @FunctionName("Hex") @Signature("Hex(number)") @Description( "Returns a String representing the hexadecimal value of a number.") public static String hex(Object number) { if (number instanceof Number) { return Integer.toHexString(((Number) number).intValue()) .toUpperCase(); } else { throw new InvalidArgumentException( "Invalid parameter. " + "number parameter " + number + " of Hex function must be " + "of type number"); } } @FunctionName("Int") @Signature("Int(number)") @Description( "Returns the integer portion of a number. If negative, returns the " + "negative number less than or equal to the number.") public static int int_(Object number) { if (number instanceof Number) { int v = ((Number) number).intValue(); double dv = ((Number) number).doubleValue(); if (v < 0 && v > dv) { v--; } return v; } else { throw new InvalidArgumentException( "Invalid parameter. " + "number parameter " + number + " of Int function must be " + "of type number"); } } /** * Equivalent of the {@link #int_} function on the native 'double' type. * Not an MDX function. * * @param dv Double value * @return Value rounded towards negative infinity */ static int intNative(double dv) { int v = (int) dv; if (v < 0 && v > dv) { v--; } return v; } // public String oct$(Object number) @FunctionName("Oct") @Signature("Oct(number)") @Description( "Returns a Variant (String) representing the octal value of a number.") public static String oct(Object number) { if (number instanceof Number) { return Integer.toOctalString(((Number) number).intValue()); } else { throw new InvalidArgumentException( "Invalid parameter. " + "number parameter " + number + " of Oct function must be " + "of type number"); } } // public String str$(Object number) @FunctionName("Str") @Signature("Str(number)") @Description("Returns a Variant (String) representation of a number.") public static String str(Object number) { // When numbers are converted to strings, a leading space is always // reserved for the sign of number. If number is positive, the returned // string contains a leading space and the plus sign is implied. // // Use the Format function to convert numeric values you want formatted // as dates, times, or currency or in other user-defined formats. // Unlike Str, the Format function doesn't include a leading space for // the sign of number. // // Note The Str function recognizes only the period (.) as a valid // decimal separator. When different decimal separators may be used // (for example, in international applications), use CStr to convert a // number to a string. if (number instanceof Number) { if (((Number) number).doubleValue() >= 0) { return " " + number.toString(); } else { return number.toString(); } } else { throw new InvalidArgumentException( "Invalid parameter. " + "number parameter " + number + " of Str function must be " + "of type number"); } } @FunctionName("Val") @Signature("Val(string)") @Description( "Returns the numbers contained in a string as a numeric value of " + "appropriate type.") public static double val(String string) { // The Val function stops reading the string at the first character it // can't recognize as part of a number. Symbols and characters that are // often considered parts of numeric values, such as dollar signs and // commas, are not recognized. However, the function recognizes the // radix prefixes &O (for octal) and &H (for hexadecimal). Blanks, // tabs, and linefeed characters are stripped from the argument. // // The following returns the value 1615198: // // Val(" 1615 198th Street N.E.") // In the code below, Val returns the decimal value -1 for the // hexadecimal value shown: // // Val("&HFFFF") // Note The Val function recognizes only the period (.) as a valid // decimal separator. When different decimal separators are used, as in // international applications, use CDbl instead to convert a string to // a number. string = string.replaceAll("\\s", ""); // remove all whitespace if (string.startsWith("&H")) { string = string.substring(2); Pattern p = Pattern.compile("[0-9a-fA-F]*"); Matcher m = p.matcher(string); m.find(); return Integer.parseInt(m.group(), 16); } else if (string.startsWith("&O")) { string = string.substring(2); Pattern p = Pattern.compile("[0-7]*"); Matcher m = p.matcher(string); m.find(); return Integer.parseInt(m.group(), 8); } else { // find the first number Pattern p = Pattern.compile("-?[0-9]*[.]?[0-9]*"); Matcher m = p.matcher(string); m.find(); return Double.parseDouble(m.group()); } } // DateTime // public Calendar calendar() // public void calendar(Calendar val) // public String date$() // public void date$(String val) @FunctionName("DateAdd") @Signature("DateAdd(interval, number, date)") @Description( "Returns a Variant (Date) containing a date to which a specified time " + "interval has been added.") public static Date dateAdd(String intervalName, double number, Date date) { Interval interval = Interval.valueOf(intervalName); final double floor = Math.floor(number); // We use the local calendar here. This method will therefore return // different results in different locales: it depends whether the // initial date and the final date are in DST. Calendar calendar = Calendar.getInstance(); calendar.setTime(date); if (floor != number) { final double ceil = Math.ceil(number); interval.add(calendar, (int) ceil); final long ceilMillis = calendar.getTimeInMillis(); calendar.setTime(date); interval.add(calendar, (int) floor); final long floorMillis = calendar.getTimeInMillis(); final long amount = (long) (((double) (ceilMillis - floorMillis)) * (number - floor)); calendar.add( Calendar.DAY_OF_YEAR, (int) (amount / MILLIS_IN_A_DAY)); calendar.add( Calendar.MILLISECOND, (int) (amount % MILLIS_IN_A_DAY)); } else { interval.add(calendar, (int) floor); } return calendar.getTime(); } @FunctionName("DateDiff") @Signature( "DateDiff(interval, date1, date2[, firstdayofweek[, firstweekofyear]])") @Description( "Returns a Variant (Long) specifying the number of time intervals " + "between two specified dates.") public static long dateDiff(String interval, Date date1, Date date2) { return _dateDiff( interval, date1, date2, Calendar.SUNDAY, FirstWeekOfYear.vbFirstJan1); } @FunctionName("DateDiff") @Signature( "DateDiff(interval, date1, date2[, firstdayofweek[, firstweekofyear]])") @Description( "Returns a Variant (Long) specifying the number of time intervals " + "between two specified dates.") public static long dateDiff( String interval, Date date1, Date date2, int firstDayOfWeek) { return _dateDiff( interval, date1, date2, firstDayOfWeek, FirstWeekOfYear.vbFirstJan1); } @FunctionName("DateDiff") @Signature( "DateDiff(interval, date1, date2[, firstdayofweek[, firstweekofyear]])") @Description( "Returns a Variant (Long) specifying the number of time intervals " + "between two specified dates.") public static long dateDiff( String interval, Date date1, Date date2, int firstDayOfWeek, int firstWeekOfYear) { return _dateDiff( interval, date1, date2, firstDayOfWeek, FirstWeekOfYear.values()[firstWeekOfYear]); } private static long _dateDiff( String intervalName, Date date1, Date date2, int firstDayOfWeek, FirstWeekOfYear firstWeekOfYear) { Interval interval = Interval.valueOf(intervalName); Calendar calendar1 = Calendar.getInstance(); firstWeekOfYear.apply(calendar1); calendar1.setTime(date1); Calendar calendar2 = Calendar.getInstance(); firstWeekOfYear.apply(calendar2); calendar2.setTime(date2); return interval.diff(calendar1, calendar2, firstDayOfWeek); } @FunctionName("DatePart") @Signature("DatePart(interval, date[,firstdayofweek[, firstweekofyear]])") @Description( "Returns a Variant (Integer) containing the specified part of a given " + "date.") public static int datePart(String interval, Date date) { return _datePart( interval, date, Calendar.SUNDAY, FirstWeekOfYear.vbFirstJan1); } @FunctionName("DatePart") @Signature("DatePart(interval, date[,firstdayofweek[, firstweekofyear]])") @Description( "Returns a Variant (Integer) containing the specified part of a given " + "date.") public static int datePart(String interval, Date date, int firstDayOfWeek) { return _datePart( interval, date, firstDayOfWeek, FirstWeekOfYear.vbFirstJan1); } @FunctionName("DatePart") @Signature("DatePart(interval, date[,firstdayofweek[, firstweekofyear]])") @Description( "Returns a Variant (Integer) containing the specified part of a given " + "date.") public static int datePart( String interval, Date date, int firstDayOfWeek, int firstWeekOfYear) { return _datePart( interval, date, firstDayOfWeek, FirstWeekOfYear.values()[firstWeekOfYear]); } private static int _datePart( String intervalName, Date date, int firstDayOfWeek, FirstWeekOfYear firstWeekOfYear) { Interval interval = Interval.valueOf(intervalName); Calendar calendar = Calendar.getInstance(); calendar.setTime(date); switch (interval) { case w: case ww: // firstWeekOfYear and firstDayOfWeek only matter for 'w' and 'ww' firstWeekOfYear.apply(calendar); calendar.setFirstDayOfWeek(firstDayOfWeek); break; } return interval.datePart(calendar); } @FunctionName("Date") @Signature("Date") @Description("Returns a Variant (Date) containing the current system date.") public static Date date() { Calendar calendar = Calendar.getInstance(); calendar.clear(); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); return calendar.getTime(); } // public void date(Object val) @FunctionName("DateSerial") @Signature("DateSerial(year, month, day)") @Description( "Returns a Variant (Date) for a specified year, month, and day.") public static Date dateSerial(int year, int month, int day) { Calendar calendar = Calendar.getInstance(); calendar.clear(); calendar.set(year, month - 1, day); return calendar.getTime(); } @FunctionName("DateValue") @Signature("DateValue(date)") @Description("Returns a Variant (Date).") public static Date dateValue(Date date) { final Calendar calendar = Calendar.getInstance(); calendar.clear(); calendar.setTime(date); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); return calendar.getTime(); } @FunctionName("Day") @Signature("Day(date)") @Description( "Returns a Variant (Integer) specifying a whole number between 1 and " + "31, inclusive, representing the day of the month.") public static int day(Date date) { final Calendar calendar = Calendar.getInstance(); calendar.setTime(date); return calendar.get(Calendar.DAY_OF_MONTH); } @FunctionName("Hour") @Signature("Hour(time)") @Description( "Returns a Variant (Integer) specifying a whole number between 0 and " + "23, inclusive, representing the hour of the day.") public static int hour(Date time) { final Calendar calendar = Calendar.getInstance(); calendar.setTime(time); return calendar.get(Calendar.HOUR_OF_DAY); } @FunctionName("Minute") @Signature("Minute(time)") @Description( "Returns a Variant (Integer) specifying a whole number between 0 and " + "59, inclusive, representing the minute of the hour.") public static int minute(Date time) { final Calendar calendar = Calendar.getInstance(); calendar.setTime(time); return calendar.get(Calendar.MINUTE); } @FunctionName("Month") @Signature("Month(date)") @Description( "Returns a Variant (Integer) specifying a whole number between 1 and " + "12, inclusive, representing the month of the year.") public static int month(Date date) { final Calendar calendar = Calendar.getInstance(); calendar.setTime(date); final int month = calendar.get(Calendar.MONTH); return month + 1; // convert from 0- to 1-based } @FunctionName("Now") @Signature("Now()") @Description( "Returns a Variant (Date) specifying the current date and time " + "according your computer's system date and time.") public static Date now() { return new Date(); } @FunctionName("Second") @Signature("Second(time)") @Description( "Returns a Variant (Integer) specifying a whole number between 0 and " + "59, inclusive, representing the second of the minute.") public static int second(Date time) { final Calendar calendar = Calendar.getInstance(); calendar.setTime(time); return calendar.get(Calendar.SECOND); } // public String time$() // public void time$(String val) @FunctionName("Time") @Signature("Time()") @Description("Returns a Variant (Date) indicating the current system time.") public static Date time() { return new Date(); } // public void time(Object val) @FunctionName("TimeSerial") @Signature("TimeSerial(hour, minute, second)") @Description( "Returns a Variant (Date) containing the time for a specific hour, " + "minute, and second.") public static Date timeSerial(int hour, int minute, int second) { final Calendar calendar = Calendar.getInstance(); calendar.clear(); calendar.set(Calendar.HOUR_OF_DAY, hour); calendar.set(Calendar.MINUTE, minute); calendar.set(Calendar.SECOND, second); return calendar.getTime(); } @FunctionName("TimeValue") @Signature("TimeValue(time)") @Description("Returns a Variant (Date) containing the time.") public static Date timeValue(Date time) { final Calendar calendar = Calendar.getInstance(); calendar.clear(); calendar.setTime(time); calendar.set(1970, 0, 1); return calendar.getTime(); } @FunctionName("Timer") @Signature("Timer()") @Description( "Returns a Single representing the number of seconds elapsed since " + "midnight.") public static float timer() { final Calendar calendar = Calendar.getInstance(); final long now = calendar.getTimeInMillis(); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); final long midnight = calendar.getTimeInMillis(); return ((float) (now - midnight)) / 1000f; } @FunctionName("Weekday") @Signature("Weekday(date[, firstDayOfWeek])") @Description( "Returns a Variant (Integer) containing a whole number representing " + "the day of the week.") public static int weekday(Date date) { return weekday(date, Calendar.SUNDAY); } @FunctionName("Weekday") @Signature("Weekday(date[, firstDayOfWeek])") @Description( "Returns a Variant (Integer) containing a whole number representing " + "the day of the week.") public static int weekday(Date date, int firstDayOfWeek) { final Calendar calendar = Calendar.getInstance(); calendar.setTime(date); int weekday = calendar.get(Calendar.DAY_OF_WEEK); // adjust for start of week weekday -= (firstDayOfWeek - 1); // bring into range 1..7 weekday = (weekday + 6) % 7 + 1; return weekday; } @FunctionName("Year") @Signature("Year(date)") @Description( "Returns a Variant (Integer) containing a whole number representing " + "the year.") public static int year(Date date) { final Calendar calendar = Calendar.getInstance(); calendar.setTime(date); return calendar.get(Calendar.YEAR); } // // /* FileSystem */ // public void chDir(String path) // public void chDrive(String drive) // public String curDir$(Object drive) // public Object curDir(Object drive) // public String dir(Object pathName, FileAttribute attributes /* default // FileAttribute.Normal */) // public boolean EOF(int fileNumber) // public int fileAttr(int fileNumber, int returnType /* default 1 */) // public void fileCopy(String source, String destination) // public Object fileDateTime(String pathName) // public int fileLen(String pathName) // public int freeFile(Object rangeNumber) // public FileAttribute getAttr(String pathName) // public void kill(Object pathName) // public int LOF(int fileNumber) // public int loc(int fileNumber) // public void mkDir(String path) // public void reset() // public void rmDir(String path) // public int seek(int fileNumber) // public void setAttr(String pathName, FileAttribute attributes) // // Financial @FunctionName("DDB") @Signature("DDB(cost, salvage, life, period[, factor])") @Description( "Returns a Double specifying the depreciation of an asset for a " + "specific time period using the double-declining balance method or " + "some other method you specify.") public static double dDB( double cost, double salvage, double life, double period) { return dDB(cost, salvage, life, period, 2.0); } @FunctionName("DDB") @Signature("DDB(cost, salvage, life, period[, factor])") @Description( "Returns a Double specifying the depreciation of an asset for a " + "specific time period using the double-declining balance method or " + "some other method you specify.") public static double dDB( double cost, double salvage, double life, double period, double factor) { return (((cost - salvage) * factor) / life) * period; } @FunctionName("FV") @Signature("FV(rate, nper, pmt[, pv[, type]])") @Description( "Returns a Double specifying the future value of an annuity based on " + "periodic, fixed payments and a fixed interest rate.") public static double fV(double rate, double nPer, double pmt) { return fV(rate, nPer, pmt, 0d, false); } @FunctionName("FV") @Signature("FV(rate, nper, pmt[, pv[, type]])") @Description( "Returns a Double specifying the future value of an annuity based on " + "periodic, fixed payments and a fixed interest rate.") public static double fV(double rate, double nPer, double pmt, double pv) { return fV(rate, nPer, pmt, pv, false); } @FunctionName("FV") @Signature("FV(rate, nper, pmt[, pv[, type]])") @Description( "Returns a Double specifying the future value of an annuity based on " + "periodic, fixed payments and a fixed interest rate.") public static double fV( double rate, double nPer, double pmt, double pv, boolean type) { if (rate == 0) { return -(pv + (nPer * pmt)); } else { double r1 = rate + 1; return ((1 - Math.pow(r1, nPer)) * (type ? r1 : 1) * pmt) / rate - pv * Math.pow(r1, nPer); } } @FunctionName("IPmt") @Signature("IPmt(rate, per, nper, pv[, fv[, type]])") @Description( "Returns a Double specifying the interest payment for a given period " + "of an annuity based on periodic, fixed payments and a fixed " + "interest rate.") public static double iPmt(double rate, double per, double nPer, double PV) { return iPmt(rate, per, nPer, PV, 0); } @FunctionName("IPmt") @Signature("IPmt(rate, per, nper, pv[, fv[, type]])") @Description( "Returns a Double specifying the interest payment for a given period " + "of an annuity based on periodic, fixed payments and a fixed " + "interest rate.") public static double iPmt( double rate, double per, double nPer, double PV, double fv) { return iPmt(rate, per, nPer, PV, fv, false); } @FunctionName("IPmt") @Signature("IPmt(rate, per, nper, pv[, fv[, type]])") @Description( "Returns a Double specifying the interest payment for a given period " + "of an annuity based on periodic, fixed payments and a fixed " + "interest rate.") public static double iPmt( double rate, double per, double nPer, double PV, double fv, boolean due) { double pmtVal = pmt(rate, nPer, PV, fv, due); double pValm1 = PV - pV(rate, per - 1, pmtVal, fv, due); return - pValm1 * rate; } @FunctionName("IRR") @Signature("IRR(values()[, guess])") @Description( "Returns a Double specifying the internal rate of return for a series " + "of periodic cash flows (payments and receipts).") public static double IRR(double[] valueArray) { return IRR(valueArray, 0.10); } @FunctionName("IRR") @Signature("IRR(values()[, guess])") @Description( "Returns a Double specifying the internal rate of return for a series " + "of periodic cash flows (payments and receipts).") public static double IRR(double[] valueArray, double guess) { // calc pV of stream (sum of pV's for valueArray) ((1 + guess) ^ index) double minGuess = 0.0; double maxGuess = 1.0; // i'm not certain int r = 1; if (valueArray[0] > 0) { r = -1; } for (int i = 0; i < 30; i++) { // first calculate overall return based on guess double totalPv = 0; for (int j = 0; j < valueArray.length; j++) { totalPv += valueArray[j] / Math.pow(1.0 + guess, j); } if ((maxGuess - minGuess) < 0.0000001) { return guess; } else if (totalPv * r < 0) { maxGuess = guess; } else { minGuess = guess; } // avg max min to determine next step guess = (maxGuess + minGuess) / 2; } // unable to find a match return -1; } @FunctionName("MIRR") @Signature("MIRR(values(), finance_rate, reinvest_rate)") @Description( "Returns a Double specifying the modified internal rate of return for " + "a series of periodic cash flows (payments and receipts).") public static double MIRR( double valueArray[], double financeRate, double reinvestRate) { // based on // http://en.wikipedia.org/wiki/Modified_Internal_Rate_of_Return double reNPV = 0.0; double fiNPV = 0.0; for (int j = 0; j < valueArray.length; j++) { if (valueArray[j] > 0) { reNPV += valueArray[j] / Math.pow(1.0 + reinvestRate, j); } else { fiNPV += valueArray[j] / Math.pow(1.0 + financeRate, j); } } double ratio = (- reNPV * Math.pow(1 + reinvestRate, valueArray.length)) / (fiNPV * (1 + financeRate)); return Math.pow(ratio, 1.0 / (valueArray.length - 1)) - 1.0; } @FunctionName("NPer") @Signature("NPer(rate, pmt, pv[, fv[, type]])") @Description( "Returns a Double specifying the number of periods for an annuity " + "based on periodic, fixed payments and a fixed interest rate.") public static double nPer( double rate, double pmt, double pv, double fv, boolean due) { if (rate == 0) { return -(fv + pv) / pmt; } else { double r1 = rate + 1; double ryr = (due ? r1 : 1) * pmt / rate; double a1 = ((ryr - fv) < 0) ? Math.log(fv - ryr) : Math.log(ryr - fv); double a2 = ((ryr - fv) < 0) ? Math.log(-pv - ryr) : Math.log(pv + ryr); double a3 = Math.log(r1); return (a1 - a2) / a3; } } @FunctionName("NPV") @Signature("NPV(rate, values())") @Description( "Returns a Double specifying the net present value of an investment " + "based on a series of periodic cash flows (payments and receipts) " + "and a discount rate.") public static double nPV(double r, double[] cfs) { double npv = 0; double r1 = r + 1; double trate = r1; for (int i = 0, iSize = cfs.length; i < iSize; i++) { npv += cfs[i] / trate; trate *= r1; } return npv; } @FunctionName("PPmt") @Signature("PPmt(rate, per, nper, pv[, fv[, type]])") @Description( "Returns a Double specifying the principal payment for a given period " + "of an annuity based on periodic, fixed payments and a fixed " + "interest rate.") public static double pPmt(double rate, double per, double nPer, double PV) { return pPmt(rate, per, nPer, PV, 0); } @FunctionName("PPmt") @Signature("PPmt(rate, per, nper, pv[, fv[, type]])") @Description( "Returns a Double specifying the principal payment for a given period " + "of an annuity based on periodic, fixed payments and a fixed " + "interest rate.") public static double pPmt( double rate, double per, double nPer, double PV, double fv) { return pPmt(rate, per, nPer, PV, fv, false); } @FunctionName("PPmt") @Signature("PPmt(rate, per, nper, pv[, fv[, type]])") @Description( "Returns a Double specifying the principal payment for a given period " + "of an annuity based on periodic, fixed payments and a fixed " + "interest rate.") public static double pPmt( double rate, double per, double nPer, double PV, double fv, boolean due) { return pmt(rate, nPer, PV, fv, due) - iPmt(rate, per, nPer, PV, fv, due); } @FunctionName("Pmt") @Signature("Pmt(rate, nper, pv[, fv[, type]])") @Description( "Returns a Double specifying the payment for an annuity based on " + "periodic, fixed payments and a fixed interest rate.") public static double pmt( double rate, double nPer, double pv, double fv, boolean due) { if (rate == 0) { return -(fv + pv) / nPer; } else { double r1 = rate + 1; return (fv + pv * Math.pow(r1, nPer)) * rate / ((due ? r1 : 1) * (1 - Math.pow(r1, nPer))); } } @FunctionName("PV") @Signature("PV(rate, nper, pmt[, fv[, type]])") @Description( "Returns a Double specifying the present value of an annuity based on " + "periodic, fixed payments to be paid in the future and a fixed " + "interest rate.") public static double pV( double rate, double nper, double pmt, double fv, boolean due) { if (rate == 0) { return -((nper * pmt) + fv); } else { double r1 = rate + 1; return (((1 - Math.pow(r1, nper)) / rate) * (due ? r1 : 1) * pmt - fv) / Math.pow(r1, nper); } } @FunctionName("Rate") @Signature("Rate(nper, pmt, pv[, fv[, type[, guess]]])") @Description( "Returns a Double specifying the interest rate per period for an " + "annuity.") public static double rate( double nPer, double pmt, double PV) { return rate(nPer, pmt, PV, 0); } @FunctionName("Rate") @Signature("Rate(nper, pmt, pv[, fv[, type[, guess]]])") @Description( "Returns a Double specifying the interest rate per period for an " + "annuity.") public static double rate( double nPer, double pmt, double PV, double fv) { return rate(nPer, pmt, PV, fv, false); } @FunctionName("Rate") @Signature("Rate(nper, pmt, pv[, fv[, type[, guess]]])") @Description( "Returns a Double specifying the interest rate per period for an " + "annuity.") public static double rate( double nPer, double pmt, double PV, double fv, boolean type) { return rate(nPer, pmt, PV, fv, type, 0.1); } @FunctionName("Rate") @Signature("Rate(nper, pmt, pv[, fv[, type[, guess]]])") @Description( "Returns a Double specifying the interest rate per period for an " + "annuity.") public static double rate( double nPer, // specifies the number of payment periods double pmt, // payment per period of annuity double PV, // the present value of the annuity (0 if a loan) double fv, // the future value of the annuity ($ if savings) boolean due, double guess) { if (nPer <= 0) { throw new InvalidArgumentException( "number of payment periods must be larger than 0"); } double minGuess = 0.0; double maxGuess = 1.0; // converge on the correct answer should use Newton's Method // for now use a binary search int r = 1; if (PV < fv) { r = -1; } // the vb method uses 20 iterations, but they also probably use newton's // method, // so i've bumped it up to 30 iterations. for (int n = 0; n < 30; n++) { double gFV = fV(guess, nPer, pmt, PV, due); double diff = gFV - fv; if ((maxGuess - minGuess) < 0.0000001) { return guess; } else { if (diff * r < 0) { maxGuess = guess; } else { minGuess = guess; } guess = (maxGuess + minGuess) / 2; } } // fail, not sure how VB fails return -1; } @FunctionName("SLN") @Signature("SLN(cost, salvage, life)") @Description( "Returns a Double specifying the straight-line depreciation of an " + "asset for a single period.") public static double sLN(double cost, double salvage, double life) { return (cost - salvage) / life; } @FunctionName("SYD") @Signature("SYD(cost, salvage, life, period)") @Description( "Returns a Double specifying the sum-of-years' digits depreciation of " + "an asset for a specified period.") public static double sYD( double cost, double salvage, double life, double period) { return (cost - salvage) * (life / (period * (period + 1) / 2)); } // Information // public Throwable err() // public Object iMEStatus() @FunctionName("IsArray") @Signature("IsArray(varname)") @Description( "Returns a Boolean value indicating whether a variable is an array.") public boolean isArray(Object varName) { // arrays are not supported at present return false; } @FunctionName("IsDate") @Signature("IsDate(varname)") @Description( "Returns a Boolean value indicating whether an expression can be " + "converted to a date.") public static boolean isDate(Object expression) { // IsDate returns True if Expression represents a valid date, a valid // time, or a valid date and time. try { Date val = cDate(expression); return (val != null); } catch (InvalidArgumentException e) { return false; } } // use mondrian's implementation of IsEmpty // public boolean isEmpty(Object expression) @FunctionName("IsError") @Signature("IsError(varname)") @Description( "Returns a Boolean value indicating whether an expression is an error " + "value.") public boolean isError(Object expression) { return expression instanceof Throwable; } @FunctionName("IsMissing") @Signature("IsMissing(varname)") @Description( "Returns a Boolean value indicating whether an optional Variant " + "argument has been passed to a procedure.") public boolean isMissing(Object argName) { // We have no way to detect missing arguments. return false; } @FunctionName("IsNull") @Signature("IsNull(varname)") @Description( "Returns a Boolean value that indicates whether an expression " + "contains no valid data (Null).") public boolean isNull(Object expression) { return expression == null; } @FunctionName("IsNumeric") @Signature("IsNumeric(varname)") @Description( "Returns a Boolean value indicating whether an expression can be " + "evaluated as a number.") public boolean isNumeric(Object expression) { return expression instanceof Number; } @FunctionName("IsObject") @Signature("IsObject(varname)") @Description( "Returns a Boolean value indicating whether an identifier represents " + "an object variable.") public boolean isObject(Object expression) { return false; } // public int qBColor(int color) // public int RGB(int red, int green, int blue) @FunctionName("TypeName") @Signature("TypeName(varname)") @Description("Returns a String that provides information about a variable.") public static String typeName(Object varName) { // The string returned by TypeName can be any one of the following: // // String returned Variable // object type An object whose type is objecttype // Byte Byte value // Integer Integer // Long Long integer // Single Single-precision floating-point number // Double Double-precision floating-point number // Currency Currency value // Decimal Decimal value // Date Date value // String String // Boolean Boolean value // Error An error value // Empty Uninitialized // Null No valid data // Object An object // Unknown An object whose type is unknown // Nothing Object variable that doesn't refer to an object if (varName == null) { return "NULL"; } else { // strip off the package information String name = varName.getClass().getName(); if (name.lastIndexOf(".") >= 0) { name = name.substring(name.lastIndexOf(".") + 1); } return name; } } // public VarType varType(Object varName) // Interaction // public void appActivate(Object title, Object wait) // public void beep() // public Object callByName(Object object, String procName, CallType // callType, Object args, int lcid) // public Object choose(float index, Object choice) // public String command$() // public Object command() // public Object createObject(String Class, String serverName) // public int doEvents() // public String environ$(Object expression) // public Object environ(Object expression) // public Object getAllSettings(String appName, String section) // public Object getObject(Object pathName, Object Class) // public String getSetting(String appName, String section, String key, // Object Default) // public Object iIf(Object expression, Object truePart, Object falsePart) // public String inputBox(Object prompt, Object title, Object Default, // Object xPos, Object yPos, Object helpFile, Object context) // public String macScript(String script) // public MsgBoxResult msgBox(Object prompt, MsgBoxStyle buttons /* default // MsgBoxStyle.OKOnly */, Object title, Object helpFile, Object context) // public Object partition(Object number, Object start, Object stop, Object // interval) // public void saveSetting(String appName, String section, String key, // String setting) // public void sendKeys(String string, Object wait) // public double shell(Object pathName, AppWinStyle windowStyle /* default // AppWinStyle.MinimizedFocus */) // public Object Switch(Object varExpr) // Mathematical @FunctionName("Abs") @Signature("Abs(number)") @Description( "Returns a value of the same type that is passed to it specifying the " + "absolute value of a number.") public static double abs(double number) { return Math.abs(number); } @FunctionName("Atn") @Signature("Atn(number)") @Description("Returns a Double specifying the arctangent of a number.") public static double atn(double number) { return Math.atan(number); } @FunctionName("Cos") @Signature("Cos(number)") @Description("Returns a Double specifying the cosine of an angle.") public static double cos(double number) { return Math.cos(number); } @FunctionName("Exp") @Signature("Exp(number)") @Description( "Returns a Double specifying e (the base of natural logarithms) " + "raised to a power.") public static double exp(double number) { return Math.exp(number); } @FunctionName("Log") @Signature("Log(number)") @Description( "Returns a Double specifying the natural logarithm of a number.") public static double log(double number) { return Math.log(number); } // Cannot implement randomize and rnd - we require context to hold the // seed // public void randomize(Object number) // public float rnd(Object number) @FunctionName("Round") @Signature("Round(number[, numDigitsAfterDecimal])") @Description( "Returns a number rounded to a specified number of decimal places.") public static double round(double number) { return Math.round(number); } @FunctionName("Round") @Signature("Round(number[, numDigitsAfterDecimal])") @Description( "Returns a number rounded to a specified number of decimal places.") public static double round(double number, int numDigitsAfterDecimal) { if (numDigitsAfterDecimal == 0) { return Math.round(number); } final double shift = Math.pow(10d, numDigitsAfterDecimal); double numberScaled = number * shift; double resultScaled = Math.round(numberScaled); return resultScaled / shift; } @FunctionName("Sgn") @Signature("Sgn(number)") @Description("Returns a Variant (Integer) indicating the sign of a number.") public static int sgn(double number) { // We could use Math.signum(double) from JDK 1.5 onwards. return number < 0.0d ? -1 : number > 0.0d ? 1 : 0; } @FunctionName("Sin") @Signature("Sin(number)") @Description("Returns a Double specifying the sine of an angle.") public static double sin(double number) { return Math.sin(number); } @FunctionName("Sqr") @Signature("Sqr(number)") @Description("Returns a Double specifying the square root of a number.") public static double sqr(double number) { return Math.sqrt(number); } @FunctionName("Tan") @Signature("Tan(number)") @Description("Returns a Double specifying the tangent of an angle.") public static double tan(double number) { return Math.tan(number); } // Strings @FunctionName("Asc") @Signature("Asc(string)") @Description( "Returns an Integer representing the character code corresponding to " + "the first letter in a string.") public static int asc(String string) { return string.charAt(0); } @FunctionName("AscB") @Signature("AscB(string)") @Description("See Asc.") public static int ascB(String string) { return (byte) string.charAt(0); } @FunctionName("AscW") @Signature("AscW(string)") @Description("See Asc.") public static int ascW(String string) { return asc(string); } // public String chr$(int charCode) // public String chrB$(int charCode) @FunctionName("Chr") @Signature("Chr(charcode)") @Description( "Returns a String containing the character associated with the " + "specified character code.") public static String chr(int charCode) { return new String(new char[] { (char) charCode }); } @FunctionName("ChrB") @Signature("ChrB(charcode)") @Description("See Chr.") public static String chrB(int charCode) { return new String(new byte[] { (byte) charCode }); } // public String chrW$(int charCode) @FunctionName("ChrW") @Signature("ChrW(charcode)") @Description("See Chr.") public static String chrW(int charCode) { return new String(new char[] { (char) charCode }); } // public Object filter(Object sourceArray, String match, boolean include /* // default 1 */, int compare /* default BinaryCompare */) // public String format$(Object expression, Object format, int // firstDayOfWeek /* default Sunday */, int firstWeekOfYear /* default // FirstJan1 */) @FunctionName("FormatCurrency") @Signature( "FormatCurrency(Expression[,NumDigitsAfterDecimal " + "[,IncludeLeadingDigit [,UseParensForNegativeNumbers " + "[,GroupDigits]]]])") @Description( "Returns an expression formatted as a currency value using the " + "currency symbol defined in the system control panel.") public static String formatCurrency(Object expression) { return formatCurrency(expression, -1, -2, -2, -2); } @FunctionName("FormatCurrency") @Signature( "FormatCurrency(Expression[,NumDigitsAfterDecimal " + "[,IncludeLeadingDigit [,UseParensForNegativeNumbers " + "[,GroupDigits]]]])") @Description( "Returns an expression formatted as a currency value using the " + "currency symbol defined in the system control panel.") public static String formatCurrency( Object expression, int numDigitsAfterDecimal) { return formatCurrency(expression, numDigitsAfterDecimal, -2, -2, -2); } @FunctionName("FormatCurrency") @Signature( "FormatCurrency(Expression[,NumDigitsAfterDecimal " + "[,IncludeLeadingDigit [,UseParensForNegativeNumbers " + "[,GroupDigits]]]])") @Description( "Returns an expression formatted as a currency value using the " + "currency symbol defined in the system control panel.") public static String formatCurrency( Object expression, int numDigitsAfterDecimal, int includeLeadingDigit) { return formatCurrency( expression, numDigitsAfterDecimal, includeLeadingDigit, -2, -2); } @FunctionName("FormatCurrency") @Signature( "FormatCurrency(Expression[,NumDigitsAfterDecimal " + "[,IncludeLeadingDigit [,UseParensForNegativeNumbers " + "[,GroupDigits]]]])") @Description( "Returns an expression formatted as a currency value using the " + "currency symbol defined in the system control panel.") public static String formatCurrency( Object expression, int numDigitsAfterDecimal, int includeLeadingDigit, int useParensForNegativeNumbers) { return formatCurrency( expression, numDigitsAfterDecimal, includeLeadingDigit, useParensForNegativeNumbers, -2); } @FunctionName("FormatCurrency") @Signature( "FormatCurrency(Expression[,NumDigitsAfterDecimal " + "[,IncludeLeadingDigit [,UseParensForNegativeNumbers " + "[,GroupDigits]]]])") @Description( "Returns an expression formatted as a currency value using the " + "currency symbol defined in the system control panel.") public static String formatCurrency( Object expression, int numDigitsAfterDecimal, int includeLeadingDigit, int useParensForNegativeNumbers, int groupDigits) { DecimalFormat format = (DecimalFormat) NumberFormat.getCurrencyInstance(); if (numDigitsAfterDecimal != -1) { format.setMaximumFractionDigits(numDigitsAfterDecimal); format.setMinimumFractionDigits(numDigitsAfterDecimal); } if (includeLeadingDigit != -2) { if (includeLeadingDigit != 0) { format.setMinimumIntegerDigits(1); } else { format.setMinimumIntegerDigits(0); } } if (useParensForNegativeNumbers != -2) { // todo: implement. // This will require tweaking of the currency expression } if (groupDigits != -2) { if (groupDigits != 0) { format.setGroupingUsed(false); } else { format.setGroupingUsed(true); } } return format.format(expression); } @FunctionName("FormatDateTime") @Signature("FormatDateTime(Date[,NamedFormat])") @Description("Returns an expression formatted as a date or time.") public static String formatDateTime(Date date) { return formatDateTime(date, 0); } @FunctionName("FormatDateTime") @Signature("FormatDateTime(Date[,NamedFormat])") @Description("Returns an expression formatted as a date or time.") public static String formatDateTime( Date date, int namedFormat /* default 0, GeneralDate */) { // todo: test // todo: how do we support VB Constants? Strings or Ints? switch (namedFormat) { // vbLongDate, 1 // Display a date using the long date format specified in your // computer's regional settings. case 1: return DateFormat.getDateInstance(DateFormat.LONG).format(date); // vbShortDate, 2 // Display a date using the short date format specified in your // computer's regional settings. case 2: return DateFormat.getDateInstance(DateFormat.SHORT).format(date); // vbLongTime, 3 // Display a time using the time format specified in your computer's // regional settings. case 3: return DateFormat.getTimeInstance(DateFormat.LONG).format(date); // vbShortTime, 4 // Display a time using the 24-hour format (hh:mm). case 4: return DateFormat.getTimeInstance(DateFormat.SHORT).format(date); // vbGeneralDate, 0 // Display a date and/or time. If there is a date part, // display it as a short date. If there is a time part, // display it as a long time. If present, both parts are // displayed. // // todo: how do we determine if there is a "time part" in java? case 0: default: return DateFormat.getDateTimeInstance().format(date); } } // Format is implemented with FormatFunDef, third and fourth params are not // supported // @FunctionName("Format") // @Signature("Format(expression[, format[, firstdayofweek[, // firstweekofyear]]])") // @Description("Returns a Variant (String) containing an expression // formatted according to instructions contained in a format expression.") @FunctionName("FormatNumber") @Signature( "FormatNumber(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit " + "[,UseParensForNegativeNumbers [,GroupDigits]]]])") @Description("Returns an expression formatted as a number.") public static String formatNumber(Object expression) { return formatNumber(expression, -1); } @FunctionName("FormatNumber") @Signature( "FormatNumber(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit " + "[,UseParensForNegativeNumbers [,GroupDigits]]]])") @Description("Returns an expression formatted as a number.") public static String formatNumber( Object expression, int numDigitsAfterDecimal) { return formatNumber(expression, numDigitsAfterDecimal, -1); } @FunctionName("FormatNumber") @Signature( "FormatNumber(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit " + "[,UseParensForNegativeNumbers [,GroupDigits]]]])") @Description("Returns an expression formatted as a number.") public static String formatNumber( Object expression, int numDigitsAfterDecimal, int includeLeadingDigit) { return formatNumber( expression, numDigitsAfterDecimal, includeLeadingDigit, -1); } @FunctionName("FormatNumber") @Signature( "FormatNumber(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit " + "[,UseParensForNegativeNumbers [,GroupDigits]]]])") @Description("Returns an expression formatted as a number.") public static String formatNumber( Object expression, int numDigitsAfterDecimal, int includeLeadingDigit, int useParensForNegativeNumbers) { return formatNumber( expression, numDigitsAfterDecimal, includeLeadingDigit, useParensForNegativeNumbers, -1); } @FunctionName("FormatNumber") @Signature( "FormatNumber(Expression[,NumDigitsAfterDecimal [,IncludeLeadingDigit " + "[,UseParensForNegativeNumbers [,GroupDigits]]]])") @Description("Returns an expression formatted as a number.") public static String formatNumber( Object expression, int numDigitsAfterDecimal /* default -1 */, int includeLeadingDigit /* default usedefault */, int useParensForNegativeNumbers /* default UseDefault */, int groupDigits /* default UseDefault */) { NumberFormat format = NumberFormat.getNumberInstance(); if (numDigitsAfterDecimal != -1) { format.setMaximumFractionDigits(numDigitsAfterDecimal); format.setMinimumFractionDigits(numDigitsAfterDecimal); } if (includeLeadingDigit != -1) { if (includeLeadingDigit != 0) { // true format.setMinimumIntegerDigits(1); } else { format.setMinimumIntegerDigits(0); } } if (useParensForNegativeNumbers != -1) { if (useParensForNegativeNumbers != 0) { DecimalFormat dformat = (DecimalFormat)format; dformat.setNegativePrefix("("); dformat.setNegativeSuffix(")"); } else { DecimalFormat dformat = (DecimalFormat)format; dformat.setNegativePrefix( "" + dformat.getDecimalFormatSymbols().getMinusSign()); dformat.setNegativeSuffix(""); } } if (groupDigits != -1) { format.setGroupingUsed(groupDigits != 0); } return format.format(expression); } @FunctionName("FormatPercent") @Signature( "FormatPercent(Expression[,NumDigitsAfterDecimal " + "[,IncludeLeadingDigit [,UseParensForNegativeNumbers " + "[,GroupDigits]]]])") @Description( "Returns an expression formatted as a percentage (multipled by 100) " + "with a trailing % character.") public static String formatPercent(Object expression) { return formatPercent(expression, -1); } @FunctionName("FormatPercent") @Signature( "FormatPercent(Expression[,NumDigitsAfterDecimal " + "[,IncludeLeadingDigit [,UseParensForNegativeNumbers " + "[,GroupDigits]]]])") @Description( "Returns an expression formatted as a percentage (multipled by 100) " + "with a trailing % character.") public static String formatPercent( // todo: impl & test Object expression, int numDigitsAfterDecimal /* default -1 */) { return formatPercent(expression, numDigitsAfterDecimal, -1); } @FunctionName("FormatPercent") @Signature( "FormatPercent(Expression[,NumDigitsAfterDecimal " + "[,IncludeLeadingDigit [,UseParensForNegativeNumbers " + "[,GroupDigits]]]])") @Description( "Returns an expression formatted as a percentage (multipled by 100) " + "with a trailing % character.") public static String formatPercent( // todo: impl & test Object expression, int numDigitsAfterDecimal /* default -1 */, int includeLeadingDigit /* default UseDefault */) { return formatPercent( expression, numDigitsAfterDecimal, includeLeadingDigit, -1); } @FunctionName("FormatPercent") @Signature( "FormatPercent(Expression[,NumDigitsAfterDecimal " + "[,IncludeLeadingDigit [,UseParensForNegativeNumbers " + "[,GroupDigits]]]])") @Description( "Returns an expression formatted as a percentage (multipled by 100) " + "with a trailing % character.") public static String formatPercent( // todo: impl & test Object expression, int numDigitsAfterDecimal /* default -1 */, int includeLeadingDigit /* default UseDefault */, int useParensForNegativeNumbers /* default UseDefault */) { return formatPercent( expression, numDigitsAfterDecimal, includeLeadingDigit, useParensForNegativeNumbers, -1); } @FunctionName("FormatPercent") @Signature( "FormatPercent(Expression[,NumDigitsAfterDecimal " + "[,IncludeLeadingDigit [,UseParensForNegativeNumbers " + "[,GroupDigits]]]])") @Description( "Returns an expression formatted as a percentage (multipled by 100) " + "with a trailing % character.") public static String formatPercent( Object expression, int numDigitsAfterDecimal /* default -1 */, int includeLeadingDigit /* default UseDefault */, int useParensForNegativeNumbers /* default UseDefault */, int groupDigits /* default UseDefault */) { NumberFormat format = NumberFormat.getPercentInstance(); if (numDigitsAfterDecimal != -1) { format.setMaximumFractionDigits(numDigitsAfterDecimal); format.setMinimumFractionDigits(numDigitsAfterDecimal); } if (includeLeadingDigit != -1) { if (includeLeadingDigit != 0) { // true format.setMinimumIntegerDigits(1); } else { format.setMinimumIntegerDigits(0); } } if (useParensForNegativeNumbers != -1) { if (useParensForNegativeNumbers != 0) { DecimalFormat dformat = (DecimalFormat)format; dformat.setNegativePrefix("("); dformat.setNegativeSuffix( "" + dformat.getDecimalFormatSymbols().getPercent() + ")"); } else { DecimalFormat dformat = (DecimalFormat)format; dformat.setNegativePrefix( "" + dformat.getDecimalFormatSymbols().getMinusSign()); dformat.setNegativeSuffix( "" + dformat.getDecimalFormatSymbols().getPercent()); } } if (groupDigits != -1) { format.setGroupingUsed(groupDigits != 0); } return format.format(expression); } // public Object inStrB(Object start, Object string1, Object string2, int // compare /* default BinaryCompare */) @FunctionName("InStr") @Signature("InStr([start, ]stringcheck, stringmatch[, compare])") @Description( "Returns a Variant (Long) specifying the position of the first " + "occurrence of one string within another.") public static int inStr(String stringCheck, String stringMatch) { return inStr(1, stringCheck, stringMatch, 0); } @FunctionName("InStr") @Signature("InStr([start, ]stringcheck, stringmatch[, compare])") @Description( "Returns the position of an occurrence of one string within " + "another.") public static int inStr( int start /* default 1 */, String stringCheck, String stringMatch) { return inStr(start, stringCheck, stringMatch, 0); } @FunctionName("InStr") @Signature("InStr([start, ]stringcheck, stringmatch[, compare])") @Description( "Returns the position of an occurrence of one string within " + "another.") public static int inStr( int start /* default 1 */, String stringCheck, String stringMatch, int compare /* default BinaryCompare */) { // todo: implement binary vs. text compare if (start == 0 || start < -1) { throw new InvalidArgumentException( "start must be -1 or a location in the string to start"); } if (start != -1) { return stringCheck.indexOf(stringMatch, start - 1) + 1; } else { return stringCheck.indexOf(stringMatch) + 1; } } @FunctionName("InStrRev") @Signature("InStrRev(stringcheck, stringmatch[, start[, compare]])") @Description( "Returns the position of an occurrence of one string within another, " + "from the end of string.") public static int inStrRev(String stringCheck, String stringMatch) { return inStrRev(stringCheck, stringMatch, -1); } @FunctionName("InStrRev") @Signature("InStrRev(stringcheck, stringmatch[, start[, compare]])") @Description( "Returns the position of an occurrence of one string within another, " + "from the end of string.") public static int inStrRev( String stringCheck, String stringMatch, int start /* default -1 */) { return inStrRev(stringCheck, stringMatch, start, 0); } @FunctionName("InStrRev") @Signature("InStrRev(stringcheck, stringmatch[, start[, compare]])") @Description( "Returns the position of an occurrence of one string within another, " + "from the end of string.") public static int inStrRev( String stringCheck, String stringMatch, int start /* default -1 */, int compare /* default BinaryCompare */) { // todo: implement binary vs. text compare if (start == 0 || start < -1) { throw new InvalidArgumentException( "start must be -1 or a location in the string to start"); } if (start != -1) { return stringCheck.lastIndexOf(stringMatch, start - 1) + 1; } else { return stringCheck.lastIndexOf(stringMatch) + 1; } } // public String join(Object sourceArray, Object delimiter) @FunctionName("LCase") @Signature("LCase(string)") @Description("Returns a String that has been converted to lowercase.") public static String lCase(String string) { return string.toLowerCase(); } // public Object lCase$(Object string) // public String lTrim$(String string) @FunctionName("LTrim") @Signature("LTrim(string)") @Description( "Returns a Variant (String) containing a copy of a specified string " + "without leading spaces.") public static String lTrim(String string) { int i = 0, n = string.length(); while (i < n) { if (string.charAt(i) > ' ') { break; } i++; } return string.substring(i); } // public String left$(String string, int length) // public String leftB$(String string, int length) // public Object leftB(Object string, int length) @FunctionName("Left") @Signature("Left(string, length)") @Description( "Returns a specified number of characters from the left side of a " + "string.") public static String left(String string, int length) { final int stringLength = string.length(); if (length >= stringLength) { return string; } return string.substring(0, length); } // public Object lenB(Object expression) // len is already implemented in BuiltinFunTable... defer // @FunctionName("Len") // @Signature("Len(String)") // @Description("Returns a Long containing the number of characters in a // string.") // public static int len(String expression) { // return expression.length(); // } // public String mid$(String string, int start, Object length) // public String midB$(String string, int start, Object length) // public Object midB(Object string, int start, Object length) @FunctionName("Mid") @Signature("Mid(value, beginIndex[, length])") @Description("Returns a specified number of characters from a string.") public static String mid(String value, int beginIndex) { // If we used 'value.length() - beginIndex' as the default value for // length, we'd have problems if beginIndex is huge; // so 'value.length()' looks like an overestimate - but will always // return the correct result. final int length = value.length(); return mid(value, beginIndex, length); } @FunctionName("Mid") @Signature("Mid(value, beginIndex[, length])") @Description("Returns a specified number of characters from a string.") public static String mid(String value, int beginIndex, int length) { // Arguments are 1-based. Spec says that the function gives an error if // Start <= 0 or Length < 0. if (beginIndex <= 0) { throw new InvalidArgumentException( "Invalid parameter. " + "Start parameter of Mid function must be positive"); } if (length < 0) { throw new InvalidArgumentException( "Invalid parameter. " + "Length parameter of Mid function must be non-negative"); } if (beginIndex > value.length()) { return ""; } // Shift from 1-based to 0-based. --beginIndex; int endIndex = beginIndex + length; return endIndex >= value.length() ? value.substring(beginIndex) : value .substring(beginIndex, endIndex); } @FunctionName("MonthName") @Signature("MonthName(month, abbreviate)") @Description("Returns a string indicating the specified month.") public static String monthName(int month, boolean abbreviate) { // VB months are 1-based, Java months are 0-based --month; return (abbreviate ? getDateFormatSymbols().getShortMonths() : getDateFormatSymbols().getMonths())[month]; } /** * Returns an instance of {@link DateFormatSymbols} for the current locale. * *

    * Todo: inherit locale from connection. * * @return a DateFormatSymbols object */ private static DateFormatSymbols getDateFormatSymbols() { // We would use DataFormatSymbols.getInstance(), but it is only // available from JDK 1.6 onwards. return DATE_FORMAT_SYMBOLS; } // public String rTrim$(String string) @FunctionName("RTrim") @Signature("RTrim(string)") @Description( "Returns a Variant (String) containing a copy of a specified string " + "without trailing spaces.") public static String rTrim(String string) { int i = string.length() - 1; while (i >= 0) { if (string.charAt(i) > ' ') { break; } i--; } return string.substring(0, i + 1); } @FunctionName("Replace") @Signature( "Replace(expression, find, replace[, start[, count[, compare]]])") @Description( "Returns a string in which a specified substring has been replaced " + "with another substring a specified number of times.") public static String replace( String expression, String find, String replace, int start, int count, int compare) { // compare is currently ignored Util.discard(compare); return _replace(expression, find, replace, start, count); } @FunctionName("Replace") @Signature( "Replace(expression, find, replace[, start[, count[, compare]]])") @Description( "Returns a string in which a specified substring has been replaced " + "with another substring a specified number of times.") public static String replace( String expression, String find, String replace, int start, int count) { return _replace(expression, find, replace, start, count); } @FunctionName("Replace") @Signature( "Replace(expression, find, replace[, start[, count[, compare]]])") @Description( "Returns a string in which a specified substring has been replaced " + "with another substring a specified number of times.") public static String replace( String expression, String find, String replace, int start) { return _replace(expression, find, replace, start, -1); } @FunctionName("Replace") @Signature( "Replace(expression, find, replace[, start[, count[, compare]]])") @Description("") public static String replace( String expression, String find, String replace) { // compare is currently ignored return _replace(expression, find, replace, 1, -1); } private static String _replace( String expression, String find, String replace, int start /* default 1 */, int count /* default -1 */) { final StringBuilder buf = new StringBuilder(expression); int i = 0; int pos = start - 1; while (true) { if (i++ == count) { break; } final int j = buf.indexOf(find, pos); if (j == -1) { break; } buf.replace(j, j + find.length(), replace); pos = j + replace.length(); } return buf.toString(); } // public String right$(String string, int length) // public String rightB$(String string, int length) // public Object rightB(Object string, int length) @FunctionName("Right") @Signature("Right(string, length)") @Description( "Returns a Variant (String) containing a specified number of " + "characters from the right side of a string.") public static String right(String string, int length) { final int stringLength = string.length(); if (length >= stringLength) { return string; } return string.substring(stringLength - length, stringLength); } // public String space$(int number) @FunctionName("Space") @Signature("Space(number)") @Description( "Returns a Variant (String) consisting of the specified number of " + "spaces.") public static String space(int number) { return string(number, ' '); } // public Object split(String expression, Object delimiter, int limit /* // default -1 */, int compare /* default BinaryCompare */) @FunctionName("StrComp") @Signature("StrComp(string1, string2[, compare])") @Description( "Returns a Variant (Integer) indicating the result of a string " + "comparison.") public static int strComp(String string1, String string2) { return strComp(string1, string2, 0); } @FunctionName("StrComp") @Signature("StrComp(string1, string2[, compare])") @Description( "Returns a Variant (Integer) indicating the result of a string " + "comparison.") public static int strComp( String string1, String string2, int compare /* default BinaryCompare */) { // Note: compare is currently ignored // Wrapper already checked whether args are null assert string1 != null; assert string2 != null; return string1.compareTo(string2); } // public Object strConv(Object string, StrConv conversion, int localeID) @FunctionName("StrReverse") @Signature("StrReverse(string)") @Description( "Returns a string in which the character order of a specified string " + "is reversed.") public static String strReverse(String expression) { final char[] chars = expression.toCharArray(); for (int i = 0, j = chars.length - 1; i < j; i++, j--) { char c = chars[i]; chars[i] = chars[j]; chars[j] = c; } return new String(chars); } // public String string$(int number, Object character) @FunctionName("String") @Signature("String(number, character)") @Description("") public static String string(int number, char character) { if (character == 0) { return ""; } final char[] chars = new char[number]; Arrays.fill(chars, (char) (character % 256)); return new String(chars); } // public String trim$(String string) @FunctionName("Trim") @Signature("Trim(string)") @Description( "Returns a Variant (String) containing a copy of a specified string " + "without leading and trailing spaces.") public static String trim(String string) { // JDK has a method for trim, but not ltrim or rtrim return string.trim(); } // ucase is already implemented in BuiltinFunTable... defer // public String uCase$(String string) // @FunctionName("UCase") // @Signature("UCase(string)") // @Description("Returns a String that has been converted to uppercase.") // public String uCase(String string) { // return string.toUpperCase(); // } // TODO: should use connection's locale to determine first day of week, // not the JVM's default @FunctionName("WeekdayName") @Signature("WeekdayName(weekday, abbreviate, firstdayofweek)") @Description("Returns a string indicating the specified day of the week.") public static String weekdayName( int weekday, boolean abbreviate, int firstDayOfWeek) { // Java and VB agree: SUNDAY = 1, ... SATURDAY = 7 final Calendar calendar = Calendar.getInstance(); if (firstDayOfWeek == 0) { firstDayOfWeek = calendar.getFirstDayOfWeek(); } // compensate for start of week weekday += (firstDayOfWeek - 1); // bring into range 1..7 weekday = (weekday - 1) % 7 + 1; if (weekday <= 0) { // negative numbers give negative modulo weekday += 7; } return (abbreviate ? getDateFormatSymbols().getShortWeekdays() : getDateFormatSymbols().getWeekdays()) [weekday]; } // Misc // public Object array(Object argList) // public String input$(int number, int fileNumber) // public String inputB$(int number, int fileNumber) // public Object inputB(int number, int fileNumber) // public Object input(int number, int fileNumber) // public void width(int fileNumber, int width) // ~ Inner classes private enum Interval { yyyy("Year", Calendar.YEAR), q("Quarter", -1), m("Month", Calendar.MONTH), y("Day of year", Calendar.DAY_OF_YEAR), d("Day", Calendar.DAY_OF_MONTH), w("Weekday", Calendar.DAY_OF_WEEK), ww("Week", Calendar.WEEK_OF_YEAR), h("Hour", Calendar.HOUR_OF_DAY), n("Minute", Calendar.MINUTE), s("Second", Calendar.SECOND); private final int dateField; Interval(String desc, int dateField) { Util.discard(desc); this.dateField = dateField; } void add(Calendar calendar, int amount) { switch (this) { case q: calendar.add(Calendar.MONTH, amount * 3); break; default: calendar.add(dateField, amount); break; } } Calendar floor(Calendar calendar) { Calendar calendar2 = Calendar.getInstance(); calendar2.setTime(calendar.getTime()); floorInplace(calendar2); return calendar2; } private void floorInplace(Calendar calendar) { switch (this) { case yyyy: calendar.set(Calendar.DAY_OF_YEAR, 1); d.floorInplace(calendar); break; case q: int month = calendar.get(Calendar.MONTH); month -= month % 3; calendar.set(Calendar.MONTH, month); calendar.set(Calendar.DAY_OF_MONTH, 1); d.floorInplace(calendar); break; case m: calendar.set(Calendar.DAY_OF_MONTH, 1); d.floorInplace(calendar); break; case w: final int dow = calendar.get(Calendar.DAY_OF_WEEK); final int firstDayOfWeek = calendar.getFirstDayOfWeek(); if (dow == firstDayOfWeek) { // nothing to do } else if (dow > firstDayOfWeek) { final int roll = firstDayOfWeek - dow; assert roll < 0; calendar.roll(Calendar.DAY_OF_WEEK, roll); } else { final int roll = firstDayOfWeek - dow - 7; assert roll < 0; calendar.roll(Calendar.DAY_OF_WEEK, roll); } d.floorInplace(calendar); break; case y: case d: calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); break; case h: calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); break; case n: calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); break; case s: calendar.set(Calendar.MILLISECOND, 0); break; } } int diff(Calendar calendar1, Calendar calendar2, int firstDayOfWeek) { switch (this) { case q: return m.diff(calendar1, calendar2, firstDayOfWeek) / 3; default: return floor(calendar1).get(dateField) - floor(calendar2).get(dateField); } } int datePart(Calendar calendar) { switch (this) { case q: return (m.datePart(calendar) + 2) / 3; case m: return calendar.get(dateField) + 1; case w: int dayOfWeek = calendar.get(dateField); dayOfWeek -= (calendar.getFirstDayOfWeek() - 1); dayOfWeek = dayOfWeek % 7; if (dayOfWeek <= 0) { dayOfWeek += 7; } return dayOfWeek; default: return calendar.get(dateField); } } } private enum FirstWeekOfYear { vbUseSystem( 0, "Use the NLS API setting."), vbFirstJan1( 1, "Start with week in which January 1 occurs (default)."), vbFirstFourDays( 2, "Start with the first week that has at least four days in the new year."), vbFirstFullWeek( 3, "Start with first full week of the year."); FirstWeekOfYear(int code, String desc) { assert code == ordinal(); assert desc != null; } void apply(Calendar calendar) { switch (this) { case vbUseSystem: break; case vbFirstJan1: calendar.setMinimalDaysInFirstWeek(1); break; case vbFirstFourDays: calendar.setMinimalDaysInFirstWeek(4); break; case vbFirstFullWeek: calendar.setMinimalDaysInFirstWeek(7); break; } } } } // End Vba.java mondrian-3.4.1/src/main/mondrian/olap/fun/vba/package.html0000644000175000017500000000020111735330606023315 0ustar drazzibdrazzib Implements the set of functions defined by the Visual Basic for Applications (VBA) specification. mondrian-3.4.1/src/main/mondrian/olap/fun/vba/Excel.java0000644000175000017500000013670211735330606022757 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2008-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.fun.vba; import mondrian.olap.InvalidArgumentException; import mondrian.olap.fun.JavaFunDef; import static mondrian.olap.fun.JavaFunDef.Description; import static mondrian.olap.fun.JavaFunDef.FunctionName; /** * Implementations of functions in the Excel worksheet library. * *

    Functions are loaded into the function table by reflection. * * @author jhyde * @since Dec 31, 2007 */ public abstract class Excel { // There follows a list of all functions defined in Excel. Functions are // marked 'todo:' if they still need to be implemented; 'skip:' if they // are implemented elsewhere, such as in Vba or there there is an explicit // implementation of FunDef for them. A FunDef typically allows a more // efficient implementation. // AccrInt Returns the accrued interest for a security that pays periodic // interest. // AccrIntM Returns the accrued interest for a security that pays interest // at maturity. @FunctionName("Acos") @JavaFunDef.Signature("Acos(number)") @Description( "Returns the arccosine, or inverse cosine, of a number. The arccosine " + "is the angle whose cosine is Arg1. The returned angle is given in " + "radians in the range 0 (zero) to pi.") public static double acos(double number) { return Math.acos(number); } @FunctionName("Acosh") @JavaFunDef.Signature("Acosh(number)") @Description( "Returns the inverse hyperbolic cosine of a number. Number must be " + "greater than or equal to 1. The inverse hyperbolic cosine is the " + "value whose hyperbolic cosine is Arg1, so Acosh(Cosh(number)) " + "equals Arg1.") public static double acosh(double x) { return Math.log(x + Math.sqrt((x * x) - 1.0)); } // Todo: AmorDegrc Returns the depreciation for each accounting // period. This function is provided for the French accounting // system. // Todo: AmorLinc Returns the depreciation for each accounting // period. This function is provided for the French accounting // system. // Skip: And Returns TRUE if all its arguments are TRUE; returns // FALSE if one or more argument is FALSE. // Todo: Asc For Double-byte character set (DBCS) languages, // changes full-width (double-byte) characters to half-width // (single-byte) characters. @FunctionName("Asin") @JavaFunDef.Signature("Asin(number)") @Description( "Returns the arcsine, or inverse sine, of a number. The arcsine is the " + "angle whose sine is Arg1. The returned angle is given in radians in " + "the range -pi/2 to pi/2.") public static double asin(double number) { return Math.asin(number); } @FunctionName("Asinh") @JavaFunDef.Signature("Asinh(number)") @Description( "Returns the inverse hyperbolic sine of a number. The inverse " + "hyperbolic sine is the value whose hyperbolic sine is Arg1, " + "so Asinh(Sinh(number)) equals Arg1.") public static double asinh(double x) { return Math.log(x + Math.sqrt(1.0 + (x * x))); } @FunctionName("Atan2") @JavaFunDef.Signature("Atan2(x, y)") @Description( "Returns the arctangent, or inverse tangent, of the specified x- and " + "y-coordinates. The arctangent is the angle from the x-axis to a " + "line containing the origin (0, 0) and a point with coordinates " + "(x_num, y_num). The angle is given in radians between -pi and pi, " + "excluding -pi.") public static double atan2(double y, double x) { return Math.atan2(y, x); } @FunctionName("Atanh") @JavaFunDef.Signature("Atanh(number)") @Description( "Returns the inverse hyperbolic tangent of a number. Number " + "must be between -1 and 1 (excluding -1 and 1).") public static double atanh(double x) { return .5 * Math.log((1.0 + x) / (1.0 - x)); } // Todo: AveDev Returns the average of the absolute deviations of data // points from their mean. AveDev is a measure of the variability in a data // set. // Todo: Average Returns the average (arithmetic mean) of the arguments. // Todo: AverageIf Returns the average (arithmetic mean) of all the cells in // a range that meet a given criteria. // Todo: AverageIfs Returns the average (arithmetic mean) of all cells that // meet multiple criteria. // Todo: BahtText Converts a number to Thai text and adds a suffix of // "Baht." // Todo: BesselI Returns the modified Bessel function, which is equivalent // to the Bessel function evaluated for purely imaginary arguments. // Todo: BesselJ Returns the Bessel function. // Todo: BesselK Returns the modified Bessel function, which is equivalent // to the Bessel functions evaluated for purely imaginary arguments. // Todo: BesselY Returns the Bessel function, which is also called the Weber // function or the Neumann function. // Todo: BetaDist Returns the beta cumulative distribution function. // Todo: BetaInv Returns the inverse of the cumulative distribution function // for a specified beta distribution. That is, if probability = // BetaDist(x,...), then BetaInv(probability,...) = x. // Todo: Bin2Dec Converts a binary number to decimal. // Todo: Bin2Hex Converts a binary number to hexadecimal. // Todo: Bin2Oct Converts a binary number to octal. // Todo: BinomDist Returns the individual term binomial distribution // probability. // Todo: Ceiling Returns number rounded up, away from zero, to the nearest // multiple of significance. // Todo: ChiDist Returns the one-tailed probability of the chi-squared // distribution. // Todo: ChiInv Returns the inverse of the one-tailed probability of the // chi-squared distribution. // Todo: ChiTest Returns the test for independence. // Todo: Choose Uses Arg1 as the index to return a value from the list of // value arguments. // Todo: Clean Removes all nonprintable characters from text. // Todo: Combin Returns the number of combinations for a given number of // items. Use Combin to determine the total possible number of groups for a // given number of items. // Todo: Complex Converts real and imaginary coefficients into a complex // number of the form x + yi or x + yj. // Todo: Confidence Returns a value that you can use to construct a // confidence interval for a population mean. // Todo: Convert Converts a number from one measurement system to // another. For example, Convert can translate a table of distances in miles // to a table of distances in kilometers. // Todo: Correl Returns the correlation coefficient of the Arg1 and Arg2 // cell ranges. @FunctionName("Cosh") @Description("Returns the hyperbolic cosine of a number.") public static double cosh(double number) { return Math.cosh(number); } // Todo: Count Counts the number of cells that contain numbers and counts // numbers within the list of arguments. // Todo: CountA Counts the number of cells that are not empty and the values // within the list of arguments. // Todo: CountBlank Counts empty cells in a specified range of cells. // Todo: CountIf Counts the number of cells within a range that meet the // given criteria. // Todo: CountIfs Counts the number of cells within a range that meet // multiple criteria. // Todo: CoupDayBs Returns the number of days from the beginning of the // coupon period to the settlement date. // Todo: CoupDays Returns the number of days in the coupon period that // contains the settlement date. // Todo: CoupDaysNc Returns the number of days from the settlement date to // the next coupon date. // Todo: CoupNcd Returns a number that represents the next coupon date after // the settlement date. // Todo: CoupNum Returns the number of coupons payable between the // settlement date and maturity date, rounded up to the nearest whole // coupon. // Todo: CoupPcd The description for this item will appear in the final // release of Office 2007. // Todo: Covar Returns covariance, the average of the products of deviations // for each data point pair. // Todo: CritBinom Returns the smallest value for which the cumulative // binomial distribution is greater than or equal to a criterion value. // Todo: CumIPmt Returns the cumulative interest paid on a loan between // start_period and end_period. // Todo: CumPrinc Returns the cumulative principal paid on a loan between // start_period and end_period. // Todo: DAverage Averages the values in a column of a list or database that // match conditions you specify. // Todo: Days360 Returns the number of days between two dates based on a // 360-day year (twelve 30-day months), which is used in some accounting // calculations. // Todo: Db Returns the depreciation of an asset for a specified period // using the fixed-declining balance method. // Todo: Dbcs The description for this item will appear in the final release // of Office 2007. // Todo: DCount Counts the cells that contain numbers in a column of a list // or database that match conditions that you specify. // Todo: DCountA Counts the nonblank cells in a column of a list or database // that match conditions that you specify. // Todo: Ddb Returns the depreciation of an asset for a specified period // using the double-declining balance method or some other method you // specify. // Todo: Dec2Bin Converts a decimal number to binary. // Todo: Dec2Hex Converts a decimal number to hexadecimal. // Todo: Dec2Oct Converts a decimal number to octal. // Todo: Degrees Converts radians into degrees. @FunctionName("Degrees") @Description("Converts radians to degrees.") public static double degrees(double number) { // 180 degrees = Pi radians return number * 180.0 / Math.PI; } // Todo: Delta Tests whether two values are equal. Returns 1 if number1 = // number2; returns 0 otherwise. // Todo: DevSq Returns the sum of squares of deviations of data points from // their sample mean. // Todo: DGet Extracts a single value from a column of a list or database // that matches conditions that you specify. // Todo: Disc Returns the discount rate for a security. // Todo: DMax Returns the largest number in a column of a list or database // that matches conditions you that specify. // Todo: DMin Returns the smallest number in a column of a list or database // that matches conditions that you specify. // Todo: Dollar The function described in this Help topic converts a number // to text format and applies a currency symbol. The name of the function // (and the symbol that it applies) depends upon your language settings. // Todo: DollarDe Converts a dollar price expressed as a fraction into a // dollar price expressed as a decimal number. Use DOLLARDE to convert // fractional dollar numbers, such as securities prices, to decimal numbers. // Todo: DollarFr Converts a dollar price expressed as a decimal number into // a dollar price expressed as a fraction. Use DOLLARFR to convert decimal // numbers to fractional dollar numbers, such as securities prices. // Todo: DProduct Multiplies the values in a column of a list or database // that match conditions that you specify. // Todo: DStDev Estimates the standard deviation of a population based on a // sample by using the numbers in a column of a list or database that match // conditions that you specify. // Todo: DStDevP Calculates the standard deviation of a population based on // the entire population by using the numbers in a column of a list or // database that match conditions that you specify. // Todo: DSum Adds the numbers in a column of a list or database that match // conditions that you specify. // Todo: Duration Returns the Macauley duration for an assumed par value of // $100. Duration is defined as the weighted average of the present value of // the cash flows and is used as a measure of a bond price's response to // changes in yield. // Todo: DVar Estimates the variance of a population based on a sample by // using the numbers in a column of a list or database that match conditions // that you specify. // Todo: DVarP Calculates the variance of a population based on the entire // population by using the numbers in a column of a list or database that // match conditions that you specify. // Todo: EDate Returns the serial number that represents the date that is // the indicated number of months before or after a specified date (the // start_date). Use EDATE to calculate maturity dates or due dates that fall // on the same day of the month as the date of issue. // Todo: Effect Returns the effective annual interest rate, given the // nominal annual interest rate and the number of compounding periods per // year. // Todo: EoMonth Returns the serial number for the last day of the month // that is the indicated number of months before or after start_date. Use // EOMONTH to calculate maturity dates or due dates that fall on the last // day of the month. // Todo: Erf Returns the error function integrated between lower_limit and // upper_limit. // Todo: ErfC The description for this item will appear in the final release // of Office 2007. // Todo: Even Returns number rounded up to the nearest even integer. You can // use this function for processing items that come in twos. For example, a // packing crate accepts rows of one or two items. The crate is full when // the number of items, rounded up to the nearest two, matches the crate's // capacity. // Todo: ExponDist Returns the exponential distribution. Use EXPONDIST to // model the time between events, such as how long an automated bank teller // takes to deliver cash. For example, you can use EXPONDIST to determine // the probability that the process takes at most 1 minute. // Todo: Fact Returns the factorial of a number. The factorial of a number // is equal to 1*2*3*...* number. // Todo: FactDouble Returns the double factorial of a number. // Todo: FDist Returns the F probability distribution. You can use this // function to determine whether two data sets have different degrees of // diversity. For example, you can examine the test scores of men and women // entering high school and determine if the variability in the females is // different from that found in the males. // Todo: Find Finds specific information in a worksheet. // Todo: FindB FIND and FINDB locate one text string within a second text // string, and return the number of the starting position of the first text // string from the first character of the second text string. // Todo: FInv Returns the inverse of the F probability distribution. If p = // FDIST(x,...), then FINV(p,...) = x. // Todo: Fisher Returns the Fisher transformation at x. This transformation // produces a function that is normally distributed rather than skewed. Use // this function to perform hypothesis testing on the correlation // coefficient. // Todo: FisherInv Returns the inverse of the Fisher transformation. Use // this transformation when analyzing correlations between ranges or arrays // of data. If y = FISHER(x), then FISHERINV(y) = x. // Todo: Fixed Rounds a number to the specified number of decimals, formats // the number in decimal format using a period and commas, and returns the // result as text. // Todo: Floor Rounds number down, toward zero, to the nearest multiple of // significance. // Todo: Forecast Calculates, or predicts, a future value by using existing // values. The predicted value is a y-value for a given x-value. The known // values are existing x-values and y-values, and the new value is predicted // by using linear regression. You can use this function to predict future // sales, inventory requirements, or consumer trends. // Todo: Frequency Calculates how often values occur within a range of // values, and then returns a vertical array of numbers. For example, use // FREQUENCY to count the number of test scores that fall within ranges of // scores. Because FREQUENCY returns an array, it must be entered as an // array formula. // Todo: FTest Returns the result of an F-test. An F-test returns the // two-tailed probability that the variances in array1 and array2 are not // significantly different. Use this function to determine whether two // samples have different variances. For example, given test scores from // public and private schools, you can test whether these schools have // different levels of test score diversity. // Todo: Fv Returns the future value of an investment based on periodic, // constant payments and a constant interest rate. // Todo: FVSchedule Returns the future value of an initial principal after // applying a series of compound interest rates. Use FVSCHEDULE to calculate // the future value of an investment with a variable or adjustable rate. // Todo: GammaDist Returns the gamma distribution. You can use this function // to study variables that may have a skewed distribution. The gamma // distribution is commonly used in queuing analysis. // Todo: GammaInv Returns the inverse of the gamma cumulative // distribution. If p = GAMMADIST(x,...), then GAMMAINV(p,...) = x. // Todo: GammaLn Returns the natural logarithm of the gamma function, ?(x). // Todo: Gcd Returns the greatest common divisor of two or more // integers. The greatest common divisor is the largest integer that divides // both number1 and number2 without a remainder. // Todo: GeoMean Returns the geometric mean of an array or range of positive // data. For example, you can use GEOMEAN to calculate average growth rate // given compound interest with variable rates. // Todo: GeStep Returns 1 if number ? step; returns 0 (zero) otherwise. Use // this function to filter a set of values. For example, by summing several // GESTEP functions you calculate the count of values that exceed a // threshold. // Todo: Growth Calculates predicted exponential growth by using existing // data. GROWTH returns the y-values for a series of new x-values that you // specify by using existing x-values and y-values. You can also use the // GROWTH worksheet function to fit an exponential curve to existing // x-values and y-values. // Todo: HarMean Returns the harmonic mean of a data set. The harmonic mean // is the reciprocal of the arithmetic mean of reciprocals. // Todo: Hex2Bin Converts a hexadecimal number to binary. // Todo: Hex2Dec Converts a hexadecimal number to decimal. // Todo: Hex2Oct Converts a hexadecimal number to octal. // Todo: HLookup Searches for a value in the top row of a table or an array // of values, and then returns a value in the same column from a row you // specify in the table or array. Use HLOOKUP when your comparison values // are located in a row across the top of a table of data, and you want to // look down a specified number of rows. Use VLOOKUP when your comparison // values are located in a column to the left of the data you want to find. // Todo: HypGeomDist Returns the hypergeometric distribution. HYPGEOMDIST // returns the probability of a given number of sample successes, given the // sample size, population successes, and population size. Use HYPGEOMDIST // for problems with a finite population, where each observation is either a // success or a failure, and where each subset of a given size is chosen // with equal likelihood. // Todo: IfError Returns a value you specify if a formula evaluates to an // error; otherwise, returns the result of the formula. Use the IFERROR // function to trap and handle errors in a formula. // Todo: ImAbs Returns the absolute value (modulus) of a complex number in x // + yi or x + yj text format. // Todo: Imaginary Returns the imaginary coefficient of a complex number in // x + yi or x + yj text format. // Todo: ImArgument Returns the argument (theta), an angle expressed in // radians, such that: // Todo: ImConjugate Returns the complex conjugate of a complex number in x // + yi or x + yj text format. // Todo: ImCos Returns the cosine of a complex number in x + yi or x + yj // text format. // Todo: ImDiv Returns the quotient of two complex numbers in x + yi or x + // yj text format. // Todo: ImExp Returns the exponential of a complex number in x + yi or x + // yj text format. // Todo: ImLn Returns the natural logarithm of a complex number in x + yi or // x + yj text format. // Todo: ImLog10 Returns the common logarithm (base 10) of a complex number // in x + yi or x + yj text format. // Todo: ImLog2 Returns the base-2 logarithm of a complex number in x + yi // or x + yj text format. // Todo: ImPower Returns a complex number in x + yi or x + yj text format // raised to a power. // Todo: ImProduct Returns the product of 2 to 29 complex numbers in x + yi // or x + yj text format. // Todo: ImReal Returns the real coefficient of a complex number in x + yi // or x + yj text format. // Todo: ImSin Returns the sine of a complex number in x + yi or x + yj text // format. // Todo: ImSqrt Returns the square root of a complex number in x + yi or x + // yj text format. // Todo: ImSub Returns the difference of two complex numbers in x + yi or x // + yj text format. // Todo: ImSum Returns the sum of two or more complex numbers in x + yi or x // + yj text format. // Todo: Index Returns a value or the reference to a value from within a // table or range. There are two forms of the INDEX function: the array form // and the reference form. // Todo: Intercept Calculates the point at which a line will intersect the // y-axis by using existing x-values and y-values. The intercept point is // based on a best-fit regression line plotted through the known x-values // and known y-values. Use the INTERCEPT function when you want to determine // the value of the dependent variable when the independent variable is 0 // (zero). For example, you can use the INTERCEPT function to predict a // metal's electrical resistance at 0C when your data points were taken at // room temperature and higher. // Todo: IntRate Returns the interest rate for a fully invested security. // Todo: Ipmt Returns the interest payment for a given period for an // investment based on periodic, constant payments and a constant interest // rate. // Todo: Irr Returns the internal rate of return for a series of cash flows // represented by the numbers in values. These cash flows do not have to be // even, as they would be for an annuity. However, the cash flows must occur // at regular intervals, such as monthly or annually. The internal rate of // return is the interest rate received for an investment consisting of // payments (negative values) and income (positive values) that occur at // regular periods. // Todo: IsErr Checks the type of value and returns TRUE or FALSE depending // if the value refers to any error value except #N/A. // Todo: IsError Checks the type of value and returns TRUE or FALSE // depending if the value refers to any error value (#N/A, #VALUE!, #REF!, // #DIV/0!, #NUM!, #NAME?, or #NULL!). // Todo: IsEven Checks the type of value and returns TRUE or FALSE depending // if the value is even. // Todo: IsLogical Checks the type of value and returns TRUE or FALSE // depending if the value refers to a logical value. // Todo: IsNA Checks the type of value and returns TRUE or FALSE depending // if the value refers to the #N/A (value not available) error value. // Todo: IsNonText Checks the type of value and returns TRUE or FALSE // depending if the value refers to any item that is not text. (Note that // this function returns TRUE if value refers to a blank cell.) // Todo: IsNumber Checks the type of value and returns TRUE or FALSE // depending if the value refers to a number. // Todo: IsOdd Checks the type of value and returns TRUE or FALSE depending // if the value is odd. // Todo: Ispmt Calculates the interest paid during a specific period of an // investment. This function is provided for compatibility with Lotus 1-2-3. // Todo: IsText Checks the type of value and returns TRUE or FALSE depending // if the value refers to text. // Todo: Kurt Returns the kurtosis of a data set. Kurtosis characterizes the // relative peakedness or flatness of a distribution compared with the // normal distribution. Positive kurtosis indicates a relatively peaked // distribution. Negative kurtosis indicates a relatively flat distribution. // Todo: Large Returns the k-th largest value in a data set. You can use // this function to select a value based on its relative standing. For // example, you can use LARGE to return the highest, runner-up, or // third-place score. // Todo: Lcm Returns the least common multiple of integers. The least common // multiple is the smallest positive integer that is a multiple of all // integer arguments number1, number2, and so on. Use LCM to add fractions // with different denominators. // Todo: LinEst Calculates the statistics for a line by using the "least // squares" method to calculate a straight line that best fits your data, // and returns an array that describes the line. Because this function // returns an array of values, it must be entered as an array formula. // Todo: Ln Returns the natural logarithm of a number. Natural logarithms // are based on the constant e (2.71828182845904). // See Vba // Skip: Log Returns the logarithm of a number to the base you specify. @FunctionName("Log10") @Description("Returns the base-10 logarithm of a number.") public static double log10(double number) { return Math.log10(number); } // Todo: LogEst In regression analysis, calculates an exponential curve that // fits your data and returns an array of values that describes the // curve. Because this function returns an array of values, it must be // entered as an array formula. // Todo: LogInv Use the lognormal distribution to analyze logarithmically // transformed data. // Todo: LogNormDist Returns the cumulative lognormal distribution of x, // where ln(x) is normally distributed with parameters mean and // standard_dev. Use this function to analyze data that has been // logarithmically transformed. // Todo: Lookup Returns a value either from a one-row or one-column range or // from an array. The LOOKUP function has two syntax forms: the vector form // and the array form. // Todo: Match Returns the relative position of an item in an array that // matches a specified value in a specified order. Use MATCH instead of one // of the LOOKUP functions when you need the position of an item in a range // instead of the item itself. // Skip: Max Returns the largest value in a set of values. Todo: MDeterm // Returns the matrix determinant of an array. /** * The MOD function. Not technically in the Excel package, but this seemed * like a good place to put it, since Excel has a MOD function. * * @param first First * @param second Second * @return First modulo second */ @FunctionName("Mod") @JavaFunDef.Signature("Mod(n, d)") @Description("Returns the remainder of dividing n by d.") public static double mod( Object first, Object second) { double iFirst; if (!(first instanceof Number)) { throw new InvalidArgumentException( "Invalid parameter. " + "first parameter " + first + " of Mod function must be of type number"); } else { iFirst = ((Number) first).doubleValue(); } double iSecond; if (!(second instanceof Number)) { throw new InvalidArgumentException( "Invalid parameter. " + "second parameter " + second + " of Mod function must be of type number"); } else { iSecond = ((Number) second).doubleValue(); } // Use formula "mod(n, d) = n - d * int(n / d)". if (iSecond == 0) { throw new ArithmeticException("/ by zero"); } return iFirst - iSecond * Vba.intNative(iFirst / iSecond); } // Todo: MDuration Returns the modified Macauley duration for a security // with an assumed par value of $100. // Skip: Median Returns the median of the given numbers. The median is the // number in the middle of a set of numbers. Skip: Min Returns the smallest // number in a set of values. Todo: MInverse Returns the inverse matrix for // the matrix stored in an array. // Todo: MIrr Returns the modified internal rate of return for a series of // periodic cash flows. MIRR considers both the cost of the investment and // the interest received on reinvestment of cash. // Todo: MMult Returns the matrix product of two arrays. The result is an // array with the same number of rows as array1 and the same number of // columns as array2. // Todo: Mode Returns the most frequently occurring, or repetitive, value in // an array or range of data. // Todo: MRound Returns a number rounded to the desired multiple. // Todo: MultiNomial Returns the ratio of the factorial of a sum of values // to the product of factorials. // Todo: NegBinomDist Returns the negative binomial // distribution. NEGBINOMDIST returns the probability that there will be // number_f failures before the number_s-th success, when the constant // probability of a success is probability_s. This function is similar to // the binomial distribution, except that the number of successes is fixed, // and the number of trials is variable. Like the binomial, trials are // assumed to be independent. // Todo: NetworkDays Returns the number of whole working days between // start_date and end_date. Working days exclude weekends and any dates // identified in holidays. Use NETWORKDAYS to calculate employee benefits // that accrue based on the number of days worked during a specific term. // Todo: Nominal Returns the nominal annual interest rate, given the // effective rate and the number of compounding periods per year. // Todo: NormDist Returns the normal distribution for the specified mean and // standard deviation. This function has a very wide range of applications // in statistics, including hypothesis testing. // Todo: NormInv Returns the inverse of the normal cumulative distribution // for the specified mean and standard deviation. // Todo: NormSDist Returns the standard normal cumulative distribution // function. The distribution has a mean of 0 (zero) and a standard // deviation of one. Use this function in place of a table of standard // normal curve areas. // Todo: NormSInv Returns the inverse of the standard normal cumulative // distribution. The distribution has a mean of zero and a standard // deviation of one. // Todo: NPer Returns the number of periods for an investment based on // periodic, constant payments and a constant interest rate. // Todo: Npv Calculates the net present value of an investment by using a // discount rate and a series of future payments (negative values) and // income (positive values). // Todo: Oct2Bin Converts an octal number to binary. // Todo: Oct2Dec Converts an octal number to decimal. // Todo: Oct2Hex Converts an octal number to hexadecimal. // Todo: Odd Returns number rounded up to the nearest odd integer. // Todo: OddFPrice Returns the price per $100 face value of a security // having an odd (short or long) first period. // Todo: OddFYield Returns the yield of a security that has an odd (short or // long) first period. // Todo: OddLPrice Returns the price per $100 face value of a security // having an odd (short or long) last coupon period. // Todo: OddLYield Returns the yield of a security that has an odd (short or // long) last period. // Skip: Or Returns TRUE if any argument is TRUE; returns FALSE if all // arguments are FALSE. Todo: Pearson Returns the Pearson product moment // correlation coefficient, r, a dimensionless index that ranges from -1.0 // to 1.0 inclusive and reflects the extent of a linear relationship between // two data sets. // We have a more efficient implementation of percentile // Skip: Percentile Returns the k-th percentile of values in a range. You // can use this function to establish a threshold of acceptance. For // example, you can decide to examine candidates who score above the 90th // percentile. // Todo: PercentRank Returns the rank of a value in a data set as a // percentage of the data set. This function can be used to evaluate the // relative standing of a value within a data set. For example, you can use // PERCENTRANK to evaluate the standing of an aptitude test score among all // scores for the test. // Todo: Permut Returns the number of permutations for a given number of // objects that can be selected from number objects. A permutation is any // set or subset of objects or events where internal order is // significant. Permutations are different from combinations, for which the // internal order is not significant. Use this function for lottery-style // probability calculations. // Todo: Phonetic Extracts the phonetic (furigana) characters from a text // string. @FunctionName("Pi") @Description( "Returns the number 3.14159265358979, the mathematical constant pi, " + "accurate to 15 digits.") public static double pi() { return Math.PI; } // Todo: Pmt Calculates the payment for a loan based on constant payments // and a constant interest rate. // Todo: Poisson Returns the Poisson distribution. A common application of // the Poisson distribution is predicting the number of events over a // specific time, such as the number of cars arriving at a toll plaza in 1 // minute. @FunctionName("Power") @Description("Returns the result of a number raised to a power.") public static double power(double x, double y) { return Math.pow(x, y); } // Todo: Ppmt Returns the payment on the principal for a given period for an // investment based on periodic, constant payments and a constant interest // rate. // Todo: Price Returns the price per $100 face value of a security that pays // periodic interest. // Todo: PriceDisc Returns the price per $100 face value of a discounted // security. // Todo: PriceMat Returns the price per $100 face value of a security that // pays interest at maturity. // Todo: Prob Returns the probability that values in a range are between two // limits. If upper_limit is not supplied, returns the probability that // values in x_range are equal to lower_limit. // Todo: Product Multiplies all the numbers given as arguments and returns // the product. // Todo: Proper Capitalizes the first letter in a text string and any other // letters in text that follow any character other than a letter. Converts // all other letters to lowercase letters. // Todo: Pv Returns the present value of an investment. The present value is // the total amount that a series of future payments is worth now. For // example, when you borrow money, the loan amount is the present value to // the lender. // Todo: Quartile Returns the quartile of a data set. Quartiles often are // used in sales and survey data to divide populations into groups. For // example, you can use QUARTILE to find the top 25 percent of incomes in a // population. // Todo: Quotient Returns the integer portion of a division. Use this // function when you want to discard the remainder of a division. @FunctionName("Radians") @Description("Converts degrees to radians.") public static double radians(double number) { // 180 degrees = Pi radians return number / 180.0 * Math.PI; } // Todo: RandBetween Returns a random integer number between the numbers you // specify. A new random integer number is returned every time the worksheet // is calculated. // Skip: Rank Returns the rank of a number in a list of numbers. The rank of // a number is its size relative to other values in a list. (If you were to // sort the list, the rank of the number would be its position.) Todo: Rate // Returns the interest rate per period of an annuity. RATE is calculated by // iteration and can have zero or more solutions. If the successive results // of RATE do not converge to within 0.0000001 after 20 iterations, RATE // returns the #NUM! error value. // Todo: Received Returns the amount received at maturity for a fully // invested security. // Todo: Replace Replaces part of a text string, based on the number of // characters you specify, with a different text string. // Todo: ReplaceB REPLACEB replaces part of a text string, based on the // number of bytes you specify, with a different text string. // Todo: Rept Repeats text a given number of times. Use REPT to fill a cell // with a number of instances of a text string. // Todo: Roman Converts an arabic numeral to roman, as text. // Todo: Round Rounds a number to a specified number of digits. // Todo: RoundDown Rounds a number down, toward zero. // Todo: RoundUp Rounds a number up, away from 0 (zero). // Todo: RSq Returns the square of the Pearson product moment correlation // coefficient through data points in known_y's and known_x's. For more // information, see PEARSON. The r-squared value can be interpreted as the // proportion of the variance in y attributable to the variance in x. // Todo: RTD This method connects to a source to receive real-time data. // Todo: Search SEARCH and SEARCHB locate one text string within a second // text string, and return the number of the starting position of the first // text string from the first character of the second text string. // Todo: SearchB SEARCH and SEARCHB locate one text string within a second // text string, and return the number of the starting position of the first // text string from the first character of the second text string. // Todo: SeriesSum Returns the sum of a power series based on the formula: // Todo: Sinh Returns the hyperbolic sine of a number. @FunctionName("Sinh") @Description("Returns the hyperbolic sine of a number.") public static double sinh(double number) { return Math.sinh(number); } // Todo: Skew Returns the skewness of a distribution. Skewness characterizes // the degree of asymmetry of a distribution around its mean. Positive // skewness indicates a distribution with an asymmetric tail extending // toward more positive values. Negative skewness indicates a distribution // with an asymmetric tail extending toward more negative values. // Todo: Sln Returns the straight-line depreciation of an asset for one // period. // Todo: Slope Returns the slope of the linear regression line through data // points in known_y's and known_x's. The slope is the vertical distance // divided by the horizontal distance between any two points on the line, // which is the rate of change along the regression line. // Todo: Small Returns the k-th smallest value in a data set. Use this // function to return values with a particular relative standing in a data // set. @FunctionName("SqrtPi") @Description("Returns the square root of (number * pi).") public static double sqrtPi(double number) { return Math.sqrt(number * Math.PI); } // Todo: Standardize Returns a normalized value from a distribution // characterized by mean and standard_dev. // Todo: StDev Estimates standard deviation based on a sample. The standard // deviation is a measure of how widely values are dispersed from the // average value (the mean). // Todo: StDevP Calculates standard deviation based on the entire population // given as arguments. The standard deviation is a measure of how widely // values are dispersed from the average value (the mean). // Todo: StEyx Returns the standard error of the predicted y-value for each // x in the regression. The standard error is a measure of the amount of // error in the prediction of y for an individual x. // Todo: Substitute Substitutes new_text for old_text in a text string. Use // SUBSTITUTE when you want to replace specific text in a text string; use // REPLACE when you want to replace any text that occurs in a specific // location in a text string. // Todo: Subtotal Creates subtotals. // Todo: Sum Adds all the numbers in a range of cells. // Todo: SumIf Adds the cells specified by a given criteria. // Todo: SumIfs Adds the cells in a range that meet multiple criteria. // Todo: SumProduct Multiplies corresponding components in the given arrays, // and returns the sum of those products. // Todo: SumSq Returns the sum of the squares of the arguments. // Todo: SumX2MY2 Returns the sum of the difference of squares of // corresponding values in two arrays. // Todo: SumX2PY2 Returns the sum of the sum of squares of corresponding // values in two arrays. The sum of the sum of squares is a common term in // many statistical calculations. // Todo: SumXMY2 Returns the sum of squares of differences of corresponding // values in two arrays. // Todo: Syd Returns the sum-of-years' digits depreciation of an asset for a // specified period. @FunctionName("Tanh") @Description("Returns the hyperbolic tangent of a number.") public static double tanh(double number) { return Math.tanh(number); } // Todo: TBillEq Returns the bond-equivalent yield for a Treasury bill. // Todo: TBillPrice Returns the price per $100 face value for a Treasury // bill. // Todo: TBillYield Returns the yield for a Treasury bill. // Todo: TDist Returns the Percentage Points (probability) for the Student // t-distribution where a numeric value (x) is a calculated value of t for // which the Percentage Points are to be computed. The t-distribution is // used in the hypothesis testing of small sample data sets. Use this // function in place of a table of critical values for the t-distribution. // Todo: Text Converts a value to text in a specific number format. // Todo: TInv Returns the t-value of the Student's t-distribution as a // function of the probability and the degrees of freedom. // Todo: Transpose Returns a vertical range of cells as a horizontal range, // or vice versa. TRANSPOSE must be entered as an array formula in a range // that has the same number of rows and columns, respectively, as an array // has columns and rows. Use TRANSPOSE to shift the vertical and horizontal // orientation of an array on a worksheet. // Todo: Trend Returns values along a linear trend. Fits a straight line // (using the method of least squares) to the arrays known_y's and // known_x's. Returns the y-values along that line for the array of new_x's // that you specify. // Todo: Trim Removes all spaces from text except for single spaces between // words. Use TRIM on text that you have received from another application // that may have irregular spacing. // Todo: TrimMean Returns the mean of the interior of a data set. TRIMMEAN // calculates the mean taken by excluding a percentage of data points from // the top and bottom tails of a data set. You can use this function when // you wish to exclude outlying data from your analysis. // Todo: TTest Returns the probability associated with a Student's // t-Test. Use TTEST to determine whether two samples are likely to have // come from the same two underlying populations that have the same mean. // Todo: USDollar The description for this item will appear in the final // release of Office 2007. // Todo: Var Estimates variance based on a sample. // Todo: VarP Calculates variance based on the entire population. // Todo: Vdb Returns the depreciation of an asset for any period you // specify, including partial periods, using the double-declining balance // method or some other method you specify. VDB stands for variable // declining balance. // Todo: VLookup Searches for a value in the first column of a table array // and returns a value in the same row from another column in the table // array. // Todo: Weekday Returns the day of the week corresponding to a date. The // day is given as an integer, ranging from 1 (Sunday) to 7 (Saturday), by // default. // Todo: WeekNum Returns a number that indicates where the week falls // numerically within a year. // Todo: Weibull Returns the Weibull distribution. Use this distribution in // reliability analysis, such as calculating a device's mean time to // failure. // Todo: WorkDay Returns a number that represents a date that is the // indicated number of working days before or after a date (the starting // date). Working days exclude weekends and any dates identified as // holidays. Use WORKDAY to exclude weekends or holidays when you calculate // invoice due dates, expected delivery times, or the number of days of work // performed. // Todo: Xirr Returns the internal rate of return for a schedule of cash // flows that is not necessarily periodic. To calculate the internal rate of // return for a series of periodic cash flows, use the IRR function. // Todo: Xnpv The description for this item will appear in the final release // of Office 2007. // Todo: YearFrac Calculates the fraction of the year represented by the // number of whole days between two dates (the start_date and the // end_date). Use the YEARFRAC worksheet function to identify the proportion // of a whole year's benefits or obligations to assign to a specific term. // Todo: YieldDisc Returns the annual yield for a discounted security. // Todo: YieldMat Returns the annual yield of a security that pays interest // at maturity. // Todo: ZTest Returns the one-tailed probability-value of a z-test. For a // given hypothesized population mean, ZTEST returns the probability that // the sample mean would be greater than the average of observations in the // data set (array) -- that is, the observed sample mean. } // End Excel.java mondrian-3.4.1/src/main/mondrian/olap/fun/MemberHierarchyFunDef.java0000644000175000017500000000304111735330606025272 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2007 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.AbstractHierarchyCalc; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; /** * Definition of the <Member>.Hierarchy MDX builtin function. * * @author jhyde * @since Mar 23, 2006 */ public class MemberHierarchyFunDef extends FunDefBase { static final MemberHierarchyFunDef instance = new MemberHierarchyFunDef(); private MemberHierarchyFunDef() { super("Hierarchy", "Returns a member's hierarchy.", "phm"); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final MemberCalc memberCalc = compiler.compileMember(call.getArg(0)); return new CalcImpl(call, memberCalc); } public static class CalcImpl extends AbstractHierarchyCalc { private final MemberCalc memberCalc; public CalcImpl(Exp exp, MemberCalc memberCalc) { super(exp, new Calc[] {memberCalc}); this.memberCalc = memberCalc; } public Hierarchy evaluateHierarchy(Evaluator evaluator) { Member member = memberCalc.evaluateMember(evaluator); return member.getHierarchy(); } } } // End MemberHierarchyFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/UdfResolver.java0000644000175000017500000003275611735330606023413 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.olap.type.*; import mondrian.spi.UserDefinedFunction; import java.util.*; /** * Resolver for user-defined functions. * * @author jhyde * @since 2.0 */ public class UdfResolver implements Resolver { private final UdfFactory factory; private final UserDefinedFunction udf; private static final String[] emptyStringArray = new String[0]; public UdfResolver(UdfFactory factory) { this.factory = factory; this.udf = factory.create(); } public String getName() { return udf.getName(); } public String getDescription() { return udf.getDescription(); } public String getSignature() { Type[] parameterTypes = udf.getParameterTypes(); int[] parameterCategories = new int[parameterTypes.length]; for (int i = 0; i < parameterCategories.length; i++) { parameterCategories[i] = TypeUtil.typeToCategory(parameterTypes[i]); } Type returnType = udf.getReturnType(parameterTypes); int returnCategory = TypeUtil.typeToCategory(returnType); return getSyntax().getSignature( getName(), returnCategory, parameterCategories); } public Syntax getSyntax() { return udf.getSyntax(); } public FunDef getFunDef() { Type[] parameterTypes = udf.getParameterTypes(); int[] parameterCategories = new int[parameterTypes.length]; for (int i = 0; i < parameterCategories.length; i++) { parameterCategories[i] = TypeUtil.typeToCategory(parameterTypes[i]); } Type returnType = udf.getReturnType(parameterTypes); return new UdfFunDef(parameterCategories, returnType); } public FunDef resolve( Exp[] args, Validator validator, List conversions) { final Type[] parameterTypes = udf.getParameterTypes(); if (args.length != parameterTypes.length) { return null; } int[] parameterCategories = new int[parameterTypes.length]; Type[] castArgTypes = new Type[parameterTypes.length]; for (int i = 0; i < parameterTypes.length; i++) { Type parameterType = parameterTypes[i]; final Exp arg = args[i]; final Type argType = arg.getType(); final int parameterCategory = TypeUtil.typeToCategory(parameterType); if (!validator.canConvert( i, arg, parameterCategory, conversions)) { return null; } parameterCategories[i] = parameterCategory; if (!parameterType.equals(argType)) { castArgTypes[i] = FunDefBase.castType(argType, parameterCategory); } } final Type returnType = udf.getReturnType(castArgTypes); return new UdfFunDef(parameterCategories, returnType); } public boolean requiresExpression(int k) { return false; } public String[] getReservedWords() { final String[] reservedWords = udf.getReservedWords(); return reservedWords == null ? emptyStringArray : reservedWords; } /** * Adapter which converts a {@link UserDefinedFunction} into a * {@link FunDef}. */ private class UdfFunDef extends FunDefBase { private Type returnType; public UdfFunDef(int[] parameterCategories, Type returnType) { super( UdfResolver.this, TypeUtil.typeToCategory(returnType), parameterCategories); this.returnType = returnType; } public Type getResultType(Validator validator, Exp[] args) { return returnType; } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Exp[] args = call.getArgs(); Calc[] calcs = new Calc[args.length]; UserDefinedFunction.Argument[] expCalcs = new UserDefinedFunction.Argument[args.length]; for (int i = 0; i < args.length; i++) { Exp arg = args[i]; final Calc calc = compiler.compileAs( arg, castType(arg.getType(), parameterCategories[i]), ResultStyle.ANY_LIST); final Calc scalarCalc = compiler.compileScalar(arg, true); final ListCalc listCalc; final IterCalc iterCalc; if (arg.getType() instanceof SetType) { listCalc = compiler.compileList(arg, true); iterCalc = compiler.compileIter(arg); } else { listCalc = null; iterCalc = null; } expCalcs[i] = new CalcExp(calc, scalarCalc, listCalc, iterCalc); } // Create a new instance of the UDF, because some UDFs use member // variables as state. UserDefinedFunction udf2 = factory.create(); if (call.getType() instanceof SetType) { return new ListCalcImpl(call, calcs, udf2, expCalcs); } else { return new ScalarCalcImpl(call, calcs, udf2, expCalcs); } } } /** * Expression that evaluates a scalar user-defined function. */ private static class ScalarCalcImpl extends GenericCalc { private final Calc[] calcs; private final UserDefinedFunction udf; private final UserDefinedFunction.Argument[] args; public ScalarCalcImpl( ResolvedFunCall call, Calc[] calcs, UserDefinedFunction udf, UserDefinedFunction.Argument[] args) { super(call); this.calcs = calcs; this.udf = udf; this.args = args; } public Calc[] getCalcs() { return calcs; } public Object evaluate(Evaluator evaluator) { try { return udf.execute(evaluator, args); } catch (Exception e) { return FunUtil.newEvalException( "Exception while executing function " + udf.getName(), e); } } public boolean dependsOn(Hierarchy hierarchy) { // Be pessimistic. This effectively disables expression caching. return true; } } /** * Expression that evaluates a list user-defined function. */ private static class ListCalcImpl extends AbstractListCalc { private final UserDefinedFunction udf; private final UserDefinedFunction.Argument[] args; public ListCalcImpl( ResolvedFunCall call, Calc[] calcs, UserDefinedFunction udf, UserDefinedFunction.Argument[] args) { super(call, calcs); this.udf = udf; this.args = args; } public TupleList evaluateList(Evaluator evaluator) { final List list = (List) udf.execute(evaluator, args); // If arity is 1, assume they have returned a list of members. // For other arity, assume a list of member arrays. if (getType().getArity() == 1) { //noinspection unchecked return new UnaryTupleList((List) list); } else { // Use an adapter to make a list of member arrays look like // a list of members laid end-to-end. final int arity = getType().getArity(); //noinspection unchecked final List memberArrayList = (List) list; return new ListTupleList( arity, new AbstractList() { @Override public Member get(int index) { return memberArrayList.get(index / arity) [index % arity]; } @Override public int size() { return memberArrayList.size() * arity; } } ); } } @Override public boolean dependsOn(Hierarchy hierarchy) { // Be pessimistic. This effectively disables expression caching. return true; } } /** * Wrapper around a {@link Calc} to make it appear as an {@link Exp}. * Only the {@link #evaluate(mondrian.olap.Evaluator)} * and {@link #evaluateScalar(mondrian.olap.Evaluator)} methods are * supported. */ private static class CalcExp implements UserDefinedFunction.Argument { private final Calc calc; private final Calc scalarCalc; private final IterCalc iterCalc; private final ListCalc listCalc; /** * Creates a CalcExp. * * @param calc Compiled expression * @param scalarCalc Compiled expression that evaluates to a scalar * @param listCalc Compiled expression that evaluates an MDX set to * a java list * @param iterCalc Compiled expression that evaluates an MDX set to */ public CalcExp( Calc calc, Calc scalarCalc, ListCalc listCalc, IterCalc iterCalc) { this.calc = calc; this.scalarCalc = scalarCalc; this.listCalc = listCalc; this.iterCalc = iterCalc; } public Type getType() { return calc.getType(); } public Object evaluate(Evaluator evaluator) { return adapt(calc.evaluate(evaluator)); } public Object evaluateScalar(Evaluator evaluator) { return scalarCalc.evaluate(evaluator); } public List evaluateList(Evaluator eval) { if (listCalc == null) { throw new RuntimeException("Expression is not a set"); } return adaptList(listCalc.evaluateList(eval)); } public Iterable evaluateIterable(Evaluator eval) { if (iterCalc == null) { throw new RuntimeException("Expression is not a set"); } return adaptIterable(iterCalc.evaluateIterable(eval)); } /** * Adapts the output of {@link TupleList} and {@link TupleIterable} * calculator expressions to the old style, that returned either members * or arrays of members. * * @param o Output of calc * @return Output in new format (lists and iterables over lists of * members) */ private Object adapt(Object o) { if (o instanceof TupleIterable) { return adaptIterable((TupleIterable) o); } return o; } private List adaptList(final TupleList tupleList) { // List is required to be mutable -- so make a copy. if (tupleList.getArity() == 1) { return new ArrayList(tupleList.slice(0)); } else { return new ArrayList( TupleCollections.asMemberArrayList(tupleList)); } } private Iterable adaptIterable(final TupleIterable tupleIterable) { if (tupleIterable instanceof TupleList) { return adaptList((TupleList) tupleIterable); } if (tupleIterable.getArity() == 1) { return tupleIterable.slice(0); } else { return TupleCollections.asMemberArrayIterable(tupleIterable); } } } /** * Factory for {@link UserDefinedFunction}. * *

    This factory is required because a user-defined function is allowed * to store state in itself. Therefore it is unsanitary to use the same * UDF in the function table for validation and runtime. In the function * table there is a factory. We use one instance of instance of the UDF to * validate, and create another for the runtime plan.

    */ public interface UdfFactory { /** * Creates a UDF. * * @return UDF */ UserDefinedFunction create(); } /** * Implementation of {@link UdfFactory} that instantiates a given class * using a public default constructor. */ public static class ClassUdfFactory implements UdfResolver.UdfFactory { private final Class clazz; private final String name; /** * Creates a ClassUdfFactory. * * @param clazz Class to instantiate * @param name Name */ public ClassUdfFactory( Class clazz, String name) { this.clazz = clazz; this.name = name; assert clazz != null; } public UserDefinedFunction create() { return Util.createUdf(clazz, name); } } } // End UdfResolver.java mondrian-3.4.1/src/main/mondrian/olap/fun/NamedSetCurrentOrdinalFunDef.java0000644000175000017500000000357511735330606026614 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.Calc; import mondrian.calc.ExpCompiler; import mondrian.calc.impl.AbstractIntegerCalc; import mondrian.mdx.NamedSetExpr; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.resource.MondrianResource; /** * Definition of the <Named Set>.CurrentOrdinal MDX builtin * function. * * @author jhyde * @since Oct 19, 2008 */ public class NamedSetCurrentOrdinalFunDef extends FunDefBase { static final NamedSetCurrentOrdinalFunDef instance = new NamedSetCurrentOrdinalFunDef(); private NamedSetCurrentOrdinalFunDef() { super( "CurrentOrdinal", "Returns the ordinal of the current iteration through a named set.", "pix"); } public Exp createCall(Validator validator, Exp[] args) { assert args.length == 1; final Exp arg0 = args[0]; if (!(arg0 instanceof NamedSetExpr)) { throw MondrianResource.instance().NotANamedSet.ex(); } return super.createCall(validator, args); } public Calc compileCall(ResolvedFunCall call, ExpCompiler compiler) { final Exp arg0 = call.getArg(0); assert arg0 instanceof NamedSetExpr : "checked this in createCall"; final NamedSetExpr namedSetExpr = (NamedSetExpr) arg0; return new AbstractIntegerCalc(call, new Calc[0]) { public int evaluateInteger(Evaluator evaluator) { return namedSetExpr.getEval(evaluator).currentOrdinal(); } }; } } // End NamedSetCurrentOrdinalFunDef.java mondrian-3.4.1/src/main/mondrian/olap/fun/FilterFunDef.java0000644000175000017500000003260111735330606023455 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.fun; import mondrian.calc.*; import mondrian.calc.impl.*; import mondrian.mdx.ResolvedFunCall; import mondrian.olap.*; import mondrian.server.Locus; import java.util.List; /** * Definition of the Filter MDX function. * *

    Syntax: *

    Filter(<Set>, <Search * Condition>)
    * * @author jhyde * @since Mar 23, 2006 */ class FilterFunDef extends FunDefBase { private static final String TIMING_NAME = FilterFunDef.class.getSimpleName(); static final FilterFunDef instance = new FilterFunDef(); private FilterFunDef() { super( "Filter", "Returns the set resulting from filtering a set based on a search condition.", "fxxb"); } public Calc compileCall(final ResolvedFunCall call, ExpCompiler compiler) { // Ignore the caller's priority. We prefer to return iterable, because // it makes NamedSet.CurrentOrdinal work. List styles = compiler.getAcceptableResultStyles(); if (call.getArg(0) instanceof ResolvedFunCall && ((ResolvedFunCall) call.getArg(0)).getFunName().equals("AS")) { styles = ResultStyle.ITERABLE_ONLY; } if (styles.contains(ResultStyle.ITERABLE) || styles.contains(ResultStyle.ANY)) { return compileCallIterable(call, compiler); } else if (styles.contains(ResultStyle.LIST) || styles.contains(ResultStyle.MUTABLE_LIST)) { return compileCallList(call, compiler); } else { throw ResultStyleException.generate( ResultStyle.ITERABLE_LIST_MUTABLELIST_ANY, styles); } } /** * Returns an IterCalc. * *

    Here we would like to get either a IterCalc or ListCalc (mutable) * from the inner expression. For the IterCalc, its Iterator * can be wrapped with another Iterator that filters each element. * For the mutable list, remove all members that are filtered. * * @param call Call * @param compiler Compiler * @return Implementation of this function call in the Iterable result style */ protected IterCalc compileCallIterable( final ResolvedFunCall call, ExpCompiler compiler) { // want iterable, mutable list or immutable list in that order Calc imlcalc = compiler.compileAs( call.getArg(0), null, ResultStyle.ITERABLE_LIST_MUTABLELIST); BooleanCalc bcalc = compiler.compileBoolean(call.getArg(1)); Calc[] calcs = new Calc[] {imlcalc, bcalc}; // check returned calc ResultStyles checkIterListResultStyles(imlcalc); if (imlcalc.getResultStyle() == ResultStyle.ITERABLE) { return new IterIterCalc(call, calcs); } else if (imlcalc.getResultStyle() == ResultStyle.LIST) { return new ImmutableIterCalc(call, calcs); } else { return new MutableIterCalc(call, calcs); } } private static abstract class BaseIterCalc extends AbstractIterCalc { protected BaseIterCalc(ResolvedFunCall call, Calc[] calcs) { super(call, calcs); } public TupleIterable evaluateIterable(Evaluator evaluator) { evaluator.getTiming().markStart(TIMING_NAME); try { ResolvedFunCall call = (ResolvedFunCall) exp; // Use a native evaluator, if more efficient. // TODO: Figure this out at compile time. SchemaReader schemaReader = evaluator.getSchemaReader(); NativeEvaluator nativeEvaluator = schemaReader.getNativeSetEvaluator( call.getFunDef(), call.getArgs(), evaluator, this); if (nativeEvaluator != null) { return (TupleIterable) nativeEvaluator.execute(ResultStyle.ITERABLE); } else { return makeIterable(evaluator); } } finally { evaluator.getTiming().markEnd(TIMING_NAME); } } protected abstract TupleIterable makeIterable(Evaluator evaluator); public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } } private static class MutableIterCalc extends BaseIterCalc { MutableIterCalc(ResolvedFunCall call, Calc[] calcs) { super(call, calcs); assert calcs[0] instanceof ListCalc; assert calcs[1] instanceof BooleanCalc; } protected TupleIterable makeIterable(Evaluator evaluator) { evaluator.getTiming().markStart(TIMING_NAME); try { Calc[] calcs = getCalcs(); ListCalc lcalc = (ListCalc) calcs[0]; BooleanCalc bcalc = (BooleanCalc) calcs[1]; TupleList list = lcalc.evaluateList(evaluator); // make list mutable; guess selectivity .5 TupleList result = TupleCollections.createList( list.getArity(), list.size() / 2); final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); TupleCursor cursor = list.tupleCursor(); while (cursor.forward()) { cursor.setContext(evaluator); if (bcalc.evaluateBoolean(evaluator)) { result.addCurrent(cursor); } } evaluator.restore(savepoint); return result; } finally { evaluator.getTiming().markEnd(TIMING_NAME); } } } private static class ImmutableIterCalc extends BaseIterCalc { ImmutableIterCalc(ResolvedFunCall call, Calc[] calcs) { super(call, calcs); assert calcs[0] instanceof ListCalc; assert calcs[1] instanceof BooleanCalc; } protected TupleIterable makeIterable(Evaluator evaluator) { Calc[] calcs = getCalcs(); ListCalc lcalc = (ListCalc) calcs[0]; BooleanCalc bcalc = (BooleanCalc) calcs[1]; TupleList members = lcalc.evaluateList(evaluator); // Not mutable, must create new list TupleList result = members.cloneList(members.size() / 2); final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); TupleCursor cursor = members.tupleCursor(); while (cursor.forward()) { cursor.setContext(evaluator); if (bcalc.evaluateBoolean(evaluator)) { result.addCurrent(cursor); } } evaluator.restore(savepoint); return result; } } private static class IterIterCalc extends BaseIterCalc { IterIterCalc(ResolvedFunCall call, Calc[] calcs) { super(call, calcs); assert calcs[0] instanceof IterCalc; assert calcs[1] instanceof BooleanCalc; } protected TupleIterable makeIterable(Evaluator evaluator) { Calc[] calcs = getCalcs(); IterCalc icalc = (IterCalc) calcs[0]; final BooleanCalc bcalc = (BooleanCalc) calcs[1]; // This does dynamics, just in time, // as needed filtering final TupleIterable iterable = icalc.evaluateIterable(evaluator); final Evaluator evaluator2 = evaluator.push(); evaluator2.setNonEmpty(false); return new AbstractTupleIterable(iterable.getArity()) { public TupleCursor tupleCursor() { return new AbstractTupleCursor(iterable.getArity()) { final TupleCursor cursor = iterable.tupleCursor(); public boolean forward() { while (cursor.forward()) { Locus.peek().execution.checkCancelOrTimeout(); cursor.setContext(evaluator2); if (bcalc.evaluateBoolean(evaluator2)) { return true; } } return false; } public List current() { return cursor.current(); } }; } }; } } /** * Returns a ListCalc. * * @param call Call * @param compiler Compiler * @return Implementation of this function call in the List result style */ protected ListCalc compileCallList( final ResolvedFunCall call, ExpCompiler compiler) { Calc ilcalc = compiler.compileList(call.getArg(0), false); BooleanCalc bcalc = compiler.compileBoolean(call.getArg(1)); Calc[] calcs = new Calc[] {ilcalc, bcalc}; // Note that all of the ListCalc's return will be mutable switch (ilcalc.getResultStyle()) { case LIST: return new ImmutableListCalc(call, calcs); case MUTABLE_LIST: return new MutableListCalc(call, calcs); } throw ResultStyleException.generateBadType( ResultStyle.MUTABLELIST_LIST, ilcalc.getResultStyle()); } private static abstract class BaseListCalc extends AbstractListCalc { protected BaseListCalc(ResolvedFunCall call, Calc[] calcs) { super(call, calcs); } public TupleList evaluateList(Evaluator evaluator) { ResolvedFunCall call = (ResolvedFunCall) exp; // Use a native evaluator, if more efficient. // TODO: Figure this out at compile time. SchemaReader schemaReader = evaluator.getSchemaReader(); NativeEvaluator nativeEvaluator = schemaReader.getNativeSetEvaluator( call.getFunDef(), call.getArgs(), evaluator, this); if (nativeEvaluator != null) { return (TupleList) nativeEvaluator.execute( ResultStyle.ITERABLE); } else { return makeList(evaluator); } } protected abstract TupleList makeList(Evaluator evaluator); public boolean dependsOn(Hierarchy hierarchy) { return anyDependsButFirst(getCalcs(), hierarchy); } } private static class MutableListCalc extends BaseListCalc { MutableListCalc(ResolvedFunCall call, Calc[] calcs) { super(call, calcs); assert calcs[0] instanceof ListCalc; assert calcs[1] instanceof BooleanCalc; } protected TupleList makeList(Evaluator evaluator) { Calc[] calcs = getCalcs(); ListCalc lcalc = (ListCalc) calcs[0]; BooleanCalc bcalc = (BooleanCalc) calcs[1]; TupleList members0 = lcalc.evaluateList(evaluator); // make list mutable; // for capacity planning, guess selectivity = .5 TupleList result = members0.cloneList(members0.size() / 2); final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final TupleCursor cursor = members0.tupleCursor(); while (cursor.forward()) { cursor.setContext(evaluator); if (bcalc.evaluateBoolean(evaluator)) { result.addCurrent(cursor); } } evaluator.restore(savepoint); return result; } } private static class ImmutableListCalc extends BaseListCalc { ImmutableListCalc(ResolvedFunCall call, Calc[] calcs) { super(call, calcs); assert calcs[0] instanceof ListCalc; assert calcs[1] instanceof BooleanCalc; } protected TupleList makeList(Evaluator evaluator) { evaluator.getTiming().markStart(TIMING_NAME); try { Calc[] calcs = getCalcs(); ListCalc lcalc = (ListCalc) calcs[0]; BooleanCalc bcalc = (BooleanCalc) calcs[1]; TupleList members0 = lcalc.evaluateList(evaluator); // Not mutable, must create new list; // for capacity planning, guess selectivity = .5 TupleList result = members0.cloneList(members0.size() / 2); final int savepoint = evaluator.savepoint(); evaluator.setNonEmpty(false); final TupleCursor cursor = members0.tupleCursor(); while (cursor.forward()) { cursor.setContext(evaluator); if (bcalc.evaluateBoolean(evaluator)) { result.addCurrent(cursor); } } evaluator.restore(savepoint); return result; } finally { evaluator.getTiming().markEnd(TIMING_NAME); } } } } // End FilterFunDef.java mondrian-3.4.1/src/main/mondrian/olap/SolveOrderMode.java0000644000175000017500000000241011735330606023234 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2008-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * Strategies for applying solve order, exposed via the property * {@link MondrianProperties#SolveOrderMode}. */ public enum SolveOrderMode { /** * The SOLVE_ORDER value is absolute regardless of * where it is defined; e.g. a query defined calculated * member with a SOLVE_ORDER of 1 always takes precedence * over a cube defined value of 2. * *

    Compatible with Analysis Services 2000, and default behavior * up to mondrian-3.0.3. */ ABSOLUTE, /** * Cube calculated members are resolved before any session * scope calculated members, and session scope members are * resolved before any query defined calculation. The * SOLVE_ORDER value only applies within the scope in which * it was defined. * *

    Compatible with Analysis Services 2005, and default behavior * from mondrian-3.0.4 and later. */ SCOPED } // End SolveOrderMode.java mondrian-3.4.1/src/main/mondrian/olap/Formula.java0000644000175000017500000005040311735330606021755 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2000-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.mdx.*; import mondrian.olap.type.*; import mondrian.resource.MondrianResource; import mondrian.rolap.RolapCalculatedMember; import java.io.PrintWriter; import java.util.*; /** * A Formula is a clause in an MDX query which defines a Set or a * Member. */ public class Formula extends QueryPart { /** name of set or member */ private final Id id; /** defining expression */ private Exp exp; // properties/solve order of member private final MemberProperty[] memberProperties; /** * true is this is a member, * false if it is a set. */ private final boolean isMember; private Member mdxMember; private NamedSet mdxSet; /** * Constructs formula specifying a set. */ public Formula(Id id, Exp exp) { this(false, id, exp, new MemberProperty[0], null, null); createElement(null); } /** * Constructs a formula specifying a member. */ public Formula( Id id, Exp exp, MemberProperty[] memberProperties) { this(true, id, exp, memberProperties, null, null); } Formula( boolean isMember, Id id, Exp exp, MemberProperty[] memberProperties, Member mdxMember, NamedSet mdxSet) { this.isMember = isMember; this.id = id; this.exp = exp; this.memberProperties = memberProperties; this.mdxMember = mdxMember; this.mdxSet = mdxSet; assert !(!isMember && mdxMember != null); assert !(isMember && mdxSet != null); } public Object clone() { return new Formula( isMember, id, exp.clone(), MemberProperty.cloneArray(memberProperties), mdxMember, mdxSet); } static Formula[] cloneArray(Formula[] x) { Formula[] x2 = new Formula[x.length]; for (int i = 0; i < x.length; i++) { x2[i] = (Formula) x[i].clone(); } return x2; } /** * Resolves identifiers into objects. * * @param validator Validation context to resolve the identifiers in this * formula */ void accept(Validator validator) { final boolean scalar = isMember; exp = validator.validate(exp, scalar); String id = this.id.toString(); final Type type = exp.getType(); if (isMember) { if (!TypeUtil.canEvaluate(type)) { throw MondrianResource.instance().MdxMemberExpIsSet.ex( exp.toString()); } } else { if (!TypeUtil.isSet(type)) { throw MondrianResource.instance().MdxSetExpNotSet.ex(id); } } for (MemberProperty memberProperty : memberProperties) { validator.validate(memberProperty); } // Get the format expression from the property list, or derive it from // the formula. if (isMember) { Exp formatExp = getFormatExp(validator); if (formatExp != null) { mdxMember.setProperty( Property.FORMAT_EXP_PARSED.name, formatExp); mdxMember.setProperty( Property.FORMAT_EXP.name, Util.unparse(formatExp)); } final List memberPropertyList = new ArrayList(Arrays.asList(memberProperties)); // put CELL_FORMATTER_SCRIPT_LANGUAGE first, if it exists; we must // see it before CELL_FORMATTER_SCRIPT. for (int i = 0; i < memberPropertyList.size(); i++) { MemberProperty memberProperty = memberPropertyList.get(i); if (memberProperty.getName().equals( Property.CELL_FORMATTER_SCRIPT_LANGUAGE.name)) { memberPropertyList.remove(i); memberPropertyList.add(0, memberProperty); } } // For each property of the formula, make it a property of the // member. for (MemberProperty memberProperty : memberPropertyList) { if (Property.FORMAT_PROPERTIES.contains( memberProperty.getName())) { continue; // we already dealt with format_string props } final Exp exp = memberProperty.getExp(); if (exp instanceof Literal) { String value = String.valueOf(((Literal) exp).getValue()); mdxMember.setProperty(memberProperty.getName(), value); } } } } /** * Creates the {@link Member} or {@link NamedSet} object which this formula * defines. */ void createElement(Query q) { // first resolve the name, bit by bit final List segments = id.getSegments(); if (isMember) { if (mdxMember != null) { return; } OlapElement mdxElement = q.getCube(); final SchemaReader schemaReader = q.getSchemaReader(false); for (int i = 0; i < segments.size(); i++) { final Id.Segment segment = segments.get(i); OlapElement parent = mdxElement; mdxElement = null; // The last segment of the id is the name of the calculated // member so no need to look for a pre-existing child. This // avoids unnecessarily executing SQL and loading children into // cache. if (i != segments.size() - 1) { mdxElement = schemaReader.getElementChild(parent, segment); } // Don't try to look up the member which the formula is // defining. We would only find one if the member is overriding // a member at the cube or schema level, and we don't want to // change that member's properties. if (mdxElement == null || i == segments.size() - 1) { // this part of the name was not found... define it Level level; Member parentMember = null; if (parent instanceof Member) { parentMember = (Member) parent; level = parentMember.getLevel().getChildLevel(); if (level == null) { throw Util.newError( "The '" + segment.name + "' calculated member cannot be created " + "because its parent is at the lowest level " + "in the " + parentMember.getHierarchy().getUniqueName() + " hierarchy."); } } else { final Hierarchy hierarchy; if (parent instanceof Dimension && MondrianProperties.instance() .SsasCompatibleNaming.get()) { Dimension dimension = (Dimension) parent; if (dimension.getHierarchies().length == 1) { hierarchy = dimension.getHierarchies()[0]; } else { hierarchy = null; } } else { hierarchy = parent.getHierarchy(); } if (hierarchy == null) { throw MondrianResource.instance() .MdxCalculatedHierarchyError.ex(id.toString()); } level = hierarchy.getLevels()[0]; } if (parentMember != null && parentMember.isCalculated()) { throw Util.newError( "The '" + parent + "' calculated member cannot be used as a parent" + " of another calculated member."); } Member mdxMember = level.getHierarchy().createMember( parentMember, level, segment.name, this); assert mdxMember != null; mdxElement = mdxMember; } } this.mdxMember = (Member) mdxElement; } else { // don't need to tell query... it's already in query.formula Util.assertTrue( segments.size() == 1, "set names must not be compound"); // Caption and description are initialized to null, and annotations // to the empty map. If named set is defined in the schema, we will // give these their true values later. mdxSet = new SetBase( segments.get(0).name, null, null, exp, false, Collections.emptyMap()); } } public Object[] getChildren() { Object[] children = new Object[1 + memberProperties.length]; children[0] = exp; System.arraycopy( memberProperties, 0, children, 1, memberProperties.length); return children; } public void unparse(PrintWriter pw) { if (isMember) { pw.print("member "); if (mdxMember != null) { pw.print(mdxMember.getUniqueName()); } else { id.unparse(pw); } } else { pw.print("set "); id.unparse(pw); } pw.print(" as '"); exp.unparse(pw); pw.print("'"); if (memberProperties != null) { for (MemberProperty memberProperty : memberProperties) { pw.print(", "); memberProperty.unparse(pw); } } } public boolean isMember() { return isMember; } public NamedSet getNamedSet() { return mdxSet; } /** * Returns the Identifier of the set or member which is declared by this * Formula. * * @return Identifier */ public Id getIdentifier() { return id; } /** Returns this formula's name. */ public String getName() { return (isMember) ? mdxMember.getName() : mdxSet.getName(); } /** Returns this formula's caption. */ public String getCaption() { return (isMember) ? mdxMember.getCaption() : mdxSet.getName(); } /** * Changes the last part of the name to newName. For example, * [Abc].[Def].[Ghi] becomes [Abc].[Def].[Xyz]; * and the member or set is renamed from Ghi to * Xyz. */ void rename(String newName) { String oldName = getElement().getName(); final List segments = this.id.getSegments(); Util.assertTrue( segments.get(segments.size() - 1).name.equalsIgnoreCase(oldName)); segments.set( segments.size() - 1, new Id.Segment(newName, Id.Quoting.QUOTED)); if (isMember) { mdxMember.setName(newName); } else { mdxSet.setName(newName); } } /** Returns the unique name of the member or set. */ String getUniqueName() { return (isMember) ? mdxMember.getUniqueName() : mdxSet.getUniqueName(); } OlapElement getElement() { return (isMember) ? (OlapElement) mdxMember : (OlapElement) mdxSet; } public Exp getExpression() { return exp; } private Exp getMemberProperty(String name) { return MemberProperty.get(memberProperties, name); } /** * Returns the Member. (Not valid if this formula defines a set.) * * @pre isMember() * @post return != null */ public Member getMdxMember() { return mdxMember; } /** * Returns the solve order. (Not valid if this formula defines a set.) * * @pre isMember() * @return Solve order, or null if SOLVE_ORDER property is not specified * or is not a number or is not constant */ public Number getSolveOrder() { return getIntegerMemberProperty(Property.SOLVE_ORDER.name); } /** * Returns the integer value of a given constant. * If the property is not set, or its * value is not an integer, or its value is not a constant, * returns null. * * @param name Property name * @return Value of the property, or null if the property is not set, or its * value is not an integer, or its value is not a constant. */ private Number getIntegerMemberProperty(String name) { Exp exp = getMemberProperty(name); if (exp != null && exp.getType() instanceof NumericType) { return quickEval(exp); } return null; } /** * Evaluates a constant numeric expression. * @param exp Expression * @return Result as a number, or null if the expression is not a constant * or not a number. */ private static Number quickEval(Exp exp) { if (exp instanceof Literal) { Literal literal = (Literal) exp; final Object value = literal.getValue(); if (value instanceof Number) { return (Number) value; } else { return null; } } if (exp instanceof FunCall) { FunCall call = (FunCall) exp; if (call.getFunName().equals("-") && call.getSyntax() == Syntax.Prefix) { final Number number = quickEval(call.getArg(0)); if (number == null) { return null; } else if (number instanceof Integer) { return - number.intValue(); } else { return - number.doubleValue(); } } } return null; } /** * Deduces a formatting expression for this calculated member. First it * looks for properties called "format", "format_string", etc. Then it looks * inside the expression, and returns the formatting expression for the * first member it finds. * @param validator */ private Exp getFormatExp(Validator validator) { // If they have specified a format string (which they can do under // several names) return that. for (String prop : Property.FORMAT_PROPERTIES) { Exp formatExp = getMemberProperty(prop); if (formatExp != null) { return formatExp; } } // Choose a format appropriate to the expression. // For now, only do it for decimals. final Type type = exp.getType(); if (type instanceof DecimalType) { int scale = ((DecimalType) type).getScale(); String formatString = "#,##0"; if (scale > 0) { formatString = formatString + "."; while (scale-- > 0) { formatString = formatString + "0"; } } return Literal.createString(formatString); } if (!mdxMember.isMeasure()) { // Don't try to do any format string inference on non-measure // calculated members; that can hide the correct formatting // from base measures (see TestCalculatedMembers.testFormatString // for an example). return null; } // Burrow into the expression. If we find a member, use its format // string. try { exp.accept(new FormatFinder(validator)); return null; } catch (FoundOne foundOne) { return foundOne.exp; } } public void compile() { // nothing to do } /** * Accepts a visitor to this Formula. * The default implementation dispatches to the * {@link MdxVisitor#visit(Formula)} method. * * @param visitor Visitor */ public Object accept(MdxVisitor visitor) { final Object o = visitor.visit(this); if (visitor.shouldVisitChildren()) { // visit the expression exp.accept(visitor); } return o; } private static class FoundOne extends RuntimeException { private final Exp exp; public FoundOne(Exp exp) { super(); this.exp = exp; } } /** *A visitor for burrowing format information given a member. */ private static class FormatFinder extends MdxVisitorImpl { private final Validator validator; /** * * @param validator to resolve unresolved expressions */ public FormatFinder(Validator validator) { this.validator = validator; } public Object visit(MemberExpr memberExpr) { Member member = memberExpr.getMember(); returnFormula(member); if (member.isCalculated() && member instanceof RolapCalculatedMember && !hasCyclicReference(memberExpr)) { Formula formula = ((RolapCalculatedMember) member).getFormula(); formula.accept(validator); returnFormula(member); } return super.visit(memberExpr); } /** * * @param expr * @return true if there is cyclic reference in expression. * This check is required to avoid infinite recursion */ private boolean hasCyclicReference(Exp expr) { List expList = new ArrayList(); return hasCyclicReference(expr, expList); } private boolean hasCyclicReference(Exp expr, List expList) { if (expr instanceof MemberExpr) { MemberExpr memberExpr = (MemberExpr) expr; if (expList.contains(expr)) { return true; } expList.add(memberExpr); Member member = memberExpr.getMember(); if (member instanceof RolapCalculatedMember) { RolapCalculatedMember calculatedMember = (RolapCalculatedMember) member; Exp exp1 = calculatedMember.getExpression().accept(validator); return hasCyclicReference(exp1, expList); } } if (expr instanceof FunCall) { FunCall funCall = (FunCall) expr; Exp[] exps = funCall.getArgs(); for (int i = 0; i < exps.length; i++) { if (hasCyclicReference( exps[i], cloneForEachBranch(expList))) { return true; } } } return false; } private List cloneForEachBranch(List expList) { ArrayList list = new ArrayList(); list.addAll(expList); return list; } private void returnFormula(Member member) { if (getFormula(member) != null) { throw new FoundOne(getFormula(member)); } } private Exp getFormula(Member member) { return (Exp) member.getPropertyValue(Property.FORMAT_EXP_PARSED.name); } } } // End Formula.java mondrian-3.4.1/src/main/mondrian/olap/Exp.java0000644000175000017500000000401511735330606021102 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1999-2005 Julian Hyde // Copyright (C) 2005-2006 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.calc.Calc; import mondrian.calc.ExpCompiler; import mondrian.mdx.MdxVisitor; import mondrian.olap.type.Type; import java.io.PrintWriter; /** * An Exp is an MDX expression. * * @author jhyde, 20 January, 1999 * @since 1.0 */ public interface Exp { Exp clone(); /** * Returns the {@link Category} of the expression. * * @post Category.instance().isValid(return) */ int getCategory(); /** * Returns the type of this expression. Never null. */ Type getType(); /** * Writes the MDX representation of this expression to a print writer. * Sub-expressions are invoked recursively. * * @param pw PrintWriter */ void unparse(PrintWriter pw); /** * Validates this expression. * * The validator acts in the role of 'visitor' (see Gang of Four * 'visitor pattern'), and an expression in the role of 'visitee'. * * @param validator Validator contains validation context * * @return The validated expression; often but not always the same as * this expression */ Exp accept(Validator validator); /** * Converts this expression into an a tree of expressions which can be * efficiently evaluated. * * @param compiler * @return A compiled expression */ Calc accept(ExpCompiler compiler); /** * Accepts a visitor to this Exp. * The implementation should generally dispatches to the * {@link MdxVisitor#visit} method appropriate to the type of expression. * * @param visitor Visitor */ Object accept(MdxVisitor visitor); } // End Exp.java mondrian-3.4.1/src/main/mondrian/olap/Namer.java0000644000175000017500000000107011735330606021406 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2000-2005 Julian Hyde // Copyright (C) 2005-2005 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * Namer contains the methods to retrieve localized attributes */ public interface Namer { public String getLocalResource(String uName, String defaultValue); } // End Namer.java mondrian-3.4.1/src/main/mondrian/olap/Literal.java0000644000175000017500000001204311735330606021742 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1998-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.calc.Calc; import mondrian.calc.ExpCompiler; import mondrian.calc.impl.ConstantCalc; import mondrian.mdx.MdxVisitor; import mondrian.olap.type.*; import org.olap4j.impl.UnmodifiableArrayMap; import java.io.PrintWriter; import java.math.BigDecimal; import java.util.Map; /** * Represents a constant value, such as a string or number, in a parse tree. * *

    Symbols, such as the ASC keyword in * Order([Store].Members, [Measures].[Unit Sales], ASC), are * also represented as Literals. * * @author jhyde, 21 January, 1999 */ public class Literal extends ExpBase { // Data members. public final int category; private final Object o; // Constants for commonly used literals. public static final Literal nullValue = new Literal(Category.Null, null); public static final Literal emptyString = new Literal(Category.String, ""); public static final Literal zero = new Literal(Category.Numeric, BigDecimal.ZERO); public static final Literal one = new Literal(Category.Numeric, BigDecimal.ONE); public static final Literal negativeOne = new Literal(Category.Numeric, BigDecimal.ONE.negate()); public static final Literal doubleZero = zero; public static final Literal doubleOne = one; public static final Literal doubleNegativeOne = negativeOne; private static final Map MAP = UnmodifiableArrayMap.of( BigDecimal.ZERO, zero, BigDecimal.ONE, one, BigDecimal.ONE.negate(), negativeOne); /** * Private constructor. * *

    Use the creation methods {@link #createString(String)} etc. */ private Literal(int type, Object o) { this.category = type; this.o = o; } /** * Creates a string literal. * @see #createSymbol */ public static Literal createString(String s) { return (s.equals("")) ? emptyString : new Literal(Category.String, s); } /** * Creates a symbol. * * @see #createString */ public static Literal createSymbol(String s) { return new Literal(Category.Symbol, s); } /** * Creates a numeric literal. * * @deprecated Use {@link #create(java.math.BigDecimal)} */ public static Literal create(Double d) { return new Literal(Category.Numeric, new BigDecimal(d)); } /** * Creates an integer literal. * * @deprecated Use {@link #create(java.math.BigDecimal)} */ public static Literal create(Integer i) { return new Literal(Category.Numeric, new BigDecimal(i)); } /** * Creates a numeric literal. * *

    Using a {@link BigDecimal} allows us to store the precise value that * the user typed. We will have to fit the value into a native * {@code double} or {@code int} later on, but parse time is not the time to * be throwing away information. */ public static Literal create(BigDecimal d) { final Literal literal = MAP.get(d); if (literal != null) { return literal; } return new Literal(Category.Numeric, d); } public Literal clone() { return this; } public void unparse(PrintWriter pw) { switch (category) { case Category.Symbol: case Category.Numeric: pw.print(o); break; case Category.String: pw.print(Util.quoteForMdx((String) o)); break; case Category.Null: pw.print("NULL"); break; default: throw Util.newInternal("bad literal type " + category); } } public int getCategory() { return category; } public Type getType() { switch (category) { case Category.Symbol: return new SymbolType(); case Category.Numeric: return new NumericType(); case Category.String: return new StringType(); case Category.Null: return new NullType(); default: throw Category.instance.badValue(category); } } public Exp accept(Validator validator) { return this; } public Calc accept(ExpCompiler compiler) { return new ConstantCalc(getType(), o); } public Object accept(MdxVisitor visitor) { return visitor.visit(this); } public Object getValue() { return o; } public int getIntValue() { if (o instanceof Number) { return ((Number) o).intValue(); } else { throw Util.newInternal("cannot convert " + o + " to int"); } } } // End Literal.java mondrian-3.4.1/src/main/mondrian/olap/Util.java0000644000175000017500000041127511735330606021275 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.mdx.*; import mondrian.olap.fun.FunUtil; import mondrian.olap.fun.Resolver; import mondrian.olap.type.Type; import mondrian.resource.MondrianResource; import mondrian.rolap.RolapCube; import mondrian.rolap.RolapCubeDimension; import mondrian.rolap.RolapUtil; import mondrian.spi.UserDefinedFunction; import mondrian.util.*; import org.apache.commons.vfs.*; import org.apache.commons.vfs.provider.http.HttpFileObject; import org.apache.log4j.Logger; import org.eigenbase.xom.XOMUtil; import org.olap4j.mdx.*; import java.io.*; import java.lang.ref.Reference; import java.lang.reflect.*; import java.math.BigDecimal; import java.net.MalformedURLException; import java.net.URL; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.sql.Statement; import java.util.*; import java.util.concurrent.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Utility functions used throughout mondrian. All methods are static. * * @author jhyde * @since 6 August, 2001 */ public class Util extends XOMUtil { public static final String nl = System.getProperty("line.separator"); private static final Logger LOGGER = Logger.getLogger(Util.class); /** * Placeholder which indicates a value NULL. */ public static final Object nullValue = new Double(FunUtil.DoubleNull); /** * Placeholder which indicates an EMPTY value. */ public static final Object EmptyValue = new Double(FunUtil.DoubleEmpty); /** * Cumulative time spent accessing the database. */ private static long databaseMillis = 0; /** * Random number generator to provide seed for other random number * generators. */ private static final Random metaRandom = createRandom(MondrianProperties.instance().TestSeed.get()); /** * Whether we are running a version of Java before 1.5. * *

    If (but not only if) this variable is true, {@link #Retrowoven} will * also be true. */ public static final boolean PreJdk15 = System.getProperty("java.version").startsWith("1.4"); /** * Whether we are running a version of Java before 1.6. */ public static final boolean PreJdk16 = PreJdk15 || System.getProperty("java.version").startsWith("1.5"); /** * Whether this is an IBM JVM. */ public static final boolean IBM_JVM = System.getProperties().getProperty("java.vendor").equals( "IBM Corporation"); /** * What version of JDBC? * Returns:

      *
    • 0x0401 in JDK 1.7 and higher
    • *
    • 0x0400 in JDK 1.6
    • *
    • 0x0300 otherwise
    • *
    */ public static final int JdbcVersion = System.getProperty("java.version").compareTo("1.7") >= 0 ? 0x0401 : System.getProperty("java.version").compareTo("1.6") >= 0 ? 0x0400 : 0x0300; /** * Whether the code base has re-engineered using retroweaver. * If this is the case, some functionality is not available, but a lot of * things are available via {@link mondrian.util.UtilCompatible}. * Retroweaver has some problems involving {@link java.util.EnumSet}. */ public static final boolean Retrowoven = Access.class.getSuperclass().getName().equals( "net.sourceforge.retroweaver.runtime.java.lang.Enum"); private static final UtilCompatible compatible; /** * Flag to control expensive debugging. (More expensive than merely * enabling assertions: as we know, a lot of people run with assertions * enabled.) */ public static final boolean DEBUG = false; static { String className; if (PreJdk15 || Retrowoven) { className = "mondrian.util.UtilCompatibleJdk14"; } else if (PreJdk16) { className = "mondrian.util.UtilCompatibleJdk15"; } else { className = "mondrian.util.UtilCompatibleJdk16"; } try { Class clazz = (Class) Class.forName(className); compatible = clazz.newInstance(); } catch (ClassNotFoundException e) { throw Util.newInternal(e, "Could not load '" + className + "'"); } catch (InstantiationException e) { throw Util.newInternal(e, "Could not load '" + className + "'"); } catch (IllegalAccessException e) { throw Util.newInternal(e, "Could not load '" + className + "'"); } } public static boolean isNull(Object o) { return o == null || o == nullValue; } /** * Returns whether a list is strictly sorted. * * @param list List * @return whether list is sorted */ public static boolean isSorted(List list) { T prev = null; for (T t : list) { if (prev != null && ((Comparable) prev).compareTo(t) >= 0) { return false; } prev = t; } return true; } /** * Parses a string and returns a SHA-256 checksum of it. * * @param value The source string to parse. * @return A checksum of the source string. */ public static byte[] digestSha256(String value) { final MessageDigest algorithm; try { algorithm = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } return algorithm.digest(value.getBytes()); } /** * Creates an MD5 hash of a String. * * @param value String to create one way hash upon. * @return MD5 hash. */ public static byte[] digestMd5(final String value) { final MessageDigest algorithm; try { algorithm = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } return algorithm.digest(value.getBytes()); } /** * Creates an {@link ExecutorService} object backed by a thread pool * with a fixed number of threads.. * @param maxNbThreads Maximum number of concurrent * threads. * @param name The name of the threads. * @return An executor service preconfigured. */ public static ExecutorService getExecutorService( final int maxNbThreads, String name) { return getExecutorService(maxNbThreads, 0, 1, -1, name); } /** * Creates an {@link ExecutorService} object backed by a thread pool * with a fixed number of threads.. * @param maximumPoolSize Maximum number of concurrent * threads. * @param corePoolSize Minimum number of concurrent * threads to maintain in the pool, even if they are * idle. * @param keepAliveTime Time, in seconds, for which to * keep alive unused threads. * @param queueLength Maximum number of tasks that can be * put in the queue of tasks to be executed. -1 * means no limit. * @param name The name of the threads. * @return An executor service preconfigured. */ public static ExecutorService getExecutorService( int maximumPoolSize, int corePoolSize, long keepAliveTime, int queueLength, final String name) { if (Util.PreJdk16) { // On JDK1.5, if you specify corePoolSize=0, nothing gets executed. // Bummer. corePoolSize = Math.max(corePoolSize, 1); } return new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, queueLength < 0 ? new LinkedBlockingQueue() : new ArrayBlockingQueue(queueLength), new ThreadFactory() { public Thread newThread(Runnable r) { final Thread t = Executors.defaultThreadFactory().newThread(r); t.setDaemon(true); t.setName(name); return t; } }); } /** * Creates an {@link ScheduledExecutorService} object backed by a * thread pool with a fixed number of threads.. * @param maxNbThreads Maximum number of concurrent * threads. * @param name The name of the threads. * @return An scheduled executor service preconfigured. */ public static ScheduledExecutorService getScheduledExecutorService( final int maxNbThreads, final String name) { return Executors.newScheduledThreadPool( maxNbThreads, new ThreadFactory() { public Thread newThread(Runnable r) { final Thread thread = Executors.defaultThreadFactory().newThread(r); thread.setDaemon(true); thread.setName(name); return thread; } } ); } /** * Creates an {@link ExecutorService} object backed by an expanding * cached thread pool. * @param name The name of the threads. * @return An executor service preconfigured. */ public static ExecutorService getExecutorService( final String name) { return Executors.newCachedThreadPool( new ThreadFactory() { public Thread newThread(Runnable r) { final Thread thread = Executors.defaultThreadFactory().newThread(r); thread.setDaemon(true); thread.setName(name); return thread; } } ); } /** * Distributes and executes a list of tasks via an executor * service. This function will only return if one of the two * following things occur: *
      *
    • all tasks have finished running
    • *
    • breakAtFirstNonNull is set to true and * one of the tasks has returned a non null value.
    • *
    * @param * @param tasks List of tasks to run. * @param executor The executor service to use. * @param breakAtFirstNonNull Whether or not to stop executing * the tasks if one of them returns a non null value. Useful * when scanning a list of items for the first match found. * @return If breakAtFirstNonNull is true, this * function returns the first non null result given by the first * task to complete. Returns null otherwise. */ public static E executeDistributedTasks( List> tasks, ExecutorService executor, boolean breakAtFirstNonNull) { final List> tasksList = new ArrayList>(); final CountDownLatch latch = new CountDownLatch(tasks.size()); try { for (final Callable call : tasks) { tasksList.add( executor.submit( new Callable() { public E call() throws Exception { E result = call.call(); latch.countDown(); return result; } })); } E result = null; taskLoop: while (true) { if (breakAtFirstNonNull) { for (Future task : tasksList) { if (task.isDone()) { E taskResult = null; try { taskResult = task.get(); } catch (InterruptedException e) { throw new MondrianException(e); } catch (ExecutionException e) { throw new MondrianException(e); } if (taskResult != null) { result = taskResult; break taskLoop; } } } } // Break anyway if all tasks are completed. if (latch.getCount() == 0) { break taskLoop; } // Sleep for some time as not all tasks seem completed. try { Thread.sleep(100); } catch (InterruptedException e) { throw new MondrianException(e); } } return result; } finally { // Make double sure all tasks are killed for (Future task : tasksList) { task.cancel(true); } } } /** * Encodes string for MDX (escapes ] as ]] inside a name). */ public static String mdxEncodeString(String st) { StringBuilder retString = new StringBuilder(st.length() + 20); for (int i = 0; i < st.length(); i++) { char c = st.charAt(i); if ((c == ']') && ((i + 1) < st.length()) && (st.charAt(i + 1) != '.')) { retString.append(']'); //escaping character } retString.append(c); } return retString.toString(); } /** * Converts a string into a double-quoted string. */ public static String quoteForMdx(String val) { StringBuilder buf = new StringBuilder(val.length() + 20); quoteForMdx(buf, val); return buf.toString(); } /** * Appends a double-quoted string to a string builder. */ public static StringBuilder quoteForMdx(StringBuilder buf, String val) { buf.append("\""); String s0 = replace(val, "\"", "\"\""); buf.append(s0); buf.append("\""); return buf; } /** * Return string quoted in [...]. For example, "San Francisco" becomes * "[San Francisco]"; "a [bracketed] string" becomes * "[a [bracketed]] string]". */ public static String quoteMdxIdentifier(String id) { StringBuilder buf = new StringBuilder(id.length() + 20); quoteMdxIdentifier(id, buf); return buf.toString(); } public static void quoteMdxIdentifier(String id, StringBuilder buf) { buf.append('['); int start = buf.length(); buf.append(id); replace(buf, start, "]", "]]"); buf.append(']'); } /** * Return identifiers quoted in [...].[...]. For example, {"Store", "USA", * "California"} becomes "[Store].[USA].[California]". */ public static String quoteMdxIdentifier(List ids) { StringBuilder sb = new StringBuilder(64); quoteMdxIdentifier(ids, sb); return sb.toString(); } public static void quoteMdxIdentifier( List ids, StringBuilder sb) { for (int i = 0; i < ids.size(); i++) { if (i > 0) { sb.append('.'); } sb.append(ids.get(i).toString()); } } /** * Quotes a string literal for Java or JavaScript. * * @param s Unquoted literal * @return Quoted string literal */ public static String quoteJavaString(String s) { return s == null ? "null" : "\"" + s.replaceAll("\\\\", "\\\\\\\\") .replaceAll("\\\"", "\\\\\"") + "\""; } /** * Returns true if two objects are equal, or are both null. * * @param s First object * @param t Second object * @return Whether objects are equal or both null */ public static boolean equals(Object s, Object t) { if (s == t) { return true; } if (s == null || t == null) { return false; } return s.equals(t); } /** * Returns true if two strings are equal, or are both null. * *

    The result is not affected by * {@link MondrianProperties#CaseSensitive the case sensitive option}; if * you wish to compare names, use {@link #equalName(String, String)}. */ public static boolean equals(String s, String t) { return equals((Object) s, (Object) t); } /** * Returns whether two names are equal. * Takes into account the * {@link MondrianProperties#CaseSensitive case sensitive option}. * Names may be null. */ public static boolean equalName(String s, String t) { if (s == null) { return t == null; } boolean caseSensitive = MondrianProperties.instance().CaseSensitive.get(); return caseSensitive ? s.equals(t) : s.equalsIgnoreCase(t); } /** * Tests two strings for equality, optionally ignoring case. * * @param s First string * @param t Second string * @param matchCase Whether to perform case-sensitive match * @return Whether strings are equal */ public static boolean equal(String s, String t, boolean matchCase) { return matchCase ? s.equals(t) : s.equalsIgnoreCase(t); } /** * Compares two names. if case sensitive flag is false, * apply finer grain difference with case sensitive * Takes into account the {@link MondrianProperties#CaseSensitive case * sensitive option}. * Names must not be null. */ public static int caseSensitiveCompareName(String s, String t) { boolean caseSensitive = MondrianProperties.instance().CaseSensitive.get(); if (caseSensitive) { return s.compareTo(t); } else { int v = s.compareToIgnoreCase(t); // if ignore case returns 0 compare in a case sensitive manner // this was introduced to solve an issue with Member.equals() // and Member.compareTo() not agreeing with each other return v == 0 ? s.compareTo(t) : v; } } /** * Compares two names. * Takes into account the {@link MondrianProperties#CaseSensitive case * sensitive option}. * Names must not be null. */ public static int compareName(String s, String t) { boolean caseSensitive = MondrianProperties.instance().CaseSensitive.get(); return caseSensitive ? s.compareTo(t) : s.compareToIgnoreCase(t); } /** * Generates a normalized form of a name, for use as a key into a map. * Returns the upper case name if * {@link MondrianProperties#CaseSensitive} is true, the name unchanged * otherwise. */ public static String normalizeName(String s) { return MondrianProperties.instance().CaseSensitive.get() ? s : s.toUpperCase(); } /** * Returns the result of ((Comparable) k1).compareTo(k2), with * special-casing for the fact that Boolean only became * comparable in JDK 1.5. * * @see Comparable#compareTo */ public static int compareKey(Object k1, Object k2) { if (k1 instanceof Boolean) { // Luckily, "F" comes before "T" in the alphabet. k1 = k1.toString(); k2 = k2.toString(); } return ((Comparable) k1).compareTo(k2); } /** * Compares integer values. * * @param i0 First integer * @param i1 Second integer * @return Comparison of integers */ public static int compare(int i0, int i1) { return i0 < i1 ? -1 : (i0 == i1 ? 0 : 1); } /** * Returns a string with every occurrence of a seek string replaced with * another. */ public static String replace(String s, String find, String replace) { // let's be optimistic int found = s.indexOf(find); if (found == -1) { return s; } StringBuilder sb = new StringBuilder(s.length() + 20); int start = 0; char[] chars = s.toCharArray(); final int step = find.length(); if (step == 0) { // Special case where find is "". sb.append(s); replace(sb, 0, find, replace); } else { for (;;) { sb.append(chars, start, found - start); if (found == s.length()) { break; } sb.append(replace); start = found + step; found = s.indexOf(find, start); if (found == -1) { found = s.length(); } } } return sb.toString(); } /** * Replaces all occurrences of a string in a buffer with another. * * @param buf String buffer to act on * @param start Ordinal within find to start searching * @param find String to find * @param replace String to replace it with * @return The string buffer */ public static StringBuilder replace( StringBuilder buf, int start, String find, String replace) { // Search and replace from the end towards the start, to avoid O(n ^ 2) // copying if the string occurs very commonly. int findLength = find.length(); if (findLength == 0) { // Special case where the seek string is empty. for (int j = buf.length(); j >= 0; --j) { buf.insert(j, replace); } return buf; } int k = buf.length(); while (k > 0) { int i = buf.lastIndexOf(find, k); if (i < start) { break; } buf.replace(i, i + find.length(), replace); // Step back far enough to ensure that the beginning of the section // we just replaced does not cause a match. k = i - findLength; } return buf; } /** * Parses an MDX identifier such as [Foo].[Bar].Baz.&Key&Key2 * and returns the result as a list of segments. * * @param s MDX identifier * @return List of segments */ public static List parseIdentifier(String s) { return convert( org.olap4j.impl.IdentifierParser.parseIdentifier(s)); } /** * Converts an array of name parts {"part1", "part2"} into a single string * "[part1].[part2]". If the names contain "]" they are escaped as "]]". */ public static String implode(List names) { StringBuilder sb = new StringBuilder(64); for (int i = 0; i < names.size(); i++) { if (i > 0) { sb.append("."); } // FIXME: should be: // names.get(i).toString(sb); // but that causes some tests to fail quoteMdxIdentifier(names.get(i).name, sb); } return sb.toString(); } public static String makeFqName(String name) { return quoteMdxIdentifier(name); } public static String makeFqName(OlapElement parent, String name) { if (parent == null) { return Util.quoteMdxIdentifier(name); } else { StringBuilder buf = new StringBuilder(64); buf.append(parent.getUniqueName()); buf.append('.'); Util.quoteMdxIdentifier(name, buf); return buf.toString(); } } public static String makeFqName(String parentUniqueName, String name) { if (parentUniqueName == null) { return quoteMdxIdentifier(name); } else { StringBuilder buf = new StringBuilder(64); buf.append(parentUniqueName); buf.append('.'); Util.quoteMdxIdentifier(name, buf); return buf.toString(); } } public static OlapElement lookupCompound( SchemaReader schemaReader, OlapElement parent, List names, boolean failIfNotFound, int category) { return lookupCompound( schemaReader, parent, names, failIfNotFound, category, MatchType.EXACT); } /** * Resolves a name such as * '[Products].[Product Department].[Produce]' by resolving the * components ('Products', and so forth) one at a time. * * @param schemaReader Schema reader, supplies access-control context * @param parent Parent element to search in * @param names Exploded compound name, such as {"Products", * "Product Department", "Produce"} * @param failIfNotFound If the element is not found, determines whether * to return null or throw an error * @param category Type of returned element, a {@link Category} value; * {@link Category#Unknown} if it doesn't matter. * * @pre parent != null * @post !(failIfNotFound && return == null) * * @see #parseIdentifier(String) */ public static OlapElement lookupCompound( SchemaReader schemaReader, OlapElement parent, List names, boolean failIfNotFound, int category, MatchType matchType) { Util.assertPrecondition(parent != null, "parent != null"); if (LOGGER.isDebugEnabled()) { StringBuilder buf = new StringBuilder(64); buf.append("Util.lookupCompound: "); buf.append("parent.name="); buf.append(parent.getName()); buf.append(", category="); buf.append(Category.instance.getName(category)); buf.append(", names="); quoteMdxIdentifier(names, buf); LOGGER.debug(buf.toString()); } // First look up a member from the cache of calculated members // (cubes and queries both have them). switch (category) { case Category.Member: case Category.Unknown: Member member = schemaReader.getCalculatedMember(names); if (member != null) { return member; } } // Likewise named set. switch (category) { case Category.Set: case Category.Unknown: NamedSet namedSet = schemaReader.getNamedSet(names); if (namedSet != null) { return namedSet; } } // Now resolve the name one part at a time. for (int i = 0; i < names.size(); i++) { Id.Segment name = names.get(i); OlapElement child = schemaReader.getElementChild(parent, name, matchType); // if we're doing a non-exact match, and we find a non-exact // match, then for an after match, return the first child // of each subsequent level; for a before match, return the // last child if (child instanceof Member && !matchType.isExact() && !Util.equalName(child.getName(), name.name)) { Member bestChild = (Member) child; for (int j = i + 1; j < names.size(); j++) { List childrenList = schemaReader.getMemberChildren(bestChild); FunUtil.hierarchizeMemberList(childrenList, false); if (matchType == MatchType.AFTER) { bestChild = childrenList.get(0); } else { bestChild = childrenList.get(childrenList.size() - 1); } if (bestChild == null) { child = null; break; } } parent = bestChild; break; } if (child == null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Util.lookupCompound: " + "parent.name=" + parent.getName() + " has no child with name=" + name); } if (!failIfNotFound) { return null; } else if (category == Category.Member) { throw MondrianResource.instance().MemberNotFound.ex( quoteMdxIdentifier(names)); } else { throw MondrianResource.instance().MdxChildObjectNotFound .ex(name.name, parent.getQualifiedName()); } } parent = child; if (matchType == MatchType.EXACT_SCHEMA) { matchType = MatchType.EXACT; } } if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Util.lookupCompound: " + "found child.name=" + parent.getName() + ", child.class=" + parent.getClass().getName()); } switch (category) { case Category.Dimension: if (parent instanceof Dimension) { return parent; } else if (parent instanceof Hierarchy) { return parent.getDimension(); } else if (failIfNotFound) { throw Util.newError( "Can not find dimension '" + implode(names) + "'"); } else { return null; } case Category.Hierarchy: if (parent instanceof Hierarchy) { return parent; } else if (parent instanceof Dimension) { return parent.getHierarchy(); } else if (failIfNotFound) { throw Util.newError( "Can not find hierarchy '" + implode(names) + "'"); } else { return null; } case Category.Level: if (parent instanceof Level) { return parent; } else if (failIfNotFound) { throw Util.newError( "Can not find level '" + implode(names) + "'"); } else { return null; } case Category.Member: if (parent instanceof Member) { return parent; } else if (failIfNotFound) { throw MondrianResource.instance().MdxCantFindMember.ex( implode(names)); } else { return null; } case Category.Unknown: assertPostcondition(parent != null, "return != null"); return parent; default: throw newInternal("Bad switch " + category); } } public static OlapElement lookup(Query q, List nameParts) { final Exp exp = lookup(q, nameParts, false); if (exp instanceof MemberExpr) { MemberExpr memberExpr = (MemberExpr) exp; return memberExpr.getMember(); } else if (exp instanceof LevelExpr) { LevelExpr levelExpr = (LevelExpr) exp; return levelExpr.getLevel(); } else if (exp instanceof HierarchyExpr) { HierarchyExpr hierarchyExpr = (HierarchyExpr) exp; return hierarchyExpr.getHierarchy(); } else if (exp instanceof DimensionExpr) { DimensionExpr dimensionExpr = (DimensionExpr) exp; return dimensionExpr.getDimension(); } else { throw Util.newInternal("Not an olap element: " + exp); } } /** * Converts an identifier into an expression by resolving its parts into * an OLAP object (dimension, hierarchy, level or member) within the * context of a query. * *

    If allowProp is true, also allows property references * from valid members, for example * [Measures].[Unit Sales].FORMATTED_VALUE. * In this case, the result will be a {@link mondrian.mdx.ResolvedFunCall}. * * @param q Query expression belongs to * @param nameParts Parts of the identifier * @param allowProp Whether to allow property references * @return OLAP object or property reference */ public static Exp lookup( Query q, List nameParts, boolean allowProp) { return lookup(q, q.getSchemaReader(true), nameParts, allowProp); } /** * Converts an identifier into an expression by resolving its parts into * an OLAP object (dimension, hierarchy, level or member) within the * context of a query. * *

    If allowProp is true, also allows property references * from valid members, for example * [Measures].[Unit Sales].FORMATTED_VALUE. * In this case, the result will be a {@link ResolvedFunCall}. * * @param q Query expression belongs to * @param schemaReader Schema reader * @param nameParts Parts of the identifier * @param allowProp Whether to allow property references * @return OLAP object or property reference */ public static Exp lookup( Query q, SchemaReader schemaReader, List nameParts, boolean allowProp) { // First, look for a calculated member defined in the query. final String fullName = quoteMdxIdentifier(nameParts); // Look for any kind of object (member, level, hierarchy, // dimension) in the cube. Use a schema reader without restrictions. final SchemaReader schemaReaderSansAc = schemaReader.withoutAccessControl().withLocus(); final Cube cube = q.getCube(); OlapElement olapElement = schemaReaderSansAc.lookupCompound( cube, nameParts, false, Category.Unknown); if (olapElement != null) { Role role = schemaReader.getRole(); if (!role.canAccess(olapElement)) { olapElement = null; } if (olapElement instanceof Member) { olapElement = schemaReader.substitute((Member) olapElement); } } if (olapElement == null) { if (allowProp && nameParts.size() > 1) { List namePartsButOne = nameParts.subList(0, nameParts.size() - 1); final String propertyName = nameParts.get(nameParts.size() - 1).name; final Member member = (Member) schemaReaderSansAc.lookupCompound( cube, namePartsButOne, false, Category.Member); if (member != null && isValidProperty(propertyName, member.getLevel())) { return new UnresolvedFunCall( propertyName, Syntax.Property, new Exp[] { createExpr(member)}); } final Level level = (Level) schemaReaderSansAc.lookupCompound( cube, namePartsButOne, false, Category.Level); if (level != null && isValidProperty(propertyName, level)) { return new UnresolvedFunCall( propertyName, Syntax.Property, new Exp[] { createExpr(level)}); } } // if we're in the middle of loading the schema, the property has // been set to ignore invalid members, and the member is // non-existent, return the null member corresponding to the // hierarchy of the element we're looking for; locate the // hierarchy by incrementally truncating the name of the element if (q.ignoreInvalidMembers()) { int nameLen = nameParts.size() - 1; olapElement = null; while (nameLen > 0 && olapElement == null) { List partialName = nameParts.subList(0, nameLen); olapElement = schemaReaderSansAc.lookupCompound( cube, partialName, false, Category.Unknown); nameLen--; } if (olapElement != null) { olapElement = olapElement.getHierarchy().getNullMember(); } else { throw MondrianResource.instance().MdxChildObjectNotFound.ex( fullName, cube.getQualifiedName()); } } else { throw MondrianResource.instance().MdxChildObjectNotFound.ex( fullName, cube.getQualifiedName()); } } // keep track of any measure members referenced; these will be used // later to determine if cross joins on virtual cubes can be // processed natively q.addMeasuresMembers(olapElement); return createExpr(olapElement); } /** * Looks up a cube in a schema reader. * * @param cubeName Cube name * @param fail Whether to fail if not found. * @return Cube, or null if not found */ static Cube lookupCube( SchemaReader schemaReader, String cubeName, boolean fail) { for (Cube cube : schemaReader.getCubes()) { if (Util.compareName(cube.getName(), cubeName) == 0) { return cube; } } if (fail) { throw MondrianResource.instance().MdxCubeNotFound.ex(cubeName); } return null; } /** * Converts an olap element (dimension, hierarchy, level or member) into * an expression representing a usage of that element in an MDX statement. */ public static Exp createExpr(OlapElement element) { if (element instanceof Member) { Member member = (Member) element; return new MemberExpr(member); } else if (element instanceof Level) { Level level = (Level) element; return new LevelExpr(level); } else if (element instanceof Hierarchy) { Hierarchy hierarchy = (Hierarchy) element; return new HierarchyExpr(hierarchy); } else if (element instanceof Dimension) { Dimension dimension = (Dimension) element; return new DimensionExpr(dimension); } else if (element instanceof NamedSet) { NamedSet namedSet = (NamedSet) element; return new NamedSetExpr(namedSet); } else { throw Util.newInternal("Unexpected element type: " + element); } } public static Member lookupHierarchyRootMember( SchemaReader reader, Hierarchy hierarchy, Id.Segment memberName) { return lookupHierarchyRootMember( reader, hierarchy, memberName, MatchType.EXACT); } /** * Finds a root member of a hierarchy with a given name. * * @param hierarchy Hierarchy * @param memberName Name of root member * @return Member, or null if not found */ public static Member lookupHierarchyRootMember( SchemaReader reader, Hierarchy hierarchy, Id.Segment memberName, MatchType matchType) { // Lookup member at first level. // // Don't use access control. Suppose we cannot see the 'nation' level, // we still want to be able to resolve '[Customer].[USA].[CA]'. List rootMembers = reader.getHierarchyRootMembers(hierarchy); // if doing an inexact search on a non-all hieararchy, create // a member corresponding to the name we're searching for so // we can use it in a hierarchical search Member searchMember = null; if (!matchType.isExact() && !hierarchy.hasAll() && !rootMembers.isEmpty()) { searchMember = hierarchy.createMember( null, rootMembers.get(0).getLevel(), memberName.name, null); } int bestMatch = -1; int k = -1; for (Member rootMember : rootMembers) { ++k; int rc; // when searching on the ALL hierarchy, match must be exact if (matchType.isExact() || hierarchy.hasAll()) { rc = rootMember.getName().compareToIgnoreCase(memberName.name); } else { rc = FunUtil.compareSiblingMembers( rootMember, searchMember); } if (rc == 0) { return rootMember; } if (!hierarchy.hasAll()) { if (matchType == MatchType.BEFORE) { if (rc < 0 && (bestMatch == -1 || FunUtil.compareSiblingMembers( rootMember, rootMembers.get(bestMatch)) > 0)) { bestMatch = k; } } else if (matchType == MatchType.AFTER) { if (rc > 0 && (bestMatch == -1 || FunUtil.compareSiblingMembers( rootMember, rootMembers.get(bestMatch)) < 0)) { bestMatch = k; } } } } if (matchType == MatchType.EXACT_SCHEMA) { return null; } if (matchType != MatchType.EXACT && bestMatch != -1) { return rootMembers.get(bestMatch); } // If the first level is 'all', lookup member at second level. For // example, they could say '[USA]' instead of '[(All // Customers)].[USA]'. return (rootMembers.size() > 0 && rootMembers.get(0).isAll()) ? reader.lookupMemberChildByName( rootMembers.get(0), memberName, matchType) : null; } /** * Finds a named level in this hierarchy. Returns null if there is no * such level. */ public static Level lookupHierarchyLevel(Hierarchy hierarchy, String s) { final Level[] levels = hierarchy.getLevels(); for (Level level : levels) { if (level.getName().equalsIgnoreCase(s)) { return level; } } return null; } /** * Finds the zero based ordinal of a Member among its siblings. */ public static int getMemberOrdinalInParent( SchemaReader reader, Member member) { Member parent = member.getParentMember(); List siblings = (parent == null) ? reader.getHierarchyRootMembers(member.getHierarchy()) : reader.getMemberChildren(parent); for (int i = 0; i < siblings.size(); i++) { if (siblings.get(i).equals(member)) { return i; } } throw Util.newInternal( "could not find member " + member + " amongst its siblings"); } /** * returns the first descendant on the level underneath parent. * If parent = [Time].[1997] and level = [Time].[Month], then * the member [Time].[1997].[Q1].[1] will be returned */ public static Member getFirstDescendantOnLevel( SchemaReader reader, Member parent, Level level) { Member m = parent; while (m.getLevel() != level) { List children = reader.getMemberChildren(m); m = children.get(0); } return m; } /** * Returns whether a string is null or empty. */ public static boolean isEmpty(String s) { return (s == null) || (s.length() == 0); } /** * Encloses a value in single-quotes, to make a SQL string value. Examples: * singleQuoteForSql(null) yields NULL; * singleQuoteForSql("don't") yields 'don''t'. */ public static String singleQuoteString(String val) { StringBuilder buf = new StringBuilder(64); singleQuoteString(val, buf); return buf.toString(); } /** * Encloses a value in single-quotes, to make a SQL string value. Examples: * singleQuoteForSql(null) yields NULL; * singleQuoteForSql("don't") yields 'don''t'. */ public static void singleQuoteString(String val, StringBuilder buf) { buf.append('\''); String s0 = replace(val, "'", "''"); buf.append(s0); buf.append('\''); } /** * Creates a random number generator. * * @param seed Seed for random number generator. * If 0, generate a seed from the system clock and print the value * chosen. (This is effectively non-deterministic.) * If -1, generate a seed from an internal random number generator. * (This is deterministic, but ensures that different tests have * different seeds.) * * @return A random number generator. */ public static Random createRandom(long seed) { if (seed == 0) { seed = new Random().nextLong(); System.out.println("random: seed=" + seed); } else if (seed == -1 && metaRandom != null) { seed = metaRandom.nextLong(); } return new Random(seed); } /** * Returns whether a property is valid for a member of a given level. * It is valid if the property is defined at the level or at * an ancestor level, or if the property is a standard property such as * "FORMATTED_VALUE". * * @param propertyName Property name * @param level Level * @return Whether property is valid */ public static boolean isValidProperty( String propertyName, Level level) { return lookupProperty(level, propertyName) != null; } /** * Finds a member property called propertyName at, or above, * level. */ public static Property lookupProperty( Level level, String propertyName) { do { Property[] properties = level.getProperties(); for (Property property : properties) { if (property.getName().equals(propertyName)) { return property; } } level = level.getParentLevel(); } while (level != null); // Now try a standard property. boolean caseSensitive = MondrianProperties.instance().CaseSensitive.get(); final Property property = Property.lookup(propertyName, caseSensitive); if (property != null && property.isMemberProperty() && property.isStandard()) { return property; } return null; } /** * Insert a call to this method if you want to flag a piece of * undesirable code. * * @deprecated */ public static T deprecated(T reason) { throw new UnsupportedOperationException(reason.toString()); } /** * Insert a call to this method if you want to flag a piece of * undesirable code. * * @deprecated */ public static T deprecated(T reason, boolean fail) { if (fail) { throw new UnsupportedOperationException(reason.toString()); } else { return reason; } } public static List addLevelCalculatedMembers( SchemaReader reader, Level level, List members) { List calcMembers = reader.getCalculatedMembers(level.getHierarchy()); List calcMembersInThisLevel = new ArrayList(); for (Member calcMember : calcMembers) { if (calcMember.getLevel().equals(level)) { calcMembersInThisLevel.add(calcMember); } } if (!calcMembersInThisLevel.isEmpty()) { List newMemberList = new ConcatenableList(); newMemberList.addAll(members); newMemberList.addAll(calcMembersInThisLevel); return newMemberList; } return members; } /** * Returns an exception which indicates that a particular piece of * functionality should work, but a developer has not implemented it yet. */ public static RuntimeException needToImplement(Object o) { throw new UnsupportedOperationException("need to implement " + o); } /** * Returns an exception indicating that we didn't expect to find this value * here. */ public static > RuntimeException badValue( Enum anEnum) { return Util.newInternal( "Was not expecting value '" + anEnum + "' for enumeration '" + anEnum.getDeclaringClass().getName() + "' in this context"); } /** * Masks Mondrian's version number from a string. * * @param str String * @return String with each occurrence of mondrian's version number * (e.g. "2.3.0.0") replaced with "${mondrianVersion}" */ public static String maskVersion(String str) { MondrianServer.MondrianVersion mondrianVersion = MondrianServer.forId(null).getVersion(); String versionString = mondrianVersion.getVersionString(); return replace(str, versionString, "${mondrianVersion}"); } /** * Converts a list of SQL-style patterns into a Java regular expression. * *

    For example, {"Foo_", "Bar%BAZ"} becomes "Foo.|Bar.*BAZ". * * @param wildcards List of SQL-style wildcard expressions * @return Regular expression */ public static String wildcardToRegexp(List wildcards) { StringBuilder buf = new StringBuilder(); for (String value : wildcards) { if (buf.length() > 0) { buf.append('|'); } int i = 0; while (true) { int percent = value.indexOf('%', i); int underscore = value.indexOf('_', i); if (percent == -1 && underscore == -1) { if (i < value.length()) { buf.append(quotePattern(value.substring(i))); } break; } if (underscore >= 0 && (underscore < percent || percent < 0)) { if (i < underscore) { buf.append( quotePattern(value.substring(i, underscore))); } buf.append('.'); i = underscore + 1; } else if (percent >= 0 && (percent < underscore || underscore < 0)) { if (i < percent) { buf.append( quotePattern(value.substring(i, percent))); } buf.append(".*"); i = percent + 1; } else { throw new IllegalArgumentException(); } } } return buf.toString(); } /** * Converts a camel-case name to an upper-case name with underscores. * *

    For example, camelToUpper("FooBar") returns "FOO_BAR". * * @param s Camel-case string * @return Upper-case string */ public static String camelToUpper(String s) { StringBuilder buf = new StringBuilder(s.length() + 10); int prevUpper = -1; for (int i = 0; i < s.length(); ++i) { char c = s.charAt(i); if (Character.isUpperCase(c)) { if (i > prevUpper + 1) { buf.append('_'); } prevUpper = i; } else { c = Character.toUpperCase(c); } buf.append(c); } return buf.toString(); } /** * Parses a comma-separated list. * *

    If a value contains a comma, escape it with a second comma. For * example, parseCommaList("x,y,,z") returns * {"x", "y,z"}. * * @param nameCommaList List of names separated by commas * @return List of names */ public static List parseCommaList(String nameCommaList) { if (nameCommaList.equals("")) { return Collections.emptyList(); } if (nameCommaList.endsWith(",")) { // Special treatment for list ending in ",", because split ignores // entries after separator. final String zzz = "zzz"; final List list = parseCommaList(nameCommaList + zzz); String last = list.get(list.size() - 1); if (last.equals(zzz)) { list.remove(list.size() - 1); } else { list.set( list.size() - 1, last.substring(0, last.length() - zzz.length())); } return list; } List names = new ArrayList(); final String[] strings = nameCommaList.split(","); for (String string : strings) { final int count = names.size(); if (count > 0 && names.get(count - 1).equals("")) { if (count == 1) { if (string.equals("")) { names.add(""); } else { names.set( 0, "," + string); } } else { names.set( count - 2, names.get(count - 2) + "," + string); names.remove(count - 1); } } else { names.add(string); } } return names; } /** * Returns an annotation of a particular class on a method. Returns the * default value if the annotation is not present, or in JDK 1.4. * * @param method Method containing annotation * @param annotationClassName Name of annotation class to find * @param defaultValue Value to return if annotation is not present * @return value of annotation */ public static T getAnnotation( Method method, String annotationClassName, T defaultValue) { return compatible.getAnnotation( method, annotationClassName, defaultValue); } /** * Closes and cancels a {@link Statement} using the correct methods * available on the current Java runtime. *

    If errors are encountered while canceling or closing a statement, * the message is logged in {@link Util}. * @param stmt The statement to cancel and close. */ public static void cancelAndCloseStatement(Statement stmt) { compatible.cancelAndCloseStatement(stmt); } public static MemoryInfo getMemoryInfo() { return compatible.getMemoryInfo(); } /** * Converts a list of a string. * * For example, * commaList("foo", Arrays.asList({"a", "b"})) * returns "foo(a, b)". * * @param s Prefix * @param list List * @return String representation of string */ public static String commaList( String s, List list) { final StringBuilder buf = new StringBuilder(s); buf.append("("); int k = -1; for (T t : list) { if (++k > 0) { buf.append(", "); } buf.append(t); } buf.append(")"); return buf.toString(); } /** * Makes a name distinct from other names which have already been used * and shorter than a length limit, adds it to the list, and returns it. * * @param name Suggested name, may not be unique * @param maxLength Maximum length of generated name * @param nameList Collection of names already used * * @return Unique name */ public static String uniquify( String name, int maxLength, Collection nameList) { assert name != null; if (name.length() > maxLength) { name = name.substring(0, maxLength); } if (nameList.contains(name)) { String aliasBase = name; int j = 0; while (true) { name = aliasBase + j; if (name.length() > maxLength) { aliasBase = aliasBase.substring(0, aliasBase.length() - 1); continue; } if (!nameList.contains(name)) { break; } j++; } } nameList.add(name); return name; } /** * Returns whether a collection contains precisely one distinct element. * Returns false if the collection is empty, or if it contains elements * that are not the same as each other. * * @param collection Collection * @return boolean true if all values are same */ public static boolean areOccurencesEqual( Collection collection) { Iterator it = collection.iterator(); if (!it.hasNext()) { // Collection is empty return false; } T first = it.next(); while (it.hasNext()) { T t = it.next(); if (!t.equals(first)) { return false; } } return true; } /** * Creates a memory-, CPU- and cache-efficient immutable list. * * @param t Array of members of list * @param Element type * @return List containing the given members */ public static List flatList(T... t) { return _flatList(t, false); } /** * Creates a memory-, CPU- and cache-efficient immutable list, * always copying the contents. * * @param t Array of members of list * @param Element type * @return List containing the given members */ public static List flatListCopy(T... t) { return _flatList(t, true); } /** * Creates a memory-, CPU- and cache-efficient immutable list, optionally * copying the list. * * @param copy Whether to always copy the list * @param t Array of members of list * @return List containing the given members */ private static List _flatList(T[] t, boolean copy) { switch (t.length) { case 0: return Collections.emptyList(); case 1: return Collections.singletonList(t[0]); case 2: return new Flat2List(t[0], t[1]); case 3: return new Flat3List(t[0], t[1], t[2]); default: // REVIEW: AbstractList contains a modCount field; we could // write our own implementation and reduce creation overhead a // bit. if (copy) { return Arrays.asList(t.clone()); } else { return Arrays.asList(t); } } } /** * Creates a memory-, CPU- and cache-efficient immutable list from an * existing list. The list is always copied. * * @param t Array of members of list * @param Element type * @return List containing the given members */ public static List flatList(List t) { switch (t.size()) { case 0: return Collections.emptyList(); case 1: return Collections.singletonList(t.get(0)); case 2: return new Flat2List(t.get(0), t.get(1)); case 3: return new Flat3List(t.get(0), t.get(1), t.get(2)); default: // REVIEW: AbstractList contains a modCount field; we could // write our own implementation and reduce creation overhead a // bit. //noinspection unchecked return (List) Arrays.asList(t.toArray()); } } /** * Parses a locale string. * *

    The inverse operation of {@link java.util.Locale#toString()}. * * @param localeString Locale string, e.g. "en" or "en_US" * @return Java locale object */ public static Locale parseLocale(String localeString) { String[] strings = localeString.split("_"); switch (strings.length) { case 1: return new Locale(strings[0]); case 2: return new Locale(strings[0], strings[1]); case 3: return new Locale(strings[0], strings[1], strings[2]); default: throw newInternal( "bad locale string '" + localeString + "'"); } } /** * Converts a list of olap4j-style segments to a list of mondrian-style * segments. * * @param olap4jSegmentList List of olap4j segments * @return List of mondrian segments */ public static List convert( List olap4jSegmentList) { final List list = new ArrayList(); for (IdentifierSegment olap4jSegment : olap4jSegmentList) { list.add(convert(olap4jSegment)); } return list; } /** * Converts an olap4j-style segment to a mondrian-style segment. * * @param olap4jSegment olap4j segment * @return mondrian segment */ public static Id.Segment convert(IdentifierSegment olap4jSegment) { if (olap4jSegment instanceof NameSegment) { NameSegment nameSegment = (NameSegment) olap4jSegment; return new Id.Segment( nameSegment.getName(), nameSegment.getQuoting() == Quoting.QUOTED ? Id.Quoting.QUOTED : Id.Quoting.UNQUOTED); } else { // Mondrian's representation of segments is inferior to olap4j's. // 1. Mondrian assumes that the key has only one part // 2. Mondrian does not specify whether key is quoted (e.g. &[foo] // vs. &foo) final KeySegment keySegment = (KeySegment) olap4jSegment; assert keySegment.getKeyParts().size() == 1 : keySegment; return new Id.Segment( keySegment.getKeyParts().get(0).getName(), Id.Quoting.KEY); } } /** * Applies a collection of filters to an iterable. * * @param iterable Iterable * @param conds Zero or more conditions * @param * @return Iterable that returns only members of underlying iterable for * for which all conditions evaluate to true */ public static Iterable filter( final Iterable iterable, final Functor1... conds) { final Functor1[] conds2 = optimizeConditions(conds); if (conds2.length == 0) { return iterable; } return new Iterable() { public Iterator iterator() { return new Iterator() { final Iterator iterator = iterable.iterator(); T next; boolean hasNext = moveToNext(); private boolean moveToNext() { outer: while (iterator.hasNext()) { next = iterator.next(); for (Functor1 cond : conds2) { if (!cond.apply(next)) { continue outer; } } return true; } return false; } public boolean hasNext() { return hasNext; } public T next() { T t = next; hasNext = moveToNext(); return t; } public void remove() { throw new UnsupportedOperationException(); } }; } }; } private static Functor1[] optimizeConditions( Functor1[] conds) { final List> functor1List = new ArrayList>(Arrays.asList(conds)); for (Iterator> funcIter = functor1List.iterator(); funcIter.hasNext();) { Functor1 booleanTFunctor1 = funcIter.next(); if (booleanTFunctor1 == trueFunctor()) { funcIter.remove(); } } if (functor1List.size() < conds.length) { //noinspection unchecked return functor1List.toArray(new Functor1[functor1List.size()]); } else { return conds; } } /** * Sorts a collection of {@link Comparable} objects and returns a list. * * @param collection Collection * @param Element type * @return Sorted list */ public static List sort( Collection collection) { Object[] a = collection.toArray(new Object[collection.size()]); Arrays.sort(a); return cast(Arrays.asList(a)); } /** * Sorts a collection of objects using a {@link java.util.Comparator} and returns a * list. * * @param collection Collection * @param comparator Comparator * @param Element type * @return Sorted list */ public static List sort( Collection collection, Comparator comparator) { Object[] a = collection.toArray(new Object[collection.size()]); //noinspection unchecked Arrays.sort(a, (Comparator) comparator); return cast(Arrays.asList(a)); } public static List toOlap4j( List segments) { List list = new ArrayList(); for (Id.Segment segment : segments) { list.add(toOlap4j(segment)); } return list; } public static IdentifierSegment toOlap4j(Id.Segment segment) { switch (segment.quoting) { case KEY: return new KeySegment( new NameSegment( null, segment.name, Quoting.QUOTED)); default: return new NameSegment( null, segment.name, toOlap4j(segment.quoting)); } } public static Quoting toOlap4j(Id.Quoting quoting) { return Quoting.valueOf(quoting.name()); } // TODO: move to IdentifierSegment public static boolean matches(IdentifierSegment segment, String name) { switch (segment.getQuoting()) { case KEY: return false; // FIXME case QUOTED: return equalName(segment.getName(), name); case UNQUOTED: return segment.getName().equalsIgnoreCase(name); default: throw unexpected(segment.getQuoting()); } } public static RuntimeException newElementNotFoundException( int category, IdentifierNode identifierNode) { String type; switch (category) { case Category.Member: return MondrianResource.instance().MemberNotFound.ex( identifierNode.toString()); case Category.Unknown: type = "Element"; break; default: type = Category.instance().getDescription(category); } return newError(type + " '" + identifierNode + "' not found"); } /** * Calls {@link java.util.concurrent.Future#get()} and converts any * throwable into a non-checked exception. * * @param future Future * @param message Message to qualify wrapped exception * @param Result type * @return Result */ public static T safeGet(Future future, String message) { try { return future.get(); } catch (InterruptedException e) { throw newError(e, message); } catch (ExecutionException e) { final Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } else if (cause instanceof Error) { throw (Error) cause; } else { throw newError(cause, message); } } } public static Set newIdentityHashSetFake() { final HashMap map = new HashMap(); return new Set() { public int size() { return map.size(); } public boolean isEmpty() { return map.isEmpty(); } public boolean contains(Object o) { return map.containsKey(o); } public Iterator iterator() { return map.keySet().iterator(); } public Object[] toArray() { return map.keySet().toArray(); } public T[] toArray(T[] a) { return map.keySet().toArray(a); } public boolean add(T t) { return map.put(t, Boolean.TRUE) == null; } public boolean remove(Object o) { return map.remove(o) == Boolean.TRUE; } public boolean containsAll(Collection c) { return map.keySet().containsAll(c); } public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } public void clear() { map.clear(); } }; } /** * Equivalent to {@link Timer#Timer(String, boolean)}. * (Introduced in JDK 1.5.) * * @param name the name of the associated thread * @param isDaemon true if the associated thread should run as a daemon * @return timer */ public static Timer newTimer(String name, boolean isDaemon) { return compatible.newTimer(name, isDaemon); } /** * As Arrays#binarySearch(Object[], int, int, Object), but * available pre-JDK 1.6. */ public static > int binarySearch( T[] ts, int start, int end, T t) { return compatible.binarySearch(ts, start, end, t); } /** * Returns the intersection of two sorted sets. Does not modify either set. * *

    Optimized for the case that both sets are {@link ArraySortedSet}.

    * * @param set1 First set * @param set2 Second set * @return Intersection of the sets */ public static SortedSet intersect( SortedSet set1, SortedSet set2) { if (set1.isEmpty()) { return set1; } if (set2.isEmpty()) { return set2; } if (!(set1 instanceof ArraySortedSet) || !(set2 instanceof ArraySortedSet)) { final TreeSet set = new TreeSet(set1); set.removeAll(set2); return set; } final Comparable[] result = new Comparable[Math.min(set1.size(), set2.size())]; final Iterator it1 = set1.iterator(); final Iterator it2 = set2.iterator(); int i = 0; E e1 = it1.next(); E e2 = it2.next(); for (;;) { final int compare = e1.compareTo(e2); if (compare == 0) { result[i++] = e1; if (!it1.hasNext() || !it2.hasNext()) { break; } e1 = it1.next(); e2 = it2.next(); } else if (compare == 1) { if (!it2.hasNext()) { break; } e2 = it2.next(); } else { if (!it1.hasNext()) { break; } e1 = it1.next(); } } return new ArraySortedSet(result, 0, i); } public static class ErrorCellValue { public String toString() { return "#ERR"; } } @SuppressWarnings({"unchecked"}) public static T[] genericArray(Class clazz, int size) { return (T[]) Array.newInstance(clazz, size); } /** * Throws an internal error if condition is not true. It would be called * assert, but that is a keyword as of JDK 1.4. */ public static void assertTrue(boolean b) { if (!b) { throw newInternal("assert failed"); } } /** * Throws an internal error with the given messagee if condition is not * true. It would be called assert, but that is a keyword as * of JDK 1.4. */ public static void assertTrue(boolean b, String message) { if (!b) { throw newInternal("assert failed: " + message); } } /** * Creates an internal error with a given message. */ public static RuntimeException newInternal(String message) { return MondrianResource.instance().Internal.ex(message); } /** * Creates an internal error with a given message and cause. */ public static RuntimeException newInternal(Throwable e, String message) { return MondrianResource.instance().Internal.ex(message, e); } /** * Creates a non-internal error. Currently implemented in terms of * internal errors, but later we will create resourced messages. */ public static RuntimeException newError(String message) { return newInternal(message); } /** * Creates a non-internal error. Currently implemented in terms of * internal errors, but later we will create resourced messages. */ public static RuntimeException newError(Throwable e, String message) { return newInternal(e, message); } /** * Returns an exception indicating that we didn't expect to find this value * here. * * @param value Value */ public static RuntimeException unexpected(Enum value) { return Util.newInternal( "Was not expecting value '" + value + "' for enumeration '" + value.getClass().getName() + "' in this context"); } /** * Checks that a precondition (declared using the javadoc @pre * tag) is satisfied. * * @param b The value of executing the condition */ public static void assertPrecondition(boolean b) { assertTrue(b); } /** * Checks that a precondition (declared using the javadoc @pre * tag) is satisfied. For example, * *
    void f(String s) {
         *    Util.assertPrecondition(s != null, "s != null");
         *    ...
         * }
    * * @param b The value of executing the condition * @param condition The text of the condition */ public static void assertPrecondition(boolean b, String condition) { assertTrue(b, condition); } /** * Checks that a postcondition (declared using the javadoc * @post tag) is satisfied. * * @param b The value of executing the condition */ public static void assertPostcondition(boolean b) { assertTrue(b); } /** * Checks that a postcondition (declared using the javadoc * @post tag) is satisfied. * * @param b The value of executing the condition */ public static void assertPostcondition(boolean b, String condition) { assertTrue(b, condition); } /** * Converts an error into an array of strings, the most recent error first. * * @param e the error; may be null. Errors are chained according to their * {@link Throwable#getCause cause}. */ public static String[] convertStackToString(Throwable e) { List list = new ArrayList(); while (e != null) { String sMsg = getErrorMessage(e); list.add(sMsg); e = e.getCause(); } return list.toArray(new String[list.size()]); } /** * Constructs the message associated with an arbitrary Java error, making * up one based on the stack trace if there is none. As * {@link #getErrorMessage(Throwable,boolean)}, but does not print the * class name if the exception is derived from {@link java.sql.SQLException} * or is exactly a {@link java.lang.Exception}. */ public static String getErrorMessage(Throwable err) { boolean prependClassName = !(err instanceof java.sql.SQLException || err.getClass() == java.lang.Exception.class); return getErrorMessage(err, prependClassName); } /** * Constructs the message associated with an arbitrary Java error, making * up one based on the stack trace if there is none. * * @param err the error * @param prependClassName should the error be preceded by the * class name of the Java exception? defaults to false, unless the error * is derived from {@link java.sql.SQLException} or is exactly a {@link * java.lang.Exception} */ public static String getErrorMessage( Throwable err, boolean prependClassName) { String errMsg = err.getMessage(); if ((errMsg == null) || (err instanceof RuntimeException)) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); err.printStackTrace(pw); return sw.toString(); } else { return (prependClassName) ? err.getClass().getName() + ": " + errMsg : errMsg; } } /** * If one of the causes of an exception is of a particular class, returns * that cause. Otherwise returns null. * * @param e Exception * @param clazz Desired class * @param Class * @return Cause of given class, or null */ public static T getMatchingCause(Throwable e, Class clazz) { for (;;) { if (clazz.isInstance(e)) { return clazz.cast(e); } final Throwable cause = e.getCause(); if (cause == null || cause == e) { return null; } e = cause; } } /** * Converts an expression to a string. */ public static String unparse(Exp exp) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); exp.unparse(pw); return sw.toString(); } /** * Converts an query to a string. */ public static String unparse(Query query) { StringWriter sw = new StringWriter(); PrintWriter pw = new QueryPrintWriter(sw); query.unparse(pw); return sw.toString(); } /** * Creates a file-protocol URL for the given file. */ public static URL toURL(File file) throws MalformedURLException { String path = file.getAbsolutePath(); // This is a bunch of weird code that is required to // make a valid URL on the Windows platform, due // to inconsistencies in what getAbsolutePath returns. String fs = System.getProperty("file.separator"); if (fs.length() == 1) { char sep = fs.charAt(0); if (sep != '/') { path = path.replace(sep, '/'); } if (path.charAt(0) != '/') { path = '/' + path; } } path = "file://" + path; return new URL(path); } /** * PropertyList is an order-preserving list of key-value * pairs. Lookup is case-insensitive, but the case of keys is preserved. */ public static class PropertyList implements Iterable>, Serializable { List> list = new ArrayList>(); public PropertyList() { this.list = new ArrayList>(); } private PropertyList(List> list) { this.list = list; } @SuppressWarnings({"CloneDoesntCallSuperClone"}) @Override public PropertyList clone() { return new PropertyList(new ArrayList>(list)); } public String get(String key) { return get(key, null); } public String get(String key, String defaultValue) { for (int i = 0, n = list.size(); i < n; i++) { Pair pair = list.get(i); if (pair.left.equalsIgnoreCase(key)) { return pair.right; } } return defaultValue; } public String put(String key, String value) { for (int i = 0, n = list.size(); i < n; i++) { Pair pair = list.get(i); if (pair.left.equalsIgnoreCase(key)) { String old = pair.right; if (key.equalsIgnoreCase("Provider")) { // Unlike all other properties, later values of // "Provider" do not supersede } else { pair.right = value; } return old; } } list.add(new Pair(key, value)); return null; } public boolean remove(String key) { boolean found = false; for (int i = 0; i < list.size(); i++) { Pair pair = list.get(i); if (pair.getKey().equalsIgnoreCase(key)) { list.remove(i); found = true; --i; } } return found; } public String toString() { StringBuilder sb = new StringBuilder(64); for (int i = 0, n = list.size(); i < n; i++) { Pair pair = list.get(i); if (i > 0) { sb.append("; "); } sb.append(pair.left); sb.append('='); final String right = pair.right; if (right == null) { sb.append("'null'"); } else { // Quote a property value if is has a semi colon in it // 'xxx;yyy'. Escape any single-quotes by doubling them. final int needsQuote = right.indexOf(';'); if (needsQuote >= 0) { // REVIEW: This logic leaves off the leading/trailing // quote if the property value already has a // leading/trailing quote. Doesn't seem right to me. if (right.charAt(0) != '\'') { sb.append("'"); } sb.append(replace(right, "'", "''")); if (right.charAt(right.length() - 1) != '\'') { sb.append("'"); } } else { sb.append(right); } } } return sb.toString(); } public Iterator> iterator() { return list.iterator(); } } /** * Converts an OLE DB connect string into a {@link PropertyList}. * *

    For example, "Provider=MSOLAP; DataSource=LOCALHOST;" * becomes the set of (key, value) pairs {("Provider","MSOLAP"), * ("DataSource", "LOCALHOST")}. Another example is * Provider='sqloledb';Data Source='MySqlServer';Initial * Catalog='Pubs';Integrated Security='SSPI';. * *

    This method implements as much as possible of the OLE DB connect string syntax * specification. To find what it actually does, take * a look at the mondrian.olap.UtilTestCase test case. */ public static PropertyList parseConnectString(String s) { return new ConnectStringParser(s).parse(); } private static class ConnectStringParser { private final String s; private final int n; private int i; private final StringBuilder nameBuf; private final StringBuilder valueBuf; private ConnectStringParser(String s) { this.s = s; this.i = 0; this.n = s.length(); this.nameBuf = new StringBuilder(64); this.valueBuf = new StringBuilder(64); } PropertyList parse() { PropertyList list = new PropertyList(); while (i < n) { parsePair(list); } return list; } /** * Reads "name=value;" or "name=value". */ void parsePair(PropertyList list) { String name = parseName(); if (name == null) { return; } String value; if (i >= n) { value = ""; } else if (s.charAt(i) == ';') { i++; value = ""; } else { value = parseValue(); } list.put(name, value); } /** * Reads "name=". Name can contain equals sign if equals sign is * doubled. Returns null if there is no name to read. */ String parseName() { nameBuf.setLength(0); while (true) { char c = s.charAt(i); switch (c) { case '=': i++; if (i < n && (c = s.charAt(i)) == '=') { // doubled equals sign; take one of them, and carry on i++; nameBuf.append(c); break; } String name = nameBuf.toString(); name = name.trim(); return name; case ' ': if (nameBuf.length() == 0) { // ignore preceding spaces i++; if (i >= n) { // there is no name, e.g. trailing spaces after // semicolon, 'x=1; y=2; ' return null; } break; } else { // fall through } default: nameBuf.append(c); i++; if (i >= n) { return nameBuf.toString().trim(); } } } } /** * Reads "value;" or "value" */ String parseValue() { char c; // skip over leading white space while ((c = s.charAt(i)) == ' ') { i++; if (i >= n) { return ""; } } if (c == '"' || c == '\'') { String value = parseQuoted(c); // skip over trailing white space while (i < n && (c = s.charAt(i)) == ' ') { i++; } if (i >= n) { return value; } else if (s.charAt(i) == ';') { i++; return value; } else { throw new RuntimeException( "quoted value ended too soon, at position " + i + " in '" + s + "'"); } } else { String value; int semi = s.indexOf(';', i); if (semi >= 0) { value = s.substring(i, semi); i = semi + 1; } else { value = s.substring(i); i = n; } return value.trim(); } } /** * Reads a string quoted by a given character. Occurrences of the * quoting character must be doubled. For example, * parseQuoted('"') reads "a ""new"" string" * and returns a "new" string. */ String parseQuoted(char q) { char c = s.charAt(i++); Util.assertTrue(c == q); valueBuf.setLength(0); while (i < n) { c = s.charAt(i); if (c == q) { i++; if (i < n) { c = s.charAt(i); if (c == q) { valueBuf.append(c); i++; continue; } } return valueBuf.toString(); } else { valueBuf.append(c); i++; } } throw new RuntimeException( "Connect string '" + s + "' contains unterminated quoted value '" + valueBuf.toString() + "'"); } } /** * Combines two integers into a hash code. */ public static int hash(int i, int j) { return (i << 4) ^ j; } /** * Computes a hash code from an existing hash code and an object (which * may be null). */ public static int hash(int h, Object o) { int k = (o == null) ? 0 : o.hashCode(); return ((h << 4) | h) ^ k; } /** * Computes a hash code from an existing hash code and an array of objects * (which may be null). */ public static int hashArray(int h, Object [] a) { // The hashcode for a null array and an empty array should be different // than h, so use magic numbers. if (a == null) { return hash(h, 19690429); } if (a.length == 0) { return hash(h, 19690721); } for (Object anA : a) { h = hash(h, anA); } return h; } /** * Concatenates one or more arrays. * *

    Resulting array has same element type as first array. Each arrays may * be empty, but must not be null. * * @param a0 First array * @param as Zero or more subsequent arrays * @return Array containing all elements */ public static T[] appendArrays( T[] a0, T[]... as) { int n = a0.length; for (T[] a : as) { n += a.length; } T[] copy = Util.copyOf(a0, n); n = a0.length; for (T[] a : as) { System.arraycopy(a, 0, copy, n, a.length); n += a.length; } return copy; } /** * Adds an object to the end of an array. The resulting array is of the * same type (e.g. String[]) as the input array. * * @param a Array * @param o Element * @return New array containing original array plus element * * @see #appendArrays */ public static T[] append(T[] a, T o) { T[] a2 = Util.copyOf(a, a.length + 1); a2[a.length] = o; return a2; } /** * Like {@link java.util.Arrays}.copyOf(double[], int), but * exists prior to JDK 1.6. * * @param original the array to be copied * @param newLength the length of the copy to be returned * @return a copy of the original array, truncated or padded with zeros * to obtain the specified length */ public static double[] copyOf(double[] original, int newLength) { double[] copy = new double[newLength]; System.arraycopy( original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } /** * Like {@link java.util.Arrays}.copyOf(int[], int), but * exists prior to JDK 1.6. * * @param original the array to be copied * @param newLength the length of the copy to be returned * @return a copy of the original array, truncated or padded with zeros * to obtain the specified length */ public static int[] copyOf(int[] original, int newLength) { int[] copy = new int[newLength]; System.arraycopy( original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } /** * Like {@link java.util.Arrays}.copyOf(long[], int), but * exists prior to JDK 1.6. * * @param original the array to be copied * @param newLength the length of the copy to be returned * @return a copy of the original array, truncated or padded with zeros * to obtain the specified length */ public static long[] copyOf(long[] original, int newLength) { long[] copy = new long[newLength]; System.arraycopy( original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } /** * Like {@link java.util.Arrays}.copyOf(Object[], int), but * exists prior to JDK 1.6. * * @param original the array to be copied * @param newLength the length of the copy to be returned * @return a copy of the original array, truncated or padded with zeros * to obtain the specified length */ public static T[] copyOf(T[] original, int newLength) { //noinspection unchecked return (T[]) copyOf(original, newLength, original.getClass()); } /** * Copies the specified array. * * @param original the array to be copied * @param newLength the length of the copy to be returned * @param newType the class of the copy to be returned * @return a copy of the original array, truncated or padded with nulls * to obtain the specified length */ public static T[] copyOf( U[] original, int newLength, Class newType) { @SuppressWarnings({"unchecked", "RedundantCast"}) T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); //noinspection SuspiciousSystemArraycopy System.arraycopy( original, 0, copy, 0, Math.min(original.length, newLength)); return copy; } /** * Returns the cumulative amount of time spent accessing the database. * * @deprecated Use {@link mondrian.server.monitor.Monitor#getServer()} and * {@link mondrian.server.monitor.ServerInfo#sqlStatementExecuteNanos}; * will be removed in 4.0. */ public static long dbTimeMillis() { return databaseMillis; } /** * Adds to the cumulative amount of time spent accessing the database. * * @deprecated Will be removed in 4.0. */ public static void addDatabaseTime(long millis) { databaseMillis += millis; } /** * Returns the system time less the time spent accessing the database. * Use this method to figure out how long an operation took: call this * method before an operation and after an operation, and the difference * is the amount of non-database time spent. * * @deprecated Will be removed in 4.0. */ public static long nonDbTimeMillis() { final long systemMillis = System.currentTimeMillis(); return systemMillis - databaseMillis; } /** * Creates a very simple implementation of {@link Validator}. (Only * useful for resolving trivial expressions.) */ public static Validator createSimpleValidator(final FunTable funTable) { return new Validator() { public Query getQuery() { return null; } public SchemaReader getSchemaReader() { throw new UnsupportedOperationException(); } public Exp validate(Exp exp, boolean scalar) { return exp; } public void validate(ParameterExpr parameterExpr) { } public void validate(MemberProperty memberProperty) { } public void validate(QueryAxis axis) { } public void validate(Formula formula) { } public FunDef getDef(Exp[] args, String name, Syntax syntax) { // Very simple resolution. Assumes that there is precisely // one resolver (i.e. no overloading) and no argument // conversions are necessary. List resolvers = funTable.getResolvers(name, syntax); final Resolver resolver = resolvers.get(0); final List conversionList = new ArrayList(); final FunDef def = resolver.resolve(args, this, conversionList); assert conversionList.isEmpty(); return def; } public boolean alwaysResolveFunDef() { return false; } public boolean canConvert( int ordinal, Exp fromExp, int to, List conversions) { return true; } public boolean requiresExpression() { return false; } public FunTable getFunTable() { return funTable; } public Parameter createOrLookupParam( boolean definition, String name, Type type, Exp defaultExp, String description) { return null; } }; } /** * Reads a Reader until it returns EOF and returns the contents as a String. * * @param rdr Reader to Read. * @param bufferSize size of buffer to allocate for reading. * @return content of Reader as String * @throws IOException on I/O error */ public static String readFully(final Reader rdr, final int bufferSize) throws IOException { if (bufferSize <= 0) { throw new IllegalArgumentException( "Buffer size must be greater than 0"); } final char[] buffer = new char[bufferSize]; final StringBuilder buf = new StringBuilder(bufferSize); int len; while ((len = rdr.read(buffer)) != -1) { buf.append(buffer, 0, len); } return buf.toString(); } /** * Reads an input stream until it returns EOF and returns the contents as an * array of bytes. * * @param in Input stream * @param bufferSize size of buffer to allocate for reading. * @return content of stream as an array of bytes * @throws IOException on I/O error */ public static byte[] readFully(final InputStream in, final int bufferSize) throws IOException { if (bufferSize <= 0) { throw new IllegalArgumentException( "Buffer size must be greater than 0"); } final byte[] buffer = new byte[bufferSize]; final ByteArrayOutputStream baos = new ByteArrayOutputStream(bufferSize); int len; while ((len = in.read(buffer)) != -1) { baos.write(buffer, 0, len); } return baos.toByteArray(); } /** * Returns the contents of a URL, substituting tokens. * *

    Replaces the tokens "${key}" if the map is not null and "key" occurs * in the key-value map. * *

    If the URL string starts with "inline:" the contents are the * rest of the URL. * * @param urlStr URL string * @param map Key/value map * @return Contents of URL with tokens substituted * @throws IOException on I/O error */ public static String readURL(final String urlStr, Map map) throws IOException { if (urlStr.startsWith("inline:")) { String content = urlStr.substring("inline:".length()); if (map != null) { content = Util.replaceProperties(content, map); } return content; } else { final URL url = new URL(urlStr); return readURL(url, map); } } /** * Returns the contents of a URL. * * @param url URL * @return Contents of URL * @throws IOException on I/O error */ public static String readURL(final URL url) throws IOException { return readURL(url, null); } /** * Returns the contents of a URL, substituting tokens. * *

    Replaces the tokens "${key}" if the map is not null and "key" occurs * in the key-value map. * * @param url URL * @param map Key/value map * @return Contents of URL with tokens substituted * @throws IOException on I/O error */ public static String readURL( final URL url, Map map) throws IOException { final Reader r = new BufferedReader(new InputStreamReader(url.openStream())); final int BUF_SIZE = 8096; try { String xmlCatalog = readFully(r, BUF_SIZE); xmlCatalog = Util.replaceProperties(xmlCatalog, map); return xmlCatalog; } finally { r.close(); } } /** * Gets content via Apache VFS. File must exist and have content * * @param url String * @return Apache VFS FileContent for further processing * @throws FileSystemException on error */ public static InputStream readVirtualFile(String url) throws FileSystemException { // Treat catalogUrl as an Apache VFS (Virtual File System) URL. // VFS handles all of the usual protocols (http:, file:) // and then some. FileSystemManager fsManager = VFS.getManager(); if (fsManager == null) { throw newError("Cannot get virtual file system manager"); } // Workaround VFS bug. if (url.startsWith("file://localhost")) { url = url.substring("file://localhost".length()); } if (url.startsWith("file:")) { url = url.substring("file:".length()); } //work around for VFS bug not closing http sockets // (Mondrian-585) if (url.startsWith("http")) { try { return new URL(url).openStream(); } catch (IOException e) { throw newError( "Could not read URL: " + url); } } File userDir = new File("").getAbsoluteFile(); FileObject file = fsManager.resolveFile(userDir, url); FileContent fileContent = null; try { // Because of VFS caching, make sure we refresh to get the latest // file content. This refresh may possibly solve the following // workaround for defect MONDRIAN-508, but cannot be tested, so we // will leave the work around for now. file.refresh(); // Workaround to defect MONDRIAN-508. For HttpFileObjects, verifies // the URL of the file retrieved matches the URL passed in. A VFS // cache bug can cause it to treat URLs with different parameters // as the same file (e.g. http://blah.com?param=A, // http://blah.com?param=B) if (file instanceof HttpFileObject && !file.getName().getURI().equals(url)) { fsManager.getFilesCache().removeFile( file.getFileSystem(), file.getName()); file = fsManager.resolveFile(userDir, url); } if (!file.isReadable()) { throw newError( "Virtual file is not readable: " + url); } fileContent = file.getContent(); } finally { file.close(); } if (fileContent == null) { throw newError( "Cannot get virtual file content: " + url); } return fileContent.getInputStream(); } public static String readVirtualFileAsString( String catalogUrl) throws IOException { InputStream in = readVirtualFile(catalogUrl); try { final byte[] bytes = Util.readFully(in, 1024); final char[] chars = new char[bytes.length]; for (int i = 0; i < chars.length; i++) { chars[i] = (char) bytes[i]; } return new String(chars); } finally { if (in != null) { in.close(); } } } /** * Converts a {@link Properties} object to a string-to-string {@link Map}. * * @param properties Properties * @return String-to-string map */ public static Map toMap(final Properties properties) { return new AbstractMap() { @SuppressWarnings({"unchecked"}) public Set> entrySet() { return (Set) properties.entrySet(); } }; } /** * Replaces tokens in a string. * *

    Replaces the tokens "${key}" if "key" occurs in the key-value map. * Otherwise "${key}" is left in the string unchanged. * * @param text Source string * @param env Map of key-value pairs * @return String with tokens substituted */ public static String replaceProperties( String text, Map env) { // As of JDK 1.5, cannot use StringBuilder - appendReplacement requires // the antediluvian StringBuffer. StringBuffer buf = new StringBuffer(text.length() + 200); Pattern pattern = Pattern.compile("\\$\\{([^${}]+)\\}"); Matcher matcher = pattern.matcher(text); while (matcher.find()) { String varName = matcher.group(1); String varValue = env.get(varName); if (varValue != null) { matcher.appendReplacement(buf, varValue); } else { matcher.appendReplacement(buf, "\\${$1}"); } } matcher.appendTail(buf); return buf.toString(); } public static String printMemory() { return printMemory(null); } public static String printMemory(String msg) { final Runtime rt = Runtime.getRuntime(); final long freeMemory = rt.freeMemory(); final long totalMemory = rt.totalMemory(); final StringBuilder buf = new StringBuilder(64); buf.append("FREE_MEMORY:"); if (msg != null) { buf.append(msg); buf.append(':'); } buf.append(' '); buf.append(freeMemory / 1024); buf.append("kb "); long hundredths = (freeMemory * 10000) / totalMemory; buf.append(hundredths / 100); hundredths %= 100; if (hundredths >= 10) { buf.append('.'); } else { buf.append(".0"); } buf.append(hundredths); buf.append('%'); return buf.toString(); } /** * Casts a Set to a Set with a different element type. * * @param set Set * @return Set of desired type */ @SuppressWarnings({"unchecked"}) public static Set cast(Set set) { return (Set) set; } /** * Casts a List to a List with a different element type. * * @param list List * @return List of desired type */ @SuppressWarnings({"unchecked"}) public static List cast(List list) { return (List) list; } /** * Returns whether it is safe to cast a collection to a collection with a * given element type. * * @param collection Collection * @param clazz Target element type * @param Element type * @return Whether all not-null elements of the collection are instances of * element type */ public static boolean canCast( Collection collection, Class clazz) { for (Object o : collection) { if (o != null && !clazz.isInstance(o)) { return false; } } return true; } /** * Casts a collection to iterable. * * Under JDK 1.4, {@link Collection} objects do not implement * {@link Iterable}, so this method inserts a casting wrapper. (Since * Iterable does not exist under JDK 1.4, they will have been compiled * under JDK 1.5 or later, then retrowoven to 1.4 class format. References * to Iterable will have been replaced with references to * com.rc.retroweaver.runtime.Retroweaver_. * *

    Under later JDKs this method is trivial. This method can be deleted * when we discontinue support for JDK 1.4. * * @param iterable Object which ought to be iterable * @param Element type * @return Object cast to Iterable */ public static Iterable castToIterable( final Object iterable) { if (Util.Retrowoven && !(iterable instanceof Iterable)) { return new Iterable() { public Iterator iterator() { return ((Collection) iterable).iterator(); } }; } return (Iterable) iterable; } /** * Looks up an enumeration by name, returning null if null or not valid. * * @param clazz Enumerated type * @param name Name of constant */ public static > E lookup(Class clazz, String name) { return lookup(clazz, name, null); } /** * Looks up an enumeration by name, returning a given default value if null * or not valid. * * @param clazz Enumerated type * @param name Name of constant * @param defaultValue Default value if constant is not found * @return Value, or null if name is null or value does not exist */ public static > E lookup( Class clazz, String name, E defaultValue) { if (name == null) { return defaultValue; } try { return Enum.valueOf(clazz, name); } catch (IllegalArgumentException e) { return defaultValue; } } /** * Make a BigDecimal from a double. On JDK 1.5 or later, the BigDecimal * precision reflects the precision of the double while with JDK 1.4 * this is not the case. * * @param d the input double * @return the BigDecimal */ public static BigDecimal makeBigDecimalFromDouble(double d) { return compatible.makeBigDecimalFromDouble(d); } /** * Returns a literal pattern String for the specified String. * *

    Specification as for {@link Pattern#quote(String)}, which was * introduced in JDK 1.5. * * @param s The string to be literalized * @return A literal string replacement */ public static String quotePattern(String s) { return compatible.quotePattern(s); } /** * Generates a unique id. * *

    From JDK 1.5 onwards, uses a {@code UUID}. * * @return A unique id */ public static String generateUuidString() { return compatible.generateUuidString(); } /** * Compiles a script to yield a Java interface. * *

    Only valid JDK 1.6 and higher; fails on JDK 1.5 and earlier.

    * * @param iface Interface script should implement * @param script Script code * @param engineName Name of engine (e.g. "JavaScript") * @param Interface * @return Object that implements given interface */ public static T compileScript( Class iface, String script, String engineName) { return compatible.compileScript(iface, script, engineName); } /** * Removes a thread local from the current thread. * *

    From JDK 1.5 onwards, calls {@link ThreadLocal#remove()}; before * that, no-ops.

    * * @param threadLocal Thread local * @param Type */ public static void threadLocalRemove(ThreadLocal threadLocal) { compatible.threadLocalRemove(threadLocal); } /** * Creates a hash set that, like {@link java.util.IdentityHashMap}, * compares keys using identity. * * @param Element type * @return Set */ public static Set newIdentityHashSet() { return compatible.newIdentityHashSet(); } /** * Creates a new udf instance from the given udf class. * * @param udfClass the class to create new instance for * @param functionName Function name, or null * @return an instance of UserDefinedFunction */ public static UserDefinedFunction createUdf( Class udfClass, String functionName) { // Instantiate class with default constructor. UserDefinedFunction udf; String className = udfClass.getName(); String functionNameOrEmpty = functionName == null ? "" : functionName; // Find a constructor. Constructor constructor; Object[] args = {}; // 0. Check that class is public and top-level or static. // Before JDK 1.5, inner classes are impossible; retroweaver cannot // handle the getEnclosingClass method, so skip the check. if (!Modifier.isPublic(udfClass.getModifiers()) || (!PreJdk15 && udfClass.getEnclosingClass() != null && !Modifier.isStatic(udfClass.getModifiers()))) { throw MondrianResource.instance().UdfClassMustBePublicAndStatic.ex( functionName, className); } // 1. Look for a constructor "public Udf(String name)". try { constructor = udfClass.getConstructor(String.class); if (Modifier.isPublic(constructor.getModifiers())) { args = new Object[] {functionName}; } else { constructor = null; } } catch (NoSuchMethodException e) { constructor = null; } // 2. Otherwise, look for a constructor "public Udf()". if (constructor == null) { try { constructor = udfClass.getConstructor(); if (Modifier.isPublic(constructor.getModifiers())) { args = new Object[] {}; } else { constructor = null; } } catch (NoSuchMethodException e) { constructor = null; } } // 3. Else, no constructor suitable. if (constructor == null) { throw MondrianResource.instance().UdfClassWrongIface.ex( functionNameOrEmpty, className, UserDefinedFunction.class.getName()); } // Instantiate class. try { udf = (UserDefinedFunction) constructor.newInstance(args); } catch (InstantiationException e) { throw MondrianResource.instance().UdfClassWrongIface.ex( functionNameOrEmpty, className, UserDefinedFunction.class.getName()); } catch (IllegalAccessException e) { throw MondrianResource.instance().UdfClassWrongIface.ex( functionName, className, UserDefinedFunction.class.getName()); } catch (ClassCastException e) { throw MondrianResource.instance().UdfClassWrongIface.ex( functionNameOrEmpty, className, UserDefinedFunction.class.getName()); } catch (InvocationTargetException e) { throw MondrianResource.instance().UdfClassWrongIface.ex( functionName, className, UserDefinedFunction.class.getName()); } return udf; } /** * Check the resultSize against the result limit setting. Throws * LimitExceededDuringCrossjoin exception if limit exceeded. * * When it is called from RolapNativeSet.checkCrossJoin(), it is only * possible to check the known input size, because the final CJ result * will come from the DB(and will be checked against the limit when * fetching from the JDBC result set, in SqlTupleReader.prepareTuples()) * * @param resultSize Result limit * @throws ResourceLimitExceededException */ public static void checkCJResultLimit(long resultSize) { int resultLimit = MondrianProperties.instance().ResultLimit.get(); // Throw an exeption, if the size of the crossjoin exceeds the result // limit. if (resultLimit > 0 && resultLimit < resultSize) { throw MondrianResource.instance().LimitExceededDuringCrossjoin.ex( resultSize, resultLimit); } // Throw an exception if the crossjoin exceeds a reasonable limit. // (Yes, 4 billion is a reasonable limit.) if (resultSize > Integer.MAX_VALUE) { throw MondrianResource.instance().LimitExceededDuringCrossjoin.ex( resultSize, Integer.MAX_VALUE); } } /** * Converts an olap4j connect string into a legacy mondrian connect string. * *

    For example, * "jdbc:mondrian:Datasource=jdbc/SampleData;Catalog=foodmart/FoodMart.xml;" * becomes * "Provider=Mondrian; * Datasource=jdbc/SampleData;Catalog=foodmart/FoodMart.xml;" * *

    This method is intended to allow legacy applications (such as JPivot * and Mondrian's XMLA server) to continue to create connections using * Mondrian's legacy connection API even when they are handed an olap4j * connect string. * * @param url olap4j connect string * @return mondrian connect string, or null if cannot be converted */ public static String convertOlap4jConnectStringToNativeMondrian( String url) { if (url.startsWith("jdbc:mondrian:")) { return "Provider=Mondrian; " + url.substring("jdbc:mondrian:".length()); } return null; } /** * Checks if a String is whitespace, empty ("") or null.

    * *
         * StringUtils.isBlank(null) = true
         * StringUtils.isBlank("") = true
         * StringUtils.isBlank(" ") = true
         * StringUtils.isBlank("bob") = false
         * StringUtils.isBlank(" bob ") = false
         * 
    * *

    (Copied from commons-lang.) * * @param str the String to check, may be null * @return true if the String is null, empty or whitespace */ public static boolean isBlank(String str) { final int strLen; if (str == null || (strLen = str.length()) == 0) { return true; } for (int i = 0; i < strLen; i++) { if (!Character.isWhitespace(str.charAt(i))) { return false; } } return true; } /** * Returns a role which has access to everything. * @param schema A schema to bind this role to. * @return A role with root access to the schema. */ public static Role createRootRole(Schema schema) { RoleImpl role = new RoleImpl(); role.grant(schema, Access.ALL); role.makeImmutable(); return role; } /** * Tries to find the cube from which a dimension is taken. * It considers private dimensions, shared dimensions and virtual * dimensions. If it can't determine with certitude the origin * of the dimension, it returns null. */ public static Cube getDimensionCube(Dimension dimension) { final Cube[] cubes = dimension.getSchema().getCubes(); for (Cube cube : cubes) { for (Dimension dimension1 : cube.getDimensions()) { // If the dimensions have the same identity, // we found an access rule. if (dimension == dimension1) { return cube; } // If the passed dimension argument is of class // RolapCubeDimension, we must validate the cube // assignment and make sure the cubes are the same. // If not, skip to the next grant. if (dimension instanceof RolapCubeDimension && dimension.equals(dimension1) && !((RolapCubeDimension)dimension1) .getCube() .equals(cube)) { continue; } // Last thing is to allow for equality correspondences // to work with virtual cubes. if (cube instanceof RolapCube && ((RolapCube)cube).isVirtual() && dimension.equals(dimension1)) { return cube; } } } return null; } public static abstract class AbstractFlatList implements List, RandomAccess { protected final List asArrayList() { //noinspection unchecked return Arrays.asList((T[]) toArray()); } public Iterator iterator() { return asArrayList().iterator(); } public ListIterator listIterator() { return asArrayList().listIterator(); } public boolean isEmpty() { return false; } public boolean add(Object t) { throw new UnsupportedOperationException(); } public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } public boolean addAll(int index, Collection c) { throw new UnsupportedOperationException(); } public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } public void clear() { throw new UnsupportedOperationException(); } public T set(int index, Object element) { throw new UnsupportedOperationException(); } public void add(int index, Object element) { throw new UnsupportedOperationException(); } public T remove(int index) { throw new UnsupportedOperationException(); } public ListIterator listIterator(int index) { return asArrayList().listIterator(index); } public List subList(int fromIndex, int toIndex) { return asArrayList().subList(fromIndex, toIndex); } public boolean contains(Object o) { return indexOf(o) >= 0; } public boolean containsAll(Collection c) { Iterator e = c.iterator(); while (e.hasNext()) { if (!contains(e.next())) { return false; } } return true; } public boolean remove(Object o) { throw new UnsupportedOperationException(); } } /** * List that stores its two elements in the two members of the class. * Unlike {@link java.util.ArrayList} or * {@link java.util.Arrays#asList(Object[])} there is * no array, only one piece of memory allocated, therefore is very compact * and cache and CPU efficient. * *

    The list is read-only, cannot be modified or resized, and neither * of the elements can be null. * *

    The list is created via {@link Util#flatList(Object[])}. * * @see mondrian.olap.Util.Flat3List * @param */ protected static class Flat2List extends AbstractFlatList { private final T t0; private final T t1; Flat2List(T t0, T t1) { this.t0 = t0; this.t1 = t1; assert t0 != null; assert t1 != null; } public String toString() { return "[" + t0 + ", " + t1 + "]"; } public T get(int index) { switch (index) { case 0: return t0; case 1: return t1; default: throw new IndexOutOfBoundsException("index " + index); } } public int size() { return 2; } public boolean equals(Object o) { if (o instanceof Flat2List) { Flat2List that = (Flat2List) o; return Util.equals(this.t0, that.t0) && Util.equals(this.t1, that.t1); } return Arrays.asList(t0, t1).equals(o); } public int hashCode() { int h = 1; h = h * 31 + t0.hashCode(); h = h * 31 + t1.hashCode(); return h; } public int indexOf(Object o) { if (t0.equals(o)) { return 0; } if (t1.equals(o)) { return 1; } return -1; } public int lastIndexOf(Object o) { if (t1.equals(o)) { return 1; } if (t0.equals(o)) { return 0; } return -1; } @SuppressWarnings({"unchecked"}) public T2[] toArray(T2[] a) { a[0] = (T2) t0; a[1] = (T2) t1; return a; } public Object[] toArray() { return new Object[] {t0, t1}; } } /** * List that stores its three elements in the three members of the class. * Unlike {@link java.util.ArrayList} or * {@link java.util.Arrays#asList(Object[])} there is * no array, only one piece of memory allocated, therefore is very compact * and cache and CPU efficient. * *

    The list is read-only, cannot be modified or resized, and none * of the elements can be null. * *

    The list is created via {@link Util#flatList(Object[])}. * * @see mondrian.olap.Util.Flat2List * @param */ protected static class Flat3List extends AbstractFlatList { private final T t0; private final T t1; private final T t2; Flat3List(T t0, T t1, T t2) { this.t0 = t0; this.t1 = t1; this.t2 = t2; assert t0 != null; assert t1 != null; assert t2 != null; } public String toString() { return "[" + t0 + ", " + t1 + ", " + t2 + "]"; } public T get(int index) { switch (index) { case 0: return t0; case 1: return t1; case 2: return t2; default: throw new IndexOutOfBoundsException("index " + index); } } public int size() { return 3; } public boolean equals(Object o) { if (o instanceof Flat3List) { Flat3List that = (Flat3List) o; return Util.equals(this.t0, that.t0) && Util.equals(this.t1, that.t1) && Util.equals(this.t2, that.t2); } return o.equals(this); } public int hashCode() { int h = 1; h = h * 31 + t0.hashCode(); h = h * 31 + t1.hashCode(); h = h * 31 + t2.hashCode(); return h; } public int indexOf(Object o) { if (t0.equals(o)) { return 0; } if (t1.equals(o)) { return 1; } if (t2.equals(o)) { return 2; } return -1; } public int lastIndexOf(Object o) { if (t2.equals(o)) { return 2; } if (t1.equals(o)) { return 1; } if (t0.equals(o)) { return 0; } return -1; } @SuppressWarnings({"unchecked"}) public T2[] toArray(T2[] a) { a[0] = (T2) t0; a[1] = (T2) t1; a[2] = (T2) t2; return a; } public Object[] toArray() { return new Object[] {t0, t1, t2}; } } /** * Garbage-collecting iterator. Iterates over a collection of references, * and if any of the references has been garbage-collected, removes it from * the collection. * * @param Element type */ public static class GcIterator implements Iterator { private final Iterator> iterator; private boolean hasNext; private T next; public GcIterator(Iterator> iterator) { this.iterator = iterator; this.hasNext = true; moveToNext(); } /** * Creates an iterator over a collection of references. * * @param referenceIterable Collection of references * @param element type * @return iterable over collection */ public static Iterable over( final Iterable> referenceIterable) { return new Iterable() { public Iterator iterator() { return new GcIterator(referenceIterable.iterator()); } }; } private void moveToNext() { while (iterator.hasNext()) { final Reference ref = iterator.next(); next = ref.get(); if (next != null) { return; } iterator.remove(); } hasNext = false; } public boolean hasNext() { return hasNext; } public T next() { final T next1 = next; moveToNext(); return next1; } public void remove() { throw new UnsupportedOperationException(); } } public static interface Functor1 { RT apply(PT param); } public static Functor1 identityFunctor() { //noinspection unchecked return (Functor1) IDENTITY_FUNCTOR; } private static final Functor1 IDENTITY_FUNCTOR = new Functor1() { public Object apply(Object param) { return param; } }; public static Functor1 trueFunctor() { //noinspection unchecked return (Functor1) TRUE_FUNCTOR; } private static final Functor1 TRUE_FUNCTOR = new Functor1() { public Boolean apply(Object param) { return true; } }; /** * Information about memory usage. * * @see mondrian.olap.Util#getMemoryInfo() */ public interface MemoryInfo { Usage get(); public interface Usage { long getUsed(); long getCommitted(); long getMax(); } } /** * A {@link Comparator} implementation which can deal * correctly with {@link RolapUtil#sqlNullValue}. */ public static class SqlNullSafeComparator implements Comparator { public static final SqlNullSafeComparator instance = new SqlNullSafeComparator(); private SqlNullSafeComparator() { } public int compare(Comparable o1, Comparable o2) { if (o1 == RolapUtil.sqlNullValue) { return -1; } if (o2 == RolapUtil.sqlNullValue) { return 1; } return o1.compareTo(o2); } } } // End Util.java mondrian-3.4.1/src/main/mondrian/olap/MondrianException.java0000644000175000017500000000234111735330606023774 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2005 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * Instances of this class are thrown for all exceptions that Mondrian * generates through as a result of known error conditions. It is used in the * resource classes generated from mondrian.resource.MondrianResource.xml. * * @author Galt Johnson (gjabx) * @see org.eigenbase.xom */ public class MondrianException extends RuntimeException { public MondrianException() { super(); } public MondrianException(Throwable cause) { super(cause); } public MondrianException(String message) { super(message); } public MondrianException(String message, Throwable cause) { super(message, cause); } public String getLocalizedMessage() { return getMessage(); } public String getMessage() { return "Mondrian Error:" + super.getMessage(); } } // End MondrianException.java mondrian-3.4.1/src/main/mondrian/olap/QueryCanceledException.java0000644000175000017500000000161011735330606024747 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2005-2007 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * Exception which indicates that a query was canceled by an end-user. * *

    See also {@link mondrian.olap.QueryTimeoutException}, which indicates that * a query was canceled automatically due to a timeout. */ public class QueryCanceledException extends ResultLimitExceededException { /** * Creates a QueryCanceledException. * * @param message Localized error message */ public QueryCanceledException(String message) { super(message); } } // End QueryCanceledException.java mondrian-3.4.1/src/main/mondrian/olap/ConnectionBase.java0000644000175000017500000000632511735330606023246 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.parser.*; import mondrian.resource.MondrianResource; import mondrian.server.Statement; import org.apache.log4j.Logger; /** * ConnectionBase implements some of the methods in * {@link Connection}. * * @author jhyde * @since 6 August, 2001 */ public abstract class ConnectionBase implements Connection { protected ConnectionBase() { } protected abstract Logger getLogger(); public String getFullConnectString() { String s = getConnectString(); String catalogName = getCatalogName(); if (catalogName != null) { int len = s.length() + catalogName.length() + 32; StringBuilder buf = new StringBuilder(len); buf.append(s); if (!s.endsWith(";")) { buf.append(';'); } buf.append("Initial Catalog="); buf.append(catalogName); buf.append(';'); s = buf.toString(); } return s; } public abstract Statement getInternalStatement(); public Query parseQuery(String query) { return (Query) parseStatement(query); } /** * Parses a query, with specified function table and the mode for strict * validation(if true then invalid members are not ignored). * *

    This method is only used in testing and by clients that need to * support customized parser behavior. That is why this method is not part * of the Connection interface. * *

    See test case mondrian.olap.CustomizedParserTest. * * @param statement Evaluation context * @param query MDX query that requires special parsing * @param funTable Customized function table to use in parsing * @param strictValidation If true, do not ignore invalid members * @return Query the corresponding Query object if parsing is successful * @throws MondrianException if parsing fails */ public QueryPart parseStatement( Statement statement, String query, FunTable funTable, boolean strictValidation) { MdxParserValidator parser = createParser(); boolean debug = false; if (funTable == null) { funTable = getSchema().getFunTable(); } if (getLogger().isDebugEnabled()) { //debug = true; getLogger().debug( Util.nl + query); } try { return parser.parseInternal( statement, query, debug, funTable, strictValidation); } catch (Exception e) { throw MondrianResource.instance().FailedToParseQuery.ex(query, e); } } protected MdxParserValidator createParser() { return true ? new JavaccParserValidatorImpl() : new MdxParserValidatorImpl(); } } // End ConnectionBase.java mondrian-3.4.1/src/main/mondrian/olap/InvalidHierarchyException.java0000644000175000017500000000143211735330606025452 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2007 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * Exception which indicates that a Cube is invalid * because there is a hierarchy with no members. */ public class InvalidHierarchyException extends MondrianException { /** * Creates a InvalidHierarchyException. * * @param message Localized error message */ public InvalidHierarchyException(String message) { super(message); } } // End InvalidHierarchyException.java mondrian-3.4.1/src/main/mondrian/olap/package.html0000644000175000017500000000022011735330606021756 0ustar drazzibdrazzib Mondrian's core package, this defines connections and the catalog metamodel, and allows you to execute queries. mondrian-3.4.1/src/main/mondrian/olap/NativeEvaluationUnsupportedException.java0000644000175000017500000000165411735330606027762 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * Exception which indicates that native evaluation of a function * was enabled but not supported, and * {@link MondrianProperties#AlertNativeEvaluationUnsupported} was * set to ERROR. * * @author John Sichi */ public class NativeEvaluationUnsupportedException extends ResultLimitExceededException { /** * Creates a NativeEvaluationUnsupportedException. * * @param message Localized error message */ public NativeEvaluationUnsupportedException(String message) { super(message); } } // End NativeEvaluationUnsupportedException.java mondrian-3.4.1/src/main/mondrian/olap/DriverManager.java0000644000175000017500000001007711735330606023101 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.rolap.RolapConnection; import mondrian.rolap.RolapConnectionProperties; import mondrian.spi.CatalogLocator; import javax.sql.DataSource; /** * The basic service for managing a set of OLAP drivers. * * @author jhyde * @since 15 January, 2002 */ public class DriverManager { public DriverManager() { } /** * Creates a connection to a Mondrian OLAP Engine * using a connect string * and a catalog locator. * * @param connectString Connect string of the form * 'property=value;property=value;...'. * See {@link mondrian.olap.Util#parseConnectString} for more details of the format. * See {@link mondrian.rolap.RolapConnectionProperties} for a list of * allowed properties. * @param locator Use to locate real catalog url by a customized * configuration value. If null, leave the catalog url * unchanged. * @return A {@link Connection}, never null */ public static Connection getConnection( String connectString, CatalogLocator locator) { Util.PropertyList properties = Util.parseConnectString(connectString); return getConnection(properties, locator); } /** * Creates a connection to a Mondrian OLAP Engine. * * @param properties Collection of properties which define the location * of the connection. * See {@link mondrian.rolap.RolapConnection} for a list of allowed properties. * @param locator Use to locate real catalog url by a customized * configuration value. If null, leave the catalog url * unchanged. * @return A {@link Connection}, never null */ public static Connection getConnection( Util.PropertyList properties, CatalogLocator locator) { return getConnection(properties, locator, null); } /** * Creates a connection to a Mondrian OLAP Engine * using a list of connection properties, * a catalog locator, * and a JDBC data source. * * @param properties Collection of properties which define the location * of the connection. * See {@link mondrian.rolap.RolapConnection} for a list of allowed properties. * @param locator Use to locate real catalog url by a customized * configuration value. If null, leave the catalog url * unchanged. * @param dataSource - if not null an external DataSource to be used * by Mondrian * @return A {@link Connection}, never null */ public static Connection getConnection( Util.PropertyList properties, CatalogLocator locator, DataSource dataSource) { String provider = properties.get("PROVIDER", "mondrian"); if (!provider.equalsIgnoreCase("mondrian")) { throw Util.newError("Provider not recognized: " + provider); } final String instance = properties.get(RolapConnectionProperties.Instance.name()); MondrianServer server = MondrianServer.forId(instance); if (server == null) { throw Util.newError("Unknown server instance: " + instance); } if (locator == null) { locator = server.getCatalogLocator(); } if (locator != null) { String catalog = properties.get( RolapConnectionProperties.Catalog.name()); properties.put( RolapConnectionProperties.Catalog.name(), locator.locate(catalog)); } final RolapConnection connection = new RolapConnection(server, properties, dataSource); server.addConnection(connection); return connection; } } // End DriverManager.java mondrian-3.4.1/src/main/mondrian/olap/CellFormatter.java0000644000175000017500000000127211735330606023113 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * An SPI to format the cell value to be displayed. * * @deprecated Use {@link mondrian.spi.CellFormatter}. This interface * exists for temporary backwards compatibility and will be removed * in mondrian-4.0. */ public interface CellFormatter extends mondrian.spi.CellFormatter { } // End CellFormatter.java mondrian-3.4.1/src/main/mondrian/olap/RoleImpl.java0000644000175000017500000007577111742752424022116 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.rolap.RolapCube; import mondrian.rolap.RolapCubeDimension; import mondrian.util.Pair; import java.util.*; import java.util.Map.Entry; /** * Default implementation of the {@link Role} interface. * * @author jhyde, lucboudreau * @since Oct 5, 2002 */ public class RoleImpl implements Role { private boolean mutable = true; private final Map schemaGrants = new HashMap(); private final Map cubeGrants = new HashMap(); private final Map dimensionGrants = new HashMap(); private final Map hierarchyGrants = new HashMap(); /** * Creates a role with no permissions. */ public RoleImpl() { } protected RoleImpl clone() { RoleImpl role = new RoleImpl(); role.mutable = mutable; role.schemaGrants.putAll(schemaGrants); role.cubeGrants.putAll(cubeGrants); role.dimensionGrants.putAll(dimensionGrants); for (Map.Entry entry : hierarchyGrants.entrySet()) { role.hierarchyGrants.put( entry.getKey(), (HierarchyAccessImpl) entry.getValue().clone()); } return role; } /** * Returns a copy of this Role which can be modified. */ public RoleImpl makeMutableClone() { RoleImpl role = clone(); role.mutable = true; return role; } /** * Prevents any further modifications. * @post !isMutable() */ public void makeImmutable() { mutable = false; } /** * Returns whether modifications are possible. */ public boolean isMutable() { return mutable; } /** * Defines access to all cubes and dimensions in a schema. * * @param schema Schema whose access to grant/deny. * @param access An {@link Access access code} * * @pre schema != null * @pre access == Access.ALL || access == Access.NONE * || access == Access.ALL_DIMENSIONS * @pre isMutable() */ public void grant(Schema schema, Access access) { assert schema != null; assert isMutable(); schemaGrants.put(schema, access); } public Access getAccess(Schema schema) { assert schema != null; final Access schemaAccess = schemaGrants.get(schema); if (schemaAccess == null) { // No specific rules means full access. return Access.CUSTOM; } else { return schemaAccess; } } /** * Converts a null Access object to {@link Access#NONE}. * * @param access Access object or null * @return Access object, never null */ private static Access toAccess(Access access) { return access == null ? Access.NONE : access; } /** * Defines access to a cube. * * @param cube Cube whose access to grant/deny. * @param access An {@link Access access code} * * @pre cube != null * @pre access == Access.ALL || access == Access.NONE * @pre isMutable() */ public void grant(Cube cube, Access access) { Util.assertPrecondition(cube != null, "cube != null"); assert access == Access.ALL || access == Access.NONE || access == Access.CUSTOM; Util.assertPrecondition(isMutable(), "isMutable()"); cubeGrants.put(cube, access); // Set the schema's access to 'custom' if no rules already exist. final Access schemaAccess = getAccess(cube.getSchema()); if (schemaAccess == Access.NONE) { grant(cube.getSchema(), Access.CUSTOM); } } public Access getAccess(Cube cube) { assert cube != null; // Check for explicit rules. // Both 'custom' and 'all' are good enough Access access = cubeGrants.get(cube); if (access != null) { return access; } // Check for inheritance from the parent schema // 'All Dimensions' and 'custom' are not good enough access = schemaGrants.get(cube.getSchema()); if (access == Access.ALL) { return Access.ALL; } // Deny access return Access.NONE; } /** * Defines access to a dimension. * * @param dimension Dimension whose access to grant/deny. * @param access An Access instance * * @pre dimension != null * @pre access == Access.ALL || access == Access.CUSTOM * || access == Access.NONE * @pre isMutable() */ public void grant(Dimension dimension, Access access) { assert dimension != null; assert access == Access.ALL || access == Access.NONE || access == Access.CUSTOM; Util.assertPrecondition(isMutable(), "isMutable()"); dimensionGrants.put(dimension, access); // Dimension grants do not cascade to the parent cube automatically. // We always figure out the inheritance at runtime since the place // where the dimension is used (either inside of a virtual cube, // a shared dimension or a cube) will influence on the decision. } public Access getAccess(Dimension dimension) { assert dimension != null; // Check for explicit rules. Access access = dimensionGrants.get(dimension); if (access == Access.CUSTOM) { // For legacy reasons, if there are no accessible hierarchies // and the dimension has an access level of custom, we deny. // TODO Remove for Mondrian 4.0 boolean canAccess = false; for (Hierarchy hierarchy : dimension.getHierarchies()) { final HierarchyAccessImpl hierarchyAccess = hierarchyGrants.get(hierarchy); if (hierarchyAccess != null && hierarchyAccess.access != Access.NONE) { canAccess = true; } } if (canAccess) { return Access.CUSTOM; } else { return Access.NONE; } } else if (access != null) { return access; } // Check if this dimension inherits the cube's access rights. // 'Custom' level is not good enough for inherited access. access = checkDimensionAccessByCubeInheritance(dimension); if (access != Access.NONE) { return access; } // Check access at the schema level. // Levels of 'custom' and 'none' are not good enough. switch (getAccess(dimension.getSchema())) { case ALL: return Access.ALL; case ALL_DIMENSIONS: // For all_dimensions to work, the cube access must be // at least 'custom' level if (access != Access.NONE) { return Access.ALL; } else { return Access.NONE; } default: return Access.NONE; } } /** * This method is used to check if the access rights over a dimension * that might be inherited from the parent cube. *

    It only checks for inherited access, and it presumes that there * are no dimension grants currently given to the dimension passed as an * argument. */ private Access checkDimensionAccessByCubeInheritance(Dimension dimension) { assert dimensionGrants.containsKey(dimension) == false; for (Map.Entry cubeGrant : cubeGrants.entrySet()) { final Access access = toAccess(cubeGrant.getValue()); // The 'none' and 'custom' access level are not good enough if (access == Access.NONE || access == Access.CUSTOM) { continue; } final Dimension[] dimensions = cubeGrant.getKey().getDimensions(); for (Dimension dimension1 : dimensions) { // If the dimensions have the same identity, // we found an access rule. if (dimension == dimension1) { return cubeGrant.getValue(); } // If the passed dimension argument is of class // RolapCubeDimension, we must validate the cube // assignment and make sure the cubes are the same. // If not, skip to the next grant. if (dimension instanceof RolapCubeDimension && dimension.equals(dimension1) && !((RolapCubeDimension)dimension1) .getCube() .equals(cubeGrant.getKey())) { continue; } // Last thing is to allow for equality correspondences // to work with virtual cubes. if (cubeGrant.getKey() instanceof RolapCube && ((RolapCube)cubeGrant.getKey()).isVirtual() && dimension.equals(dimension1)) { return cubeGrant.getValue(); } } } return Access.NONE; } /** * Defines access to a hierarchy. * * @param hierarchy Hierarchy whose access to grant/deny. * @param access An {@link Access access code} * @param topLevel Top-most level which can be accessed, or null if the * highest level. May only be specified if access is * {@link mondrian.olap.Access#CUSTOM}. * @param bottomLevel Bottom-most level which can be accessed, or null if * the lowest level. May only be specified if access is * {@link mondrian.olap.Access#CUSTOM}. * * @param rollupPolicy Rollup policy * * @pre hierarchy != null * @pre Access.instance().isValid(access) * @pre (access == Access.CUSTOM) * || (topLevel == null && bottomLevel == null) * @pre topLevel == null || topLevel.getHierarchy() == hierarchy * @pre bottomLevel == null || bottomLevel.getHierarchy() == hierarchy * @pre isMutable() */ public void grant( Hierarchy hierarchy, Access access, Level topLevel, Level bottomLevel, RollupPolicy rollupPolicy) { assert hierarchy != null; assert access != null; assert (access == Access.CUSTOM) || (topLevel == null && bottomLevel == null); assert topLevel == null || topLevel.getHierarchy() == hierarchy; assert bottomLevel == null || bottomLevel.getHierarchy() == hierarchy; assert isMutable(); assert rollupPolicy != null; hierarchyGrants.put( hierarchy, new HierarchyAccessImpl( this, hierarchy, access, topLevel, bottomLevel, rollupPolicy)); // Cascade the access right to 'custom' on the parent dim if necessary final Access dimAccess = toAccess(dimensionGrants.get(hierarchy.getDimension())); if (dimAccess == Access.NONE) { grant(hierarchy.getDimension(), Access.CUSTOM); } } public Access getAccess(Hierarchy hierarchy) { assert hierarchy != null; HierarchyAccessImpl hierarchyAccess = hierarchyGrants.get(hierarchy); if (hierarchyAccess != null) { return hierarchyAccess.access; } // There was no explicit rule for this particular hierarchy. // Let's check the parent dimension. Access access = getAccess(hierarchy.getDimension()); if (access == Access.ALL) { // Access levels of 'none' and 'custom' are not enough. return Access.ALL; } // Access denied, since we know that the dimension check has // checked for its parents as well. return Access.NONE; } public HierarchyAccess getAccessDetails(Hierarchy hierarchy) { Util.assertPrecondition(hierarchy != null, "hierarchy != null"); if (hierarchyGrants.containsKey(hierarchy)) { return hierarchyGrants.get(hierarchy); } final Access hierarchyAccess; final Access schemaGrant = schemaGrants.get(hierarchy.getDimension().getSchema()); if (schemaGrant != null) { if (schemaGrant == Access.ALL) { hierarchyAccess = Access.ALL; } else { hierarchyAccess = Access.NONE; } } else { hierarchyAccess = Access.ALL; } return new HierarchyAccessImpl( this, hierarchy, hierarchyAccess, null, null, RollupPolicy.HIDDEN); } public Access getAccess(Level level) { assert level != null; HierarchyAccessImpl hierarchyAccess = hierarchyGrants.get(level.getHierarchy()); if (hierarchyAccess != null && hierarchyAccess.access != Access.NONE) { if (checkLevelIsOkWithRestrictions( hierarchyAccess, level)) { // We're good. Let it through. return hierarchyAccess.access; } } // No information could be deducted from the parent hierarchy. // Let's use the parent dimension. return getAccess(level.getDimension()); } private static boolean checkLevelIsOkWithRestrictions( HierarchyAccessImpl hierarchyAccess, Level level) { // Check if this level is explicitly excluded by top/bototm // level restrictions. if (level.getDepth() < hierarchyAccess.topLevel.getDepth()) { return false; } if (level.getDepth() > hierarchyAccess.bottomLevel.getDepth()) { return false; } return true; } /** * Defines access to a member in a hierarchy. * *

    Notes:

      *
    1. The order of grants matters. If you grant/deny access to a * member, previous grants/denials to its descendants are ignored.
    2. *
    3. Member grants do not supersde top/bottom levels set using * {@link #grant(Hierarchy, Access, Level, Level, mondrian.olap.Role.RollupPolicy)}. *
    4. If you have access to a member, then you can see its ancestors * even those explicitly denied, up to the top level. *
    * * @pre member != null * @pre isMutable() * @pre getAccess(member.getHierarchy()) == Access.CUSTOM */ public void grant(Member member, Access access) { Util.assertPrecondition(member != null, "member != null"); assert isMutable(); assert getAccess(member.getHierarchy()) == Access.CUSTOM; HierarchyAccessImpl hierarchyAccess = hierarchyGrants.get(member.getHierarchy()); assert hierarchyAccess != null; assert hierarchyAccess.access == Access.CUSTOM; hierarchyAccess.grant(this, member, access); } public Access getAccess(Member member) { assert member != null; // Always allow access to calculated members. if (member.isCalculatedInQuery()) { return Access.ALL; } // Check if the parent hierarchy has any access // rules for this. final HierarchyAccessImpl hierarchyAccess = hierarchyGrants.get(member.getHierarchy()); if (hierarchyAccess != null) { return hierarchyAccess.getAccess(member); } // Then let's check ask the parent level. return getAccess(member.getLevel()); } public Access getAccess(NamedSet set) { Util.assertPrecondition(set != null, "set != null"); // TODO Named sets cannot be secured at the moment. return Access.ALL; } public boolean canAccess(OlapElement olapElement) { Util.assertPrecondition(olapElement != null, "olapElement != null"); if (olapElement instanceof Member) { return getAccess((Member) olapElement) != Access.NONE; } else if (olapElement instanceof Level) { return getAccess((Level) olapElement) != Access.NONE; } else if (olapElement instanceof NamedSet) { return getAccess((NamedSet) olapElement) != Access.NONE; } else if (olapElement instanceof Hierarchy) { return getAccess((Hierarchy) olapElement) != Access.NONE; } else if (olapElement instanceof Cube) { return getAccess((Cube) olapElement) != Access.NONE; } else if (olapElement instanceof Dimension) { return getAccess((Dimension) olapElement) != Access.NONE; } else { return false; } } /** * Creates an element which represents all access to a hierarchy. * * @param hierarchy Hierarchy * @return element representing all access to a given hierarchy */ public static HierarchyAccess createAllAccess(Hierarchy hierarchy) { return new HierarchyAccessImpl( Util.createRootRole(hierarchy.getDimension().getSchema()), hierarchy, Access.ALL, null, null, Role.RollupPolicy.FULL); } /** * Returns a role that is the union of the given roles. * * @param roleList List of roles * @return Union role */ public static Role union(final List roleList) { assert roleList.size() > 0; return new UnionRoleImpl(roleList); } // ~ Inner classes -------------------------------------------------------- /** * Represents the access that a role has to a particular hierarchy. */ private static class HierarchyAccessImpl implements Role.HierarchyAccess { private final Hierarchy hierarchy; private final Level topLevel; private final Access access; private final Level bottomLevel; private final Map> memberGrants = new HashMap>(); private final RollupPolicy rollupPolicy; private final Role role; /** * Creates a HierarchyAccessImpl. * @param role A role this access belongs to. * @param hierarchy A hierarchy this object describes. * @param access The access granted to this role for this hierarchy. * @param topLevel The top level to restrict the role to, or null to * grant access up top the top level of the hierarchy parameter. * @param bottomLevel The bottom level to restrict the role to, or null * to grant access down to the bottom level of the hierarchy parameter. * @param rollupPolicy The rollup policy to apply. */ HierarchyAccessImpl( Role role, Hierarchy hierarchy, Access access, Level topLevel, Level bottomLevel, RollupPolicy rollupPolicy) { assert role != null; assert hierarchy != null; assert access != null; assert rollupPolicy != null; this.role = role; this.hierarchy = hierarchy; this.access = access; this.rollupPolicy = rollupPolicy; this.topLevel = topLevel == null ? hierarchy.getLevels()[0] : topLevel; this.bottomLevel = bottomLevel == null ? hierarchy.getLevels()[hierarchy.getLevels().length - 1] : bottomLevel; } public HierarchyAccess clone() { HierarchyAccessImpl hierarchyAccess = new HierarchyAccessImpl( role, hierarchy, access, topLevel, bottomLevel, rollupPolicy); hierarchyAccess.memberGrants.putAll(memberGrants); return hierarchyAccess; } /** * Grants access to a member. * * @param member Member * @param access Access */ void grant(RoleImpl role, Member member, Access access) { Util.assertTrue(member.getHierarchy() == hierarchy); // Remove any existing grants to descendants of "member" for (Iterator> memberIter = memberGrants.values().iterator(); memberIter.hasNext();) { Member m = memberIter.next().left; if (m.isChildOrEqualTo(member)) { memberIter.remove(); } } memberGrants.put( member.getUniqueName(), new Pair(member, access)); if (access == Access.NONE) { // Since we're denying access, the immediate parent // must have an access level of at least 'custom' for (Member m = member.getParentMember(); m != null; m = m.getParentMember()) { Pair pair = memberGrants.get(m.getUniqueName()); final Access memberAccess = pair == null ? null : pair.right; // If no current access is allowed, upgrade to "custom" if (memberAccess == Access.NONE && checkLevelIsOkWithRestrictions( this, m.getLevel())) { memberGrants.put( m.getUniqueName(), new Pair(m, Access.CUSTOM)); } } } else { // Create 'custom' access for any ancestors of 'member' which // do not have explicit access but which have at least one // child visible. for (Member m = member.getParentMember(); m != null; m = m.getParentMember()) { if (checkLevelIsOkWithRestrictions( this, m.getLevel())) { Pair pair = memberGrants.get(m.getUniqueName()); final Access parentAccess = toAccess(pair == null ? null : pair.right); if (parentAccess == Access.NONE) { memberGrants.put( m.getUniqueName(), new Pair(m, Access.CUSTOM)); } } } // Also set custom access for the parent hierarchy. final Access hierarchyAccess = role.getAccess(member.getLevel().getHierarchy()); if (hierarchyAccess == Access.NONE) { // Upgrade to CUSTOM level. role.grant( hierarchy, Access.CUSTOM, topLevel, bottomLevel, rollupPolicy); } } } public Access getAccess(Member member) { if (this.access != Access.CUSTOM) { return this.access; } Pair pair = memberGrants.get(member.getUniqueName()); Access access = pair == null? null : pair.right; // Check for an explicit deny. if (access == Access.NONE) { return Access.NONE; } // Check for explicit grant if (access == Access.ALL || access == Access.CUSTOM) { return access; } // Check if the member is out of the bounds // defined by topLevel and bottomLevel if (!checkLevelIsOkWithRestrictions(this, member.getLevel())) { return Access.NONE; } // Nothing was explicitly defined for this member. // Check for grants on its parents for (Member m = member.getParentMember(); m != null; m = m.getParentMember()) { Pair parentPair = memberGrants.get(m.getUniqueName()); final Access parentAccess = parentPair == null ? null : parentPair.right; if (parentAccess == null) { // No explicit rules for this parent continue; } // Check for parent deny if (parentAccess == Access.NONE || parentAccess == Access.CUSTOM) { return Access.NONE; } return Access.ALL; } // Check for inherited access from ancestors. // "Custom" is not good enough. We are looking for "all" access. access = role.getAccess(member.getLevel()); if (access == Access.ALL) { return Access.ALL; } // This member might be at a level allowed by the // topLevel/bottomLevel attributes. If there are no explicit // member grants defined at this level but the member fits // those bounds, we give access. if (memberGrants.size() == 0 && checkLevelIsOkWithRestrictions( this, member.getLevel())) { return Access.ALL; } // No access return Access.NONE; } public final int getTopLevelDepth() { return topLevel.getDepth(); } public final int getBottomLevelDepth() { return bottomLevel.getDepth(); } public RollupPolicy getRollupPolicy() { return rollupPolicy; } public boolean hasInaccessibleDescendants(Member member) { for (Entry> entry : memberGrants.entrySet()) { switch (entry.getValue().right) { case NONE: Member grantedMember = entry.getValue().left; for (Member m = grantedMember; m != null; m = m.getParentMember()) { if (m.equals(member)) { // We have proved that this granted member is a // descendant of 'member'. return true; } } } } // All descendants are accessible. return false; } } /** * Implementation of {@link mondrian.olap.Role.HierarchyAccess} that * delegates all methods to an underlying hierarchy access. */ public static abstract class DelegatingHierarchyAccess implements HierarchyAccess { protected final HierarchyAccess hierarchyAccess; /** * Creates a DelegatingHierarchyAccess. * * @param hierarchyAccess Underlying hierarchy access */ public DelegatingHierarchyAccess(HierarchyAccess hierarchyAccess) { assert hierarchyAccess != null; this.hierarchyAccess = hierarchyAccess; } public Access getAccess(Member member) { return hierarchyAccess.getAccess(member); } public int getTopLevelDepth() { return hierarchyAccess.getTopLevelDepth(); } public int getBottomLevelDepth() { return hierarchyAccess.getBottomLevelDepth(); } public RollupPolicy getRollupPolicy() { return hierarchyAccess.getRollupPolicy(); } public boolean hasInaccessibleDescendants(Member member) { return hierarchyAccess.hasInaccessibleDescendants(member); } } /** * Implementation of {@link mondrian.olap.Role.HierarchyAccess} that caches * the access of each member and level. * *

    This reduces the number of calls to the underlying HierarchyAccess, * which is particularly useful for a union role which is based on many * roles. * *

    Caching uses two {@link java.util.WeakHashMap} objects, so should * release resources if memory is scarce. However, it may use up memory and * cause segments etc. to be removed from the cache when GC is triggered. * For this reason, you should only use this wrapper for a HierarchyAccess * which would otherwise have poor performance; currently used for union * roles with 5 or more member roles. */ static class CachingHierarchyAccess extends DelegatingHierarchyAccess { private final Map memberAccessMap = new WeakHashMap(); private RollupPolicy rollupPolicy; private Map inaccessibleDescendantsMap = new WeakHashMap(); private Integer topLevelDepth; private Integer bottomLevelDepth; /** * Creates a CachingHierarchyAccess. * * @param hierarchyAccess Underlying hierarchy access */ public CachingHierarchyAccess(HierarchyAccess hierarchyAccess) { super(hierarchyAccess); } @Override public Access getAccess(Member member) { Access access = memberAccessMap.get(member); if (access != null) { return access; } access = hierarchyAccess.getAccess(member); memberAccessMap.put(member, access); return access; } @Override public int getTopLevelDepth() { if (topLevelDepth == null) { topLevelDepth = hierarchyAccess.getTopLevelDepth(); } return topLevelDepth; } @Override public int getBottomLevelDepth() { if (bottomLevelDepth == null) { bottomLevelDepth = hierarchyAccess.getBottomLevelDepth(); } return bottomLevelDepth; } @Override public RollupPolicy getRollupPolicy() { if (rollupPolicy == null) { rollupPolicy = hierarchyAccess.getRollupPolicy(); } return rollupPolicy; } @Override public boolean hasInaccessibleDescendants(Member member) { Boolean b = inaccessibleDescendantsMap.get(member); if (b == null) { b = hierarchyAccess.hasInaccessibleDescendants(member); inaccessibleDescendantsMap.put(member, b); } return b; } } } // End RoleImpl.java mondrian-3.4.1/src/main/mondrian/olap/ResultBase.java0000644000175000017500000001064311735330606022423 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.server.Execution; import mondrian.server.Statement; import org.apache.log4j.Logger; import java.io.PrintWriter; import java.util.List; /** * Skeleton implementation of {@link Result}. * * @author jhyde * @since 10 August, 2001 */ public abstract class ResultBase implements Result { protected final Execution execution; protected final Statement statement; protected final Query query; protected final Axis[] axes; protected Axis slicerAxis; protected ResultBase(Execution execution, Axis[] axes) { this.execution = execution; this.statement = execution.getMondrianStatement(); this.query = statement.getQuery(); assert query != null; this.axes = axes == null ? new Axis[query.getAxes().length] : axes; } protected abstract Logger getLogger(); public Query getQuery() { return statement.getQuery(); } // implement Result public Axis[] getAxes() { return axes; } // implement Result public Axis getSlicerAxis() { return slicerAxis; } // implement Result public void print(PrintWriter pw) { for (int i = -1; i < axes.length; i++) { pw.println("Axis #" + (i + 1) + ":"); printAxis(pw, i < 0 ? slicerAxis : axes[i]); } // Usually there are 3 axes: {slicer, columns, rows}. Position is a // {column, row} pair. We call printRows with axis=2. When it recurses // to axis=-1, it prints. int[] pos = new int[axes.length]; printRows(pw, axes.length - 1, pos); } private void printRows(PrintWriter pw, int axis, int[] pos) { if (axis < 0) { printCell(pw, pos); } else { Axis _axis = axes[axis]; List positions = _axis.getPositions(); for (int i = 0; i < positions.size(); i++) { pos[axis] = i; if (axis == 0) { int row = axis + 1 < pos.length ? pos[axis + 1] : 0; pw.print("Row #" + row + ": "); } printRows(pw, axis - 1, pos); if (axis == 0) { pw.println(); } } } } private void printAxis(PrintWriter pw, Axis axis) { List positions = axis.getPositions(); for (Position position : positions) { boolean firstTime = true; pw.print("{"); for (Member member : position) { if (member.getDimension().isHighCardinality()) { pw.println(" -- High cardinality dimension --}"); return; } if (! firstTime) { pw.print(", "); } pw.print(member.getUniqueName()); firstTime = false; } pw.println("}"); } } private void printCell(PrintWriter pw, int[] pos) { Cell cell = getCell(pos); pw.print(cell.getFormattedValue()); } /** * Returns the current member of a given hierarchy at a given location. * * @param pos Coordinates in cell set * @param hierarchy Hierarchy * @return current member of given hierarchy */ public Member getMember(int[] pos, Hierarchy hierarchy) { for (int i = -1; i < axes.length; i++) { Axis axis = slicerAxis; int index = 0; if (i >= 0) { axis = axes[i]; index = pos[i]; } List positions = axis.getPositions(); Position position = positions.get(index); for (Member member : position) { if (member.getHierarchy() == hierarchy) { return member; } } } return hierarchy.getHierarchy().getDefaultMember(); } public void close() { } } // End ResultBase.java mondrian-3.4.1/src/main/mondrian/olap/MondrianProperties.xml0000644000175000017500000014533111735330606024060 0ustar drazzibdrazzib QueryLimit mondrian.query.limit

    Maximum number of simultaneous queries the system will allow.

    Oracle fails if you try to run more than the 'processes' parameter in init.ora, typically 150. The throughput of Oracle and other databases will probably reduce long before you get to their limit.

    int 40 JdbcDrivers mondrian.jdbcDrivers Property containing a list of JDBC drivers to load automatically. Must be a comma-separated list of class names, and the classes must be on the class path. String sun.jdbc.odbc.JdbcOdbcDriver,org.hsqldb.jdbcDriver,oracle.jdbc.OracleDriver,com.mysql.jdbc.Driver ResultLimit mondrian.result.limit Integer property that, if set to a value greater than zero, limits the maximum size of a result set. int 0 HighCardChunkSize mondrian.result.highCardChunkSize Property that establishes the amount of chunks for querying cells involving high-cardinality dimensions. Should prime with {@link #ResultLimit mondrian.result.limit}. int 1 TestName mondrian.test.Name Testing

    String property that determines which tests are run.

    This is a regular expression as defined by {@link java.util.regex.Pattern}. If this property is specified, only tests whose names match the pattern in its entirety will be run.

    @see #TestClass
    String
    TestClass mondrian.test.Class Testing

    String property that determines which test class to run.

    This is the name of the class. It must either implement {@code junit.framework.Test} or have a method {@code public [static] junit.framework.Test suite()}.

    Example:

    mondrian.test.Class=mondrian.test.FoodMartTestCase
    @see #TestName
    String
    TestConnectString mondrian.test.connectString Testing

    Property containing the connect string which regresssion tests should use to connect to the database.

    Format is specified in {@link Util#parseConnectString(String)}.

    String
    TestHighCardinalityDimensionList mondrian.test.highCardDimensions Property containing a list of dimensions in the Sales cube that should be treated as high-cardinality dimensions by the testing infrastructure. This allows us to run the full suite of tests with high-cardinality functionality enabled. String FoodmartJdbcURL mondrian.foodmart.jdbcURL Testing

    Property containing the JDBC URL of the FoodMart database. The default value is to connect to an ODBC data source called "MondrianFoodMart".

    To run the test suite, first load the FoodMart data set into the database of your choice. Then set the driver.classpath, mondrian.foodmart.jdbcURL and mondrian.jdbcDrivers properties, by uncommenting and modifying one of the sections below. Put the JDBC driver jar into mondrian/testlib.

    Here are example property settings for various databases.

    Derby: needs user and password

    mondrian.foodmart.jdbcURL=jdbc:derby:demo/derby/foodmart
    mondrian.foodmart.jdbcUser=sa
    mondrian.foodmart.jdbcPassword=sa
    mondrian.jdbcDrivers=org.apache.derby.jdbc.EmbeddedDriver
    driver.classpath=testlib/derby.jar

    FireBirdSQL

    mondrian.foodmart.jdbcURL=jdbc:firebirdsql:localhost/3050:/mondrian/foodmart.gdb
    mondrian.jdbcDrivers=org.firebirdsql.jdbc.FBDriver
    driver.classpath=/jdbc/fb/firebirdsql-full.jar

    Greenplum (similar to Postgres)

    mondrian.foodmart.jdbcURL=jdbc:postgresql://localhost/foodmart?user=gpadmin&password=xxxx
    mondrian.foodmart.jdbcUser=foodmart
    mondrian.foodmart.jdbcPassword=foodmart
    mondrian.jdbcDrivers=org.postgresql.Driver
    driver.classpath=lib/postgresql-8.2-504.jdbc3.jar

    LucidDB (see instructions)

    mondrian.foodmart.jdbcURL=jdbc:luciddb:http://localhost
    mondrian.foodmart.jdbcUser=foodmart
    mondrian.jdbcDrivers=org.luciddb.jdbc.LucidDbClientDriver
    driver.classpath=/path/to/luciddb/plugin/LucidDbClient.jar

    Oracle (needs user and password)

    oracle.home=G:/oracle/product/10.1.0/Db_1
    mondrian.foodmart.jdbcURL.oracle=jdbc:oracle:thin:@//host:port/service_name
    mondrian.foodmart.jdbcURL=jdbc:oracle:thin:foodmart/foodmart@//stilton:1521/orcl
    mondrian.foodmart.jdbcURL=jdbc:oracle:oci8:foodmart/foodmart@orcl
    mondrian.foodmart.jdbcUser=FOODMART
    mondrian.foodmart.jdbcPassword=oracle
    mondrian.jdbcDrivers=oracle.jdbc.OracleDriver
    driver.classpath=/home/jhyde/open/mondrian/lib/ojdbc14.jar

    ODBC (Microsoft Access)

    mondrian.foodmart.jdbcURL=jdbc:odbc:MondrianFoodMart
    mondrian.jdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver
    driver.classpath=

    Hypersonic

    mondrian.foodmart.jdbcURL=jdbc:hsqldb:demo/hsql/FoodMart
    mondrian.jdbcDrivers=org.hsqldb.jdbcDriver
    driver.classpath=xx

    MySQL: can have user and password set in JDBC URL

    mondrian.foodmart.jdbcURL=jdbc:mysql://localhost/foodmart?user=foodmart&password=foodmart
    mondrian.foodmart.jdbcURL=jdbc:mysql://localhost/foodmart
    mondrian.foodmart.jdbcUser=foodmart
    mondrian.foodmart.jdbcPassword=foodmart
    mondrian.jdbcDrivers=com.mysql.jdbc.Driver
    driver.classpath=D:/mysql-connector-3.1.12

    Infobright

    As MySQL. (Infobright uses a MySQL driver, version 5.1 and later.)

    Ingres

    mondrian.foodmart.jdbcURL=jdbc:ingres://192.168.200.129:II7/MondrianFoodMart;LOOP=on;AUTO=multi;UID=ingres;PWD=sergni
    mondrian.jdbcDrivers=com.ingres.jdbc.IngresDriver
    driver.classpath=c:/ingres2006/ingres/lib/iijdbc.jar

    Postgres: needs user and password

    mondrian.foodmart.jdbcURL=jdbc:postgresql://localhost/FM3
    mondrian.foodmart.jdbcUser=postgres
    mondrian.foodmart.jdbcPassword=pgAdmin
    mondrian.jdbcDrivers=org.postgresql.Driver

    Neoview

    mondrian.foodmart.jdbcURL=jdbc:hpt4jdbc://localhost:18650/:schema=PENTAHO;serverDataSource=PENTAHO_DataSource
    mondrian.foodmart.jdbcUser=user
    mondrian.foodmart.jdbcPassword=password
    mondrian.jdbcDrivers=com.hp.t4jdbc.HPT4Driver
    driver.classpath=/some/path/hpt4jdbc.jar

    Netezza: mimics Postgres

    mondrian.foodmart.jdbcURL=jdbc:netezza://127.0.1.10/foodmart
    mondrian.foodmart.jdbcUser=user
    mondrian.foodmart.jdbcPassword=password
    mondrian.jdbcDrivers=org.netezza.Driver
    driver.classpath=/some/path/nzjdbc.jar

    Sybase

    mondrian.foodmart.jdbcURL=jdbc:jtds:sybase://xxx.xxx.xxx.xxx:port/dbName
    mondrian.foodmart.jdbcUser=user
    mondrian.foodmart.jdbcPassword=password
    mondrian.jdbcDrivers=net.sourceforge.jtds.jdbc.Driver
    driver.classpath=/some/path/jtds-1.2.jar

    Teradata

    mondrian.foodmart.jdbcURL=jdbc:teradata://DatabaseServerName/DATABASE=FoodMart
    mondrian.foodmart.jdbcUser=user
    mondrian.foodmart.jdbcPassword=password
    mondrian.jdbcDrivers=com.ncr.teradata.TeraDriver
    driver.classpath=/some/path/terajdbc/classes/terajdbc4.jar

    Vertica

    mondrian.foodmart.jdbcURL=jdbc:vertica://xxx.xxx.xxx.xxx:port/dbName
    mondrian.foodmart.jdbcUser=user
    mondrian.foodmart.jdbcPassword=password
    mondrian.jdbcDrivers=com.vertica.Driver
    driver.classpath=/some/path/vertica.jar

    Vertorwise

    mondrian.foodmart.jdbcURL=jdbc:ingres://xxx.xxx.xxx.xxxport/dbName
    mondrian.foodmart.jdbcUser=user
    mondrian.foodmart.jdbcPassword=password
    mondrian.jdbcDrivers=com.ingres.jdbc.IngresDriver
    driver.classpath=/some/path/iijdbc.jar
    String jdbc:odbc:MondrianFoodMart
    TestJdbcUser mondrian.foodmart.jdbcUser Testing Property containing the JDBC user of a test database. The default value is null, to cope with DBMSs that don't need this. String TestJdbcPassword mondrian.foodmart.jdbcPassword Testing Property containing the JDBC password of a test database. The default value is null, to cope with DBMSs that don't need this. String RollupAnalyzerNumberThreads mondrian.rolap.RollupAnalyzerNumberThreads Property which defines how many threads are created in order to analyze possible segment rollup opportunities. This value defaults to 4, but it can be set to a higher value on big multi-core systems. int 4 SegmentCache mondrian.rolap.SegmentCache Property which defines which SegmentCache implementation to use. Specify the value as a fully qualified class name, such as org.example.SegmentCacheImpl where SegmentCacheImpl is an implementation of {@link mondrian.spi.SegmentCache}. String SparseSegmentCountThreshold mondrian.rolap.SparseSegmentValueThreshold

    Property that, with {@link #SparseSegmentDensityThreshold}, determines whether to choose a sparse or dense representation when storing collections of cell values in memory.

    When storing collections of cell values, Mondrian has to choose between a sparse and a dense representation, based upon the possible and actual number of values. The density is actual / possible.

    We use a sparse representation if (possible - {@link #SparseSegmentCountThreshold countThreshold}) * {@link #SparseSegmentDensityThreshold densityThreshold} > actual

    For example, at the default values ({@link #SparseSegmentCountThreshold countThreshold} = 1000, {@link #SparseSegmentDensityThreshold} = 0.5), we use a dense representation for

    • (1000 possible, 0 actual), or
    • (2000 possible, 500 actual), or
    • (3000 possible, 1000 actual).

    Any fewer actual values, or any more possible values, and Mondrian will use a sparse representation.

    int 1000
    SparseSegmentDensityThreshold mondrian.rolap.SparseSegmentDensityThreshold Property that, with {@link #SparseSegmentCountThreshold}, determines whether to choose a sparse or dense representation when storing collections of cell values in memory. double 0.5 QueryFilePattern mondrian.test.QueryFilePattern Testing Property that defines a pattern for which test XML files to run. Pattern has to match a file name of the form: querywhatever.xml in the directory.

    Example:

    mondrian.test.QueryFilePattern=queryTest_fec[A-Za-z0-9_]*.xml
    String
    QueryFileDirectory mondrian.test.QueryFileDirectory Testing Property defining where the test XML files are. String Iterations mondrian.test.Iterations Testing Not documented. int 1 VUsers mondrian.test.VUsers Testing Not documented. int 1 TimeLimit mondrian.test.TimeLimit Testing Property that returns the time limit for the test run in seconds. If the test is running after that time, it is terminated. int 0 Warmup mondrian.test.Warmup Testing Property that indicates whether this is a "warmup test". boolean false CatalogURL mondrian.catalogURL Testing Property that contains the URL of the catalog to be used by {@link mondrian.tui.CmdRunner} and XML/A Test. String EnableCacheHitCounters mondrian.rolap.agg.enableCacheHitCounters

    Property that controls whether aggregation cache hit / miss counters will be enabled.

    Note that this will affect performance due to existence of sync blocks.

    @deprecated This property is no longer used, and will be removed in mondrian-4.0.
    boolean false
    WarnIfNoPatternForDialect mondrian.test.WarnIfNoPatternForDialect Testing

    Property that controls if warning messages should be printed if a sql comparison tests do not contain expected sqls for the specified dialect. The tests are skipped if no expected sqls are found for the current dialect.

    Possible values are the following:

    • "NONE": no warning (default)
    • "ANY": any dialect
    • "ACCESS"
    • "DERBY"
    • "LUCIDDB"
    • "MYSQL"
    • ... and any Dialect enum in SqlPattern.Dialect

    Specific tests can overwrite the default setting. The priority is:

    • Settings besides "ANY" in mondrian.properties file
    • < Any setting in the test
    • < "ANY"

    String NONE
    UseAggregates mondrian.rolap.aggregates.Use Aggregate tables

    Boolean property that controls whether Mondrian uses aggregate tables.

    If true, then Mondrian uses aggregate tables. This property is queried prior to each aggregate query so that changing the value of this property dynamically (not just at startup) is meaningful.

    Aggregates can be read from the database using the {@link #ReadAggregates} property but will not be used unless this property is set to true.

    boolean false
    ReadAggregates mondrian.rolap.aggregates.Read Aggregate tables

    Boolean property that determines whether Mondrian should read aggregate tables.

    If set to true, then Mondrian scans the database for aggregate tables. Unless mondrian.rolap.aggregates.Use is set to true, the aggregates found will not be used.

    boolean false
    ChooseAggregateByVolume mondrian.rolap.aggregates.ChooseByVolume Aggregate tables

    Boolean property that controls whether aggregate tables are ordered by their volume or row count.

    If true, Mondrian uses the aggregate table with the smallest volume (number of rows multiplied by number of columns); if false, Mondrian uses the aggregate table with the fewest rows.

    boolean false
    AggregateRules mondrian.rolap.aggregates.rules Aggregate tables

    String property containing the name of the file which defines the rules for recognizing an aggregate table. Can be either a resource in the Mondrian jar or a URL.

    The default value is "/DefaultRules.xml", which is in the mondrian.rolap.aggmatcher package in Mondrian.jar.

    Normally, this property is not set by a user.

    String /DefaultRules.xml
    AggregateRuleTag mondrian.rolap.aggregates.rule.tag Aggregate tables

    String property that is the AggRule element's tag value.

    Normally, this property is not set by a user.

    String default
    GenerateAggregateSql mondrian.rolap.aggregates.generateSql Aggregate tables

    Boolean property that controls whether to print the SQL code generated for aggregate tables.

    If set, then as each aggregate request is processed, both the lost and collapsed dimension create and insert sql code is printed. This is for use in the CmdRunner allowing one to create aggregate table generation sql.

    boolean false
    DisableCaching mondrian.rolap.star.disableCaching Caching Boolean property that controls whether a RolapStar's aggregate data cache is cleared after each query. If true, no RolapStar will cache aggregate data from one query to the next (the cache is cleared after each query). boolean false EnableTriggers mondrian.olap.triggers.enable

    Boolean property that controls whether to notify the Mondrian system when a {@link MondrianProperties property value} changes.

    This allows objects dependent on Mondrian properties to react (that is, reload), when a given property changes via, say, MondrianProperties.instance().populate(null) or MondrianProperties.instance().QueryLimit.set(50).

    boolean true
    GenerateFormattedSql mondrian.rolap.generate.formatted.sql SQL generation

    Boolean property that controls pretty-print mode.

    If true, the all SqlQuery SQL strings will be generated in pretty-print mode, formatted for ease of reading.

    boolean false
    EnableNonEmptyOnAllAxis mondrian.rolap.nonempty Boolean property that controls whether each query axis implicit has the NON EMPTY option set. The default is false. boolean false ExpandNonNative mondrian.native.ExpandNonNative SQL generation If this property is true, when looking for native evaluation of an expression, Mondrian will expand non-native sub-expressions into lists of members. boolean false CompareSiblingsByOrderKey mondrian.rolap.compareSiblingsByOrderKey Boolean property that controls whether sibling members are compared according to order key value fetched from their ordinal expression. The default is false (only database ORDER BY is used). boolean false EnableExpCache mondrian.expCache.enable Caching Boolean property that controls whether to use a cache for frequently evaluated expressions. With the cache disabled, an expression like Rank([Product].CurrentMember, Order([Product].MEMBERS, [Measures].[Unit Sales])) would perform many redundant sorts. The default is true. boolean true TestExpDependencies mondrian.test.ExpDependencies Testing

    Integer property that controls whether to test operators' dependencies, and how much time to spend doing it.

    If this property is positive, Mondrian's test framework allocates an expression evaluator which evaluates each expression several times, and makes sure that the results of the expression are independent of dimensions which the expression claims to be independent of.

    The default is 0.

    int 0
    TestSeed mondrian.test.random.seed Testing

    Seed for random number generator used by some of the tests.

    Any value besides 0 or -1 gives deterministic behavior. The default value is 1234: most users should use this. Setting the seed to a different value can increase coverage, and therefore may uncover new bugs.

    If you set the value to 0, the system will generate its own pseudo-random seed.

    If you set the value to -1, Mondrian uses the next seed from an internal random-number generator. This is a little more deterministic than setting the value to 0.

    int 1234
    LocalePropFile mondrian.rolap.localePropFile

    String property that holds the name of the class whose resource bundle is to be used to for this schema. For example, if the class is {@code com.acme.MyResource}, mondrian will look for a resource bundle called {@code com/acme/MyResource_locale.properties} on the class path. (This property has a confusing name because in a previous release it actually held a file name.)

    Used for the {@link mondrian.i18n.LocalizingDynamicSchemaProcessor}; see Internationalization for more details.

    Default value is null.

    String
    EnableNativeCrossJoin mondrian.native.crossjoin.enable SQL generation If enabled some NON EMPTY CrossJoin will be computed in SQL. boolean true EnableNativeTopCount mondrian.native.topcount.enable SQL generation If enabled some TopCount will be computed in SQL. boolean true EnableNativeFilter mondrian.native.filter.enable SQL generation If enabled some Filter() will be computed in SQL. boolean true EnableNativeNonEmpty mondrian.native.nonempty.enable SQL generation

    If enabled some NON EMPTY set operations like member.children, level.members and member descendants will be computed in SQL.

    boolean true
    AlertNativeEvaluationUnsupported mondrian.native.unsupported.alert SQL generation

    Alerting action to take in case native evaluation of a function is enabled but not supported for that function's usage in a particular query. (No alert is ever raised in cases where native evaluation would definitely have been wasted effort.)

    Recognized actions:

    • OFF: do nothing (default action, also used if unrecognized action is specified)
    • WARN: log a warning to RolapUtil logger
    • ERROR: throw an instance of {@link NativeEvaluationUnsupportedException}
    String OFF
    EnableDrillThrough mondrian.drillthrough.enable If disabled, Mondrian will throw an exception if someone attempts to perform a drillthrough of any kind. boolean true EnableTotalCount mondrian.xmla.drillthroughTotalCount.enable XML/A If enabled, first row in the result of an XML/A drill-through request will be filled with the total count of rows in underlying database. boolean true CaseSensitive mondrian.olap.case.sensitive Boolean property that controls whether the MDX parser resolves uses case-sensitive matching when looking up identifiers. The default is false. boolean false MaxRows mondrian.xmla.drillthroughMaxRows XML/A Property that defines limit on the number of rows returned by XML/A drill through request. int 1000 MaxConstraints mondrian.rolap.maxConstraints SQL generation

    Max number of constraints in a single 'IN' SQL clause.

    This value may be variant among database prodcuts and their runtime settings. Oracle, for example, gives the error "ORA-01795: maximum number of expressions in a list is 1000".

    Recommended values:

    • Oracle: 1,000
    • DB2: 2,500
    • Other: 10,000
    int 1000
    OptimizePredicates mondrian.rolap.aggregates.optimizePredicates Aggregate tables

    Boolean property that determines whether Mondrian optimizes predicates.

    If true, Mondrian may retrieve a little more data than specified in MDX query and cache it for future use. For example, if you ask for data on 48 states of the United States for 3 quarters of 2011, Mondrian will round out to all 50 states and all 4 quarters. If false, Mondrian still optimizes queries that involve all members of a dimension.

    boolean true
    MaxEvalDepth mondrian.rolap.evaluate.MaxEvalDepth

    Boolean property that defines the maximum number of passes allowable while evaluating an MDX expression.

    If evaluation exceeds this depth (for example, while evaluating a very complex calculated member), Mondrian will throw an error.

    int 10
    JdbcFactoryClass mondrian.rolap.aggregates.jdbcFactoryClass Aggregate tables

    Property that defines the JdbcSchema factory class which determines the list of tables and columns of a specific datasource.

    @see mondrian.rolap.aggmatcher.JdbcSchema
    String
    DataSourceResolverClass mondrian.spi.dataSourceResolverClass Factories

    Property that defines the name of the plugin class that resolves data source names to {@link javax.sql.DataSource} objects. The class must implement the {@link mondrian.spi.DataSourceResolver} interface. If not specified, the default implementation uses JNDI to perform resolution.

    Example:

    mondrian.spi.dataSourceResolverClass=mondrian.spi.impl.JndiDataSourceResolver
    String
    QueryTimeout mondrian.rolap.queryTimeout

    Property that defines the timeout value (in seconds) for queries. A value of 0 (the default), indicates no timeout.

    int 0
    RolapConnectionShepherdThreadPollingInterval mondrian.rolap.shepherdThreadPollingInterval

    Property that defines the interval value (in miliseconds) between polling operations performed by the RolapConnection shepherd thread. This controls query timeouts and calcelation, so a small value (a few miliseconds) is best. Setting this to a value higher than mondrian.rolap.queryTimeout will result the timeout not being enforced as expected. Defaults to 1000ms.

    int 1000
    RolapConnectionShepherdNbThreads mondrian.rolap.maxQueryThreads

    Maximum number of user threads per Mondrian server instance. Defaults to 10.

    int 10
    IgnoreInvalidMembers mondrian.rolap.ignoreInvalidMembers

    Property that defines whether non-existent member errors should be ignored during schema load. If so, the non-existent member is treated as a null member.

    boolean false
    IgnoreInvalidMembersDuringQuery mondrian.rolap.ignoreInvalidMembersDuringQuery

    Property that defines whether non-existent member errors should be ignored during query validation. If so, the non-existent member is treated as a null member.

    boolean false
    NullMemberRepresentation mondrian.olap.NullMemberRepresentation

    Property that determines how a null member value is represented in the result output.

    AS 2000 shows this as empty value

    AS 2005 shows this as "(null)" value

    String #null
    IterationLimit mondrian.rolap.iterationLimit

    Integer property indicating the maximum number of iterations allowed when iterating over members to compute aggregates. A value of 0 (the default) indicates no limit.

    int 0
    MemoryMonitor mondrian.util.memoryMonitor.enable Memory monitoring

    Property that defines whether the MemoryMonitor should be enabled. By default it is disabled; memory monitor is not available before Java version 1.5.

    boolean false
    MemoryMonitorThreshold mondrian.util.memoryMonitor.percentage.threshold Memory monitoring

    Property that defines the default MemoryMonitor percentage threshold. If enabled, when Java's memory monitor detects that post-garbage collection is above this value, notifications are generated.

    int 90
    MemoryMonitorClass mondrian.util.MemoryMonitor.class Factories

    Property that defines the name of the class used as a memory monitor.

    If the value is non-null, it is used by the MemoryMonitorFactory to create the implementation.

    String
    ExpCompilerClass mondrian.calc.ExpCompiler.class Factories

    Property that defines the name of the class used to compile scalar expressions.

    If the value is non-null, it is used by the ExpCompiler.Factory to create the implementation.

    To test that for all test MDX queries that all functions can handle requests for ITERABLE, LIST and MUTABLE_LIST evalutation results, use the following:

    mondrian.calc.ExpCompiler.class=mondrian.olap.fun.ResultStyleCompiler
    String
    PropertyValueMapFactoryClass mondrian.rolap.RolapMember.PropertyValueMapFactory.class Factories

    Property that defines the name of the factory class used to create maps of member properties to their respective values.

    If the value is non-null, it is used by the PropertyValueFactory to create the implementation. If unset, {@link mondrian.rolap.RolapMemberBase.DefaultPropertyValueMapFactory} will be used.

    String
    SqlMemberSourceValuePoolFactoryClass mondrian.rolap.SqlMemberSource.ValuePoolFactory.class Factories

    Property that defines the name of the class used in SqlMemberSource to pool common values.

    If the value is non-null, it is used by the SqlMemberSource.ValueMapFactory to create the implementation. If it is not set, then {@link mondrian.rolap.SqlMemberSource.NullValuePoolFactory} will be used, meaning common values will not be pooled.

    String
    CrossJoinOptimizerSize mondrian.olap.fun.crossjoin.optimizer.size

    Property that defines when to apply the crossjoin optimization algorithm.

    If a crossjoin input list's size is larger than this property's value and the axis has the "NON EMPTY" qualifier, then the crossjoin non-empty optimizer is applied. Setting this value to '0' means that for all crossjoin input lists in non-empty axes will have the optimizer applied. On the other hand, if the value is set larger than any possible list, say Integer.MAX_VALUE, then the optimizer will never be applied.

    int 0
    NullDenominatorProducesNull mondrian.olap.NullDenominatorProducesNull

    Property that defines the behavior of division if the denominator evaluates to zero.

    If false (the default), if a division has a non-null numerator and a null denominator, it evaluates to "Infinity", which conforms to MSAS behavior.

    If true, the result is null if the denominator is null. Setting to true enables the old semantics of evaluating this to null; this does not conform to MSAS, but is useful in some applications.

    boolean false
    EnableGroupingSets mondrian.rolap.groupingsets.enable SQL generation

    Property that defines whether to generate SQL queries using the GROUPING SETS construct for rollup. By default it is not enabled.

    Ignored on databases which do not support the GROUPING SETS construct (see {@link mondrian.spi.Dialect#supportsGroupingSets}).

    boolean false
    IgnoreMeasureForNonJoiningDimension mondrian.olap.agg.IgnoreMeasureForNonJoiningDimension

    Property that defines whether to ignore measure when non joining dimension is in the tuple during aggregation.

    If there are unrelated dimensions to a measure in context during aggregation, the measure is ignored in the evaluation context. This behaviour kicks in only if the cubeusage for this measure has IgnoreUnrelatedDimensions attribute set to false.

    For example, Gender doesn't join with [Warehouse Sales] measure.

    With mondrian.olap.agg.IgnoreMeasureForNonJoiningDimension=true Warehouse Sales gets eliminated and is ignored in the aggregate value.

    [Store Sales] + [Warehouse Sales] SUM({Product.members * Gender.members}) 7,913,333.82

    With mondrian.olap.agg.IgnoreMeasureForNonJoiningDimension=false Warehouse Sales with Gender All level member contributes to the aggregate value.

    [Store Sales] + [Warehouse Sales] SUM({Product.members * Gender.members}) 9,290,730.03

    On a report where Gender M, F and All members exist a user will see a large aggregated value compared to the aggregated value that can be arrived at by suming up values against Gender M and F. This can be confusing to the user. This feature can be used to eliminate such a situation.

    boolean false
    NeedDimensionPrefix mondrian.olap.elements.NeedDimensionPrefix

    Property determines if elements of dimension (levels, hierarchies, members) need to be prefixed with dimension name in MDX query.

    For example when the property is true, the following queries will error out. The same queries will work when this property is set to false.

    select {[M]} on 0 from sales
    select {[USA]} on 0 from sales
    select {[USA].[CA].[Santa Monica]} on 0 from sales

    When the property is set to true, any query where elements are prefixed with dimension name as below will work

    select {[Gender].[F]} on 0 from sales
    select {[Customers].[Santa Monica]} on 0 from sales

    Please note that this property does not govern the behaviour wherein

    [Gender].[M]

    is resolved into a fully qualified

    [Gender].[M]

    In a scenario where the schema is very large and dimensions have large number of members a MDX query that has a invalid member in it will cause mondrian to to go through all the dimensions, levels, hierarchies, members and properties trying to resolve the element name. This behavior consumes considerable time and resources on the server. Setting this property to true will make it fail fast in a scenario where it is desirable.

    boolean false
    EnableRolapCubeMemberCache mondrian.rolap.EnableRolapCubeMemberCache Caching

    Property that determines whether to cache RolapCubeMember objects, each of which associates a member of a shared hierarchy with a particular cube in which it is being used.

    The default is {@code true}, that is, use a cache. If you wish to use the member cache control aspects of {@link mondrian.olap.CacheControl}, you must set this property to {@code false}.

    RolapCubeMember has recently become more lightweight to construct, and we may obsolete this cache and this property.

    boolean true
    SolveOrderMode mondrian.rolap.SolveOrderMode Property that controls the behavior of {@link Property#SOLVE_ORDER solve order} of calculated members and sets.

    Valid values are "absolute" and "scoped" (the default). See {@link mondrian.olap.SolveOrderMode} for details.

    String ABSOLUTE
    NativizeMinThreshold mondrian.native.NativizeMinThreshold

    Property that controls minimum expected cardinality required in order for NativizeSet to natively evaluate a query.

    If the expected cardinality falls below this level the query is executed non-natively.

    It is possible for the actual cardinality to fall below this threshold even though the expected cardinality falls above this threshold. In this case the query will be natively evaluated.

    int 100000
    NativizeMaxResults mondrian.native.NativizeMaxResults

    Property that controls the maximum number of results contained in a NativizeSet result set.

    If the number of tuples contained in the result set exceeds this value Mondrian throws a LimitExceededDuringCrossjoin error.

    int 150000
    SsasCompatibleNaming mondrian.olap.SsasCompatibleNaming

    Property that defines whether to enable new naming behavior.

    If true, hierarchies are named [Dimension].[Hierarchy]; if false, [Dimension.Hierarchy].

    boolean false
    XmlaSchemaRefreshInterval mondrian.xmla.SchemaRefreshInterval

    Interval, in milliseconds, at which to refresh the list of XML/A catalogs. (Usually known as the datasources.xml file.)

    It is not an active process; no threads will be created. It only serves as a rate limiter. The refresh process is triggered by requests to the doPost() servlet method.

    Default value is 3000 milliseconds (3 seconds).

    See also {@link mondrian.xmla.impl.DynamicDatasourceXmlaServlet}.

    int 3000
    FilterChildlessSnowflakeMembers mondrian.rolap.FilterChildlessSnowflakeMembers SQL generation

    Property that defines whether to generate joins to filter out members in a snowflake dimension that do not have any children.

    If true (the default), some queries to query members of high levels snowflake dimensions will be more expensive. If false, and if there are rows in an outer snowflake table that are not referenced by a row in an inner snowflake table, then some queries will return members that have no children.

    Our recommendation, for best performance, is to remove rows outer snowflake tables are not referenced by any row in an inner snowflake table, during your ETL process, and to set this property to {@code false}.

    boolean true
    WebappDeploy mondrian.webapp.deploy

    Where mondrian.war will be deployed to. (Used by mondrian's build.xml ant file only.)

    Example: mondrian.webapp.deploy=C:/jboss-4.0.2/server/default/deploy

    false String
    WebappConnectString mondrian.webapp.connectString

    Connect string for the webapp. (Used by the webapp only.)

    To achieve access control, append say ;Role='California manager' to the connect string.

    false String Provider=mondrian;Jdbc=jdbc:odbc:MondrianFoodMart;Catalog=/WEB-INF/queries/FoodMart.xml;JdbcDrivers=sun.jdbc.odbc.JdbcOdbcDriver
    Log4jConfiguration log4j.configuration

    Set mondrian logging information if not provided by containing application.

    Examples:

    log4j.configuration=file://full/path/log4j.xml
    log4j.configuration=file:log4j.properties
    false String
    mondrian-3.4.1/src/main/mondrian/olap/Cube.java0000644000175000017500000000577611735330606021243 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1999-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import java.util.List; import java.util.Set; /** * Cube. * * @author jhyde */ public interface Cube extends OlapElement, Annotated { String getName(); Schema getSchema(); /** * Returns the dimensions of this cube. */ Dimension[] getDimensions(); /** * Returns the named sets of this cube. */ NamedSet[] getNamedSets(); /** * Finds a hierarchy whose name (or unique name, if unique is * true) equals s. */ Hierarchy lookupHierarchy(Id.Segment s, boolean unique); /** * Returns Member[]. It builds Member[] by analyzing cellset, which * gets created by running mdx sQuery. query has to be in the * format of something like "[with calculated members] select *members* on * columns from this". */ Member[] getMembersForQuery(String query, List calcMembers); /** * Helper method that returns the Year Level or returns null if the Time * Dimension does not exist or if Year is not defined in the Time Dimension. * * @return Level or null. */ Level getYearLevel(); /** * Return Quarter Level or null. * * @return Quarter Level or null. */ Level getQuarterLevel(); /** * Return Month Level or null. * * @return Month Level or null. */ Level getMonthLevel(); /** * Return Week Level or null. * * @return Week Level or null. */ Level getWeekLevel(); /** * Returns a {@link SchemaReader} for which this cube is the context for * lookup up members. * If role is null, the returned schema reader also obeys the * access-control profile of role. */ SchemaReader getSchemaReader(Role role); /** * Creates a calculated member in this cube. * *

    The XML string must be a <CalculatedMember/> * element, as defined in Mondrian.xml. * * @param xml XML string */ Member createCalculatedMember(String xml); /** * Finds out non joining dimensions for this cube. * * @param tuple array of members * @return Set of dimensions that do not exist (non joining) in this cube */ Set nonJoiningDimensions(Member[] tuple); /** * Finds out non joining dimensions for this cube. * * @param otherDims Set of dimensions to be tested for existence * in this cube * @return Set of dimensions that do not exist (non joining) in this cube */ Set nonJoiningDimensions(Set otherDims); } // End Cube.java mondrian-3.4.1/src/main/mondrian/olap/CacheControl.java0000644000175000017500000003427611735330606022726 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import java.io.PrintWriter; import java.util.List; import java.util.Map; import javax.sql.DataSource; /** * API for controlling the contents of the cell cache and the member cache. * A {@link CellRegion} denotes a portion of the cell cache, and a * {@link MemberSet} denotes a portion of the member cache. Both caches can be * flushed, and the member cache can be edited. * *

    To create an instance of this interface, use * {@link mondrian.olap.Connection#getCacheControl}.

    * *

    Methods concerning cell cache:

      *
    • {@link #createMemberRegion(Member, boolean)}
    • *
    • {@link #createMemberRegion(boolean, Member, boolean, Member, boolean)}
    • *
    • {@link #createUnionRegion(mondrian.olap.CacheControl.CellRegion[])}
    • *
    • {@link #createCrossjoinRegion(mondrian.olap.CacheControl.CellRegion[])}
    • *
    • {@link #createMeasuresRegion(Cube)}
    • *
    • {@link #flush(mondrian.olap.CacheControl.CellRegion)}
    • *

    * *

    Methods concerning member cache:

      *
    • {@link #createMemberSet(Member, boolean)}
    • *
    • {@link #createMemberSet(boolean, Member, boolean, Member, boolean)}
    • *
    • {@link #createAddCommand(Member)}
    • *
    • {@link #createDeleteCommand(Member)}
    • *
    • {@link #createDeleteCommand(mondrian.olap.CacheControl.MemberSet)}
    • *
    • {@link #createCompoundCommand(java.util.List)}
    • *
    • {@link #createCompoundCommand(mondrian.olap.CacheControl.MemberEditCommand[])}
    • *
    • {@link #createSetPropertyCommand(Member, String, Object)}
    • *
    • {@link #createSetPropertyCommand(mondrian.olap.CacheControl.MemberSet,java.util.Map)}
    • *
    • {@link #flush(mondrian.olap.CacheControl.MemberSet)}
    • *
    • {@link #execute(mondrian.olap.CacheControl.MemberEditCommand)}
    • *

    * * @author jhyde * @since Sep 27, 2006 */ public interface CacheControl { // cell cache control /** * Creates a cell region consisting of a single member. * * @param member the member * @param descendants When true, include descendants of the member in the * region. * @return the new cell region */ CellRegion createMemberRegion(Member member, boolean descendants); /** * Creates a cell region consisting of a range between two members. * *

    The members must belong to the same level of the same hierarchy. * One of the bounds may be null. * *

    For example, given * *

    Member member97Q3; // [Time].[1997].[Q3]
         * Member member98Q2; // [Time].[1998].[Q2]
         * 
    * * then * * * * * * * * * * * * * * * * * * * * * *
    ExpressionMeaning
    * createMemberRegion(true, member97Q3, true, member98Q2, * false) * The members between 97Q3 and 98Q2, inclusive:
    * [Time].[1997].[Q3],
    * [Time].[1997].[Q4],
    * [Time].[1998].[Q1],
    * [Time].[1998].[Q2]
    * createMemberRegion(true, member97Q3, false, member98Q2, * false) * The members between 97Q3 and 98Q2, exclusive:
    * [Time].[1997].[Q4],
    * [Time].[1998].[Q1]
    * createMemberRegion(true, member97Q3, false, member98Q2, * false) * The members between 97Q3 and 98Q2, including their descendants, and * including the lower bound but not the upper bound:
    * [Time].[1997].[Q3],
    * [Time].[1997].[Q3].[7],
    * [Time].[1997].[Q3].[8],
    * [Time].[1997].[Q3].[9],
    * [Time].[1997].[Q4],
    * [Time].[1997].[Q4].[10],
    * [Time].[1997].[Q4].[11],
    * [Time].[1997].[Q4].[12],
    * [Time].[1998].[Q1],
    * [Time].[1998].[Q1].[1],
    * [Time].[1998].[Q1].[2],
    * [Time].[1998].[Q1].[3]
    * * @param lowerInclusive whether the the range includes the lower bound; * ignored if the lower bound is not specified * @param lowerMember lower bound member. * If null, takes all preceding members * @param upperInclusive whether the the range includes the upper bound; * ignored if the upper bound is not specified * @param upperMember upper bound member. * If null, takes all preceding members * @param descendants when true, include descendants of the member in the * region * @return the new cell region */ CellRegion createMemberRegion( boolean lowerInclusive, Member lowerMember, boolean upperInclusive, Member upperMember, boolean descendants); /** * Forms the cartesian product of two or more cell regions. * @param regions the operands * @return the cartesian product of the operands */ CellRegion createCrossjoinRegion(CellRegion... regions); /** * Forms the union of two or more cell regions. * The regions must have the same dimensionality. * * @param regions the operands * @return the cartesian product of the operands */ CellRegion createUnionRegion(CellRegion... regions); /** * Creates a region consisting of all measures in a given cube. * @param cube a cube * @return the region */ CellRegion createMeasuresRegion(Cube cube); /** * Atomically flushes all the cells in the cell cache that correspond to * measures in a cube and to a given region. * * @param region a region */ void flush(CellRegion region); /** * Prints the state of the cell cache as it pertains to a given region. * @param pw the output target * @param region the CellRegion of interest */ void printCacheState(PrintWriter pw, CellRegion region); // member cache control /** * Creates a member set containing either a single member, or a member and * its descendants. * @param member a member * @param descendants when true, include descendants in the set * @return the set */ MemberSet createMemberSet(Member member, boolean descendants); /** * Creates a member set consisting of a range between two members. * The members must belong to the same level of the same hierarchy. One of * the bounds may be null. (Similar to {@link #createMemberRegion(boolean, * Member, boolean, Member, boolean)}, which see for examples.) * * @param lowerInclusive whether the the range includes the lower bound; * ignored if the lower bound is not specified * @param lowerMember lower bound member. * If null, takes all preceding members * @param upperInclusive whether the the range includes the upper bound; * ignored if the upper bound is not specified * @param upperMember upper bound member. * If null, takes all preceding members * @param descendants when true, include descendants of the member in the * region * @return the set */ MemberSet createMemberSet( boolean lowerInclusive, Member lowerMember, boolean upperInclusive, Member upperMember, boolean descendants); /** * Forms the union of two or more member sets. * * @param sets the operands * @return the union of the operands */ MemberSet createUnionSet(MemberSet... sets); /** * Filters a member set, keeping all members at a given Level. * * @param level Level * @param baseSet Member set * @return Member set with members not at the given level removed */ MemberSet filter(Level level, MemberSet baseSet); /** * Atomically flushes all members in the member cache which belong to a * given set. * * @param set a set of members */ void flush(MemberSet set); /** * Prints the state of the member cache as it pertains to a given member * set. * @param pw the output target * @param set the MemberSet of interest */ void printCacheState(PrintWriter pw, MemberSet set); // edit member cache contents /** * Executes a command that edits the member cache. * @param cmd the command */ void execute(MemberEditCommand cmd); /** * Builds a compound command which is executed atomically. * * @param cmds a list of the component commands * @return the compound command */ MemberEditCommand createCompoundCommand(List cmds); /** * Builds a compound command which is executed atomically. * @param cmds the component commands * @return the compound command */ MemberEditCommand createCompoundCommand(MemberEditCommand... cmds); // commands to change the structure of the member cache /** * Creates a command to delete a member and its descendants from the member * cache. * * @param member the member * @return the command */ MemberEditCommand createDeleteCommand(Member member); /** * Creates a command to delete a set of members from the member cache. * * @param memberSet the set * @return the command */ MemberEditCommand createDeleteCommand(MemberSet memberSet); /** * Creates a command to add a member to the cache. The added member and its * parent must have the same Dimension and the correct Levels, Null parent * means add to the top level of its Dimension. * *

    The ordinal position of the new member among its siblings is implied * by its properties.

    * * @param member the new member * @return the command * * @throws IllegalArgumentException if member null * or if member belongs to a parent-child hierarchy */ MemberEditCommand createAddCommand( Member member) throws IllegalArgumentException; /** * Creates a command to Move a member (with its descendants) to a new * location, that is to a new parent. * @param member the member moved * @param loc the new parent * @return the command * * @throws IllegalArgumentException if member is null, * or loc is null, * or member belongs to a parent-child hierarchy, * or if loc is incompatible with member */ MemberEditCommand createMoveCommand( Member member, Member loc) throws IllegalArgumentException; // commands to change member properties /** * Creates a command to change one property of a member. * * @param member the member * @param name the property name * @param value the property value * @return the command * @throws IllegalArgumentException if the property is invalid for the * member */ MemberEditCommand createSetPropertyCommand( Member member, String name, Object value) throws IllegalArgumentException; /** * Creates a command to several properties changes over a set of * members. All members must belong to the same Level. * * @param set the set of members * @param propertyValues Collection of property-value pairs * @return the command * @throws IllegalArgumentException for an invalid property, or if all * members in the set do not belong to the same Level. */ MemberEditCommand createSetPropertyCommand( MemberSet set, Map propertyValues) throws IllegalArgumentException; // other /** * Prints a debug message. * * @param message the message */ void trace(String message); /** * Flushes the cache which maps schema URLs to metadata. * *

    This cache is referenced only when creating a new connection, so * existing connections will continue to use the same schema definition. * *

    Flushing the schema cache will flush all aggregations and segments * associated to it as well. */ void flushSchemaCache(); /** * Flushes the given Schema instance from the pool. It resolves the * schema to flush by using its catalog URL, connection key and * JDBC username. * *

    Flushing the schema cache will flush all aggregations and segments * associated to it as well. */ void flushSchema( final String catalogUrl, final String connectionKey, final String jdbcUser, String dataSourceStr); /** * Flushes the given Schema instance from the pool. It resolves the * schema to flush by using its catalog URL and DataSource object. * *

    Flushing the schema cache will flush all aggregations and segments * associated to it as well. */ void flushSchema( String catalogUrl, DataSource dataSource); /** * Flushes the given Schema instance from the pool * *

    Flushing the schema cache will flush all aggregations and segments * associated to it as well. * * @param schema Schema */ void flushSchema(Schema schema); /** a region of cells in the cell cache */ public interface CellRegion { /** * Returns the dimensionality of a region. * @return a list of {@link mondrian.olap.Dimension} objects. */ List getDimensionality(); } /** * A specification of a set of members in the member cache. * *

    Member sets can be created using methods * {@link CacheControl#createMemberSet(Member, boolean)}, * {@link CacheControl#createMemberSet(boolean, Member, boolean, Member, boolean)}, * {@link CacheControl#createUnionSet(mondrian.olap.CacheControl.MemberSet[])}. */ public interface MemberSet { } /** * An operation to be applied to the member cache. The operation does not * take effect until {@link CacheControl#execute} is called. */ public interface MemberEditCommand { } } // End CacheControl.java mondrian-3.4.1/src/main/mondrian/olap/Query.java0000644000175000017500000021077311735330606021465 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1998-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.calc.*; import mondrian.mdx.*; import mondrian.olap.fun.ParameterFunDef; import mondrian.olap.type.*; import mondrian.resource.MondrianResource; import mondrian.rolap.*; import mondrian.server.*; import mondrian.spi.ProfileHandler; import mondrian.util.ArrayStack; import org.apache.commons.collections.collection.CompositeCollection; import org.olap4j.impl.IdentifierParser; import org.olap4j.mdx.IdentifierSegment; import java.io.PrintWriter; import java.sql.SQLException; import java.util.*; /** * Query is an MDX query. * *

    It is created by calling {@link Connection#parseQuery}, * and executed by calling {@link Connection#execute}, * to return a {@link Result}.

    * *

    Query control

    * *

    Most queries are model citizens, executing quickly (often using cached * results from previous queries), but some queries take more time, or more * database resources, or more results, than is reasonable. Mondrian offers * three ways to control rogue queries:

      * *
    • You can set a query timeout by setting the * {@link MondrianProperties#QueryTimeout} parameter. If the query * takes longer to execute than the value of this parameter, the system * will kill it.
    • * *
    • The {@link MondrianProperties#QueryLimit} parameter limits the number * of cells returned by a query.
    • * *
    • At any time while a query is executing, another thread can cancel the * query by calling * {@link #getStatement()}.{@link Statement#cancel() cancel()}. * The call to {@link Connection#execute(Query)} * will throw an exception.
    • * *
    * * @author jhyde, 20 January, 1999 */ public class Query extends QueryPart { private Formula[] formulas; /** * public-private: This must be public because it is still accessed in * rolap.RolapConnection */ public QueryAxis[] axes; private QueryAxis slicerAxis; /** * Definitions of all parameters used in this query. */ private final List parameters = new ArrayList(); private final Map parametersByName = new HashMap(); /** * Cell properties. Not currently used. */ private final QueryPart[] cellProps; /** * Cube this query belongs to. */ private final Cube cube; private final Statement statement; public Calc[] axisCalcs; public Calc slicerCalc; /** * Set of FunDefs for which alerts about non-native evaluation * have already been posted. */ Set alertedNonNativeFunDefs; /** * Unique list of members referenced from the measures dimension. * Will be used to determine if cross joins can be processed natively * for virtual cubes. */ private Set measuresMembers; /** * If true, virtual cubes can be processed using native cross joins. * It defaults to true, unless functions are applied on measures. */ private boolean nativeCrossJoinVirtualCube; /** * Used for virtual cubes. * Comtains a list of base cubes related to a virtual cube */ private List baseCubes; /** * If true, enforce validation even when ignoreInvalidMembers is set. */ private boolean strictValidation; /** * How should the query be returned? Valid values are: * ResultStyle.ITERABLE * ResultStyle.LIST * ResultStyle.MUTABLE_LIST * For java4, use LIST */ private ResultStyle resultStyle = Util.Retrowoven ? ResultStyle.LIST : ResultStyle.ITERABLE; private Map evalCache = new HashMap(); /** * List of aliased expressions defined in this query, and where they are * defined. There might be more than one aliased expression with the same * name. */ private final List scopedNamedSets = new ArrayList(); private boolean ownStatement; /** * Creates a Query. */ public Query( Statement statement, Formula[] formulas, QueryAxis[] axes, String cube, QueryAxis slicerAxis, QueryPart[] cellProps, boolean strictValidation) { this( statement, Util.lookupCube(statement.getSchemaReader(), cube, true), formulas, axes, slicerAxis, cellProps, new Parameter[0], strictValidation); } /** * Creates a Query. */ public Query( Statement statement, Cube mdxCube, Formula[] formulas, QueryAxis[] axes, QueryAxis slicerAxis, QueryPart[] cellProps, Parameter[] parameters, boolean strictValidation) { this.statement = statement; this.cube = mdxCube; this.formulas = formulas; this.axes = axes; normalizeAxes(); this.slicerAxis = slicerAxis; this.cellProps = cellProps; this.parameters.addAll(Arrays.asList(parameters)); this.measuresMembers = new HashSet(); // assume, for now, that cross joins on virtual cubes can be // processed natively; as we parse the query, we'll know otherwise this.nativeCrossJoinVirtualCube = true; this.strictValidation = strictValidation; this.alertedNonNativeFunDefs = new HashSet(); statement.setQuery(this); resolve(); if (RolapUtil.PROFILE_LOGGER.isDebugEnabled() && statement.getProfileHandler() == null) { statement.enableProfiling( new ProfileHandler() { public void explain(String plan, QueryTiming timing) { if (timing != null) { plan += "\n" + timing; } RolapUtil.PROFILE_LOGGER.debug(plan); } } ); } } /** * Sets the timeout in milliseconds of this Query. * *

    Zero means no timeout. * * @param queryTimeoutMillis Timeout in milliseconds * * @deprecated This method will be removed in mondrian-4.0 */ public void setQueryTimeoutMillis(long queryTimeoutMillis) { statement.setQueryTimeoutMillis(queryTimeoutMillis); } /** * Checks whether the property name is present in the query. */ public boolean hasCellProperty(String propertyName) { for (QueryPart cellProp : cellProps) { if (((CellProperty)cellProp).isNameEquals(propertyName)) { return true; } } return false; } /** * Checks whether any cell property present in the query */ public boolean isCellPropertyEmpty() { return cellProps.length == 0; } /** * Adds a new formula specifying a set * to an existing query. */ public void addFormula(Id id, Exp exp) { addFormula( new Formula(false, id, exp, new MemberProperty[0], null, null)); } /** * Adds a new formula specifying a member * to an existing query. * * @param id Name of member * @param exp Expression for member * @param memberProperties Properties of member */ public void addFormula( Id id, Exp exp, MemberProperty[] memberProperties) { addFormula(new Formula(true, id, exp, memberProperties, null, null)); } /** * Adds a new formula specifying a member or a set * to an existing query; resolve is called after * the formula has been added. * * @param formula Formula to add to query */ public void addFormula(Formula formula) { formulas = Util.append(formulas, formula); resolve(); } /** * Adds some number of new formulas specifying members * or sets to an existing query; resolve is only called * once, after all the new members have been added to * the query. * * @param additions Formulas to add to query */ public void addFormulas(Formula... additions) { formulas = Util.appendArrays(formulas, additions); resolve(); } /** * Creates a validator for this query. * * @return Validator */ public Validator createValidator() { return createValidator( statement.getSchema().getFunTable(), false); } /** * Creates a validator for this query that uses a given function table and * function validation policy. * * @param functionTable Function table * @param alwaysResolveFunDef Whether to always resolve function * definitions (see {@link Validator#alwaysResolveFunDef()}) * @return Validator */ public Validator createValidator( FunTable functionTable, boolean alwaysResolveFunDef) { return new QueryValidator( functionTable, alwaysResolveFunDef, Query.this); } /** * @deprecated Please use {@link #clone}; this method will be removed in * mondrian-4.0 */ public Query safeClone() { return clone(); } @SuppressWarnings({ "CloneDoesntCallSuperClone", "CloneDoesntDeclareCloneNotSupportedException" }) public Query clone() { return new Query( statement, cube, Formula.cloneArray(formulas), QueryAxis.cloneArray(axes), (slicerAxis == null) ? null : (QueryAxis) slicerAxis.clone(), cellProps, parameters.toArray(new Parameter[parameters.size()]), strictValidation); } public Connection getConnection() { return statement.getMondrianConnection(); } /** * Issues a cancel request on this Query object. Once the thread * running the query detects the cancel request, the query execution will * throw an exception. See BasicQueryTest.testCancel for an * example of usage of this method. * * @deprecated This method is deprecated and will be removed in mondrian-4.0 */ public void cancel() { try { statement.cancel(); } catch (SQLException e) { throw new RuntimeException(e); } } /** * Checks if either a cancel request has been issued on the query or * the execution time has exceeded the timeout value (if one has been * set). Exceptions are raised if either of these two conditions are * met. This method should be called periodically during query execution * to ensure timely detection of these events, particularly before/after * any potentially long running operations. * * @deprecated This method will be removed in mondrian-4.0 */ public void checkCancelOrTimeout() { final Execution execution0 = statement.getCurrentExecution(); if (execution0 == null) { return; } execution0.checkCancelOrTimeout(); } /** * Gets the query start time * @return start time * * @deprecated Use {@link Execution#getStartTime}. This method is deprecated * and will be removed in mondrian-4.0 */ public long getQueryStartTime() { final Execution currentExecution = statement.getCurrentExecution(); return currentExecution == null ? 0 : currentExecution.getStartTime(); } /** * Determines whether an alert for non-native evaluation needs * to be posted. * * @param funDef function type to alert for * * @return true if alert should be raised */ public boolean shouldAlertForNonNative(FunDef funDef) { return alertedNonNativeFunDefs.add(funDef); } private void normalizeAxes() { for (int i = 0; i < axes.length; i++) { AxisOrdinal correctOrdinal = AxisOrdinal.StandardAxisOrdinal.forLogicalOrdinal(i); if (axes[i].getAxisOrdinal() != correctOrdinal) { for (int j = i + 1; j < axes.length; j++) { if (axes[j].getAxisOrdinal() == correctOrdinal) { // swap axes QueryAxis temp = axes[i]; axes[i] = axes[j]; axes[j] = temp; break; } } } } } /** * Performs type-checking and validates internal consistency of a query, * using the default resolver. * *

    This method is called automatically when a query is created; you need * to call this method manually if you have modified the query's expression * tree in any way. */ public void resolve() { final Validator validator = createValidator(); resolve(validator); // resolve self and children // Create a dummy result so we can use its evaluator final Evaluator evaluator = RolapUtil.createEvaluator(statement); ExpCompiler compiler = createCompiler( evaluator, validator, Collections.singletonList(resultStyle)); compile(compiler); } /** * @return true if the relevant property for ignoring invalid members is * set to true for this query's environment (a different property is * checked depending on whether environment is schema load vs query * validation) */ public boolean ignoreInvalidMembers() { MondrianProperties props = MondrianProperties.instance(); final boolean load = ((RolapCube) getCube()).isLoadInProgress(); return !strictValidation && (load ? props.IgnoreInvalidMembers.get() : props.IgnoreInvalidMembersDuringQuery.get()); } /** * A Query's ResultStyle can only be one of the following: * ResultStyle.ITERABLE * ResultStyle.LIST * ResultStyle.MUTABLE_LIST * * @param resultStyle */ public void setResultStyle(ResultStyle resultStyle) { switch (resultStyle) { case ITERABLE: // For java4, use LIST this.resultStyle = (Util.Retrowoven) ? ResultStyle.LIST : ResultStyle.ITERABLE; break; case LIST: case MUTABLE_LIST: this.resultStyle = resultStyle; break; default: throw ResultStyleException.generateBadType( ResultStyle.ITERABLE_LIST_MUTABLELIST, resultStyle); } } public ResultStyle getResultStyle() { return resultStyle; } /** * Generates compiled forms of all expressions. * * @param compiler Compiler */ private void compile(ExpCompiler compiler) { if (formulas != null) { for (Formula formula : formulas) { formula.compile(); } } if (axes != null) { axisCalcs = new Calc[axes.length]; for (int i = 0; i < axes.length; i++) { axisCalcs[i] = axes[i].compile(compiler, resultStyle); } } if (slicerAxis != null) { slicerCalc = slicerAxis.compile(compiler, resultStyle); } } /** * Performs type-checking and validates internal consistency of a query. * * @param validator Validator */ public void resolve(Validator validator) { // Before commencing validation, create all calculated members, // calculated sets, and parameters. if (formulas != null) { // Resolving of formulas should be done in two parts // because formulas might depend on each other, so all calculated // mdx elements have to be defined during resolve. for (Formula formula : formulas) { formula.createElement(validator.getQuery()); } } // Register all parameters. parameters.clear(); parametersByName.clear(); accept(new ParameterFinder()); // Register all aliased expressions ('expr AS alias') as named sets. accept(new AliasedExpressionFinder()); // Validate formulas. if (formulas != null) { for (Formula formula : formulas) { validator.validate(formula); } } // Validate axes. if (axes != null) { Set axisNames = new HashSet(); for (QueryAxis axis : axes) { validator.validate(axis); if (!axisNames.add(axis.getAxisOrdinal().logicalOrdinal())) { throw MondrianResource.instance().DuplicateAxis.ex( axis.getAxisName()); } } // Make sure that there are no gaps. If there are N axes, then axes // 0 .. N-1 should exist. int seekOrdinal = AxisOrdinal.StandardAxisOrdinal.COLUMNS.logicalOrdinal(); for (QueryAxis axis : axes) { if (!axisNames.contains(seekOrdinal)) { AxisOrdinal axisName = AxisOrdinal.StandardAxisOrdinal.forLogicalOrdinal( seekOrdinal); throw MondrianResource.instance().NonContiguousAxis.ex( seekOrdinal, axisName.name()); } ++seekOrdinal; } } if (slicerAxis != null) { slicerAxis.validate(validator); } // Make sure that no hierarchy is used on more than one axis. for (Hierarchy hierarchy : ((RolapCube) getCube()).getHierarchies()) { int useCount = 0; for (QueryAxis axis : allAxes()) { if (axis.getSet().getType().usesHierarchy(hierarchy, true)) { ++useCount; } } if (useCount > 1) { throw MondrianResource.instance().HierarchyInIndependentAxes.ex( hierarchy.getUniqueName()); } } } @Override public void explain(PrintWriter pw) { final boolean profiling = getStatement().getProfileHandler() != null; final CalcWriter calcWriter = new CalcWriter(pw, profiling); for (Formula formula : formulas) { formula.getMdxMember(); // TODO: } if (slicerCalc != null) { pw.println("Axis (FILTER):"); slicerCalc.accept(calcWriter); pw.println(); } int i = -1; for (QueryAxis axis : axes) { ++i; pw.println("Axis (" + axis.getAxisName() + "):"); axisCalcs[i].accept(calcWriter); pw.println(); } pw.flush(); } /** * Returns a collection of all axes, including the slicer as the first * element, if there is a slicer. * * @return Collection of all axes including slicer */ private Collection allAxes() { if (slicerAxis == null) { return Arrays.asList(axes); } else { //noinspection unchecked return new CompositeCollection( new Collection[] { Collections.singletonList(slicerAxis), Arrays.asList(axes)}); } } public void unparse(PrintWriter pw) { if (formulas != null) { for (int i = 0; i < formulas.length; i++) { if (i == 0) { pw.print("with "); } else { pw.print(" "); } formulas[i].unparse(pw); pw.println(); } } pw.print("select "); if (axes != null) { for (int i = 0; i < axes.length; i++) { axes[i].unparse(pw); if (i < axes.length - 1) { pw.println(","); pw.print(" "); } else { pw.println(); } } } if (cube != null) { pw.println("from [" + cube.getName() + "]"); } if (slicerAxis != null) { pw.print("where "); slicerAxis.unparse(pw); pw.println(); } } /** Returns the MDX query string. */ public String toString() { resolve(); return Util.unparse(this); } public Object[] getChildren() { // Chidren are axes, slicer, and formulas (in that order, to be // consistent with replaceChild). List list = new ArrayList(); list.addAll(Arrays.asList(axes)); if (slicerAxis != null) { list.add(slicerAxis); } list.addAll(Arrays.asList(formulas)); return list.toArray(); } public QueryAxis getSlicerAxis() { return slicerAxis; } public void setSlicerAxis(QueryAxis axis) { this.slicerAxis = axis; } /** * Adds a level to an axis expression. */ public void addLevelToAxis(AxisOrdinal axis, Level level) { assert axis != null; axes[axis.logicalOrdinal()].addLevel(level); } /** * Returns the hierarchies in an expression. * *

    If the expression's type is a dimension with several hierarchies, * assumes that the expression yields a member of the first (default) * hierarchy of the dimension. * *

    For example, the expression *

    Crossjoin( * Hierarchize( * Union( * {[Time].LastSibling}, [Time].LastSibling.Children)), * {[Measures].[Unit Sales], [Measures].[Store Cost]}) *
    * * has type {[Time.Monthly], [Measures]} even though * [Time].LastSibling might return a member of either * [Time.Monthly] or [Time.Weekly]. */ private Hierarchy[] collectHierarchies(Exp queryPart) { Type exprType = queryPart.getType(); if (exprType instanceof SetType) { exprType = ((SetType) exprType).getElementType(); } if (exprType instanceof TupleType) { final Type[] types = ((TupleType) exprType).elementTypes; ArrayList hierarchyList = new ArrayList(); for (Type type : types) { hierarchyList.add(getTypeHierarchy(type)); } return hierarchyList.toArray(new Hierarchy[hierarchyList.size()]); } return new Hierarchy[] {getTypeHierarchy(exprType)}; } private Hierarchy getTypeHierarchy(final Type type) { Hierarchy hierarchy = type.getHierarchy(); if (hierarchy != null) { return hierarchy; } final Dimension dimension = type.getDimension(); if (dimension != null) { return dimension.getHierarchy(); } return null; } /** * Assigns a value to the parameter with a given name. * * @throws RuntimeException if there is not parameter with the given name */ public void setParameter(final String parameterName, final Object value) { // Need to resolve query before we set parameters, in order to create // slots to store them in. (This code will go away when parameters // belong to prepared statements.) if (parameters.isEmpty()) { resolve(); } final Parameter param = getSchemaReader(false).getParameter(parameterName); if (param == null) { throw MondrianResource.instance().UnknownParameter.ex( parameterName); } if (!param.isModifiable()) { throw MondrianResource.instance().ParameterIsNotModifiable.ex( parameterName, param.getScope().name()); } final Object value2 = Locus.execute( new Execution(statement, 0), "Query.quickParse", new Locus.Action() { public Object execute() { return quickParse( parameterName, param.getType(), value, Query.this); } } ); param.setValue(value2); } /** * Converts a value into something appropriate for a given type. * *

    Viz: *

      *
    • For numerics, takes number or string and returns a {@link Number}. *
    • For strings, takes string, or calls {@link Object#toString()} on any * other type *
    • For members, takes member or string *
    • For sets of members, requires a list of members or strings and * converts each element to a member. *
    * * @param type Type * @param value Value * @param query Query * @return Value of appropriate type * @throws NumberFormatException If value needs to be a number but isn't */ private static Object quickParse( String parameterName, Type type, Object value, Query query) throws NumberFormatException { int category = TypeUtil.typeToCategory(type); switch (category) { case Category.Numeric: if (value instanceof Number || value == null) { return value; } if (value instanceof String) { String s = (String) value; try { return new Integer(s); } catch (NumberFormatException e) { return new Double(s); } } throw Util.newInternal( "Invalid value '" + value + "' for parameter '" + parameterName + "', type " + type); case Category.String: if (value == null) { return null; } return value.toString(); case Category.Set: if (value instanceof String) { value = IdentifierParser.parseIdentifierList((String) value); } if (!(value instanceof List)) { throw Util.newInternal( "Invalid value '" + value + "' for parameter '" + parameterName + "', type " + type); } List expList = new ArrayList(); final List list = (List) value; final SetType setType = (SetType) type; final Type elementType = setType.getElementType(); for (Object o : list) { // In keeping with MDX semantics, null members are omitted from // lists. if (o == null) { continue; } final Member member = (Member) quickParse(parameterName, elementType, o, query); expList.add(member); } return expList; case Category.Member: if (value == null) { // Setting a member parameter to null is the same as setting to // the null member of the hierarchy. May not be equivalent to // the default value of the parameter, nor the same as the all // member. if (type.getHierarchy() != null) { value = type.getHierarchy().getNullMember(); } else if (type.getDimension() != null) { value = type.getDimension().getHierarchy().getNullMember(); } } if (value instanceof String) { value = Util.parseIdentifier((String) value); } if (value instanceof List && Util.canCast((List) value, Id.Segment.class)) { final List segmentList = Util.cast((List) value); final OlapElement olapElement = Util.lookup(query, segmentList); if (olapElement instanceof Member) { value = olapElement; } } if (value instanceof List && Util.canCast((List) value, IdentifierSegment.class)) { final List olap4jSegmentList = Util.cast((List) value); final List segmentList = Util.convert(olap4jSegmentList); final OlapElement olapElement = Util.lookup(query, segmentList); if (olapElement instanceof Member) { value = olapElement; } } if (value instanceof Member) { if (type.isInstance(value)) { return value; } } throw Util.newInternal( "Invalid value '" + value + "' for parameter '" + parameterName + "', type " + type); default: throw Category.instance.badValue(category); } } /** * Swaps the x- and y- axes. * Does nothing if the number of axes != 2. */ public void swapAxes() { if (axes.length == 2) { Exp e0 = axes[0].getSet(); boolean nonEmpty0 = axes[0].isNonEmpty(); Exp e1 = axes[1].getSet(); boolean nonEmpty1 = axes[1].isNonEmpty(); axes[1].setSet(e0); axes[1].setNonEmpty(nonEmpty0); axes[0].setSet(e1); axes[0].setNonEmpty(nonEmpty1); // showSubtotals ??? } } /** * Returns the parameters defined in this query. */ public Parameter[] getParameters() { return parameters.toArray(new Parameter[parameters.size()]); } public Cube getCube() { return cube; } /** * Returns a schema reader. * * @param accessControlled If true, schema reader returns only elements * which are accessible to the statement's current role * * @return schema reader */ public SchemaReader getSchemaReader(boolean accessControlled) { final Role role; if (accessControlled) { // full access control role = getConnection().getRole(); } else { role = null; } final SchemaReader cubeSchemaReader = cube.getSchemaReader(role); return new QuerySchemaReader(cubeSchemaReader, Query.this); } /** * Looks up a member whose unique name is memberUniqueName * from cache. If the member is not in cache, returns null. */ public Member lookupMemberFromCache(String memberUniqueName) { // first look in defined members for (Member member : getDefinedMembers()) { if (Util.equalName(member.getUniqueName(), memberUniqueName) || Util.equalName( getUniqueNameWithoutAll(member), memberUniqueName)) { return member; } } return null; } private String getUniqueNameWithoutAll(Member member) { // build unique string Member parentMember = member.getParentMember(); if ((parentMember != null) && !parentMember.isAll()) { return Util.makeFqName( getUniqueNameWithoutAll(parentMember), member.getName()); } else { return Util.makeFqName(member.getHierarchy(), member.getName()); } } /** * Looks up a named set. */ private NamedSet lookupNamedSet(String name) { for (Formula formula : formulas) { if (!formula.isMember() && formula.getElement() != null && formula.getName().equals(name)) { return (NamedSet) formula.getElement(); } } return null; } /** * Creates a named set defined by an alias. */ public ScopedNamedSet createScopedNamedSet( String name, QueryPart scope, Exp expr) { final ScopedNamedSet scopedNamedSet = new ScopedNamedSet( name, scope, expr); scopedNamedSets.add(scopedNamedSet); return scopedNamedSet; } /** * Looks up a named set defined by an alias. * * @param nameParts Multi-part identifier for set * @param scopeList Parse tree node where name is used (last in list) and */ ScopedNamedSet lookupScopedNamedSet( List nameParts, ArrayStack scopeList) { if (nameParts.size() != 1) { return null; } String name = nameParts.get(0).name; ScopedNamedSet bestScopedNamedSet = null; int bestScopeOrdinal = -1; for (ScopedNamedSet scopedNamedSet : scopedNamedSets) { if (Util.equalName(scopedNamedSet.name, name)) { int scopeOrdinal = scopeList.indexOf(scopedNamedSet.scope); if (scopeOrdinal > bestScopeOrdinal) { bestScopedNamedSet = scopedNamedSet; bestScopeOrdinal = scopeOrdinal; } } } return bestScopedNamedSet; } /** * Returns an array of the formulas used in this query. */ public Formula[] getFormulas() { return formulas; } /** * Returns an array of this query's axes. */ public QueryAxis[] getAxes() { return axes; } /** * Remove a formula from the query. If failIfUsedInQuery is * true, checks and throws an error if formula is used somewhere in the * query. */ public void removeFormula(String uniqueName, boolean failIfUsedInQuery) { Formula formula = findFormula(uniqueName); if (failIfUsedInQuery && formula != null) { OlapElement mdxElement = formula.getElement(); //search the query tree to see if this formula expression is used //anywhere (on the axes or in another formula) Walker walker = new Walker(this); while (walker.hasMoreElements()) { Object queryElement = walker.nextElement(); if (!queryElement.equals(mdxElement)) { continue; } // mdxElement is used in the query. lets find on on which axis // or formula String formulaType = formula.isMember() ? MondrianResource.instance().CalculatedMember.str() : MondrianResource.instance().CalculatedSet.str(); int i = 0; Object parent = walker.getAncestor(i); Object grandParent = walker.getAncestor(i + 1); while ((parent != null) && (grandParent != null)) { if (grandParent instanceof Query) { if (parent instanceof Axis) { throw MondrianResource.instance() .MdxCalculatedFormulaUsedOnAxis.ex( formulaType, uniqueName, ((QueryAxis) parent).getAxisName()); } else if (parent instanceof Formula) { String parentFormulaType = ((Formula) parent).isMember() ? MondrianResource.instance() .CalculatedMember.str() : MondrianResource.instance() .CalculatedSet.str(); throw MondrianResource.instance() .MdxCalculatedFormulaUsedInFormula.ex( formulaType, uniqueName, parentFormulaType, ((Formula) parent).getUniqueName()); } else { throw MondrianResource.instance() .MdxCalculatedFormulaUsedOnSlicer.ex( formulaType, uniqueName); } } ++i; parent = walker.getAncestor(i); grandParent = walker.getAncestor(i + 1); } throw MondrianResource.instance() .MdxCalculatedFormulaUsedInQuery.ex( formulaType, uniqueName, Util.unparse(this)); } } // remove formula from query List formulaList = new ArrayList(); for (Formula formula1 : formulas) { if (!formula1.getUniqueName().equalsIgnoreCase(uniqueName)) { formulaList.add(formula1); } } // it has been found and removed this.formulas = formulaList.toArray(new Formula[formulaList.size()]); } /** * Returns whether a formula can safely be removed from the query. It can be * removed if the member or set it defines it not used anywhere else in the * query, including in another formula. * * @param uniqueName Unique name of the member or set defined by the formula * @return whether the formula can safely be removed */ public boolean canRemoveFormula(String uniqueName) { Formula formula = findFormula(uniqueName); if (formula == null) { return false; } OlapElement mdxElement = formula.getElement(); // Search the query tree to see if this formula expression is used // anywhere (on the axes or in another formula). Walker walker = new Walker(this); while (walker.hasMoreElements()) { Object queryElement = walker.nextElement(); if (queryElement instanceof MemberExpr && ((MemberExpr) queryElement).getMember().equals(mdxElement)) { return false; } if (queryElement instanceof NamedSetExpr && ((NamedSetExpr) queryElement).getNamedSet().equals( mdxElement)) { return false; } } return true; } /** * Looks up a calculated member or set defined in this Query. * * @param uniqueName Unique name of calculated member or set * @return formula defining calculated member, or null if not found */ public Formula findFormula(String uniqueName) { for (Formula formula : formulas) { if (formula.getUniqueName().equalsIgnoreCase(uniqueName)) { return formula; } } return null; } /** * Finds formula by name and renames it to new name. */ public void renameFormula(String uniqueName, String newName) { Formula formula = findFormula(uniqueName); if (formula == null) { throw MondrianResource.instance().MdxFormulaNotFound.ex( "formula", uniqueName, Util.unparse(this)); } formula.rename(newName); } List getDefinedMembers() { List definedMembers = new ArrayList(); for (final Formula formula : formulas) { if (formula.isMember() && formula.getElement() != null && getConnection().getRole().canAccess(formula.getElement())) { definedMembers.add((Member) formula.getElement()); } } return definedMembers; } /** * Finds axis by index and sets flag to show empty cells on that axis. */ public void setAxisShowEmptyCells(int axis, boolean showEmpty) { if (axis >= axes.length) { throw MondrianResource.instance().MdxAxisShowSubtotalsNotSupported .ex(axis); } axes[axis].setNonEmpty(!showEmpty); } /** * Returns Hierarchy[] used on axis. It calls * {@link #collectHierarchies}. */ public Hierarchy[] getMdxHierarchiesOnAxis(AxisOrdinal axis) { if (axis.logicalOrdinal() >= axes.length) { throw MondrianResource.instance().MdxAxisShowSubtotalsNotSupported .ex(axis.logicalOrdinal()); } QueryAxis queryAxis = axis.isFilter() ? slicerAxis : axes[axis.logicalOrdinal()]; return collectHierarchies(queryAxis.getSet()); } /** * Compiles an expression, using a cached compiled expression if available. * * @param exp Expression * @param scalar Whether expression is scalar * @param resultStyle Preferred result style; if null, use query's default * result style; ignored if expression is scalar * @return compiled expression */ public Calc compileExpression( Exp exp, boolean scalar, ResultStyle resultStyle) { // REVIEW: Set query on a connection's shared internal statement is // not re-entrant. statement.setQuery(this); Evaluator evaluator = RolapEvaluator.create(statement); final Validator validator = createValidator(); List resultStyleList; resultStyleList = Collections.singletonList( resultStyle != null ? resultStyle : this.resultStyle); final ExpCompiler compiler = createCompiler( evaluator, validator, resultStyleList); if (scalar) { return compiler.compileScalar(exp, false); } else { return compiler.compile(exp); } } public ExpCompiler createCompiler() { // REVIEW: Set query on a connection's shared internal statement is // not re-entrant. statement.setQuery(this); Evaluator evaluator = RolapEvaluator.create(statement); Validator validator = createValidator(); return createCompiler( evaluator, validator, Collections.singletonList(resultStyle)); } private ExpCompiler createCompiler( final Evaluator evaluator, final Validator validator, List resultStyleList) { ExpCompiler compiler = ExpCompiler.Factory.getExpCompiler( evaluator, validator, resultStyleList); final int expDeps = MondrianProperties.instance().TestExpDependencies.get(); final ProfileHandler profileHandler = statement.getProfileHandler(); if (profileHandler != null) { // Cannot test dependencies and profile at the same time. Profiling // trumps. compiler = RolapUtil.createProfilingCompiler(compiler); } else if (expDeps > 0) { compiler = RolapUtil.createDependencyTestingCompiler(compiler); } return compiler; } /** * Keeps track of references to members of the measures dimension * * @param olapElement potential measure member */ public void addMeasuresMembers(OlapElement olapElement) { if (olapElement instanceof Member) { Member member = (Member) olapElement; if (member.isMeasure()) { measuresMembers.add(member); } } } /** * @return set of members from the measures dimension referenced within * this query */ public Set getMeasuresMembers() { return Collections.unmodifiableSet(measuresMembers); } /** * Indicates that the query cannot use native cross joins to process * this virtual cube */ public void setVirtualCubeNonNativeCrossJoin() { nativeCrossJoinVirtualCube = false; } /** * @return true if the query can use native cross joins on a virtual * cube */ public boolean nativeCrossJoinVirtualCube() { return nativeCrossJoinVirtualCube; } /** * Saves away the base cubes related to the virtual cube * referenced in this query * * @param baseCubes set of base cubes */ public void setBaseCubes(List baseCubes) { this.baseCubes = baseCubes; } /** * return the set of base cubes associated with the virtual cube referenced * in this query * * @return set of base cubes */ public List getBaseCubes() { return baseCubes; } public Object accept(MdxVisitor visitor) { Object o = visitor.visit(this); if (visitor.shouldVisitChildren()) { // visit formulas for (Formula formula : formulas) { formula.accept(visitor); } // visit axes for (QueryAxis axis : axes) { axis.accept(visitor); } if (slicerAxis != null) { slicerAxis.accept(visitor); } } return o; } /** * Put an Object value into the evaluation cache with given key. * This is used by Calc's to store information between iterations * (rather than re-generate each time). * * @param key the cache key * @param value the cache value */ public void putEvalCache(String key, Object value) { evalCache.put(key, value); } /** * Gets the Object associated with the value. * * @param key the cache key * @return the cached value or null. */ public Object getEvalCache(String key) { return evalCache.get(key); } /** * Remove all entries in the evaluation cache */ public void clearEvalCache() { evalCache.clear(); } /** * Closes this query. * *

    Releases any resources held. Writes statistics to log if profiling * is enabled. * *

    This method is idempotent. * * @deprecated This method will be removed in mondrian-4.0. */ public void close() { if (ownStatement) { statement.close(); } } public Statement getStatement() { return statement; } /** * Sets that the query owns its statement; therefore it will need to * close it when the query is closed. * * @param ownStatement Whether the statement belongs to the query */ public void setOwnStatement(boolean ownStatement) { this.ownStatement = ownStatement; } /** * Source of metadata within the scope of a query. * *

    Note especially that {@link #getCalculatedMember(java.util.List)} * returns the calculated members defined in this query. It does not * perform access control; all calculated members defined in a query are * visible to everyone. */ private static class QuerySchemaReader extends DelegatingSchemaReader implements NameResolver.Namespace { private final Query query; public QuerySchemaReader(SchemaReader cubeSchemaReader, Query query) { super(cubeSchemaReader); this.query = query; } public SchemaReader withoutAccessControl() { return new QuerySchemaReader( schemaReader.withoutAccessControl(), query); } public Member getMemberByUniqueName( List uniqueNameParts, boolean failIfNotFound, MatchType matchType) { final String uniqueName = Util.implode(uniqueNameParts); Member member = query.lookupMemberFromCache(uniqueName); if (member == null) { // Not a calculated member in the query, so go to the cube. member = schemaReader.getMemberByUniqueName( uniqueNameParts, failIfNotFound, matchType); } if (!failIfNotFound && member == null) { return null; } if (getRole().canAccess(member)) { return member; } else { return null; } } public List getLevelMembers( Level level, boolean includeCalculated) { List members = super.getLevelMembers(level, false); if (includeCalculated) { members = Util.addLevelCalculatedMembers(this, level, members); } return members; } public Member getCalculatedMember(List nameParts) { for (final Formula formula : query.formulas) { if (!formula.isMember()) { continue; } Member member = (Member) formula.getElement(); if (member == null) { continue; } if (!match(member, nameParts)) { continue; } if (!query.getConnection().getRole().canAccess(member)) { continue; } return member; } return null; } private static boolean match( Member member, List nameParts) { Id.Segment segment = nameParts.get(nameParts.size() - 1); while (member.getParentMember() != null) { if (!segment.matches(member.getName())) { return false; } member = member.getParentMember(); nameParts = nameParts.subList(0, nameParts.size() - 1); segment = nameParts.get(nameParts.size() - 1); } if (segment.matches(member.getName())) { return Util.equalName( member.getHierarchy().getUniqueName(), Util.implode(nameParts.subList(0, nameParts.size() - 1))); } else if (member.isAll()) { return Util.equalName( member.getHierarchy().getUniqueName(), Util.implode(nameParts)); } else { return false; } } public List getCalculatedMembers(Hierarchy hierarchy) { List result = new ArrayList(); // Add calculated members in the cube. final List calculatedMembers = super.getCalculatedMembers(hierarchy); result.addAll(calculatedMembers); // Add calculated members defined in the query. for (Member member : query.getDefinedMembers()) { if (member.getHierarchy().equals(hierarchy)) { result.add(member); } } return result; } public List getCalculatedMembers(Level level) { List hierarchyMembers = getCalculatedMembers(level.getHierarchy()); List result = new ArrayList(); for (Member member : hierarchyMembers) { if (member.getLevel().equals(level)) { result.add(member); } } return result; } public List getCalculatedMembers() { return query.getDefinedMembers(); } public OlapElement getElementChild(OlapElement parent, Id.Segment s) { return getElementChild(parent, s, MatchType.EXACT); } public OlapElement getElementChild( OlapElement parent, Id.Segment s, MatchType matchType) { // first look in cube OlapElement mdxElement = schemaReader.getElementChild(parent, s, matchType); if (mdxElement != null) { return mdxElement; } // then look in defined members (fixes MONDRIAN-77) // then in defined sets for (Formula formula : query.formulas) { if (formula.isMember()) { continue; // have already done these } Id id = formula.getIdentifier(); if (id.getSegments().size() == 1 && id.getSegments().get(0).matches(s.name)) { return formula.getNamedSet(); } } return mdxElement; } @Override public OlapElement lookupCompoundInternal( OlapElement parent, List names, boolean failIfNotFound, int category, MatchType matchType) { if (matchType == MatchType.EXACT) { OlapElement oe = lookupCompound( parent, names, failIfNotFound, category, MatchType.EXACT_SCHEMA); if (oe != null) { return oe; } } // First look to ourselves. switch (category) { case Category.Unknown: case Category.Member: if (parent == query.cube) { final Member calculatedMember = getCalculatedMember(names); if (calculatedMember != null) { return calculatedMember; } } } switch (category) { case Category.Unknown: case Category.Set: if (parent == query.cube) { final NamedSet namedSet = getNamedSet(names); if (namedSet != null) { return namedSet; } } } // Then delegate to the next reader. OlapElement olapElement = super.lookupCompoundInternal( parent, names, failIfNotFound, category, matchType); if (olapElement instanceof Member) { Member member = (Member) olapElement; final Formula formula = (Formula) member.getPropertyValue(Property.FORMULA.name); if (formula != null) { // This is a calculated member defined against the cube. // Create a free-standing formula using the same // expression, then use the member defined in that formula. final Formula formulaClone = (Formula) formula.clone(); formulaClone.createElement(query); formulaClone.accept(query.createValidator()); olapElement = formulaClone.getMdxMember(); } } return olapElement; } public NamedSet getNamedSet(List nameParts) { if (nameParts.size() != 1) { return null; } return query.lookupNamedSet(nameParts.get(0).name); } public Parameter getParameter(String name) { // Look for a parameter defined in the query. for (Parameter parameter : query.parameters) { if (parameter.getName().equals(name)) { return parameter; } } // Look for a parameter defined in this statement. if (Util.lookup(RolapConnectionProperties.class, name) != null) { Object value = query.statement.getProperty(name); // TODO: Don't assume it's a string. // TODO: Create expression which will get the value from the // statement at the time the query is executed. Literal defaultValue = Literal.createString(String.valueOf(value)); return new ConnectionParameterImpl(name, defaultValue); } return super.getParameter(name); } public OlapElement lookupChild( OlapElement parent, IdentifierSegment segment, MatchType matchType) { // ignore matchType return lookupChild(parent, segment); } public OlapElement lookupChild( OlapElement parent, IdentifierSegment segment) { // Only look for calculated members and named sets defined in the // query. for (Formula formula : query.getFormulas()) { if (NameResolver.matches(formula, parent, segment)) { return formula.getElement(); } } return null; } public List getNamespaces() { final List list = new ArrayList(); list.add(this); list.addAll(super.getNamespaces()); return list; } } private static class ConnectionParameterImpl extends ParameterImpl { public ConnectionParameterImpl(String name, Literal defaultValue) { super(name, defaultValue, "Connection property", new StringType()); } public Scope getScope() { return Scope.Connection; } public void setValue(Object value) { throw MondrianResource.instance().ParameterIsNotModifiable.ex( getName(), getScope().name()); } } /** * Implementation of {@link mondrian.olap.Validator} that works within a * particular query. * *

    It's unlikely that we would want a validator that is * NOT within a particular query, but by organizing the code this way, with * the majority of the code in {@link mondrian.olap.ValidatorImpl}, the * dependencies between Validator and Query are explicit. */ private static class QueryValidator extends ValidatorImpl { private final boolean alwaysResolveFunDef; private Query query; private final SchemaReader schemaReader; /** * Creates a QueryValidator. * * @param functionTable Function table * @param alwaysResolveFunDef Whether to always resolve function * definitions (see {@link #alwaysResolveFunDef()}) * @param query Query */ public QueryValidator( FunTable functionTable, boolean alwaysResolveFunDef, Query query) { super(functionTable); this.alwaysResolveFunDef = alwaysResolveFunDef; this.query = query; this.schemaReader = new ScopedSchemaReader(this, true); } public SchemaReader getSchemaReader() { return schemaReader; } protected void defineParameter(Parameter param) { final String name = param.getName(); query.parameters.add(param); query.parametersByName.put(name, param); } public Query getQuery() { return query; } public boolean alwaysResolveFunDef() { return alwaysResolveFunDef; } public ArrayStack getScopeStack() { return stack; } } /** * Schema reader that depends on the current scope during the validation * of a query. Depending on the scope, different calculated sets may be * visible. The scope is represented by the expression stack inside the * validator. */ private static class ScopedSchemaReader extends DelegatingSchemaReader implements NameResolver.Namespace { private final QueryValidator queryValidator; private final boolean accessControlled; /** * Creates a ScopedSchemaReader. * * @param queryValidator Validator that is being used to validate the * query * @param accessControlled Access controlled */ private ScopedSchemaReader( QueryValidator queryValidator, boolean accessControlled) { super(queryValidator.getQuery().getSchemaReader(accessControlled)); this.queryValidator = queryValidator; this.accessControlled = accessControlled; } public SchemaReader withoutAccessControl() { if (!accessControlled) { return this; } return new ScopedSchemaReader(queryValidator, false); } public List getNamespaces() { final List list = new ArrayList(); list.add(this); list.addAll(super.getNamespaces()); return list; } @Override public OlapElement lookupCompoundInternal( OlapElement parent, final List names, boolean failIfNotFound, int category, MatchType matchType) { switch (category) { case Category.Set: case Category.Unknown: final ScopedNamedSet namedSet = queryValidator.getQuery().lookupScopedNamedSet( names, queryValidator.getScopeStack()); if (namedSet != null) { return namedSet; } } return super.lookupCompoundInternal( parent, names, failIfNotFound, category, matchType); } public OlapElement lookupChild( OlapElement parent, IdentifierSegment segment, MatchType matchType) { // ignore matchType return lookupChild(parent, segment); } public OlapElement lookupChild( OlapElement parent, IdentifierSegment segment) { if (!(parent instanceof Cube)) { return null; } return queryValidator.getQuery().lookupScopedNamedSet( Collections.singletonList(Util.convert(segment)), queryValidator.getScopeStack()); } } public static class ScopedNamedSet implements NamedSet { private final String name; private final QueryPart scope; private Exp expr; /** * Creates a ScopedNamedSet. * * @param name Name * @param scope Scope of named set (the function call that encloses * the 'expr AS name', often GENERATE or FILTER) * @param expr Expression that defines the set */ private ScopedNamedSet(String name, QueryPart scope, Exp expr) { this.name = name; this.scope = scope; this.expr = expr; } public String getName() { return name; } public String getNameUniqueWithinQuery() { return System.identityHashCode(this) + ""; } public boolean isDynamic() { return true; } public Exp getExp() { return expr; } public void setExp(Exp expr) { this.expr = expr; } public void setName(String newName) { throw new UnsupportedOperationException(); } public Type getType() { return expr.getType(); } public Map getAnnotationMap() { return Collections.emptyMap(); } public NamedSet validate(Validator validator) { Exp newExpr = expr.accept(validator); final Type type = newExpr.getType(); if (type instanceof MemberType || type instanceof TupleType) { newExpr = new UnresolvedFunCall( "{}", Syntax.Braces, new Exp[] {newExpr}) .accept(validator); } this.expr = newExpr; return this; } public String getUniqueName() { return name; } public String getDescription() { throw new UnsupportedOperationException(); } public OlapElement lookupChild( SchemaReader schemaReader, Id.Segment s, MatchType matchType) { throw new UnsupportedOperationException(); } public String getQualifiedName() { throw new UnsupportedOperationException(); } public String getCaption() { throw new UnsupportedOperationException(); } public boolean isVisible() { throw new UnsupportedOperationException(); } public Hierarchy getHierarchy() { throw new UnsupportedOperationException(); } public Dimension getDimension() { throw new UnsupportedOperationException(); } public String getLocalized(LocalizedProperty prop, Locale locale) { throw new UnsupportedOperationException(); } } /** * Visitor that locates and registers parameters. */ private class ParameterFinder extends MdxVisitorImpl { public Object visit(ParameterExpr parameterExpr) { Parameter parameter = parameterExpr.getParameter(); if (!parameters.contains(parameter)) { parameters.add(parameter); parametersByName.put(parameter.getName(), parameter); } return null; } public Object visit(UnresolvedFunCall call) { if (call.getFunName().equals("Parameter")) { // Is there already a parameter with this name? String parameterName = ParameterFunDef.getParameterName(call.getArgs()); if (parametersByName.get(parameterName) != null) { throw MondrianResource.instance() .ParameterDefinedMoreThanOnce.ex(parameterName); } Type type = ParameterFunDef.getParameterType(call.getArgs()); // Create a temporary parameter. We don't know its // type yet. The default of NULL is temporary. Parameter parameter = new ParameterImpl( parameterName, Literal.nullValue, null, type); parameters.add(parameter); parametersByName.put(parameterName, parameter); } return null; } } /** * Visitor that locates and registers all aliased expressions * ('expr AS alias') as named sets. The resulting named sets have scope, * therefore they can only be seen and used within that scope. */ private class AliasedExpressionFinder extends MdxVisitorImpl { @Override public Object visit(QueryAxis queryAxis) { registerAlias(queryAxis, queryAxis.getSet()); return super.visit(queryAxis); } public Object visit(UnresolvedFunCall call) { registerAliasArgs(call); return super.visit(call); } public Object visit(ResolvedFunCall call) { registerAliasArgs(call); return super.visit(call); } /** * Registers all arguments of a function that are named sets. * * @param call Function call */ private void registerAliasArgs(FunCall call) { for (Exp exp : call.getArgs()) { registerAlias((QueryPart) call, exp); } } /** * Registers a named set if an expression is of the form "expr AS * alias". * * @param parent Parent node * @param exp Expression that may be an "AS" */ private void registerAlias(QueryPart parent, Exp exp) { if (exp instanceof FunCall) { FunCall call2 = (FunCall) exp; if (call2.getSyntax() == Syntax.Infix && call2.getFunName().equals("AS")) { // Scope is the function enclosing the 'AS' expression. // For example, in // Filter(Time.Children AS s, x > y) // the scope of the set 's' is the Filter function. assert call2.getArgCount() == 2; if (call2.getArg(1) instanceof Id) { final Id id = (Id) call2.getArg(1); createScopedNamedSet( id.getSegments().get(0).name, parent, call2.getArg(0)); } else if (call2.getArg(1) instanceof NamedSetExpr) { NamedSetExpr set = (NamedSetExpr) call2.getArg(1); createScopedNamedSet( set.getNamedSet().getName(), parent, call2.getArg(0)); } } } } } } // End Query.java mondrian-3.4.1/src/main/mondrian/olap/OlapElement.java0000644000175000017500000000517211735330606022560 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1998-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import java.util.Locale; /** * An OlapElement is a catalog object (dimension, hierarchy, * level, member). * * @author jhyde, 21 January, 1999 */ public interface OlapElement { String getUniqueName(); String getName(); String getDescription(); /** * Looks up a child element, returning null if it does not exist. */ OlapElement lookupChild( SchemaReader schemaReader, Id.Segment s, MatchType matchType); /** * Returns the name of this element qualified by its class, for example * "hierarchy 'Customers'". */ String getQualifiedName(); String getCaption(); /** * Returns the value of a property (caption or description) of * this element in the given locale. * * @param locale Locale * @return Localized caption or description */ String getLocalized(LocalizedProperty prop, Locale locale); Hierarchy getHierarchy(); /** * Returns the dimension of a this expression, or null if no dimension is * defined. Applicable only to set expressions. * *

    Example 1: *

         * [Sales].children
         * 
    * has dimension [Sales].

    * *

    Example 2: *

         * order(except([Promotion Media].[Media Type].members,
         *              {[Promotion Media].[Media Type].[No Media]}),
         *       [Measures].[Unit Sales], DESC)
         * 
    * has dimension [Promotion Media].

    * *

    Example 3: *

         * CrossJoin([Product].[Product Department].members,
         *           [Gender].members)
         * 
    * has no dimension (well, actually it is [Product] x [Gender], but we * can't represent that, so we return null);

    */ Dimension getDimension(); /** * Returns whether this element is visible to end-users. * *

    Visibility is a hint for client applications. An element's visibility * does not affect how it is treated when MDX queries are evaluated. * * @return Whether this element is visible */ boolean isVisible(); enum LocalizedProperty { CAPTION, DESCRIPTION } } // End OlapElement.java mondrian-3.4.1/src/main/mondrian/olap/Dimension.java0000644000175000017500000000311511735330606022273 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1999-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * A Dimension represents a dimension of a cube. * * @author jhyde, 1 March, 1999 */ public interface Dimension extends OlapElement, Annotated { final String MEASURES_UNIQUE_NAME = "[Measures]"; final String MEASURES_NAME = "Measures"; /** * Returns an array of the hierarchies which belong to this dimension. */ Hierarchy[] getHierarchies(); /** * Returns whether this is the [Measures] dimension. */ boolean isMeasures(); /** * Returns the type of this dimension * ({@link DimensionType#StandardDimension} or * {@link DimensionType#TimeDimension} */ DimensionType getDimensionType(); /** * Returns the schema this dimension belongs to. */ Schema getSchema(); /** * Returns whether the dimension should be considered as a "high * cardinality" or "low cardinality" according to cube definition. * * Mondrian tends to evaluate high cardinality dimensions using * iterators rather than lists, avoiding instantiating the dimension in * memory. * * @return whether this dimension is high-cardinality */ boolean isHighCardinality(); } // End Dimension.java mondrian-3.4.1/src/main/mondrian/olap/Hierarchy.java0000644000175000017500000000454611735330606022275 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1999-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * A Hierarchy is a set of members, organized into levels. */ public interface Hierarchy extends OlapElement, Annotated { /** * Returns the dimension this hierarchy belongs to. */ Dimension getDimension(); /** * Returns the levels in this hierarchy. * *

    If a hierarchy is subject to access-control, some of the levels may * not be visible; use {@link SchemaReader#getHierarchyLevels} instead. * * @post return != null */ Level[] getLevels(); /** * Returns the default member of this hierarchy. * *

    If a hierarchy is subject to access-control, the default member may * not be visible, so use {@link SchemaReader#getHierarchyDefaultMember}. * * @post return != null */ Member getDefaultMember(); /** * Returns the "All" member of this hierarchy. * * @post return != null */ Member getAllMember(); /** * Returns a special member representing the "null" value. This never * occurs on an axis, but may occur if functions such as Lead, * NextMember and ParentMember walk off the end * of the hierarchy. * * @post return != null */ Member getNullMember(); boolean hasAll(); /** * Creates a member of this hierarchy. If this is the measures hierarchy, a * calculated member is created, and formula must not be null. */ Member createMember( Member parent, Level level, String name, Formula formula); /** * Returns the unique name of this hierarchy, always including the dimension * name, e.g. "[Time].[Time]", regardless of whether * {@link MondrianProperties#SsasCompatibleNaming} is enabled. * * @deprecated Will be removed in mondrian-4.0, when * {@link #getUniqueName()} will have this behavior. * * @return Unique name of hierarchy. */ String getUniqueNameSsas(); } // End Hierarchy.java mondrian-3.4.1/src/main/mondrian/olap/ExpBase.java0000644000175000017500000000315211735330606021676 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1999-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.calc.Calc; import mondrian.calc.ExpCompiler; import java.io.PrintWriter; /** * Skeleton implementation of {@link Exp} interface. * * @author jhyde, 20 January, 1999 */ public abstract class ExpBase extends QueryPart implements Exp { protected static Exp[] cloneArray(Exp[] a) { Exp[] a2 = new Exp[a.length]; for (int i = 0; i < a.length; i++) { a2[i] = a[i].clone(); } return a2; } protected ExpBase() { } public abstract Exp clone(); public static void unparseList( PrintWriter pw, Exp[] exps, String start, String mid, String end) { pw.print(start); for (int i = 0; i < exps.length; i++) { if (i > 0) { pw.print(mid); } exps[i].unparse(pw); } pw.print(end); } public static int[] getTypes(Exp[] exps) { int[] types = new int[exps.length]; for (int i = 0; i < exps.length; i++) { types[i] = exps[i].getCategory(); } return types; } public Calc accept(ExpCompiler compiler) { throw new UnsupportedOperationException(this.toString()); } } // End ExpBase.java mondrian-3.4.1/src/main/mondrian/olap/SetBase.java0000644000175000017500000000714311735330606021701 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.olap.type.*; import org.apache.log4j.Logger; import java.util.Map; /** * Skeleton implementation of {@link NamedSet} interface. * * @author jhyde * @since 6 August, 2001 */ public class SetBase extends OlapElementBase implements NamedSet { private static final Logger LOGGER = Logger.getLogger(SetBase.class); private String name; private Map annotationMap; private String description; private final String uniqueName; private Exp exp; private boolean validated; /** * Creates a SetBase. * * @param name Name * @param caption Caption * @param description Description * @param exp Expression * @param validated Whether has been validated * @param annotationMap Annotations */ SetBase( String name, String caption, String description, Exp exp, boolean validated, Map annotationMap) { this.name = name; this.annotationMap = annotationMap; this.caption = caption; this.description = description; this.exp = exp; this.validated = validated; this.uniqueName = "[" + name + "]"; } public Map getAnnotationMap() { return annotationMap; } public String getNameUniqueWithinQuery() { return System.identityHashCode(this) + ""; } public boolean isDynamic() { return false; } public Object clone() { return new SetBase( name, caption, description, exp.clone(), validated, annotationMap); } protected Logger getLogger() { return LOGGER; } public String getUniqueName() { return uniqueName; } public String getName() { return name; } public String getQualifiedName() { return null; } public String getDescription() { return description; } public Hierarchy getHierarchy() { return exp.getType().getHierarchy(); } public Dimension getDimension() { return getHierarchy().getDimension(); } public OlapElement lookupChild( SchemaReader schemaReader, Id.Segment s, MatchType matchType) { return null; } public void setName(String name) { this.name = name; } public void setDescription(String description) { this.description = description; } public void setAnnotationMap(Map annotationMap) { this.annotationMap = annotationMap; } public Exp getExp() { return exp; } public NamedSet validate(Validator validator) { if (!validated) { exp = validator.validate(exp, false); validated = true; } return this; } public Type getType() { Type type = exp.getType(); if (type instanceof MemberType || type instanceof TupleType) { // You can use a member or tuple as the expression for a set. It is // implicitly converted to a set. The expression may not have been // converted yet, so we wrap the type here. type = new SetType(type); } return type; } } // End SetBase.java mondrian-3.4.1/src/main/mondrian/olap/StringScanner.java0000644000175000017500000000153211735330606023127 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1998-2005 Julian Hyde // Copyright (C) 2005-2005 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * Lexical analyzer whose input is a string. * * @author jhyde, 20 January, 1999 */ public class StringScanner extends Scanner { private final String s; private int i; public StringScanner(String s, boolean debug) { super(debug); this.s = s; i = 0; } // Override Scanner.getChar(). protected int getChar() { return (i >= s.length()) ? -1 : s.charAt(i++); } } // End StringScanner.java mondrian-3.4.1/src/main/mondrian/olap/Aggregator.java0000644000175000017500000000474011735330606022435 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho // All Rights Reserved. */ package mondrian.olap; import mondrian.calc.Calc; import mondrian.calc.TupleList; import mondrian.spi.Dialect.Datatype; import mondrian.spi.SegmentBody; import java.util.List; /** * Describes an aggregation operator, such as "sum" or "count". * * @see FunDef * @see Evaluator * * @author jhyde$ * @since Jul 9, 2003$ */ public interface Aggregator { /** * Returns the aggregator used to combine sub-totals into a grand-total. * * @return aggregator used to combine sub-totals into a grand-total */ Aggregator getRollup(); /** * Applies this aggregator to an expression over a set of members and * returns the result. * * @param evaluator Evaluation context * @param members List of members, not null * @param calc Expression to evaluate * * @return result of applying this aggregator to a set of members/tuples */ Object aggregate(Evaluator evaluator, TupleList members, Calc calc); /** * Tells Mondrian if this aggregator can perform fast aggregation * using only the raw data of a given object type. This will * determine if Mondrian will attempt to perform in-memory rollups * on raw segment data by invoking {@link #aggregate(java.util.List)}. * *

    This is only invoked for rollup operations. * * @param datatype The datatype of the object we would like to rollup. * @return Whether this aggregator supports fast aggregation */ boolean supportsFastAggregates(Datatype datatype); /** * Applies this aggregator over a raw list of objects for a rollup * operation. This is useful when the values are already resolved * and we are dealing with a raw {@link SegmentBody} object. * *

    Only gets called if * {@link #supportsFastAggregates(mondrian.spi.Dialect.Datatype)} is true. * *

    This is only invoked for rollup operations. * * @param rawData An array of values in its raw form, to be aggregated. * @return A rolled up value of the raw data. * if the object type is not supported. */ Object aggregate(List rawData); } // End Aggregator.java mondrian-3.4.1/src/main/mondrian/olap/Walker.java0000644000175000017500000002301511735330606021574 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1999-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. // // jhyde, 1 March, 1999 */ package mondrian.olap; import java.io.PrintWriter; import java.util.Enumeration; import java.util.Stack; /** * Walks over a tree, returning nodes in prefix order. Objects which are an * instance of {@link Walkable} supply their children using * getChildren(); other objects are assumed to have no children. * *

    If the tree is modified during the enumeration, strange things may happen. * *

    Example use:

     *    Tree t;
     *    Walker w = new Walker(t);
     *    while (w.hasMoreElements()) {
     *      Tree node = (Tree) w.nextNode();
     *      System.out.println(node.toString());
     *    }
     * 
    */ public class Walker implements Enumeration { /** * The active parts of the tree from the root to nextNode are held in a * stack. When the stack is empty, the enumeration finishes. currentFrame * holds the frame of the 'current node' (the node last returned from * nextElement()) because it may no longer be on the stack. */ private final Stack stack; private Frame currentFrame; private Object nextNode; private class Frame { Frame(Frame parent, Object node) { this.parent = parent; this.node = node; this.children = getChildren(node); this.childIndex = -1; // haven't visited first child yet } final Frame parent; final Object node; final Object[] children; int childIndex; } public Walker(Walkable root) { stack = new Stack(); currentFrame = null; visit(null, root); } private void moveToNext() { if (stack.empty()) { return; } currentFrame = (Frame) stack.peek(); // Unwind stack until we find a level we have not completed. do { Frame frame = (Frame) stack.peek(); if (frame.children != null && ++frame.childIndex < frame.children.length) { // Here is an unvisited child. Visit it. visit(frame, frame.children[frame.childIndex]); return; } stack.pop(); } while (!stack.empty()); nextNode = null; } private void visit(Frame parent, Object node) { nextNode = node; stack.addElement(new Frame(parent, node)); } public boolean hasMoreElements() { return nextNode != null; } public Object nextElement() { moveToNext(); return currentFrame.node; } /** Tell walker that we don't want to visit any (more) children of this * node. The next node visited will be (a return visit to) the node's * parent. Not valid until nextElement() has been called. */ public void prune() { if (currentFrame.children != null) { currentFrame.childIndex = currentFrame.children.length; } //we need to make that next frame on the stack is not a child //of frame we just pruned. if it is, we need to prune it too if (this.hasMoreElements()) { Object nextFrameParentNode = ((Frame)stack.peek()).parent.node; if (nextFrameParentNode != currentFrame.node) { return; } //delete the child of current member from the stack stack.pop(); if (currentFrame.parent != null) { currentFrame = currentFrame.parent; } nextElement(); } } public void pruneSiblings() { prune(); currentFrame = currentFrame.parent; if (currentFrame != null) { prune(); } } /** returns the current object. Not valid until nextElement() has been called. */ public Object currentElement() { return currentFrame.node; } /** returns level in the tree of the current element (that is, last element * returned from nextElement()). The level of the root element is 0. */ public int level() { int i = 0; for (Frame f = currentFrame; f != null; f = f.parent) { i++; } return i; } public final Object getParent() { return getAncestor(1); } public final Object getAncestor(int iDepth) { Frame f = getAncestorFrame(iDepth); return f == null ? null : f.node; } /** returns the iDepthth ancestor of the current element */ private Frame getAncestorFrame(int iDepth) { for (Frame f = currentFrame; f != null; f = f.parent) { if (iDepth-- == 0) { return f; } } return null; } /** get the ordinal within its parent node of the current node. Returns 0 for the root element. Equivalent to getAncestorOrdinal(0). */ public int getOrdinal() { // We can't use currentFrame.parent.iChild because moveToNext() may // have changed it. return currentFrame.parent == null ? 0 : arrayFind(currentFrame.parent.children, currentFrame.node); } /** get the ordinal within its parent node of the iDepthth * ancestor. */ public int getAncestorOrdinal(int iDepth) { Frame f = getAncestorFrame(iDepth); return f == null ? -1 : f.parent == null ? 0 : arrayFind(f.parent.children, f.node); } /** Override this function to prune the tree, or to allow objects which are * not Walkable to have children. */ public Object[] getChildren(Object node) { if (node instanceof Walkable) { return ((Walkable) node).getChildren(); } else { return null; } } private static int arrayFind(Object[] array, Object o) { for (int i = 0; i < array.length; i++) { if (array[i] == o) { return i; } } return -1; } private static class Region implements Walkable { String name; Region[] children; Region(String name, Region[] children) { this.name = name; this.children = children; } public Object[] getChildren() { return children; } public static void walkUntil(Walker walker, String name) { while (walker.hasMoreElements()) { Region region = (Region) walker.nextElement(); if (region.name.equals(name)) { break; } } } }; public static void main(String[] args) { PrintWriter pw = new PrintWriter(System.out); Region usa = new Region( "USA", new Region[] { new Region( "CA", new Region[] { new Region( "San Francisco", new Region[] { new Region( "WesternAddition", new Region[] { new Region("Haight", null)}), new Region("Soma", null) }), new Region("Los Angeles", null)}), new Region( "WA", new Region[] { new Region("Seattle", null), new Region("Tacoma", null)})}); Walker walker = new Walker(usa); if (false) { while (walker.hasMoreElements()) { Region region = (Region) walker.nextElement(); pw.println(region.name); pw.flush(); } } Region.walkUntil(walker, "CA"); walker.prune(); Region region = (Region) walker.nextElement(); // should be WA pw.println(region.name); pw.flush(); walker = new Walker(usa); Region.walkUntil(walker, "USA"); walker.prune(); region = (Region) walker.nextElement(); // should be null if (region == null) { pw.println("null"); } pw.flush(); walker = new Walker(usa); Region.walkUntil(walker, "Los Angeles"); walker.prune(); region = (Region) walker.nextElement(); // should be WA pw.println(region.name); pw.flush(); walker = new Walker(usa); Region.walkUntil(walker, "Haight"); walker.prune(); region = (Region) walker.nextElement(); // should be Soma pw.println(region.name); pw.flush(); walker = new Walker(usa); Region.walkUntil(walker, "Soma"); walker.prune(); region = (Region) walker.nextElement(); // should be Los Angeles pw.println(region.name); pw.flush(); walker = new Walker(usa); Region.walkUntil(walker, "CA"); walker.pruneSiblings(); region = (Region) walker.nextElement(); // should be Los Angeles if (region == null) { pw.println("null"); pw.flush(); } walker = new Walker(usa); Region.walkUntil(walker, "Soma"); walker.pruneSiblings(); region = (Region) walker.nextElement(); // should be Los Angeles if (region == null) { pw.println("null"); pw.flush(); } } } // End Walker.java mondrian-3.4.1/src/main/mondrian/olap/Category.java0000644000175000017500000001224211735330606022124 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho // All Rights Reserved. */ package mondrian.olap; /** * Category enumerates the possible expression types. * *

    Values of this enumeration are returned by {@link Exp#getCategory()}, * {@link FunDef#getParameterCategories()}, and * {@link FunDef#getReturnCategory()}. * *

    For modern code, the more descriptive type system * ({@link mondrian.olap.type.Type}) is preferred. * * @author jhyde * @since Feb 21, 2003 */ public class Category extends EnumeratedValues { /** * The singleton instance of Category. */ public static final Category instance = new Category(); private Category() { super( new String[] { "unknown", "array", "dimension", "hierarchy", "level", "logical", "member", "numeric", "set", "string", "tuple", "symbol", "cube", "value", "integer", "null", "empty", "datetime", }, new int[] { Unknown, Array, Dimension, Hierarchy, Level, Logical, Member, Numeric, Set, String, Tuple, Symbol, Cube, Value, Integer, Null, Empty, DateTime, }, new String[] { "Unknown", "Array", "Dimension", "Hierarchy", "Level", "Logical Expression", "Member", "Numeric Expression", "Set", "String", "Tuple", "Symbol", "Cube", "Value", "Integer", "Null", "Empty", "DateTime", } ); } /** * Returns the singleton instance of Category. * * @return the singleton instance */ public static Category instance() { return instance; } /** * Unknown is an expression whose type is as yet unknown. */ public static final int Unknown = 0; /** * Array is an expression of array type. */ public static final int Array = 1; /** * Dimension is a dimension expression. * @see Dimension */ public static final int Dimension = 2; /** * Hierarchy is a hierarchy expression. * @see Hierarchy */ public static final int Hierarchy = 3; /** * Level is a level expression. * @see Level */ public static final int Level = 4; /** * Logical is a boolean expression. */ public static final int Logical = 5; /** * Member is a member expression. * @see Member */ public static final int Member = 6; /** * Numeric is a numeric expression. */ public static final int Numeric = 7; /** * Set is a set of members or tuples. */ public static final int Set = 8; /** * String is a string expression. */ public static final int String = 9; /** * Tuple is a tuple expression. */ public static final int Tuple = 10; /** * Symbol is a symbol, for example the BASC * keyword to the Order() function. */ public static final int Symbol = 11; /** * Cube is a cube expression. * @see Cube */ public static final int Cube = 12; /** * Value is any expression yielding a string or numeric value. */ public static final int Value = 13; /** * Integer is an integer expression. This is a subtype of * {@link #Numeric}. */ public static final int Integer = 15; /** * Represents a Null value */ public static final int Null = 16; /** * Represents an empty expression. */ public static final int Empty = 17; /** * Represents a DataTime expression. */ public static final int DateTime = 18; /** * Expression is a flag which, when bitwise-OR-ed with a * category value, indicates an expression (as opposed to a constant). */ public static final int Expression = 0; /** Constant is a flag which, when bitwise-OR-ed with a * category value, indicates a constant (as opposed to an expression). */ public static final int Constant = 64; /** Mask is a mask to remove flags. */ public static final int Mask = 31; /** * Returns whether a category represents a scalar type. * * @param category Category * @return Whether is scalar */ public static boolean isScalar(int category) { switch (category & Mask) { case Value: case Logical: case Numeric: case Integer: case String: case DateTime: return true; default: return false; } } } // End Category.java mondrian-3.4.1/src/main/mondrian/olap/ResourceLimitExceededException.java0000644000175000017500000000142111735330606026440 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * Exception which indicates some resource limit was exceeded. */ public class ResourceLimitExceededException extends ResultLimitExceededException { /** * Creates a ResourceLimitExceededException * * @param message Localized message */ public ResourceLimitExceededException(String message) { super(message); } } // End ResourceLimitExceededException.java mondrian-3.4.1/src/main/mondrian/olap/PropertyFormatter.java0000644000175000017500000000131311735330606024054 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * SPI to redefine a member property display string. * * @deprecated Use {@link mondrian.spi.PropertyFormatter}. This interface * exists for temporary backwards compatibility and will be removed * in mondrian-4.0. */ public interface PropertyFormatter extends mondrian.spi.PropertyFormatter { } // End PropertyFormatter.java mondrian-3.4.1/src/main/mondrian/olap/Validator.java0000644000175000017500000000771111735330606022301 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho // All Rights Reserved. */ package mondrian.olap; import mondrian.mdx.ParameterExpr; import mondrian.olap.fun.Resolver; import mondrian.olap.type.Type; import java.util.List; /** * Provides context necessary to resolve identifiers to objects, function * calls to specific functions. * *

    An expression calls {@link #validate} on each of its children, * which in turn calls {@link Exp#accept}. * * @author jhyde */ public interface Validator { /** * Returns the {@link Query} which is being validated. */ Query getQuery(); /** * Validates an expression, and returns the expression it resolves to. * * @param exp Expression to validate * @param scalar Whether the context requires that the expression is * evaluated to a value, as opposed to a tuple */ Exp validate(Exp exp, boolean scalar); /** * Validates a usage of a parameter. * *

    It must resolve to the same object (although sub-objects may change). */ void validate(ParameterExpr parameterExpr); /** * Validates a child member property. * *

    It must resolve to the same object (although sub-objects may change). */ void validate(MemberProperty memberProperty); /** * Validates an axis. * * It must resolve to the same object (although sub-objects may change). */ void validate(QueryAxis axis); /** * Validates a formula. * * It must resolve to the same object (although sub-objects may change). */ void validate(Formula formula); /** * Returns whether the current context requires an expression. */ boolean requiresExpression(); /** * Returns whether we can convert an argument to a parameter type. * * @param ordinal argument ordinal * @param fromExp argument type * @param to parameter type * @param conversions List of conversions performed; * method adds an element for each non-trivial conversion (for * example, converting a member to a level). * @return Whether we can convert an argument to a parameter type */ boolean canConvert( int ordinal, Exp fromExp, int to, List conversions); /** * Returns the table of function and operator definitions. */ FunTable getFunTable(); /** * Creates or retrieves the parameter corresponding to a "Parameter" or * "ParamRef" function call. */ Parameter createOrLookupParam( boolean definition, String name, Type type, Exp defaultExp, String description); /** * Resolves a function call to a particular function. If the function is * overloaded, returns as precise a match to the argument types as * possible. */ FunDef getDef( Exp[] args, String name, Syntax syntax); /** * Whether to resolve function name and arguments to a function definition * each time a node is validated, not just the first time. * *

    Default implementation returns {@code false}. * * @return whether to resolve function each time */ boolean alwaysResolveFunDef(); /** * Returns the schema reader with which to resolve names of MDX objects * (dimensions, hierarchies, levels, members, named sets). * *

    The schema reader is initially in the context of the query's cube, * and during a traversal it may change if named sets are introduced using * the 'expr AS alias' construct. * * @return Schema reader */ SchemaReader getSchemaReader(); } // End Validator.java mondrian-3.4.1/src/main/mondrian/olap/Role.java0000644000175000017500000001272311735330606021254 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2002-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. // // jhyde, Oct 5, 2002 */ package mondrian.olap; /** * A Role is a collection of access rights to cubes, permissions, * and so forth. * *

    At present, the only way to create a role is programmatically. You then * add appropriate permissions, and associate the role with a connection. * Queries executed for the duration of the connection will b. * *

    Mondrian does not have any notion of a 'user'. It is the client * application's responsibility to create a role appropriate for the user who * is establishing the connection. * * @author jhyde * @since Oct 5, 2002 */ public interface Role { /** * Returns the access this role has to a given schema. * * @pre schema != null * @post return == Access.ALL * || return == Access.NONE * || return == Access.ALL_DIMENSIONS */ Access getAccess(Schema schema); /** * Returns the access this role has to a given cube. * * @pre cube != null * @post return == Access.ALL || return == Access.NONE */ Access getAccess(Cube cube); /** * Represents the access that a role has to a particular hierarchy. */ public interface HierarchyAccess { /** * Returns the access the current role has to a given member. * *

    Visibility is:

      *
    • {@link Access#NONE} if member is not visible, *
    • {@link Access#ALL} if member and all children are visible, *
    • {@link Access#CUSTOM} if some of the children are not visible. *

    * *

    For these purposes, children which are below the bottom level are * regarded as visible.

    * * @param member Member * @return current role's access to member */ Access getAccess(Member member); /** * Returns the depth of the highest level to which the current Role has * access. The 'all' level, if present, has a depth of zero. * * @return depth of the highest accessible level */ int getTopLevelDepth(); /** * Returns the depth of the lowest level to which the current Role has * access. The 'all' level, if present, has a depth of zero. * * @return depth of the lowest accessible level */ int getBottomLevelDepth(); /** * Returns the policy by which cell values are calculated if not all * of a member's children are visible. * * @return rollup policy */ RollupPolicy getRollupPolicy(); /** * Returns true if at least one of the descendants of the * given Member is inaccessible to this Role. * *

    Descendants which are inaccessible because they are below the * bottom level are ignored. * * @param member Member * @return whether a descendant is inaccessible */ boolean hasInaccessibleDescendants(Member member); } /** * Returns the access this role has to a given dimension. * * @pre dimension != null * @post Access.instance().isValid(return) */ Access getAccess(Dimension dimension); /** * Returns the access this role has to a given hierarchy. * * @pre hierarchy != null * @post return == Access.NONE * || return == Access.ALL * || return == Access.CUSTOM */ Access getAccess(Hierarchy hierarchy); /** * Returns the details of this hierarchy's access, or null if the hierarchy * has not been given explicit access. * * @pre hierarchy != null */ HierarchyAccess getAccessDetails(Hierarchy hierarchy); /** * Returns the access this role has to a given level. * * @pre level != null * @post Access.instance().isValid(return) */ Access getAccess(Level level); /** * Returns the access this role has to a given member. * * @pre member != null * @pre isMutable() * @post return == Access.NONE * || return == Access.ALL * || return == Access.CUSTOM */ Access getAccess(Member member); /** * Returns the access this role has to a given named set. * * @pre set != null * @pre isMutable() * @post return == Access.NONE || return == Access.ALL */ Access getAccess(NamedSet set); /** * Returns whether this role is allowed to see a given element. * @pre olapElement != null */ boolean canAccess(OlapElement olapElement); /** * Enumeration of the policies by which a cell is calculated if children * of a member are not accessible. */ enum RollupPolicy { /** * The value of the cell is null if any of the children are * inaccessible. */ HIDDEN, /** * The value of the cell is obtained by rolling up the values of * accessible children. */ PARTIAL, /** * The value of the cell is obtained by rolling up the values of all * children. */ FULL, } } // End Role.java mondrian-3.4.1/src/main/mondrian/olap/OlapElementBase.java0000644000175000017500000000737411735330606023361 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import org.apache.log4j.Logger; import java.util.Locale; import java.util.Map; /** * OlapElementBase is an abstract base class for implementations of * {@link OlapElement}. * * @author jhyde * @since 6 August, 2001 */ public abstract class OlapElementBase implements OlapElement { protected String caption = null; protected boolean visible = true; // cache hash-code because it is often used and elements are immutable private int hash; protected OlapElementBase() { } protected abstract Logger getLogger(); public boolean equals(Object o) { return (o == this) || ((o instanceof OlapElement) && equals((OlapElement) o)); } public boolean equals(OlapElement mdxElement) { return mdxElement != null && getClass() == mdxElement.getClass() && getUniqueName().equalsIgnoreCase(mdxElement.getUniqueName()); } public int hashCode() { if (hash == 0) { hash = computeHashCode(); } return hash; } /** * Computes this object's hash code. Called at most once. * * @return hash code */ protected int computeHashCode() { return (getClass().hashCode() << 8) ^ getUniqueName().hashCode(); } public String toString() { return getUniqueName(); } public Object clone() { return this; } /** * Returns the display name of this catalog element. * If no caption is defined, the name is returned. */ public String getCaption() { if (caption != null) { return caption; } else { return getName(); } } /** * Sets the display name of this catalog element. */ public void setCaption(String caption) { this.caption = caption; } public boolean isVisible() { return visible; } public String getLocalized(LocalizedProperty prop, Locale locale) { if (this instanceof Annotated) { Annotated annotated = (Annotated) this; final Map annotationMap = annotated.getAnnotationMap(); if (!annotationMap.isEmpty()) { String seek = prop.name().toLowerCase() + "." + locale; for (;;) { for (Map.Entry entry : annotationMap.entrySet()) { if (entry.getKey().startsWith(seek)) { return entry.getValue().getValue().toString(); } } // No match for locale. Is there a match for the parent // locale? For example, we've just looked for // 'caption.en_US', now look for 'caption.en'. final int underscore = seek.lastIndexOf('_'); if (underscore < 0) { break; } seek = seek.substring(0, underscore - 1); } } } // No annotation. Fall back to the default caption/description. switch (prop) { case CAPTION: return getCaption(); case DESCRIPTION: return getDescription(); default: throw Util.unexpected(prop); } } } // End OlapElementBase.java mondrian-3.4.1/src/main/mondrian/olap/Syntax.java0000644000175000017500000002546411735330606021647 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho // All Rights Reserved. */ package mondrian.olap; import java.io.PrintWriter; /** * Enumerated values describing the syntax of an expression. * * @author jhyde * @since 21 July, 2003 */ public enum Syntax { /** * Defines syntax for expression invoked FUNCTION() or * FUNCTION(args). */ Function { public void unparse(String fun, Exp[] args, PrintWriter pw) { ExpBase.unparseList(pw, args, fun + "(", ", ", ")"); } }, /** * Defines syntax for expression invoked as object.PROPERTY. */ Property { public void unparse(String fun, Exp[] args, PrintWriter pw) { Util.assertTrue(args.length >= 1); args[0].unparse(pw); // 'this' pw.print("."); pw.print(fun); } public String getSignature( String name, int returnType, int[] argTypes) { // e.g. ".Current" return getTypeDescription(argTypes[0]) + "." + name; } }, /** * Defines syntax for expression invoked invoked as * object.METHOD() or * object.METHOD(args). */ Method { public void unparse(String fun, Exp[] args, PrintWriter pw) { Util.assertTrue(args.length >= 1); args[0].unparse(pw); // 'this' pw.print("."); pw.print(fun); pw.print("("); for (int i = 1; i < args.length; i++) { if (i > 1) { pw.print(", "); } args[i].unparse(pw); } pw.print(")"); } public String getSignature(String name, int returnType, int[] argTypes) { // e.g. ".Lead()" return (returnType == Category.Unknown ? "" : getTypeDescription(returnType) + " ") + getTypeDescription(argTypes[0]) + "." + name + "(" + getTypeDescriptionCommaList(argTypes, 1) + ")"; } }, /** * Defines syntax for expression invoked as arg OPERATOR arg * (like '+' or 'AND'). */ Infix { public void unparse(String fun, Exp[] args, PrintWriter pw) { if (needParen(args)) { ExpBase.unparseList(pw, args, "(", " " + fun + " ", ")"); } else { ExpBase.unparseList(pw, args, "", " " + fun + " ", ""); } } public String getSignature(String name, int returnType, int[] argTypes) { // e.g. " / " return getTypeDescription(argTypes[0]) + " " + name + " " + getTypeDescription(argTypes[1]); } }, /** * Defines syntax for expression invoked as OPERATOR arg * (like unary '-'). */ Prefix { public void unparse(String fun, Exp[] args, PrintWriter pw) { if (needParen(args)) { ExpBase.unparseList(pw, args, "(" + fun + " ", null, ")"); } else { ExpBase.unparseList(pw, args, fun + " ", null, ""); } } public String getSignature(String name, int returnType, int[] argTypes) { // e.g. "- " return name + " " + getTypeDescription(argTypes[0]); } }, /** * Defines syntax for expression invoked as arg OPERATOR * (like IS EMPTY). */ Postfix { public void unparse(String fun, Exp[] args, PrintWriter pw) { if (needParen(args)) { ExpBase.unparseList(pw, args, "(", null, " " + fun + ")"); } else { ExpBase.unparseList(pw, args, "", null, " " + fun); } } public String getSignature(String name, int returnType, int[] argTypes) { // e.g. " IS NULL" return getTypeDescription(argTypes[0]) + " " + name; } }, /** * Defines syntax for expression invoked as * {ARG, ...}; that * is, the set construction operator. */ Braces { public String getSignature(String name, int returnType, int[] argTypes) { return "{" + getTypeDescriptionCommaList(argTypes, 0) + "}"; } public void unparse(String fun, Exp[] args, PrintWriter pw) { ExpBase.unparseList(pw, args, "{", ", ", "}"); } }, /** * Defines syntax for expression invoked as (ARG) or * (ARG, ...); that is, parentheses for grouping * expressions, and the tuple construction operator. */ Parentheses { public String getSignature(String name, int returnType, int[] argTypes) { return "(" + getTypeDescriptionCommaList(argTypes, 0) + ")"; } public void unparse(String fun, Exp[] args, PrintWriter pw) { ExpBase.unparseList(pw, args, "(", ", ", ")"); } }, /** * Defines syntax for expression invoked as CASE ... END. */ Case { public void unparse(String fun, Exp[] args, PrintWriter pw) { if (fun.equals("_CaseTest")) { pw.print("CASE"); int j = 0; int clauseCount = (args.length - j) / 2; for (int i = 0; i < clauseCount; i++) { pw.print(" WHEN "); args[j++].unparse(pw); pw.print(" THEN "); args[j++].unparse(pw); } if (j < args.length) { pw.print(" ELSE "); args[j++].unparse(pw); } Util.assertTrue(j == args.length); pw.print(" END"); } else { Util.assertTrue(fun.equals("_CaseMatch")); pw.print("CASE "); int j = 0; args[j++].unparse(pw); int clauseCount = (args.length - j) / 2; for (int i = 0; i < clauseCount; i++) { pw.print(" WHEN "); args[j++].unparse(pw); pw.print(" THEN "); args[j++].unparse(pw); } if (j < args.length) { pw.print(" ELSE "); args[j++].unparse(pw); } Util.assertTrue(j == args.length); pw.print(" END"); } } public String getSignature(String name, int returnType, int[] argTypes) { String s = getTypeDescription(argTypes[0]); if (argTypes[0] == Category.Logical) { return "CASE WHEN " + s + " THEN ... END"; } else { return "CASE " + s + " WHEN " + s + " THEN ... END"; } } }, /** * Defines syntax for expression generated by the Mondrian system which * cannot be specified syntactically. */ Internal, /** * Defines syntax for a CAST expression * CAST(expression AS type). */ Cast { 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( AS )"; } }, /** * Defines syntax for expression invoked object.&PROPERTY * (a variant of {@link #Property}). */ QuotedProperty, /** * Defines syntax for expression invoked object.[&PROPERTY] * (a variant of {@link #Property}). */ AmpersandQuotedProperty, /** * Defines the syntax for an empty expression. Empty expressions can occur * within function calls, and are denoted by a pair of commas with only * whitespace between them, for example * *

    * DrillDownLevelTop({[Product].[All Products]}, 3, , * [Measures].[Unit Sales]) *
    */ Empty { public void unparse(String fun, Exp[] args, PrintWriter pw) { assert args.length == 0; } public String getSignature(String name, int returnType, int[] argTypes) { return ""; }}; /** * Converts a call to a function of this syntax into source code. * * @param fun Function name * @param args Arguments to the function * @param pw Writer */ public void unparse(String fun, Exp[] args, PrintWriter pw) { throw new UnsupportedOperationException(); } /** * Returns a description of the signature of a function call, for * example, "CoalesceEmpty(, )". * * @param name Function name * @param returnType Function's return category * @param argTypes Categories of the function's arguments * @return Function signature */ public String getSignature(String name, int returnType, int[] argTypes) { // e.g. "StripCalculatedMembers()" return (returnType == Category.Unknown ? "" : getTypeDescription(returnType) + " ") + name + "(" + getTypeDescriptionCommaList(argTypes, 0) + ")"; } private static boolean needParen(Exp[] args) { return !(args.length == 1 && args[0] instanceof FunCall && ((FunCall) args[0]).getSyntax() == Syntax.Parentheses); } private static String getTypeDescription(int type) { return "<" + Category.instance.getDescription(type & Category.Mask) + ">"; } private static String getTypeDescriptionCommaList(int[] types, int start) { int initialSize = (types.length - start) * 16; StringBuilder sb = new StringBuilder(initialSize > 0 ? initialSize : 16); for (int i = start; i < types.length; i++) { if (i > start) { sb.append(", "); } sb.append("<") .append( Category.instance.getDescription(types[i] & Category.Mask)) .append(">"); } return sb.toString(); } } // End Syntax.java mondrian-3.4.1/src/main/mondrian/olap/DimensionType.java0000644000175000017500000000154011735330606023135 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 Galt Johnson // Copyright (C) 2004-2005 Julian Hyde // Copyright (C) 2005-2007 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * Enumerates the types of dimensions. * * @author Galt Johnson * @since 5 April, 2004 */ public enum DimensionType { /** * Indicates that the dimension is not related to time. */ StandardDimension, /** * Indicates that a dimension is a time dimension. */ TimeDimension, /** * Indicates the a dimension is the measures dimension. */ MeasuresDimension, } // End DimensionType.java mondrian-3.4.1/src/main/mondrian/olap/InvalidArgumentException.java0000644000175000017500000000142711735330606025322 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2007 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * Exception which indicates that an argument is invalid * * @author Thiyagu * @since April 5, 2007 */ public class InvalidArgumentException extends MondrianException { /** * Creates a InvalidArgumentException. * * @param message Localized error message */ public InvalidArgumentException(String message) { super(message); } } // End InvalidArgumentException.javamondrian-3.4.1/src/main/mondrian/olap/Schema.java0000644000175000017500000000504111735330606021546 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2010 Pentaho // All Rights Reserved. */ package mondrian.olap; import java.util.Date; import java.util.List; /** * A Schema is a collection of cubes, shared dimensions, and roles. * * @author jhyde */ public interface Schema extends Annotated { /** * Returns the name of this schema. * @post return != null * @post return.length() > 0 */ String getName(); /** * Returns the uniquely generated id of this schema. */ String getId(); /** * Finds a cube called cube in this schema; if no cube * exists, failIfNotFound controls whether to raise an error * or return null. */ Cube lookupCube(String cube, boolean failIfNotFound); /** * Returns a list of all cubes in this schema. */ Cube[] getCubes(); /** * Returns a list of shared dimensions in this schema. */ Hierarchy[] getSharedHierarchies(); /** * Creates a dimension in the given cube by parsing an XML string. The XML * string must be either a <Dimension> or a <DimensionUsage>. * Returns the dimension created. */ Dimension createDimension(Cube cube, String xml); /** * Creates a cube by parsing an XML string. Returns the cube created. */ Cube createCube(String xml); /** * Removes a cube. * * @return Whether cube was removed */ boolean removeCube(String cubeName); /** * Creates a {@link SchemaReader} without any access control. */ SchemaReader getSchemaReader(); /** * Finds a role with a given name in the current catalog, or returns * null if no such role exists. */ Role lookupRole(String role); /** * Returns this schema's function table. */ FunTable getFunTable(); /** * Returns this schema's parameters. */ Parameter[] getParameters(); /** * Returns when this schema was last loaded. * * @return Date and time when this schema was last loaded */ Date getSchemaLoadDate(); /** * Returns a list of warnings and errors that occurred while loading this * schema. * * @return list of warnings */ List getWarnings(); } // End Schema.java mondrian-3.4.1/src/main/mondrian/olap/DelegatingRole.java0000644000175000017500000000401011735330606023226 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * DelegatingRole implements {@link Role} by * delegating all methods to an underlying {@link Role}. * *

    It is a convenient base class if you want to override just a few of * {@link Role}'s methods. * * @author Richard M. Emberson * @since Mar 29 2007 */ public class DelegatingRole implements Role { protected final Role role; /** * Creates a DelegatingRole. * * @param role Underlying role */ public DelegatingRole(Role role) { assert role != null; this.role = role; } public Access getAccess(Schema schema) { return role.getAccess(schema); } public Access getAccess(Cube cube) { return role.getAccess(cube); } public Access getAccess(Dimension dimension) { return role.getAccess(dimension); } public Access getAccess(Hierarchy hierarchy) { return role.getAccess(hierarchy); } /** * {@inheritDoc} * *

    This implementation returns the same access as the underlying role. * Derived class may choose to refine access by creating a subclass of * {@link mondrian.olap.RoleImpl.DelegatingHierarchyAccess}. */ public HierarchyAccess getAccessDetails(Hierarchy hierarchy) { return role.getAccessDetails(hierarchy); } public Access getAccess(Level level) { return role.getAccess(level); } public Access getAccess(Member member) { return role.getAccess(member); } public Access getAccess(NamedSet set) { return role.getAccess(set); } public boolean canAccess(OlapElement olapElement) { return role.canAccess(olapElement); } } // End DelegatingRole.java mondrian-3.4.1/src/main/mondrian/olap/QueryAxis.java0000644000175000017500000002164711735330606022312 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1998-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.calc.*; import mondrian.mdx.*; import mondrian.olap.type.DimensionType; import mondrian.olap.type.*; import mondrian.resource.MondrianResource; import java.io.PrintWriter; /** * An axis in an MDX query. For example, the typical MDX query has two axes, * which appear as the "ON COLUMNS" and "ON ROWS" clauses. * * @author jhyde, 20 January, 1999 */ public class QueryAxis extends QueryPart { private boolean nonEmpty; private boolean ordered; private Exp exp; private final AxisOrdinal axisOrdinal; /** * Whether to show subtotals on this axis. * The "(show\hide)Subtotals" operation changes its valud. */ private SubtotalVisibility subtotalVisibility; private final Id[] dimensionProperties; /** * Creates an axis. * * @param nonEmpty Whether to filter out members of this axis whose cells * are all empty * @param set Expression to populate the axis * @param axisOrdinal Which axis (ROWS, COLUMNS, etc.) * @param subtotalVisibility Whether to show subtotals * @param dimensionProperties List of dimension properties */ public QueryAxis( boolean nonEmpty, Exp set, AxisOrdinal axisOrdinal, SubtotalVisibility subtotalVisibility, Id[] dimensionProperties) { assert dimensionProperties != null; assert axisOrdinal != null; this.nonEmpty = nonEmpty || (MondrianProperties.instance().EnableNonEmptyOnAllAxis.get() && !axisOrdinal.isFilter()); this.exp = set; this.axisOrdinal = axisOrdinal; this.subtotalVisibility = subtotalVisibility; this.dimensionProperties = dimensionProperties; this.ordered = false; } /** * Creates an axis with no dimension properties. * * @see #QueryAxis(boolean,Exp,AxisOrdinal,mondrian.olap.QueryAxis.SubtotalVisibility,Id[]) */ public QueryAxis( boolean nonEmpty, Exp set, AxisOrdinal axisOrdinal, SubtotalVisibility subtotalVisibility) { this(nonEmpty, set, axisOrdinal, subtotalVisibility, new Id[0]); } public Object clone() { return new QueryAxis( nonEmpty, exp.clone(), axisOrdinal, subtotalVisibility, dimensionProperties.clone()); } static QueryAxis[] cloneArray(QueryAxis[] a) { QueryAxis[] a2 = new QueryAxis[a.length]; for (int i = 0; i < a.length; i++) { a2[i] = (QueryAxis) a[i].clone(); } return a2; } public Object accept(MdxVisitor visitor) { final Object o = visitor.visit(this); if (visitor.shouldVisitChildren()) { // visit the expression which forms the axis exp.accept(visitor); } return o; } public Calc compile(ExpCompiler compiler, ResultStyle resultStyle) { Exp exp = this.exp; if (axisOrdinal.isFilter()) { exp = normalizeSlicerExpression(exp); exp = exp.accept(compiler.getValidator()); } switch (resultStyle) { case LIST: return compiler.compileList(exp, false); case MUTABLE_LIST: return compiler.compileList(exp, true); case ITERABLE: return compiler.compileIter(exp); default: throw Util.unexpected(resultStyle); } } private static Exp normalizeSlicerExpression(Exp exp) { Exp slicer = exp; if (slicer instanceof LevelExpr || slicer instanceof HierarchyExpr || slicer instanceof DimensionExpr) { slicer = new UnresolvedFunCall( "DefaultMember", Syntax.Property, new Exp[] { slicer}); } if (slicer == null) { ; } else if (slicer instanceof FunCall && ((FunCall) slicer).getSyntax() == Syntax.Parentheses) { slicer = new UnresolvedFunCall( "{}", Syntax.Braces, new Exp[] {slicer}); } else { slicer = new UnresolvedFunCall( "{}", Syntax.Braces, new Exp[] { new UnresolvedFunCall( "()", Syntax.Parentheses, new Exp[] { slicer})}); } return slicer; } public String getAxisName() { return axisOrdinal.name(); } /** * Returns the ordinal of this axis, for example * {@link mondrian.olap.AxisOrdinal.StandardAxisOrdinal#ROWS}. */ public AxisOrdinal getAxisOrdinal() { return axisOrdinal; } /** * Returns whether the axis has the NON EMPTY property set. */ public boolean isNonEmpty() { return nonEmpty; } /** * Sets whether the axis has the NON EMPTY property set. * See {@link #isNonEmpty()}. */ public void setNonEmpty(boolean nonEmpty) { this.nonEmpty = nonEmpty; } /** * Returns whether the axis has the ORDER property set. */ public boolean isOrdered() { return ordered; } /** * Sets whether the axis has the ORDER property set. */ public void setOrdered(boolean ordered) { this.ordered = ordered; } /** * Returns the expression which is used to compute the value of this axis. */ public Exp getSet() { return exp; } /** * Sets the expression which is used to compute the value of this axis. * See {@link #getSet()}. */ public void setSet(Exp set) { this.exp = set; } public void resolve(Validator validator) { exp = validator.validate(exp, false); final Type type = exp.getType(); if (!TypeUtil.isSet(type)) { // If expression is a member or a tuple, implicitly convert it // into a set. Dimensions and hierarchies can be converted to // members, thence to sets. if (type instanceof MemberType || type instanceof TupleType || type instanceof DimensionType || type instanceof HierarchyType) { exp = new UnresolvedFunCall( "{}", Syntax.Braces, new Exp[] {exp}); exp = validator.validate(exp, false); } else { throw MondrianResource.instance().MdxAxisIsNotSet.ex( axisOrdinal.name()); } } } public Object[] getChildren() { return new Object[] {exp}; } public void unparse(PrintWriter pw) { if (nonEmpty) { pw.print("NON EMPTY "); } if (exp != null) { exp.unparse(pw); } if (dimensionProperties.length > 0) { pw.print(" DIMENSION PROPERTIES "); for (int i = 0; i < dimensionProperties.length; i++) { Id dimensionProperty = dimensionProperties[i]; if (i > 0) { pw.print(", "); } dimensionProperty.unparse(pw); } } if (!axisOrdinal.isFilter()) { pw.print(" ON " + axisOrdinal.name()); } } public void addLevel(Level level) { Util.assertTrue(level != null, "addLevel needs level"); exp = new UnresolvedFunCall( "Crossjoin", Syntax.Function, new Exp[] { exp, new UnresolvedFunCall( "Members", Syntax.Property, new Exp[] { new LevelExpr(level)})}); } void setSubtotalVisibility(boolean bShowSubtotals) { subtotalVisibility = bShowSubtotals ? SubtotalVisibility.Show : SubtotalVisibility.Hide; } public SubtotalVisibility getSubtotalVisibility() { return subtotalVisibility; } public void resetSubtotalVisibility() { this.subtotalVisibility = SubtotalVisibility.Undefined; } public void validate(Validator validator) { if (axisOrdinal.isFilter()) { if (exp != null) { exp = validator.validate(exp, false); } } } public Id[] getDimensionProperties() { return dimensionProperties; } /** * SubtotalVisibility enumerates the allowed values of * whether subtotals are visible. */ public enum SubtotalVisibility { Undefined, Hide, Show; } } // End QueryAxis.java mondrian-3.4.1/src/main/mondrian/olap/SchemaReader.java0000644000175000017500000004106111735330606022673 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap; import mondrian.calc.Calc; import mondrian.rolap.RolapSchema; import java.util.List; import javax.sql.DataSource; /** * A SchemaReader queries schema objects ({@link Schema}, * {@link Cube}, {@link Dimension}, {@link Hierarchy}, {@link Level}, * {@link Member}). * *

    It is generally created using {@link Connection#getSchemaReader}, * but also via {@link Cube#getSchemaReader(Role)}.

    * *

    SchemaReader is deprecated for code outside of mondrian. For new code, * use the metadata provided by olap4j, for example * {@link mondrian.olap4j.MondrianOlap4jSchema#getCubes()}. * *

    If you use a SchemaReader from outside of a mondrian statement, you may * get a {@link java.util.EmptyStackException} indicating that mondrian cannot * deduce the current locus (statement context). If you get that error, call * {@link #withLocus()} to create a SchemaReader that automatically provides a * locus whenever a call is made.

    * * @author jhyde * @since Feb 24, 2003 */ public interface SchemaReader { /** * Returns the schema. * * @return Schema, never null */ RolapSchema getSchema(); /** * Returns the access-control profile that this SchemaReader * is implementing. */ Role getRole(); /** * Returns the accessible dimensions of a cube. * * @pre dimension != null * @post return != null */ List getCubeDimensions(Cube cube); /** * Returns the accessible hierarchies of a dimension. * * @pre dimension != null * @post return != null */ List getDimensionHierarchies(Dimension dimension); /** * Returns an array of the root members of hierarchy. * * @param hierarchy Hierarchy * @see #getCalculatedMembers(Hierarchy) */ List getHierarchyRootMembers(Hierarchy hierarchy); /** * Returns number of children parent of a member, * if the information can be retrieved from cache, otherwise -1. */ int getChildrenCountFromCache(Member member); /** * Returns the number of members in a level, returning an approximation if * acceptable. * * @param level Level * @param approximate Whether an approximation is acceptable * @param materialize Whether to go to disk if no approximation * for the count is available and the members are not in * cache. If false, returns {@link Integer#MIN_VALUE} if value * is not in cache. */ int getLevelCardinality( Level level, boolean approximate, boolean materialize); /** * Substitutes a member with an equivalent member which enforces the * access control policy of this SchemaReader. */ Member substitute(Member member); /** * Returns direct children of member. * @pre member != null * @post return != null */ List getMemberChildren(Member member); /** * Returns direct children of member, optimized * for NON EMPTY. *

    * If context == null then * there is no context and all members are returned - then * its identical to {@link #getMemberChildren(Member)}. * If context is not null, the resulting members * may be restricted to those members that have a * non empty row in the fact table for context. * Wether or not optimization is possible depends * on the SchemaReader implementation. */ List getMemberChildren(Member member, Evaluator context); /** * Returns direct children of each element of members. * * @param members Array of members * @return array of child members * * @pre members != null * @post return != null */ List getMemberChildren(List members); /** * Returns direct children of each element of members * which is not empty in context. * * @param members Array of members * @param context Evaluation context * @return array of child members * * @pre members != null * @post return != null */ List getMemberChildren(List members, Evaluator context); /** * Returns a list of contributing children of a member of a parent-child * hierarchy. * * @param dataMember Data member for a member of the parent-child hierarcy * @param hierarchy Hierarchy * @param list List of members to populate */ void getParentChildContributingChildren( Member dataMember, Hierarchy hierarchy, List list); /** * Returns the parent of member. * * @param member Member * @pre member != null * @return null if member is a root member */ Member getMemberParent(Member member); /** * Returns a list of ancestors of member, in depth order. * *

    For example, for [Store].[USA].[CA], returns * {[Store].[USA], [Store].[All Stores]}. * * @param member Member * @param ancestorList List of ancestors */ void getMemberAncestors(Member member, List ancestorList); /** * Returns the depth of a member. * *

    This may not be the same as * member.{@link Member#getLevel getLevel}(). * {@link Level#getDepth getDepth}() * for three reasons:

      *
    1. Access control. The most senior visible member has * level 0. If the client is not allowed to see the "All" and "Nation" * levels of the "Store" hierarchy, then members of the "State" level will * have depth 0.
    2. *
    3. Parent-child hierarchies. Suppose Fred reports to Wilma, and * Wilma reports to no one. "All Employees" has depth 0, Wilma has depth * 1, and Fred has depth 2. Fred and Wilma are both in the "Employees" * level, which has depth 1.
    4. *
    5. Ragged hierarchies. If Israel has only one, hidden, province * then the depth of Tel Aviv, Israel is 2, whereas the depth of another * city, San Francisco, CA, USA is 3.
    6. *
    */ int getMemberDepth(Member member); /** * Finds a member based upon its unique name. * * @param uniqueNameParts Unique name of member * @param failIfNotFound Whether to throw an error, as opposed to returning * null, if there is no such member. * @param matchType indicates the match mode; if not specified, EXACT * @return The member, or null if not found */ Member getMemberByUniqueName( List uniqueNameParts, boolean failIfNotFound, MatchType matchType); /** * Finds a member based upon its unique name, requiring an exact match. * *

    This method is equivalent to calling * {@link #getMemberByUniqueName(java.util.List, boolean, MatchType)} * with {@link MatchType#EXACT}. * * @param uniqueNameParts Unique name of member * @param failIfNotFound Whether to throw an error, as opposed to returning * null, if there is no such member. * @return The member, or null if not found */ Member getMemberByUniqueName( List uniqueNameParts, boolean failIfNotFound); /** * Looks up an MDX object by name, specifying how to * match if no object exactly matches the name. * *

    Resolves a name such as * '[Products].[Product Department].[Produce]' by resolving the * components ('Products', and so forth) one at a time. * * @param parent Parent element to search in * @param names Exploded compound name, such as {"Products", * "Product Department", "Produce"} * @param failIfNotFound If the element is not found, determines whether * to return null or throw an error * @param category Type of returned element, a {@link Category} value; * {@link Category#Unknown} if it doesn't matter. * @param matchType indicates the match mode; if not specified, EXACT * * @pre parent != null * @post !(failIfNotFound && return == null) */ OlapElement lookupCompound( OlapElement parent, List names, boolean failIfNotFound, int category, MatchType matchType); /** * Looks up an MDX object by name. * *

    Resolves a name such as * '[Products].[Product Department].[Produce]' by resolving the * components ('Products', and so forth) one at a time. * * @param parent Parent element to search in * @param names Exploded compound name, such as {"Products", * "Product Department", "Produce"} * @param failIfNotFound If the element is not found, determines whether * to return null or throw an error * @param category Type of returned element, a {@link Category} value; * {@link Category#Unknown} if it doesn't matter. * * @pre parent != null * @post !(failIfNotFound && return == null) */ OlapElement lookupCompound( OlapElement parent, List names, boolean failIfNotFound, int category); /** * Should only be called by implementations of * {@link #lookupCompound(OlapElement, java.util.List, boolean, int, MatchType)}. * * @param parent Parent element to search in * @param names Exploded compound name, such as {"Products", * "Product Department", "Produce"} * @param failIfNotFound If the element is not found, determines whether * to return null or throw an error * @param category Type of returned element, a {@link Category} value; * {@link Category#Unknown} if it doesn't matter. * @param matchType indicates the match mode; if not specified, EXACT * @return Found element OlapElement lookupCompoundInternal( OlapElement parent, List names, boolean failIfNotFound, int category, MatchType matchType); */ /** * Looks up a calculated member by name. If the name is not found in the * current scope, returns null. */ Member getCalculatedMember(List nameParts); /** * Looks up a set by name. If the name is not found in the current scope, * returns null. */ NamedSet getNamedSet(List nameParts); /** * Appends to list all members between startMember * and endMember (inclusive) which belong to * level. */ void getMemberRange( Level level, Member startMember, Member endMember, List list); /** * Returns a member n further along in the same level from * member. * * @pre member != null */ Member getLeadMember(Member member, int n); /** * Compares a pair of {@link Member}s according to their order in a prefix * traversal. (that is, it * is an ancestor or a earlier), is a sibling, or comes later in a prefix * traversal. * @return A negative integer if m1 is an ancestor, an earlier * sibling of an ancestor, or a descendent of an earlier sibling, of * m2; * zero if m1 is a sibling of m2; * a positive integer if m1 comes later in the prefix * traversal then m2. */ int compareMembersHierarchically(Member m1, Member m2); /** * Looks up the child of parent called name, or * an approximation according to matchType, returning * null if no element is found. * * @param parent Parent element to search in * @param name Compound in compound name, such as "[Product]" or "&[1]" * @param matchType Match type * * @return Element with given name, or null */ OlapElement getElementChild( OlapElement parent, Id.Segment name, MatchType matchType); /** * Looks up the child of parent name, returning * null if no element is found. * *

    Always equivalent to * getElementChild(parent, name, MatchType.EXACT). * * @param parent Parent element to search in * @param name Compound in compound name, such as "[Product]" or "&[1]" * * @return Element with given name, or null */ OlapElement getElementChild( OlapElement parent, Id.Segment name); /** * Returns the members of a level, optionally including calculated members. */ List getLevelMembers( Level level, boolean includeCalculated); /** * Returns the members of a level, optionally filtering out members which * are empty. * * @param level Level * @param context Context for filtering * @return Members of this level */ List getLevelMembers( Level level, Evaluator context); /** * Returns the accessible levels of a hierarchy. * * @param hierarchy Hierarchy * * @pre hierarchy != null * @post return.length >= 1 */ List getHierarchyLevels(Hierarchy hierarchy); /** * Returns the default member of a hierarchy. If the default member is in * an inaccessible level, returns the nearest ascendant/descendant member. * * @param hierarchy Hierarchy * * @return Default member of hierarchy */ Member getHierarchyDefaultMember(Hierarchy hierarchy); /** * Returns whether a member has visible children. */ boolean isDrillable(Member member); /** * Returns whether a member is visible. */ boolean isVisible(Member member); /** * Returns the list of accessible cubes. */ Cube[] getCubes(); /** * Returns a list of calculated members in a given hierarchy. */ List getCalculatedMembers(Hierarchy hierarchy); /** * Returns a list of calculated members in a given level. */ List getCalculatedMembers(Level level); /** * Returns the list of calculated members. */ List getCalculatedMembers(); /** * Finds a child of a member with a given name. */ Member lookupMemberChildByName( Member parent, Id.Segment childName, MatchType matchType); /** * Returns an object which can evaluate an expression in native SQL, or * null if this is not possible. * * @param fun Function * @param args Arguments to the function * @param evaluator Evaluator, provides context * @param calc */ NativeEvaluator getNativeSetEvaluator( FunDef fun, Exp[] args, Evaluator evaluator, Calc calc); /** * Returns the definition of a parameter with a given name, or null if not * found. */ Parameter getParameter(String name); /** * Returns the data source. * * @return data source */ DataSource getDataSource(); /** * Returns a similar schema reader that has no access control. * * @return Schema reader that has a similar perspective (e.g. cube) but * no access control */ SchemaReader withoutAccessControl(); /** * Returns the default cube in which to look for dimensions etc. * * @return Default cube */ Cube getCube(); /** * Returns a schema reader that automatically assigns a locus to each * operation. * *

    It is less efficient; use this only if the operation is occurring * outside the context of a statement. If you get the internal error * "no locus", that's a sign you should use this method.

    * * @return Schema reader that assigns a locus to each operation */ SchemaReader withLocus(); /** * Returns a list of namespaces to search when resolving elements by name. * *

    For example, a schema reader from the perspective of a cube will * return cube and schema namespaces.

    * * @return List of namespaces */ List getNamespaces(); } // End SchemaReader.java mondrian-3.4.1/src/main/mondrian/olap/DrillThrough.java0000644000175000017500000000422011735330606022753 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2010 Pentaho // All Rights Reserved. */ package mondrian.olap; import java.io.PrintWriter; import java.util.List; /** * Drill through statement. * * @author jhyde */ public class DrillThrough extends QueryPart { private final Query query; private final int maxRowCount; private final int firstRowOrdinal; private final List returnList; /** * Creates a DrillThrough. * * @param query Query * @param maxRowCount Maximum number of rows to return, or -1 * @param firstRowOrdinal Ordinal of first row to return, or -1 * @param returnList List of columns to return */ DrillThrough( Query query, int maxRowCount, int firstRowOrdinal, List returnList) { this.query = query; this.maxRowCount = maxRowCount; this.firstRowOrdinal = firstRowOrdinal; this.returnList = returnList; } @Override public void unparse(PrintWriter pw) { pw.print("DRILLTHROUGH"); if (maxRowCount >= 0) { pw.print(" MAXROWS "); pw.print(maxRowCount); } if (firstRowOrdinal >= 0) { pw.print(" FIRSTROWSET "); pw.print(firstRowOrdinal); } pw.print(" "); query.unparse(pw); if (returnList != null) { ExpBase.unparseList( pw, returnList.toArray(new Exp[returnList.size()]), " RETURN ", ", ", ""); } } @Override public Object[] getChildren() { return new Object[] {maxRowCount, firstRowOrdinal, query, returnList}; } public Query getQuery() { return query; } public int getMaxRowCount() { return maxRowCount; } public int getFirstRowOrdinal() { return firstRowOrdinal; } public List getReturnList() { return returnList; } } // End DrillThrough.java mondrian-3.4.1/src/main/mondrian/olap/Explain.java0000644000175000017500000000213011735330606021742 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2011 Pentaho // All Rights Reserved. */ package mondrian.olap; import java.io.PrintWriter; /** * Explain statement. * * @author jhyde */ public class Explain extends QueryPart { private final QueryPart query; /** * Creates an Explain statement. * * @param query Query (SELECT or DRILLTHROUGH) */ Explain( QueryPart query) { this.query = query; assert this.query != null; assert this.query instanceof Query || this.query instanceof DrillThrough; } @Override public void unparse(PrintWriter pw) { pw.print("EXPLAIN PLAN FOR "); query.unparse(pw); } @Override public Object[] getChildren() { return new Object[] {query}; } public QueryPart getQuery() { return query; } } // End Explain.java mondrian-3.4.1/src/main/mondrian/olap/Mondrian.xml0000644000175000017500000025651711735330606022014 0ustar drazzibdrazzib This is the XML model for Mondrian schemas.

    Revision is $Id$

    A schema is a collection of cubes and virtual cubes. It can also contain shared dimensions (for use by those cubes), named sets, roles, and declarations of user-defined functions.

    Name of this schema Description of this schema. Label for the measures dimension. Can be localized from Properties file using #{propertyname}. The name of the default role for connections to this schema Contains values of user-defined properties. This schema's parameter definitions. Shared dimensions in this schema. Cubes in this schema. Virtual cubes in this schema. Named sets in this schema. Roles in this schema. Declarations of user-defined functions in this schema. A CubeDimension is either a usage of a Dimension ('shared dimension', in MSOLAP parlance), or a 'private dimension'. A string being displayed instead of the Dimension's name. Can be localized from Properties file using #{propertyname}. Whether this dimension is visible in the user-interface. Default true. Description of this dimension. Can be localized from Properties file using #{propertyname}. The name of the column in the fact table which joins to the leaf level of this dimension. Required in a private Dimension or a DimensionUsage, but not in a public Dimension. Flag to mark this dimension as a high cardinality one and avoid caching. Contains values of user-defined properties. Never returns null; if the dimension cannot be * found, throws an error. * * @param schema Schema, never null * @pre schema != null * @post return != null */ public abstract Dimension getDimension(Schema schema);]]> Definition of a cube. Name of this cube. A string being displayed instead of the cube's name. Can be localized from Properties file using #{propertyname}. Whether this cube is visible in the user-interface. Default true. Description of this cube. Can be localized from Properties file using #{propertyname}. The name of the measure that would be taken as the default measure of the cube. Should the Fact table data for this Cube be cached by Mondrian or not. The default action is to cache the data. Whether element is enabled - if true, then the Cube is realized otherwise it is ignored. Contains values of user-defined properties. The fact table is the source of all measures in this cube. If this is a Table and the schema name is not present, table name is left unqualified. Calculated members in this cube. Named sets in this cube. A VirtualCube is a set of dimensions and measures gleaned from other cubes. Whether this element is enabled - if true, then the Virtual Cube is realized otherwise it is ignored. The name of the measure that would be taken as the default measure of the cube. A string being displayed instead of the cube's name. Can be localized from Properties file using #{propertyname}. Whether this cube is visible in the user-interface. Default true. Description of this virtual cube. Can be localized from Properties file using #{propertyname}. Contains values of user-defined properties. Calculated members that belong to this virtual cube. (Calculated members inherited from other cubes should not be in this list.) Named sets in this cube. List of base cubes used by the virtual cube. Name of the cube which the virtualCube uses. Unrelated dimensions to measures in this cube will be pushed to top level member. A VirtualCubeDimension is a usage of a Dimension in a VirtualCube. Name of the cube which the dimension belongs to, or unspecified if the dimension is shared. Name of the dimension. A VirtualCubeMeasure is a usage of a Measure in a VirtualCube. Name of the cube which the measure belongs to. Unique name of the measure within its cube. Whether this member is visible in the user-interface. Default true. Contains values of user-defined properties. A DimensionUsage is usage of a shared Dimension within the context of a cube. Name of the source dimension. Must be a dimension in this schema. Case-sensitive. Name of the level to join to. If not specified, joins to the lowest level of the dimension. If present, then this is prepended to the Dimension column names during the building of collapse dimension aggregates allowing 1) different dimension usages to be disambiguated during aggregate table recognition and 2) multiple shared dimensions that have common column names to be disambiguated. A Dimension is a collection of hierarchies. There are two kinds: a public dimension belongs to a Schema, and be used by several cubes; a private dimension belongs to a Cube. The foreignKey field is only applicable to private dimensions. The dimension's type may be one of "Standard" or "Time". A time dimension will allow the use of the MDX time functions (WTD, YTD, QTD, etc.). Use a standard dimension if the dimension is not a time-related dimension. The default value is "Standard". StandardDimension TimeDimension A string being displayed instead of the dimensions's name. Can be localized from Properties file using #{propertyname}. Description of this dimension. Can be localized from Properties file using #{propertyname}. If present, then this is prepended to the Dimension column names during the building of collapse dimension aggregates allowing 1) different dimensions to be disambiguated during aggregate table recognition. This should only be set for private dimensions. // implement CubeDimension public Dimension getDimension(Schema schema) { Util.assertPrecondition(schema != null, "schema != null"); return this; } // Return the dimension's enumerated type. public DimensionType getDimensionType() { if (type == null) { return null; //DimensionType.StandardDimension; } else { return DimensionType.valueOf(type); } } Defines a hierarchy.

    You must specify at most one <Relation> or memberReaderClass. If you specify none, the hierarchy is assumed to come from the same fact table of the current cube. Name of the hierarchy. If this is not specified, the hierarchy has the same name as its dimension. Whether this hierarchy is visible in the user-interface. Default true. Whether this hierarchy has an 'all' member. Name of the 'all' member. If this attribute is not specified, the all member is named 'All hierarchyName', for example, 'All Store'. A string being displayed instead as the all member's name. Can be localized from Properties file using #{propertyname}. Name of the 'all' level. If this attribute is not specified, the all member is named '(All)'. Can be localized from Properties file using #{propertyname}. The name of the column which identifies members, and which is referenced by rows in the fact table. If not specified, the key of the lowest level is used. See also CubeDimension.foreignKey. The name of the table which contains primaryKey. If the hierarchy has only one table, defaults to that; it is required. Name of the custom member reader class. Must implement the mondrian.rolap.MemberReader interface. A string to be displayed in the user interface. If not specified, the hierarchy's name is used. Can be localized from Properties file using #{propertyname}. Description of this hierarchy. Can be localized from Properties file using #{propertyname}. Should be set to the level (if such a level exists) at which depth it is known that all members have entirely unique rows, allowing SQL GROUP BY clauses to be completely eliminated from the query. Contains values of user-defined properties. The {@link MondrianDef.Table table}, {@link MondrianDef.Join set of tables}, {@link MondrianDef.View SQL statement}, or {@link MondrianDef.InlineTable inline table} which populates this hierarchy. The estimated number of members in this level. Setting this property improves the performance of MDSCHEMA_LEVELS, MDSCHEMA_HIERARCHIES and MDSCHEMA_DIMENSIONS XMLA requests Whether this level is visible in the user-interface. Default true. The name of the table that the column comes from. If this hierarchy is based upon just one table, defaults to the name of that table; otherwise, it is required. Can be localized from Properties file using #{propertyname}. The name of the column which holds the unique identifier of this level. The name of the column which holds the user identifier of this level. The name of the column which holds member ordinals. If this column is not specified, the key column is used for ordering. The name of the column which references the parent member in a parent-child hierarchy. Value which identifies null parents in a parent-child hierarchy. Typical values are 'NULL' and '0'. Indicates the type of this level's key column: String, Numeric, Integer, Boolean, Date, Time or Timestamp. When generating SQL statements, Mondrian encloses values for String columns in quotation marks, but leaves values for Integer and Numeric columns un-quoted.

    Date, Time, and Timestamp values are quoted according to the SQL dialect. For a SQL-compliant dialect, the values appear prefixed by their typename, for example, "DATE '2006-06-01'". String Numeric Integer Boolean Date Time Timestamp Indicates the Java type that Mondrian uses to store this level's key column. It also determines the JDBC method that Mondrian will call to retrieve the column; for example, if the Java type is 'int', Mondrian will call 'ResultSet.getInt(int)'.

    Usually this attribute is not needed, because Mondrian can choose a sensible type based on the type of the database column.

    Allowable values are: 'int', 'long', 'Object', 'String'. int long Object String Whether members are unique across all parents. For example, zipcodes are unique across all states. The first level's members are always unique. Whether this is a regular or a time-related level. The value makes a difference to time-related functions such as YTD (year-to-date).

    The "TimeHalfYear" value is deprecated and will be removed in mondrian-4.0; use "TimeHalfYears" instead.

    Regular TimeYears TimeHalfYears TimeHalfYear TimeQuarters TimeMonths TimeWeeks TimeDays TimeHours TimeMinutes TimeSeconds TimeUndefined Condition which determines whether a member of this level is hidden. If a hierarchy has one or more levels with hidden members, then it is possible that not all leaf members are the same distance from the root, and it is termed a ragged hierarchy.

    Allowable values are: Never (a member always appears; the default); IfBlankName (a member doesn't appear if its name is null, empty or all whitespace); and IfParentsName (a member appears unless its name matches the parent's.

    Never IfBlankName IfParentsName
    Name of a formatter class for the member labels being displayed. The class must implement the mondrian.spi.MemberFormatter interface.

    This attribute is deprecated. Please use a nested MemberFormatter element.

    A string being displayed instead of the level's name. Can be localized from Properties file using #{propertyname}. Description of this level. Can be localized from Properties file using #{propertyname}. The name of the column which holds the caption for members. Contains values of user-defined properties. The SQL expression used to populate this level's key. The SQL expression used to populate this level's name. If not specified, the level's key is used. The SQL expression used to populate this level's caption. If not specified, the level's name is used. The SQL expression used to populate this level's ordinal. The SQL expression used to join to the parent member in a parent-child hierarchy. Member formatter. public Expression getKeyExp() { if (keyExp != null) { return keyExp; } else if (column != null) { return new Column(table, column); } else { return null; } } public Expression getNameExp() { if (nameExp != null) { return nameExp; } else if (nameColumn != null) { return new Column(table, nameColumn); } else { return null; } } public Expression getCaptionExp() { if (captionExp != null) { return captionExp; } else if (captionColumn != null) { return new Column(table, captionColumn); } else { return null; } } public Expression getOrdinalExp() { if (ordinalExp != null) { return ordinalExp; } else if (ordinalColumn != null) { return new Column(table, ordinalColumn); } else { return null; } } public Expression getParentExp() { if (parentExp != null) { return parentExp; } else if (parentColumn != null) { return new Column(table, parentColumn); } else { return null; } } public Expression getPropertyExp(int i) { return new Column(table, properties[i].column); } public mondrian.spi.Dialect.Datatype getDatatype() { return mondrian.spi.Dialect.Datatype.valueOf(type); } Specifies the transitive closure of a parent-child hierarchy. Optional, but recommended for better performance. The closure is provided as a set of (parent/child) pairs: since it is the transitive closure these are actually (ancestor/descendant) pairs. Member property. Data type of this property: String, Numeric, Integer, Boolean, Date, Time or Timestamp. String Numeric Integer Boolean Date Time Timestamp

    Name of a formatter class for the appropriate property value being displayed.

    The class must implement the mondrian.spi.PropertyFormatter interface.

    This attribute is deprecated. Please use a nested PropertyFormatter element.

    A string being displayed instead of the name. Can be localized from Properties file using #{propertyname}. Description of this member property. Can be localized from Properties file using #{propertyname}. Should be set to true if the value of the property is functionally dependent on the level value. This permits the associated property column to be omitted from the GROUP BY clause (if the database permits columns in the SELECT that are not in the GROUP BY). This can be a significant performance enhancement on some databases, such as MySQL. Property formatter.
    Name of this measure. Column which is source of this measure's values. If not specified, a measure expression must be specified. The datatype of this measure: String, Numeric, Integer, Boolean, Date, Time or Timestamp.

    The default datatype of a measure is 'Integer' if the measure's aggregator is 'Count', otherwise it is 'Numeric'. String Numeric Integer Boolean Date Time Timestamp Format string with which to format cells of this measure. For more details, see the mondrian.util.Format class. Aggregation function. Allowed values are "sum", "count", "min", "max", "avg", and "distinct-count". ("distinct count" is allowed for backwards compatibility, but is deprecated because XML enumerated attributes in a DTD cannot legally contain spaces.)

    Name of a formatter class for the appropriate cell being displayed.

    The class must implement the mondrian.spi.CellFormatter interface.

    This attribute is deprecated. Please use a nested CellFormatter element.

    A string being displayed instead of the name. Can be localized from Properties file using #{propertyname}. Description of this measure. Can be localized from Properties file using #{propertyname}. Whether this member is visible in the user-interface. Default true. Contains values of user-defined properties. The SQL expression used to calculate a measure. Must be specified if a source column is not specified. Cell formatter.
    Name of this calculated member. Format string with which to format cells of this member. For more details, see {@link mondrian.util.Format}. A string being displayed instead of the name. Can be localized from Properties file using #{propertyname}. Description of this calculated member. Can be localized from Properties file using #{propertyname}. MDX expression which gives the value of this member. Equivalent to the Formula sub-element.

    Name of the dimension that this member belongs to.

    Deprecated: use {@code hierarchy} attribute instead.

    Name of the hierarchy that this member belongs to.

    Fully-qualified name of the parent member. If not specified, the member will be at the lowest level (besides the 'all' level) in the hierarchy. Whether this member is visible in the user-interface. Default true. Contains values of user-defined properties. MDX expression which gives the value of this member. Cell formatter. /** * Returns the formula, looking for a sub-element called * "Formula" first, then looking for an attribute called * "formula". */ public String getFormula() { if (formulaElement != null) { return formulaElement.cdata; } else { return formula; } }
    Property of a calculated member defined against a cube. It must have either an expression or a value. Name of this member property. A string being displayed instead of the name of this calculated member property. Can be localized from Properties file using #{propertyname}. Description of this calculated member property. Can be localized from Properties file using #{propertyname}. MDX expression which defines the value of this property. If the expression is a constant string, you could enclose it in quotes, or just specify the 'value' attribute instead. Value of this property. If the value is not constant, specify the 'expression' attribute instead. Defines a named set which can be used in queries in the same way as a set defined using a WITH SET clause.

    A named set can be defined against a particular cube, or can be global to a schema. If it is defined against a cube, it is only available to queries which use that cube.

    A named set defined against a cube is not inherited by a virtual cubes defined against that cube. (But you can define a named set against a virtual cube.)

    A named set defined against a schema is available in all cubes and virtual cubes in that schema. However, it is only valid if the cube contains dimensions with the names required to make the formula valid.

    ]]>
    Name of this named set. Caption of this named set. Can be localized from Properties file using #{propertyname}. Description of this named set. Can be localized from Properties file using #{propertyname}. MDX expression which gives the value of this set. Equivalent to the Formula sub-element. Contains values of user-defined properties. MDX expression which gives the value of this set. /** * Returns the formula, looking for a sub-element called * "Formula" first, then looking for an attribute called * "formula". */ public String getFormula() { if (formulaElement != null) { return formulaElement.cdata; } else { return formula; } }
    Not used A table or a join public abstract Relation find(String seekAlias); public boolean equals(Object o) { return this == o; } public int hashCode() { return System.identityHashCode(this); } A table, inline table or view public abstract String getAlias(); A collection of SQL statements, one per dialect. /** * Copy constructor. */ public View(View view) { this.alias = view.alias; this.selects = view.selects.clone(); } public String toString() { return selects[0].cdata; } public View find(String seekAlias) { if (seekAlias.equals(alias)) { return this; } else { return null; } } public String getAlias() { return alias; } public SqlQuery.CodeSet getCodeSet() { return SQL.toCodeSet(selects); } public void addCode(String dialect, String code) { if (selects == null) { selects = new SQL[1]; } else { SQL[] olds = selects; selects = new SQL[olds.length + 1]; System.arraycopy(olds, 0, selects, 0, olds.length); } SQL sql = new SQL(); sql.dialect = dialect; sql.cdata = code; selects[selects.length - 1] = sql; } public boolean equals(Object o) { if (o instanceof View) { View that = (View) o; if (!this.alias.equals(that.alias)) { return false; } if (this.selects == null || that.selects == null || this.selects.length != that.selects.length) { return false; } for (int i = 0; i < selects.length; i++) { if (!Util.equals(this.selects[i].dialect, that.selects[i].dialect) || !Util.equals(this.selects[i].cdata, that.selects[i].cdata)) { return false; } } return true; } else { return false; } } Dialect of SQL the view is intended for. Valid values include, but are not limited to:
    • generic
    • access
    • db2
    • derby
    • firebird
    • hsqldb
    • mssql
    • mysql
    • oracle
    • postgres
    • sybase
    • teradata
    • ingres
    • infobright
    • luciddb
    • vertica
    Defaults to left's alias if left is a table, otherwise required. Defaults to right's alias if right is a table, otherwise required. /** Convenience constructor. */ public Join( String leftAlias, String leftKey, RelationOrJoin left, String rightAlias, String rightKey, RelationOrJoin right) { this.leftAlias = leftAlias; this.leftKey = leftKey; this.left = left; this.rightAlias = rightAlias; this.rightKey = rightKey; this.right = right; } /** * Returns the alias of the left join key, defaulting to left's * alias if left is a table. */ public String getLeftAlias() { if (leftAlias != null) { return leftAlias; } if (left instanceof Relation) { return ((Relation) left).getAlias(); } throw Util.newInternal( "alias is required because " + left + " is not a table"); } /** * Returns the alias of the right join key, defaulting to right's * alias if right is a table. */ public String getRightAlias() { if (rightAlias != null) { return rightAlias; } if (right instanceof Relation) { return ((Relation) right).getAlias(); } if (right instanceof Join) { return ((Join) right).getLeftAlias(); } throw Util.newInternal( "alias is required because " + right + " is not a table"); } public String toString() { return "(" + left + ") join (" + right + ") on " + leftAlias + "." + leftKey + " = " + rightAlias + "." + rightKey; } public Relation find(String seekAlias) { Relation relation = left.find(seekAlias); if (relation == null) { relation = right.find(seekAlias); } return relation; } Optional qualifier for table. Alias to be used with this table when it is used to form queries. If not specified, defaults to the table name, but in any case, must be unique within the schema. (You can use the same table in different hierarchies, but it must have different aliases.) The SQL WHERE clause expression to be appended to any select statement Table optimization hints; may be ignored by dialect. hintMap; /** Convenience constructor. */ public Table(Table table) { this(table.schema, table.name, table.alias, table.tableHints); } public Table(String schema, String name, String alias, Hint[] tablehints) { this(); this.schema = schema; this.name = name; this.alias = alias; this.hintMap = buildHintMap(tablehints); } private java.util.Map buildHintMap(Hint[] th) { java.util.Map h = new java.util.HashMap(); if (th != null) { for (int i = 0; i < th.length; i++) { h.put(th[i].type, th[i].cdata); } } return h; } /** Returns the alias or, if it is null, the table name. */ public String getAlias() { return (alias != null) ? alias : name; } public String toString() { return (schema == null) ? name : schema + "." + name; } public Table find(String seekAlias) { return seekAlias.equals(name) ? this : (alias != null) && seekAlias.equals(alias) ? this : null; } public boolean equals(Object o) { if (o instanceof Table) { Table that = (Table) o; return this.name.equals(that.name) && Util.equals(this.alias, that.alias) && Util.equals(this.schema, that.schema); } else { return false; } } public int hashCode() { return toString().hashCode(); } public String getFilter() { return (filter == null) ? null : filter.cdata; } public AggExclude[] getAggExcludes() { return aggExcludes; } public AggTable[] getAggTables() { return aggTables; } public java.util.Map getHintMap() { if (hintMap == null) { hintMap = buildHintMap(this.tableHints); } return hintMap; } ]]> Dialect-specific table optimization hints. Type of hint, interpreted and applied on a per-dialect basis. Alias to be used with this table when it is used to form queries. If not specified, defaults to the table name, but in any case, must be unique within the schema. (You can use the same table in different hierarchies, but it must have different aliases.) "; } public InlineTable find(String seekAlias) { return seekAlias.equals(this.alias) ? this : null; } public boolean equals(Object o) { if (o instanceof InlineTable) { InlineTable that = (InlineTable) o; return this.alias.equals(that.alias); } else { return false; } } public int hashCode() { return toString().hashCode(); } ]]> Holder for an array of ColumnDef elements Column definition for an inline table. Name of the column. Type of the column: String, Numeric, Integer, Boolean, Date, Time or Timestamp. String Numeric Integer Boolean Date Time Timestamp Holder for an array of Row elements Row definition for an inline table. Must have one Column for each ColumnDef in the InlineTable. Column value for an inline table. The CDATA holds the value of the column. Name of the column. A definition of an aggregate table for a base fact table. This aggregate table must be in the same schema as the base fact table. Whether or not the match should ignore case. What does the fact_count column look like. public boolean isIgnoreCase() { return ignorecase.booleanValue(); } public AggFactCount getAggFactCount() { return factcount; } public AggIgnoreColumn[] getAggIgnoreColumns() { return ignoreColumns; } public AggForeignKey[] getAggForeignKeys() { return foreignKeys; } public AggMeasure[] getAggMeasures() { return measures; } public AggLevel[] getAggLevels() { return levels; } The Table name of a Specific aggregate table. The estimated number of rows in this aggregation table. Setting this property improves the performance of the aggregation optimizer and prevents it from issuing a 'select count(*)' query over the aggregation table. public String getNameAttribute() { return name; } public String getApproxRowCountAttribute() { return approxRowCount; } A Table pattern used to define a set of aggregate tables. public String getPattern() { return pattern; } public AggExclude[] getAggExcludes() { return excludes; } A Table pattern not to be matched. The Table name not to be matched. Whether or not the match should ignore case. public String getNameAttribute() { return name; } public String getPattern() { return pattern; } public boolean isIgnoreCase() { return ignorecase.booleanValue(); } The name of the fact count column. public String getColumnName() { return column; } The name of the column mapping from base fact table foreign key to aggregate table foreign key. The name of the base fact table foreign key. The name of the aggregate table foreign key. public String getFactFKColumnName() { return factColumn; } public String getAggregateFKColumnName() { return aggColumn; } The name of the column mapping to the level name. The name of the Dimension Hierarchy level. Whether this is a collapsed level. The parents of that level are also present in the aggregation table. public String getNameAttribute() { return name; } public String getColumnName() { return column; } public boolean isCollapsed() { return collapsed; } The name of the column mapping to the measure name. The name of the Cube measure. public String getNameAttribute() { return name; } public String getColumn() { return column; } public abstract String getExpression(SqlQuery query); public abstract String getGenericExpression(); public abstract String getTableAlias(); Alias of the table which contains this column. Not required if the query only has one table. Name of the column. A collection of SQL expressions, one per dialect. A role defines an access-control profile. It has a series of grants (or denials) for schema elements. Contains values of user-defined properties. Values correspond to Access. all custom none all_dimensions Grants (or denies) this role access to this schema. access may be "all", "all_dimensions", "custom" or "none". If access is "all_dimensions", the role has access to all dimensions but still needs explicit access to cubes. If access is "custom", no access will be inherited by cubes for which no explicit rule is set. If access is "all_dimensions", an implicut access is given to all dimensions of the schema's cubes, provided the cube's access attribute is either "custom" or "all". See mondrian.olap.Role#grant(mondrian.olap.Schema,int). Grants (or denies) this role access to a cube. access may be "all", "custom", or "none". If access is "custom", no access will be inherited by the dimensions of this cube, unless the parent SchemaGrant is set to "ALL_DIMENSIONS". See mondrian.olap.Role#grant(mondrian.olap.Cube,int). The unique name of the cube Grants (or denies) this role access to a dimension. access may be "all", "custom" or "none". Note that a role is implicitly given access to a dimension when it is given "ALL" acess to a cube. If access is "custom", no access will be inherited by the hierarchies of this dimension. If the parent schema access is "ALL_DIMENSIONS", this timension will inherit access "ALL". See also the "all_dimensions" option of the "SchemaGrant" element. See mondrian.olap.Role#grant(mondrian.olap.Dimension,int). The unique name of the dimension Grants (or denies) this role access to a hierarchy. access may be "all", "custom" or "none". If access is "custom", you may also specify the attributes topLevel, bottomLevel, and the member grants. If access is "custom", the child levels of this hierarchy will not inherit access rights from this hierarchy, should there be no explicit rules defined for the said child level. See mondrian.olap.Role#grant(mondrian.olap.Hierarchy, int, mondrian.olap.Level). The unique name of the hierarchy Unique name of the highest level of the hierarchy from which this role is allowed to see members. May only be specified if the HierarchyGrant.access is "custom". If not specified, role can see members up to the top level. Unique name of the lowest level of the hierarchy from which this role is allowed to see members. May only be specified if the HierarchyGrant.access is "custom". If not specified, role can see members down to the leaf level. Policy which determines how cell values are calculated if not all of the children of the current cell are visible to the current role. Allowable values are 'full' (the default), 'partial', and 'hidden'. Grants (or denies) this role access to a member. The children of this member inherit that access. You can implicitly see a member if you can see any of its children. See mondrian.olap.Role#grant(mondrian.olap.Member,int). The unique name of the member all none Body of a Role definition which defines a Role to be the union of several Roles. The RoleUsage elements must refer to Roles that have been declared earlier in this schema file. Usage of a Role in a union Role. A UserDefinedFunction is a function which extends the MDX language. It must be implemented by a Java class which implements the interface mondrian.spi.UserDefinedFunction. Name with which the user-defined function will be referenced in MDX expressions. Name of the class which implemenets this user-defined function. Must implement the mondrian.spi.UserDefinedFunction interface. Script to implement this user-defined function.

    Either the "Script" element or the "className" attribute must be specified.

    A Parameter defines a schema parameter. It can be referenced from an MDX statement using the ParamRef function and, if not final, its value can be overridden. Name of this parameter. Description of this parameter. Indicates the type of this parameter: String, Numeric, Integer, Boolean, Date, Time, Timestamp, or Member. String Numeric Integer Boolean Date Time Timestamp Member If false, statement cannot change the value of this parameter; the parameter becomes effectively constant (provided that its default value expression always returns the same value). Default is true. Expression for the default value of this parameter. Holder for an array of Annotation elements User-defined property value. Name of the annotation. Script fragment to implement an SPI such as user-defined function, member formatter, cell formatter. The language of the script. Must be a supported scripting language in the current JVM. See {@link javax.script.ScriptEngineManager}. Default value is 'JavaScript'. Plugin that formats the values of cells. It must be implemented by a Java class which implements the interface mondrian.spi.CellFormatter, or by a script. Name of the class which implemenets this cell formatter. Must implement the mondrian.spi.CellFormatter interface.

    Either the "Script" element or the "className" attribute must be specified.

    Script to implement this cell formatter.

    Either the "Script" element or the "className" attribute must be specified.

    Plugin that formats members. It must be implemented by a Java class which implements the interface mondrian.spi.MemberFormatter, or by a script. Name of the class which implemenets this member formatter. Must implement the mondrian.spi.MemberFormatter interface.

    Either the "Script" element or the "className" attribute must be specified.

    Script to implement this member formatter.

    Either the "Script" element or the "className" attribute must be specified.

    Plugin that formats properties. It must be implemented by a Java class which implements the interface mondrian.spi.PropertyFormatter, or by a script. Name of the class which implemenets this property formatter. Must implement the mondrian.spi.PropertyFormatter interface.

    Either the "Script" element or the "className" attribute must be specified.

    Script to implement this property formatter.

    Either the "Script" element or the "className" attribute must be specified.

    mondrian-3.4.1/src/main/mondrian/olap/type/0000755000175000017500000000000011735330606020464 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/olap/type/LevelType.java0000644000175000017500000001200511735330606023236 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.type; import mondrian.olap.*; /** * The type of an expression which represents a level. * * @author jhyde * @since Feb 17, 2005 */ public class LevelType implements Type { private final Dimension dimension; private final Hierarchy hierarchy; private final Level level; private final String digest; public static final LevelType Unknown = new LevelType(null, null, null); /** * Creates a type representing a level. * * @param dimension Dimension which values of this type must belong to, or * null if not known * @param hierarchy Hierarchy which values of this type must belong to, or * null if not known * @param level Level which values of this type must belong to, or null if */ public LevelType(Dimension dimension, Hierarchy hierarchy, Level level) { this.dimension = dimension; this.hierarchy = hierarchy; this.level = level; if (level != null) { Util.assertPrecondition(hierarchy != null, "hierarchy != null"); Util.assertPrecondition( level.getHierarchy() == hierarchy, "level.getHierarchy() == hierarchy"); } if (hierarchy != null) { Util.assertPrecondition(dimension != null, "dimension != null"); Util.assertPrecondition( hierarchy.getDimension() == dimension, "hierarchy.getDimension() == dimension"); } StringBuilder buf = new StringBuilder("LevelType<"); if (level != null) { buf.append("level=").append(level.getUniqueName()); } else if (hierarchy != null) { buf.append("hierarchy=").append(hierarchy.getUniqueName()); } else if (dimension != null) { buf.append("dimension=").append(dimension.getUniqueName()); } buf.append(">"); this.digest = buf.toString(); } public static LevelType forType(Type type) { return new LevelType( type.getDimension(), type.getHierarchy(), type.getLevel()); } public static LevelType forLevel(Level level) { return new LevelType( level.getDimension(), level.getHierarchy(), level); } public boolean usesDimension(Dimension dimension, boolean definitely) { return this.dimension == dimension || (!definitely && this.dimension == null); } public boolean usesHierarchy(Hierarchy hierarchy, boolean definitely) { return this.hierarchy == hierarchy || (!definitely && this.hierarchy == null && (this.dimension == null || this.dimension == hierarchy.getDimension())); } public Dimension getDimension() { return dimension; } public Hierarchy getHierarchy() { return hierarchy; } public Level getLevel() { return level; } public String toString() { return digest; } public int hashCode() { return digest.hashCode(); } public boolean equals(Object obj) { if (obj instanceof LevelType) { LevelType that = (LevelType) obj; return Util.equals(this.level, that.level) && Util.equals(this.hierarchy, that.hierarchy) && Util.equals(this.dimension, that.dimension); } return false; } public Type computeCommonType(Type type, int[] conversionCount) { if (!(type instanceof LevelType)) { return null; } LevelType that = (LevelType) type; if (this.getLevel() != null && this.getLevel().equals(that.getLevel())) { return this; } if (this.getHierarchy() != null && this.getHierarchy().equals(that.getHierarchy())) { return new LevelType( this.getDimension(), this.getHierarchy(), null); } if (this.getDimension() != null && this.getDimension().equals(that.getDimension())) { return new LevelType( this.getDimension(), null, null); } return LevelType.Unknown; } public boolean isInstance(Object value) { return value instanceof Level && (level == null || value.equals(level)) && (hierarchy == null || ((Level) value).getHierarchy().equals(hierarchy)) && (dimension == null || ((Level) value).getDimension().equals(dimension)); } public int getArity() { return 1; } } // End LevelType.java mondrian-3.4.1/src/main/mondrian/olap/type/StringType.java0000644000175000017500000000146111735330606023441 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.type; /** * The type of a string expression. * * @author jhyde * @since Feb 17, 2005 */ public class StringType extends ScalarType { /** * Creates a string type. */ public StringType() { super("STRING"); } public boolean equals(Object obj) { return obj instanceof StringType; } public boolean isInstance(Object value) { return value instanceof String; } } // End StringType.java mondrian-3.4.1/src/main/mondrian/olap/type/NullType.java0000644000175000017500000000132111735330606023100 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.type; /** * The type of a null expression. * * @author medstat * @since Aug 21, 2006 */ public class NullType extends ScalarType { /** * Creates a null type. */ public NullType() { super(""); } public boolean equals(Object obj) { return obj instanceof NullType; } } // End NullType.java mondrian-3.4.1/src/main/mondrian/olap/type/package.html0000644000175000017500000000007711735330606022751 0ustar drazzibdrazzib Type system for MDX expessions. mondrian-3.4.1/src/main/mondrian/olap/type/BooleanType.java0000644000175000017500000000146711735330606023560 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.type; /** * The type of a boolean expression. * * @author jhyde * @since Feb 17, 2005 */ public class BooleanType extends ScalarType { /** * Creates a BooleanType. */ public BooleanType() { super("BOOLEAN"); } public boolean equals(Object obj) { return obj instanceof BooleanType; } public boolean isInstance(Object value) { return value instanceof Boolean; } } // End BooleanType.java mondrian-3.4.1/src/main/mondrian/olap/type/SetType.java0000644000175000017500000000651011735330606022726 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.type; import mondrian.olap.*; import java.util.List; /** * Set type. * * @author jhyde * @since Feb 17, 2005 */ public class SetType implements Type { private final Type elementType; private final String digest; /** * Creates a type representing a set of elements of a given type. * * @param elementType The type of the elements in the set, or null if not * known */ public SetType(Type elementType) { if (elementType != null) { assert elementType instanceof MemberType || elementType instanceof TupleType; } this.elementType = elementType; this.digest = "SetType<" + elementType + ">"; } public int hashCode() { return digest.hashCode(); } public boolean equals(Object obj) { if (obj instanceof SetType) { SetType that = (SetType) obj; return Util.equals(this.elementType, that.elementType); } else { return false; } } public String toString() { return digest; } /** * Returns the type of the elements of this set. * * @return the type of the elements in this set */ public Type getElementType() { return elementType; } public boolean usesDimension(Dimension dimension, boolean definitely) { if (elementType == null) { return definitely; } return elementType.usesDimension(dimension, definitely); } public boolean usesHierarchy(Hierarchy hierarchy, boolean definitely) { if (elementType == null) { return definitely; } return elementType.usesHierarchy(hierarchy, definitely); } public Dimension getDimension() { return elementType == null ? null : elementType.getDimension(); } public Hierarchy getHierarchy() { return elementType == null ? null : elementType.getHierarchy(); } public Level getLevel() { return elementType == null ? null : elementType.getLevel(); } public int getArity() { return elementType.getArity(); } public Type computeCommonType(Type type, int[] conversionCount) { if (!(type instanceof SetType)) { return null; } SetType that = (SetType) type; final Type mostGeneralElementType = this.getElementType().computeCommonType( that.getElementType(), conversionCount); if (mostGeneralElementType == null) { return null; } return new SetType(mostGeneralElementType); } public boolean isInstance(Object value) { if (!(value instanceof List)) { return false; } List list = (List) value; for (Object o : list) { if (!elementType.isInstance(o)) { return false; } } return true; } } // End SetType.java mondrian-3.4.1/src/main/mondrian/olap/type/EmptyType.java0000644000175000017500000000153011735330606023266 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.type; /** * The type of a empty expression. * *

    An example of an empty expression is the third argument to the call * DrilldownLevelTop({[Store].[USA]}, 2, , [Measures].[Unit * Sales]). *

    * * @author medstat * @since Jan 26, 2009 */ public class EmptyType extends ScalarType { /** * Creates an empty type. */ public EmptyType() { super(""); } public boolean equals(Object obj) { return obj instanceof EmptyType; } } // End EmptyType.java mondrian-3.4.1/src/main/mondrian/olap/type/MemberType.java0000644000175000017500000001567511735330606023416 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.type; import mondrian.olap.*; /** * The type of an expression which represents a member. * * @author jhyde * @since Feb 17, 2005 */ public class MemberType implements Type { private final Hierarchy hierarchy; private final Dimension dimension; private final Level level; private final Member member; private final String digest; public static final MemberType Unknown = new MemberType(null, null, null, null); /** * Creates a type representing a member. * * @param dimension Dimension the member belongs to, or null if not known * @param hierarchy Hierarchy the member belongs to, or null if not known * @param level Level the member belongs to, or null if not known * @param member The precise member, or null if not known */ public MemberType( Dimension dimension, Hierarchy hierarchy, Level level, Member member) { this.dimension = dimension; this.hierarchy = hierarchy; this.level = level; this.member = member; if (member != null) { Util.assertPrecondition(level != null); Util.assertPrecondition(member.getLevel() == level); } if (level != null) { Util.assertPrecondition(hierarchy != null); Util.assertPrecondition(level.getHierarchy() == hierarchy); } if (hierarchy != null) { Util.assertPrecondition(dimension != null); Util.assertPrecondition(hierarchy.getDimension() == dimension); } StringBuilder buf = new StringBuilder("MemberType<"); if (member != null) { buf.append("member=").append(member.getUniqueName()); } else if (level != null) { buf.append("level=").append(level.getUniqueName()); } else if (hierarchy != null) { buf.append("hierarchy=").append(hierarchy.getUniqueName()); } else if (dimension != null) { buf.append("dimension=").append(dimension.getUniqueName()); } buf.append(">"); this.digest = buf.toString(); } public static MemberType forDimension(Dimension dimension) { return new MemberType(dimension, null, null, null); } public static MemberType forHierarchy(Hierarchy hierarchy) { final Dimension dimension; if (hierarchy == null) { dimension = null; } else { dimension = hierarchy.getDimension(); } return new MemberType(dimension, hierarchy, null, null); } public static MemberType forLevel(Level level) { final Dimension dimension; final Hierarchy hierarchy; if (level == null) { dimension = null; hierarchy = null; } else { dimension = level.getDimension(); hierarchy = level.getHierarchy(); } return new MemberType(dimension, hierarchy, level, null); } public static MemberType forMember(Member member) { final Dimension dimension; final Hierarchy hierarchy; final Level level; if (member == null) { dimension = null; hierarchy = null; level = null; } else { dimension = member.getDimension(); hierarchy = member.getHierarchy(); level = member.getLevel(); } return new MemberType(dimension, hierarchy, level, member); } public String toString() { return digest; } public Hierarchy getHierarchy() { return hierarchy; } public Level getLevel() { return level; } public Member getMember() { return member; } public boolean usesDimension(Dimension dimension, boolean definitely) { return this.dimension == dimension || (!definitely && this.dimension == null); } public boolean usesHierarchy(Hierarchy hierarchy, boolean definitely) { return this.hierarchy == hierarchy || (!definitely && this.hierarchy == null && (this.dimension == null || this.dimension == hierarchy.getDimension())); } public Type getValueType() { // todo: when members have more type information (double vs. integer // vs. string), return better type if member != null. return new ScalarType(); } public Dimension getDimension() { return dimension; } public static MemberType forType(Type type) { if (type instanceof MemberType) { return (MemberType) type; } else { return new MemberType( type.getDimension(), type.getHierarchy(), type.getLevel(), null); } } public Type computeCommonType(Type type, int[] conversionCount) { if (type instanceof ScalarType) { return getValueType().computeCommonType(type, conversionCount); } if (type instanceof TupleType) { return type.computeCommonType(this, conversionCount); } if (!(type instanceof MemberType)) { return null; } MemberType that = (MemberType) type; if (this.getMember() != null && this.getMember().equals(that.getMember())) { return this; } if (this.getLevel() != null && this.getLevel().equals(that.getLevel())) { return new MemberType( this.getDimension(), this.getHierarchy(), this.getLevel(), null); } if (this.getHierarchy() != null && this.getHierarchy().equals(that.getHierarchy())) { return new MemberType( this.getDimension(), this.getHierarchy(), null, null); } if (this.getDimension() != null && this.getDimension().equals(that.getDimension())) { return new MemberType( this.getDimension(), null, null, null); } return MemberType.Unknown; } public boolean isInstance(Object value) { return value instanceof Member && (level == null || ((Member) value).getLevel().equals(level)) && (hierarchy == null || ((Member) value).getHierarchy().equals(hierarchy)) && (dimension == null || ((Member) value).getDimension().equals(dimension)); } public int getArity() { return 1; } } // End MemberType.java mondrian-3.4.1/src/main/mondrian/olap/type/DimensionType.java0000644000175000017500000000714011735330606024120 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.type; import mondrian.olap.*; /** * The type of an expression which represents a Dimension. * * @author jhyde * @since Feb 17, 2005 */ public class DimensionType implements Type { private final Dimension dimension; private final String digest; public static final DimensionType Unknown = new DimensionType(null); /** * Creates a type representing a dimension. * * @param dimension Dimension that values of this type must belong to, or * null if the dimension is unknown */ public DimensionType(Dimension dimension) { this.dimension = dimension; StringBuilder buf = new StringBuilder("DimensionType<"); if (dimension != null) { buf.append("dimension=").append(dimension.getUniqueName()); } buf.append(">"); this.digest = buf.toString(); } public static DimensionType forDimension(Dimension dimension) { return new DimensionType(dimension); } public static DimensionType forType(Type type) { return new DimensionType(type.getDimension()); } public boolean usesDimension(Dimension dimension, boolean definitely) { // REVIEW: Should be '!definitely'? return this.dimension == dimension || (definitely && this.dimension == null); } public boolean usesHierarchy(Hierarchy hierarchy, boolean definitely) { // If hierarchy belongs to this type's dimension, we might use it. return hierarchy.getDimension() == this.dimension && !definitely; } public Hierarchy getHierarchy() { return dimension == null ? null : dimension.getHierarchies().length > 1 ? null : dimension.getHierarchies()[0]; } public Level getLevel() { return null; } public Dimension getDimension() { return dimension; } public int hashCode() { return digest.hashCode(); } public boolean equals(Object obj) { if (obj instanceof DimensionType) { DimensionType that = (DimensionType) obj; return Util.equals(this.getDimension(), that.getDimension()); } return false; } public String toString() { return digest; } public Type computeCommonType(Type type, int[] conversionCount) { if (conversionCount != null && type instanceof HierarchyType) { HierarchyType hierarchyType = (HierarchyType) type; if (Util.equals(hierarchyType.getDimension(), dimension)) { ++conversionCount[0]; return this; } return null; } if (!(type instanceof DimensionType)) { return null; } DimensionType that = (DimensionType) type; if (this.getDimension() != null && this.getDimension().equals(that.getDimension())) { return new DimensionType( this.getDimension()); } return DimensionType.Unknown; } public boolean isInstance(Object value) { return value instanceof Dimension && (dimension == null || value.equals(dimension)); } public int getArity() { return 1; } } // End DimensionType.java mondrian-3.4.1/src/main/mondrian/olap/type/SymbolType.java0000644000175000017500000000117511735330606023442 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.type; /** * The type of a symbolic expression. * * @author jhyde * @since Feb 17, 2005 */ public class SymbolType extends ScalarType { /** * Creates a symbol type. */ public SymbolType() { super("SYMBOL"); } } // End SymbolType.java mondrian-3.4.1/src/main/mondrian/olap/type/TypeUtil.java0000644000175000017500000005064611735330606023121 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho // All Rights Reserved. */ package mondrian.olap.type; import mondrian.mdx.UnresolvedFunCall; import mondrian.olap.*; import mondrian.olap.fun.Resolver; import java.util.*; /** * Utility methods relating to types. * * @author jhyde * @since Feb 17, 2005 */ public class TypeUtil { /** * Given a set type, returns the element type. Or its element type, if it * is a set type. And so on. * * @param type Type * @return underlying element type which is not a set type */ public static Type stripSetType(Type type) { while (type instanceof SetType) { type = ((SetType) type).getElementType(); } return type; } /** * Converts a type to a member or tuple type. * If it cannot, returns null. * * @param type Type * @return member or tuple type */ public static Type toMemberOrTupleType(Type type) { type = stripSetType(type); if (type instanceof TupleType) { return type; } else { return toMemberType(type); } } /** * Converts a type to a member type. * If it is a set, strips the set. * If it is a member type, returns the type unchanged. * If it is a dimension, hierarchy or level type, converts it to * a member type. * If it is a tuple, number, string, or boolean, returns null. * * @param type Type * @return type as a member type */ public static MemberType toMemberType(Type type) { type = stripSetType(type); if (type instanceof MemberType) { return (MemberType) type; } else if (type instanceof DimensionType || type instanceof HierarchyType || type instanceof LevelType) { return MemberType.forType(type); } else { return null; } } /** * Returns whether this type is union-compatible with another. * In general, to be union-compatible, types must have the same * dimensionality. * * @param type1 First type * @param type2 Second type * @return whether types are union-compatible */ public static boolean isUnionCompatible(Type type1, Type type2) { if (type1 instanceof TupleType) { TupleType tupleType1 = (TupleType) type1; if (type2 instanceof TupleType) { TupleType tupleType2 = (TupleType) type2; if (tupleType1.elementTypes.length == tupleType2.elementTypes.length) { for (int i = 0; i < tupleType1.elementTypes.length; i++) { if (!isUnionCompatible( tupleType1.elementTypes[i], tupleType2.elementTypes[i])) { return false; } } return true; } } return false; } else { final MemberType memberType1 = toMemberType(type1); if (memberType1 == null) { return false; } final MemberType memberType2 = toMemberType(type2); if (memberType2 == null) { return false; } final Hierarchy hierarchy1 = memberType1.getHierarchy(); final Hierarchy hierarchy2 = memberType2.getHierarchy(); return equal(hierarchy1, hierarchy2); } } /** * Returns whether two hierarchies are equal. * * @param hierarchy1 First hierarchy * @param hierarchy2 Second hierarchy * @return Whether hierarchies are equal */ private static boolean equal( final Hierarchy hierarchy1, final Hierarchy hierarchy2) { return hierarchy1 == null || hierarchy2 == null || hierarchy2.getUniqueName().equals( hierarchy1.getUniqueName()); } /** * Returns whether a value of a given type can be evaluated to a scalar * value. * *

    The rules are as follows:

      *
    • Clearly boolean, numeric and string expressions can be evaluated. *
    • Member and tuple expressions can be interpreted as a scalar value. * The expression is evaluated to establish the context where a measure * can be evaluated. *
    • Hierarchy and dimension expressions are implicitly * converted into the current member, and evaluated as above. *
    • Level expressions cannot be evaluated *
    • Cube and Set (even sets with a single member) cannot be evaluated. *
    * * @param type Type * @return Whether an expression of this type can be evaluated to yield a * scalar value. */ public static boolean canEvaluate(Type type) { return ! (type instanceof SetType || type instanceof CubeType || type instanceof LevelType); } /** * Returns whether a type is a set type. * * @param type Type * @return Whether a value of this type can be evaluated to yield a set. */ public static boolean isSet(Type type) { return type instanceof SetType; } public static boolean couldBeMember(Type type) { return type instanceof MemberType || type instanceof HierarchyType || type instanceof DimensionType; } /** * Converts a {@link Type} value to a {@link Category} ordinal. * * @param type Type * @return category ordinal */ public static int typeToCategory(Type type) { if (type instanceof NullType) { return Category.Null; } else if (type instanceof EmptyType) { return Category.Empty; } else if (type instanceof DateTimeType) { return Category.DateTime; } else if (type instanceof NumericType) { return Category.Numeric; } else if (type instanceof BooleanType) { return Category.Logical; } else if (type instanceof DimensionType) { return Category.Dimension; } else if (type instanceof HierarchyType) { return Category.Hierarchy; } else if (type instanceof MemberType) { return Category.Member; } else if (type instanceof LevelType) { return Category.Level; } else if (type instanceof SymbolType) { return Category.Symbol; } else if (type instanceof StringType) { return Category.String; } else if (type instanceof ScalarType) { return Category.Value; } else if (type instanceof SetType) { return Category.Set; } else if (type instanceof TupleType) { return Category.Tuple; } else { throw Util.newInternal("Unknown type " + type); } } /** * Returns a type sufficiently broad to hold any value of several types, * but as narrow as possible. If there is no such type, returns null. * *

    The result is equivalent to calling * {@link Type#computeCommonType(Type, int[])} pairwise. * * @param allowConversions Whether to allow implicit conversions * @param types Array of types * @return Most general type which encompases all types */ public static Type computeCommonType( boolean allowConversions, Type... types) { if (types.length == 0) { return null; } Type type = types[0]; int[] conversionCount = allowConversions ? new int[] {0} : null; for (int i = 1; i < types.length; ++i) { if (type == null) { return null; } type = type.computeCommonType(types[i], conversionCount); } return type; } /** * Returns whether we can convert an argument of a given category to a * given parameter category. * * @param ordinal argument ordinal * @param fromType actual argument type * @param to formal parameter category * @param conversions list of implicit conversions required (out) * @return whether can convert from 'from' to 'to' */ public static boolean canConvert( int ordinal, Type fromType, int to, List conversions) { final int from = typeToCategory(fromType); if (from == to) { return true; } RuntimeException e = null; switch (from) { case Category.Array: return false; case Category.Dimension: // We can go from Dimension to Hierarchy if the dimension has a // default hierarchy. From there, we can go to Member or Tuple. // Even if the dimension does not have a default hierarchy, we claim // now that we can do the conversion, to prevent other overloads // from being chosen; we will hit an error either at compile time or // at run time. switch (to) { case Category.Member: case Category.Tuple: case Category.Hierarchy: // It is more difficult to convert dimension->hierarchy than // hierarchy->dimension conversions.add(new ConversionImpl(from, to, ordinal, 2, e)); return true; case Category.Level: // It is more difficult to convert dimension->level than // dimension->member or dimension->hierarchy->member. conversions.add(new ConversionImpl(from, to, ordinal, 3, null)); return true; default: return false; } case Category.Hierarchy: // Seems funny that you can 'downcast' from a hierarchy, doesn't // it? But we add an implicit 'CurrentMember', for example, // '[Product].PrevMember' actually means // '[Product].CurrentMember.PrevMember'. switch (to) { case Category.Dimension: case Category.Member: case Category.Tuple: conversions.add(new ConversionImpl(from, to, ordinal, 1, null)); return true; default: return false; } case Category.Level: switch (to) { case Category.Dimension: // It's more difficult to convert to a dimension than a // hierarchy. For example, we want '[Store City].CurrentMember' // to resolve to .CurrentMember rather than // .CurrentMember. conversions.add(new ConversionImpl(from, to, ordinal, 2, null)); return true; case Category.Hierarchy: conversions.add(new ConversionImpl(from, to, ordinal, 1, null)); return true; default: return false; } case Category.Logical: switch (to) { case Category.Value: return true; default: return false; } case Category.Member: switch (to) { case Category.Dimension: case Category.Hierarchy: case Category.Level: case Category.Tuple: conversions.add(new ConversionImpl(from, to, ordinal, 1, null)); return true; case Category.Set: // It is more expensive to convert from Member->Set (cost=2) // than Member->Tuple (cost=1). In particular, m.Tuple(n) should // resolve to .Item() rather than // .Item(). conversions.add(new ConversionImpl(from, to, ordinal, 2, null)); return true; case Category.Numeric: // It is more expensive to convert from Member->Scalar (cost=3) // than Member->Set (cost=2). In particular, we want 'member * // set' to resolve to the crossjoin operator, '{m} * set'. conversions.add(new ConversionImpl(from, to, ordinal, 3, null)); return true; case Category.Value: case Category.String: // We assume that measures are numeric, so a cast to a string or // general value expression is more expensive (cost=4) than a // conversion to a numeric expression (cost=3). conversions.add(new ConversionImpl(from, to, ordinal, 4, null)); return true; default: return false; } case Category.Numeric | Category.Constant: switch (to) { case Category.Value: case Category.Numeric: return true; default: return false; } case Category.Numeric: switch (to) { case Category.Logical: conversions.add(new ConversionImpl(from, to, ordinal, 2, null)); return true; case Category.Value: case Category.Integer: case (Category.Integer | Category.Constant): case (Category.Numeric | Category.Constant): return true; default: return false; } case Category.Integer: switch (to) { case Category.Value: case (Category.Integer | Category.Constant): case Category.Numeric: case (Category.Numeric | Category.Constant): return true; default: return false; } case Category.Set: return false; case Category.String | Category.Constant: switch (to) { case Category.Value: case Category.String: return true; default: return false; } case Category.String: switch (to) { case Category.Value: case (Category.String | Category.Constant): return true; default: return false; } case Category.DateTime | Category.Constant: switch (to) { case Category.Value: case Category.DateTime: return true; default: return false; } case Category.DateTime: switch (to) { case Category.Value: case (Category.DateTime | Category.Constant): return true; default: return false; } case Category.Tuple: switch (to) { case Category.Set: conversions.add(new ConversionImpl(from, to, ordinal, 2, null)); return true; case Category.Numeric: // It is more expensive to convert from Tuple->Scalar (cost=4) // than Tuple->Set (cost=3). In particular, we want 'tuple * // set' to resolve to the crossjoin operator, '{tuple} * set'. // This is analogous to Member->Numeric conversion. conversions.add(new ConversionImpl(from, to, ordinal, 3, null)); return true; case Category.String: case Category.Value: // We assume that measures are numeric, so a cast to a string or // general value expression is more expensive (cost=4) than a // conversion to a numeric expression (cost=3). conversions.add(new ConversionImpl(from, to, ordinal, 4, null)); return true; default: return false; } case Category.Value: // We can implicitly cast from value to a more specific scalar type, // but the cost is significant. switch (to) { case Category.String: case Category.Numeric: case Category.Logical: conversions.add(new ConversionImpl(from, to, ordinal, 2, null)); return true; default: return false; } case Category.Symbol: return false; case Category.Null: // now null supports members as well as scalars; but scalar is // preferred if (Category.isScalar(to)) { return true; } else if (to == Category.Member) { conversions.add(new ConversionImpl(from, to, ordinal, 2, null)); return true; } else { return false; } case Category.Empty: return false; default: throw Util.newInternal( "unknown category " + from + " for type " + fromType); } } static T neq(T t1, T t2) { return t1 == null ? t2 : t2 == null ? t1 : t1.equals(t2) ? t1 : null; } /** * Returns the hierarchies in a set, member, or tuple type. * * @param type Type * @return List of hierarchies */ public static List getHierarchies(Type type) { if (type instanceof SetType) { type = ((SetType) type).getElementType(); } if (type instanceof TupleType) { final TupleType tupleType = (TupleType) type; List hierarchyList = new ArrayList(); for (Type elementType : tupleType.elementTypes) { hierarchyList.add(elementType.getHierarchy()); } return hierarchyList; } else { return Collections.singletonList(type.getHierarchy()); } } /** * Implementation of {@link mondrian.olap.fun.Resolver.Conversion}. */ private static class ConversionImpl implements Resolver.Conversion { final int from; final int to; /** * Which argument. Arguments are 0-based, and in particular the 'this' * of a call of member or method call syntax is argument 0. Argument -1 * is the return. */ final int ordinal; /** * Score of the conversion. A higher value is more onerous and therefore * a call using such a conversion is less likly to be chosen. */ final int cost; final RuntimeException e; /** * Creates a conversion. * * @param from From type * @param to To type * @param ordinal Ordinal of argument * @param cost Cost of conversion * @param e Exception */ public ConversionImpl( int from, int to, int ordinal, int cost, RuntimeException e) { this.from = from; this.to = to; this.ordinal = ordinal; this.cost = cost; this.e = e; } public int getCost() { return cost; } public void checkValid() { if (e != null) { throw e; } } public void apply(Validator validator, List args) { final Exp arg = args.get(ordinal); switch (from) { case Category.Member: case Category.Tuple: switch (to) { case Category.Set: final Exp newArg = validator.validate( new UnresolvedFunCall( "{}", Syntax.Braces, new Exp[]{arg}), false); args.set(ordinal, newArg); break; default: // do nothing } default: // do nothing } } // for debug public String toString() { return "Conversion(from=" + Category.instance().getName(from) + ", to=" + Category.instance().getName(to) + ", ordinal=" + ordinal + ", cost=" + cost + ", e=" + e + ")"; } } } // End TypeUtil.java mondrian-3.4.1/src/main/mondrian/olap/type/TupleType.java0000644000175000017500000001440411735330606023265 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.type; import mondrian.olap.*; import mondrian.resource.MondrianResource; import java.util.*; /** * Tuple type. * * @author jhyde * @since Feb 17, 2005 */ public class TupleType implements Type { public final Type[] elementTypes; private final String digest; /** * Creates a type representing a tuple whose fields are the given types. * * @param elementTypes Array of types of the members in this tuple */ public TupleType(Type[] elementTypes) { assert elementTypes != null; this.elementTypes = elementTypes.clone(); final StringBuilder buf = new StringBuilder(); buf.append("TupleType<"); int k = 0; for (Type elementType : elementTypes) { if (k++ > 0) { buf.append(", "); } buf.append(elementType); } buf.append(">"); digest = buf.toString(); } public String toString() { return digest; } public boolean equals(Object obj) { if (obj instanceof TupleType) { TupleType that = (TupleType) obj; return Arrays.equals(this.elementTypes, that.elementTypes); } else { return false; } } public int hashCode() { return digest.hashCode(); } public boolean usesDimension(Dimension dimension, boolean definitely) { for (Type elementType : elementTypes) { if (elementType.usesDimension(dimension, definitely)) { return true; } } return false; } public boolean usesHierarchy(Hierarchy hierarchy, boolean definitely) { for (Type elementType : elementTypes) { if (elementType.usesHierarchy(hierarchy, definitely)) { return true; } } return false; } public List getHierarchies() { final List hierarchies = new ArrayList(elementTypes.length); for (Type elementType : elementTypes) { hierarchies.add(elementType.getHierarchy()); } return hierarchies; } public int getArity() { return elementTypes.length; } public Dimension getDimension() { throw new UnsupportedOperationException(); } public Hierarchy getHierarchy() { throw new UnsupportedOperationException(); } public Level getLevel() { throw new UnsupportedOperationException(); } public Type getValueType() { for (Type elementType : elementTypes) { if (elementType instanceof MemberType) { MemberType memberType = (MemberType) elementType; Dimension dimension = memberType.getDimension(); if (dimension != null && dimension.isMeasures()) { return memberType.getValueType(); } } } return new ScalarType(); } public Type computeCommonType(Type type, int[] conversionCount) { if (type instanceof ScalarType) { return getValueType().computeCommonType(type, conversionCount); } if (type instanceof MemberType) { return commonTupleType( new TupleType(new Type[]{type}), conversionCount); } if (!(type instanceof TupleType)) { return null; } return commonTupleType(type, conversionCount); } public boolean isInstance(Object value) { if (!(value instanceof Object[])) { return false; } Object[] objects = (Object[]) value; if (objects.length != elementTypes.length) { return false; } for (int i = 0; i < objects.length; i++) { if (!elementTypes[i].isInstance(objects[i])) { return false; } } return true; } private Type commonTupleType(Type type, int[] conversionCount) { TupleType that = (TupleType) type; if (this.elementTypes.length < that.elementTypes.length) { return createCommonTupleType(that, conversionCount); } return that.createCommonTupleType(this, conversionCount); } private Type createCommonTupleType(TupleType that, int[] conversionCount) { final List elementTypes = new ArrayList(); for (int i = 0; i < this.elementTypes.length; i++) { Type commonType = this.elementTypes[i].computeCommonType( that.elementTypes[i], conversionCount); elementTypes.add(commonType); if (commonType == null) { return null; } } if (elementTypes.size() < that.elementTypes.length) { for (int i = elementTypes.size(); i < that.elementTypes.length; i++) { elementTypes.add(new ScalarType()); } } return new TupleType( elementTypes.toArray(new Type[elementTypes.size()])); } /** * Checks that there are no duplicate dimensions in a list of member types. * If so, the member types will form a valid tuple type. * If not, throws {@link mondrian.olap.MondrianException}. * * @param memberTypes Array of member types */ public static void checkHierarchies(MemberType[] memberTypes) { for (int i = 0; i < memberTypes.length; i++) { MemberType memberType = memberTypes[i]; for (int j = 0; j < i; j++) { MemberType member1 = memberTypes[j]; final Hierarchy hierarchy = memberType.getHierarchy(); final Hierarchy hierarchy1 = member1.getHierarchy(); if (hierarchy != null && hierarchy == hierarchy1) { throw MondrianResource.instance().DupHierarchiesInTuple.ex( hierarchy.getUniqueName()); } } } } } // End TupleType.java mondrian-3.4.1/src/main/mondrian/olap/type/NumericType.java0000644000175000017500000000173311735330606023577 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.type; /** * The type of a numeric expression. * * @author jhyde * @since Feb 17, 2005 */ public class NumericType extends ScalarType { /** * Creates a numeric type. */ public NumericType() { this("NUMERIC"); } protected NumericType(String digest) { super(digest); } public boolean equals(Object obj) { return obj instanceof NumericType && toString().equals(obj.toString()); } public boolean isInstance(Object value) { return value instanceof Number || value instanceof Character; } } // End NumericType.java mondrian-3.4.1/src/main/mondrian/olap/type/Type.java0000644000175000017500000001347711735330606022264 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.type; import mondrian.olap.*; /** * Type of an MDX expression. * * @author jhyde * @since Feb 17, 2005 */ public interface Type { /** * Returns whether this type contains a given dimension.

    * * For example: *

      *
    • DimensionType([Gender]) uses only the * [Gender] dimension.
    • *
    • TupleType(MemberType([Gender]), MemberType([Store])) * uses [Gender] and [Store] * dimensions.
    • *

    * * The definitely parameter comes into play when the * dimensional information is incomplete. For example, when applied to * TupleType(MemberType(null), MemberType([Store])), * usesDimension([Gender], false) returns true because it * is possible that the expression returns a member of the * [Gender] dimension; but * usesDimension([Gender], true) returns true because it * is possible that the expression returns a member of the * [Gender] dimension. * * @param dimension Dimension * @param definitely If true, returns true only if this type definitely * uses the dimension * * @return whether this Type uses the given Dimension */ boolean usesDimension(Dimension dimension, boolean definitely); /** * Returns whether this type contains a given hierarchy.

    * * For example: *

      *
    • HierarchyType([Customer].[Gender]) uses only the * [Customer].[Gender] hierarchy.
    • *
    • TupleType(MemberType([Customer].[Gender]), * MemberType([Store].[Store])) * uses [Gender] and [Store] * dimensions.
    • *

    * * The definitely parameter comes into play when the * dimensional information is incomplete. For example, when applied to * TupleType(MemberType([Customer]), MemberType([Store])), * usesDimension([Customer].[Gender], false) returns true * because the expression returns a member of one hierarchy of the * [Customer] dimension and that might be a member of the * [Customer].[Gender] hierarchy; but * usesDimension([Customer].[Gender], true) returns false * because might return a member of a different hierarchy, such as * [Customer].[State]. * * @param hierarchy Hierarchy * @param definitely If true, returns true only if this type definitely * uses the hierarchy * * @return whether this Type uses the given Hierarchy */ boolean usesHierarchy(Hierarchy hierarchy, boolean definitely); /** * Returns the Dimension of this Type, or null if not known. * If not applicable, throws. * * @return the Dimension of this Type, or null if not known. */ Dimension getDimension(); /** * Returns the Hierarchy of this Type, or null if not known. * If not applicable, throws. * * @return the Hierarchy of this type, or null if not known */ Hierarchy getHierarchy(); /** * Returns the Level of this Type, or null if not known. * If not applicable, throws. * * @return the Level of this Type */ Level getLevel(); /** * Returns a Type which is more general than this and the given Type. * The type returned is broad enough to hold any value of either type, * but no broader. If there is no such type, returns null. * *

    Some examples:

      *
    • The common type for StringType and NumericType is ScalarType. *
    • The common type for NumericType and DecimalType(4, 2) is * NumericType. *
    • DimensionType and NumericType have no common type. *

    * *

    If conversionCount is not null, implicit conversions * such as HierarchyType to DimensionType are considered; the parameter * is incremented by the number of conversions performed. * *

    Some examples:

      *
    • The common type for HierarchyType(hierarchy=Time.Weekly) * and LevelType(dimension=Time), if conversions are allowed, is * HierarchyType(dimension=Time). *
    * *

    One use of common types is to determine the types of the arguments * to the Iif function. For example, the call * *

    Iif(1 > 2, [Measures].[Unit Sales], * 5)
    * * has type ScalarType, because DecimalType(-1, 0) is a subtype of * ScalarType, and MeasureType can be converted implicitly to ScalarType. * * @param type Type * * @param conversionCount Number of conversions; output parameter that is * incremented each time a conversion is performed; if null, conversions * are not considered * * @return More general type */ Type computeCommonType(Type type, int[] conversionCount); /** * Returns whether a value is valid for a type. * * @param value Value * @return Whether value is valid for this type */ boolean isInstance(Object value); /** * Returns the number of fields in a tuple type, or a set of tuples. * For most other types, in particular member type, returns 1. * * @return Arity of type */ int getArity(); } // End Type.java mondrian-3.4.1/src/main/mondrian/olap/type/DecimalType.java0000644000175000017500000000453411735330606023535 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.type; import mondrian.olap.Util; /** * Subclass of {@link NumericType} which guarantees fixed number of decimal * places. In particular, a decimal with zero scale is an integer. * * @author jhyde * @since May 3, 2005 */ public class DecimalType extends NumericType { private final int precision; private final int scale; /** * Creates a decimal type with precision and scale. * *

    Examples:

      *
    • 123.45 has precision 5, scale 2. *
    • 12,345,000 has precision 5, scale -3. *
    * *

    The largest value is 10 ^ (precision - scale). Hence the largest * DECIMAL(5, -3) value is 10 ^ 8. * * @param precision Maximum number of decimal digits which a value of * this type can have. * Must be greater than zero. * Use {@link Integer#MAX_VALUE} if the precision is unbounded. * @param scale Number of digits to the right of the decimal point. */ public DecimalType(int precision, int scale) { super( precision == Integer.MAX_VALUE ? "DecimalType(" + scale + ")" : "DecimalType(" + precision + ", " + scale + ")"); Util.assertPrecondition(precision > 0, "precision > 0"); this.precision = precision; this.scale = scale; } /** * Returns the maximum number of decimal digits which a value of * this type can have. * * @return precision of this type */ public int getPrecision() { return precision; } /** * Returns the number of digits to the right of the decimal point. * * @return scale of this type */ public int getScale() { return scale; } public boolean equals(Object obj) { if (obj instanceof DecimalType) { DecimalType that = (DecimalType) obj; return this.precision == that.precision && this.scale == that.scale; } return false; } } // End DecimalType.java mondrian-3.4.1/src/main/mondrian/olap/type/ScalarType.java0000644000175000017500000000573511735330606023410 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.type; import mondrian.olap.*; /** * Base class for types which represent scalar values. * *

    An instance of this class means a scalar value of unknown type. * Usually one of the derived classes {@link NumericType}, * {@link StringType}, {@link BooleanType} is used instead. * * @author jhyde * @since Feb 17, 2005 */ public class ScalarType implements Type { private final String digest; /** * Creates a ScalarType. */ public ScalarType() { this("SCALAR"); } /** * Creates a ScalarType (or subtype) with a given digest. * *

    The digest is used for {@link #toString()} and {@link #hashCode()}. * * @param digest Description of this type */ protected ScalarType(String digest) { this.digest = digest; } public int hashCode() { return digest.hashCode(); } public boolean equals(Object obj) { return obj != null && obj.getClass() == ScalarType.class; } public String toString() { return digest; } public boolean usesDimension(Dimension dimension, boolean definitely) { return false; } public boolean usesHierarchy(Hierarchy hierarchy, boolean definitely) { return false; } public Hierarchy getHierarchy() { return null; } public Level getLevel() { return null; } public Type computeCommonType(Type type, int[] conversionCount) { if (this.equals(type)) { return this; } else if (type instanceof NullType) { return this; } else if (this instanceof NullType && type instanceof ScalarType) { return type; } else if (this.getClass() == ScalarType.class && type instanceof ScalarType) { return this; } else if (type.getClass() == ScalarType.class) { return type; } else if (type instanceof ScalarType) { return new ScalarType(); } else if (type instanceof MemberType) { return computeCommonType( ((MemberType) type).getValueType(), conversionCount); } else if (type instanceof TupleType) { return computeCommonType( ((TupleType) type).getValueType(), conversionCount); } else { return null; } } public Dimension getDimension() { return null; } public boolean isInstance(Object value) { // Somewhat pessimistic. return false; } public int getArity() { return 1; } } // End ScalarType.java mondrian-3.4.1/src/main/mondrian/olap/type/HierarchyType.java0000644000175000017500000000755011735330606024116 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.type; import mondrian.olap.*; /** * The type of an expression which represents a hierarchy. * * @author jhyde * @since Feb 17, 2005 */ public class HierarchyType implements Type { private final Dimension dimension; private final Hierarchy hierarchy; private final String digest; public static final HierarchyType Unknown = new HierarchyType(null, null); /** * Creates a type representing a hierarchy. * * @param dimension Dimension that values of this type must belong to, or * null if the dimension is unknown * @param hierarchy Hierarchy that values of this type must belong to, * null if the hierarchy is unknown */ public HierarchyType(Dimension dimension, Hierarchy hierarchy) { this.dimension = dimension; this.hierarchy = hierarchy; StringBuilder buf = new StringBuilder("HierarchyType<"); if (hierarchy != null) { buf.append("hierarchy=").append(hierarchy.getUniqueName()); } else if (dimension != null) { buf.append("dimension=").append(dimension.getUniqueName()); } buf.append(">"); this.digest = buf.toString(); } public static HierarchyType forHierarchy(Hierarchy hierarchy) { return new HierarchyType(hierarchy.getDimension(), hierarchy); } public static HierarchyType forType(Type type) { return new HierarchyType(type.getDimension(), type.getHierarchy()); } public boolean usesDimension(Dimension dimension, boolean definitely) { return this.dimension == dimension || (!definitely && this.dimension == null); } public boolean usesHierarchy(Hierarchy hierarchy, boolean definitely) { return this.hierarchy == hierarchy || (!definitely && this.hierarchy == null && (this.dimension == null || this.dimension == hierarchy.getDimension())); } public Dimension getDimension() { return dimension; } public Hierarchy getHierarchy() { return hierarchy; } public Level getLevel() { return null; } public String toString() { return digest; } public int hashCode() { return digest.hashCode(); } public boolean equals(Object obj) { if (obj instanceof HierarchyType) { HierarchyType that = (HierarchyType) obj; return Util.equals(this.hierarchy, that.hierarchy) && Util.equals(this.dimension, that.dimension); } return false; } public Type computeCommonType(Type type, int[] conversionCount) { if (!(type instanceof HierarchyType)) { return null; } HierarchyType that = (HierarchyType) type; if (this.getHierarchy() != null && this.getHierarchy().equals(that.getHierarchy())) { return this; } if (this.getDimension() != null && this.getDimension().equals(that.getDimension())) { return new HierarchyType( this.getDimension(), null); } return HierarchyType.Unknown; } public boolean isInstance(Object value) { return value instanceof Hierarchy && (hierarchy == null || value.equals(hierarchy)) && (dimension == null || ((Hierarchy) value).getDimension().equals(dimension)); } public int getArity() { return 1; } } // End HierarchyType.java mondrian-3.4.1/src/main/mondrian/olap/type/CubeType.java0000644000175000017500000000363511735330606023056 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap.type; import mondrian.olap.*; /** * The type of an expression which represents a Cube or Virtual Cube. * * @author jhyde * @since Feb 17, 2005 */ public class CubeType implements Type { private final Cube cube; /** * Creates a type representing a cube. */ public CubeType(Cube cube) { this.cube = cube; } /** * Returns the cube. * * @return Cube */ public Cube getCube() { return cube; } public boolean usesDimension(Dimension dimension, boolean definitely) { return false; } public boolean usesHierarchy(Hierarchy hierarchy, boolean definitely) { return false; } public Dimension getDimension() { return null; } public Hierarchy getHierarchy() { return null; } public Level getLevel() { return null; } public int hashCode() { return cube.hashCode(); } public boolean equals(Object obj) { if (obj instanceof CubeType) { CubeType that = (CubeType) obj; return this.cube.equals(that.cube); } else { return false; } } public Type computeCommonType(Type type, int[] conversionCount) { return this.equals(type) ? this : null; } public boolean isInstance(Object value) { return value instanceof Cube; } public int getArity() { // not meaningful; cube cannot be used in an expression throw new UnsupportedOperationException(); } } // End CubeType.java mondrian-3.4.1/src/main/mondrian/olap/type/DateTimeType.java0000644000175000017500000000147511735330606023674 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho // All Rights Reserved. */ package mondrian.olap.type; /** * The type of an expression representing a date, time or timestamp. * * @author jhyde * @since Jan 2, 2008 */ public class DateTimeType extends ScalarType { /** * Creates a DateTime type. */ public DateTimeType() { super("DATETIME"); } public boolean equals(Object obj) { return obj instanceof DateTimeType; } public boolean isInstance(Object value) { return value instanceof java.util.Date; } } // End DateTimeType.java mondrian-3.4.1/src/main/mondrian/olap/CellProperty.java0000644000175000017500000000202711735330606022773 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1998-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * Represents Cell Property. * * @author Shishir * @since 08 May, 2007 */ public class CellProperty extends QueryPart { private String name; public CellProperty(Object name) { this.name = name.toString(); } /** * checks whether cell property is equals to passed parameter. * It adds '[' and ']' before and after the propertyName before comparing. * The comparison is case insensitive. */ public boolean isNameEquals(String propertyName) { return name.equalsIgnoreCase(Util.quoteMdxIdentifier(propertyName)); } public String toString() { return name; } } // End CellProperty.java mondrian-3.4.1/src/main/mondrian/olap/CubeAccess.java0000644000175000017500000001356711735330606022362 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1999-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.resource.MondrianResource; import java.util.ArrayList; import java.util.List; /** * This class implements object of type GrantCube to apply permissions * on user's MDX query * * @author lkrivopaltsev, 01 November, 1999 */ public class CubeAccess { private boolean hasRestrictions; /** array of hierarchies with no access */ private Hierarchy[] noAccessHierarchies; /** array of members which have limited access */ private Member[] limitedMembers; private final List hierarchyList = new ArrayList(); private final List memberList = new ArrayList(); private final Cube mdxCube; /** * Creates a CubeAccess object. * *

    User's code should be responsible for * filling cubeAccess with restricted hierarchies and restricted * members by calling addSlicer(). Do NOT forget to call * {@link #normalizeCubeAccess()} after you done filling cubeAccess. */ public CubeAccess(Cube mdxCube) { this.mdxCube = mdxCube; noAccessHierarchies = null; limitedMembers = null; hasRestrictions = false; } public boolean hasRestrictions() { return hasRestrictions; } public Hierarchy[] getNoAccessHierarchies() { return noAccessHierarchies; } public Member[] getLimitedMembers() { return limitedMembers; } public List getNoAccessHierarchyList() { return hierarchyList; } public List getLimitedMemberList() { return memberList; } public boolean isHierarchyAllowed(Hierarchy mdxHierarchy) { String hierName = mdxHierarchy.getUniqueName(); if (noAccessHierarchies == null || hierName == null) { return true; } for (Hierarchy noAccessHierarchy : noAccessHierarchies) { if (hierName.equalsIgnoreCase(noAccessHierarchy.getUniqueName())) { return false; } } return true; } public Member getLimitedMemberForHierarchy(Hierarchy mdxHierarchy) { String hierName = mdxHierarchy.getUniqueName(); if (limitedMembers == null || hierName == null) { return null; } for (Member limitedMember : limitedMembers) { Hierarchy limitedHierarchy = limitedMember.getHierarchy(); if (hierName.equalsIgnoreCase(limitedHierarchy.getUniqueName())) { return limitedMember; } } return null; } /** * Adds restricted hierarchy or limited member based on bMember */ public void addGrantCubeSlicer( String sHierarchy, String sMember, boolean bMember) { if (bMember) { boolean fail = false; List sMembers = Util.parseIdentifier(sMember); SchemaReader schemaReader = mdxCube.getSchemaReader(null); Member member = schemaReader.getMemberByUniqueName(sMembers, fail); if (member == null) { throw MondrianResource.instance().MdxCubeSlicerMemberError.ex( sMember, sHierarchy, mdxCube.getUniqueName()); } // there should be only slicer per hierarchy; ignore the rest if (getLimitedMemberForHierarchy(member.getHierarchy()) == null) { memberList.add(member); } } else { boolean fail = false; Hierarchy hierarchy = mdxCube.lookupHierarchy( new Id.Segment(sHierarchy, Id.Quoting.UNQUOTED), fail); if (hierarchy == null) { throw MondrianResource.instance().MdxCubeSlicerHierarchyError .ex(sHierarchy, mdxCube.getUniqueName()); } hierarchyList.add(hierarchy); } } /** * Initializes internal arrays of restricted hierarchies and limited * members. It has to be called after all 'addSlicer()' calls. */ public void normalizeCubeAccess() { if (memberList.size() > 0) { limitedMembers = memberList.toArray(new Member[memberList.size()]); hasRestrictions = true; } if (hierarchyList.size() > 0) { noAccessHierarchies = hierarchyList.toArray( new Hierarchy[hierarchyList.size()]); hasRestrictions = true; } } public boolean equals(Object object) { if (!(object instanceof CubeAccess)) { return false; } CubeAccess cubeAccess = (CubeAccess) object; List hierarchyList = cubeAccess.getNoAccessHierarchyList(); List limitedMemberList = cubeAccess.getLimitedMemberList(); if ((this.hierarchyList.size() != hierarchyList.size()) || (this.memberList.size() != limitedMemberList.size())) { return false; } for (Hierarchy o : hierarchyList) { if (!this.hierarchyList.contains(o)) { return false; } } for (Member member : limitedMemberList) { if (!this.memberList.contains(member)) { return false; } } return true; } public int hashCode() { int h = mdxCube.hashCode(); h = Util.hash(h, hierarchyList); h = Util.hash(h, memberList); h = Util.hashArray(h, noAccessHierarchies); h = Util.hashArray(h, limitedMembers); return h; } } // End CubeAccess.java mondrian-3.4.1/src/main/mondrian/olap/DimensionBase.java0000644000175000017500000001005611735330606023070 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.resource.MondrianResource; import java.util.List; /** * Abstract implementation for a {@link Dimension}. * * @author jhyde * @since 6 August, 2001 */ public abstract class DimensionBase extends OlapElementBase implements Dimension { protected final String name; protected final String uniqueName; protected final String description; protected final boolean highCardinality; protected Hierarchy[] hierarchies; protected DimensionType dimensionType; /** * Creates a DimensionBase. * * @param name Name * @param dimensionType Type * @param highCardinality Whether high-cardinality */ protected DimensionBase( String name, String caption, boolean visible, String description, DimensionType dimensionType, boolean highCardinality) { this.name = name; this.caption = caption; this.visible = visible; this.uniqueName = Util.makeFqName(name); this.description = description; this.dimensionType = dimensionType; this.highCardinality = highCardinality; } public String getUniqueName() { return uniqueName; } public String getName() { return name; } public String getDescription() { return description; } public Hierarchy[] getHierarchies() { return hierarchies; } public Hierarchy getHierarchy() { return hierarchies[0]; } public Dimension getDimension() { return this; } public DimensionType getDimensionType() { return dimensionType; } public String getQualifiedName() { return MondrianResource.instance().MdxDimensionName.str( getUniqueName()); } public boolean isMeasures() { return getUniqueName().equals(MEASURES_UNIQUE_NAME); } public OlapElement lookupChild( SchemaReader schemaReader, Id.Segment s, MatchType matchType) { OlapElement oe = lookupHierarchy(s); // Original mondrian behavior: // If the user is looking for [Marital Status].[Marital Status] we // should not return oe "Marital Status", because he is // looking for level - we can check that by checking of hierarchy and // dimension name is the same. // if (!MondrianProperties.instance().SsasCompatibleNaming.get()) { if (oe == null || oe.getName().equalsIgnoreCase(getName())) { OlapElement oeLevel = getHierarchy().lookupChild(schemaReader, s, matchType); if (oeLevel != null) { return oeLevel; // level match overrides hierarchy match } } return oe; } else { // New (SSAS-compatible) behavior. If there is no matching // hierarchy, find the first level with the given name. if (oe != null) { return oe; } final List hierarchyList = schemaReader.getDimensionHierarchies(this); for (Hierarchy hierarchy : hierarchyList) { oe = hierarchy.lookupChild(schemaReader, s, matchType); if (oe != null) { return oe; } } return null; } } public boolean isHighCardinality() { return this.highCardinality; } private Hierarchy lookupHierarchy(Id.Segment s) { for (Hierarchy hierarchy : hierarchies) { if (Util.equalName(hierarchy.getName(), s.name)) { return hierarchy; } } return null; } } // End DimensionBase.java mondrian-3.4.1/src/main/mondrian/olap/ResultLimitExceededException.java0000644000175000017500000000142311735330606026131 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2005-2007 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * Abstract base class for exceptions that indicate some limit was exceeded. */ public abstract class ResultLimitExceededException extends MondrianException { /** * Creates a ResultLimitExceededException. * * @param message Localized message */ public ResultLimitExceededException(String message) { super(message); } } // End ResultLimitExceededException.java mondrian-3.4.1/src/main/mondrian/olap/MatchType.java0000644000175000017500000000221111735330606022240 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.olap; /** * MatchType enumerates the allowable match modes when * searching for a member based on its unique name. * * @author Zelaine Fong */ public enum MatchType { /** Match the unique name exactly, do not query database for members */ EXACT_SCHEMA, /** Match the unique name exactly */ EXACT, /** If no exact match, return the preceding member */ BEFORE, /** If no exact match, return the next member */ AFTER, /** Return the first child */ FIRST, /** Return the last child */ LAST; /** * Return true if either Exact or Exact Schema value * is selected. * * @return true if exact */ public boolean isExact() { return this == EXACT || this == EXACT_SCHEMA; } } // End MatchType.java mondrian-3.4.1/src/main/mondrian/olap/Connection.java0000644000175000017500000000645511735330606022457 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2000-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import java.io.PrintWriter; import java.util.Locale; import javax.sql.DataSource; /** * Connection to a multi-dimensional database. * * @see DriverManager * * @author jhyde */ public interface Connection { /** * Get the Connect String associated with this Connection. * * @return the Connect String (never null). */ String getConnectString(); /** * Get the name of the Catalog associated with this Connection. * * @return the Catalog name (never null). */ String getCatalogName(); /** * Get the Schema associated with this Connection. * * @return the Schema (never null). */ Schema getSchema(); /** * Closes this Connection. You may not use this * Connection after closing it. */ void close(); /** * Executes a query. * * @throws RuntimeException if another thread cancels the query's statement. * * @deprecated This method is deprecated and will be removed in * mondrian-4.0. It operates by internally creating a statement. Better * to use olap4j and explicitly create a statement. */ Result execute(Query query); /** * Returns the locale this connection belongs to. Determines, for example, * the currency string used in formatting cell values. * * @see mondrian.util.Format */ Locale getLocale(); /** * Parses an expresion. */ Exp parseExpression(String s); /** * Parses a query. */ Query parseQuery(String s); /** * Parses a statement. * * @param mdx MDX string * @return A {@link Query} if it is a SELECT statement, a * {@link DrillThrough} if it is a DRILLTHROUGH statement */ QueryPart parseStatement(String mdx); /** * Sets the privileges for the this connection. * * @pre role != null * @pre role.isMutable() */ void setRole(Role role); /** * Returns the access-control profile for this connection. * @post role != null * @post role.isMutable() */ Role getRole(); /** * Returns a schema reader with access control appropriate to the current * role. */ SchemaReader getSchemaReader(); /** * Returns the value of a connection property. * * @param name Name of property, for example "JdbcUser". * @return Value of property, or null if property is not defined. */ Object getProperty(String name); /** * Returns an object with which to explicitly control the contents of the * cache. * * @param pw Writer to which to write logging information; may be null */ CacheControl getCacheControl(PrintWriter pw); /** * Returns the data source this connection uses to create connections * to the underlying JDBC database. * * @return Data source */ DataSource getDataSource(); } // End Connection.java mondrian-3.4.1/src/main/mondrian/olap/Level.java0000644000175000017500000000346111735330606021421 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1999-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.spi.MemberFormatter; /** * A Level is a group of {@link Member}s in a {@link Hierarchy}, * all with the same attributes and at the same depth in the hierarchy. * * @author jhyde, 1 March, 1999 */ public interface Level extends OlapElement, Annotated { /** * Returns the depth of this level. * *

    Note #1: In an access-controlled context, the first visible level of * a hierarchy (as returned by {@link SchemaReader#getHierarchyLevels}) may * not have a depth of 0.

    * *

    Note #2: In a parent-child hierarchy, the depth of a member (as * returned by {@link SchemaReader#getMemberDepth}) may not be the same as * the depth of its level. */ int getDepth(); Hierarchy getHierarchy(); Level getChildLevel(); Level getParentLevel(); boolean isAll(); boolean areMembersUnique(); LevelType getLevelType(); /** Returns properties defined against this level. */ Property[] getProperties(); /** Returns properties defined against this level and parent levels. */ Property[] getInheritedProperties(); /** * Returns the object that is used to format members of this level. */ MemberFormatter getMemberFormatter(); /** * Returns the approximate number of members in this level, or * {@link Integer#MIN_VALUE} if no approximation is known. */ int getApproxRowCount(); } // End Level.java mondrian-3.4.1/src/main/mondrian/olap/Walkable.java0000644000175000017500000000130011735330606022062 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1999-2005 Julian Hyde // Copyright (C) 2005-2005 Pentaho and others // All Rights Reserved. // // jhyde, 1 March, 1999 */ package mondrian.olap; /** * An object which implements Walkable can be tree-walked by * {@link Walker}. */ interface Walkable { /** * Returns an array of the object's children. Those which are not {@link * Walkable} are ignored. */ Object[] getChildren(); } // End Walkable.java mondrian-3.4.1/src/main/mondrian/olap/NativeEvaluator.java0000644000175000017500000000112211735330606023453 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2007 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.calc.ResultStyle; /** * Allows expressions to be evaluated native, e.g. in SQL. * * @author av * @since Nov 11, 2005 */ public interface NativeEvaluator { Object execute(ResultStyle resultStyle); } // End NativeEvaluator.java mondrian-3.4.1/src/main/mondrian/olap/EnumeratedValues.java0000644000175000017500000002674311735330606023633 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1998-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import java.util.*; /** * EnumeratedValues is a helper class for declaring a set of * symbolic constants which have names, ordinals, and possibly descriptions. * The ordinals do not have to be contiguous. * *

    Typically, for a particular set of constants, you derive a class from this * interface, and declare the constants as public static final * members. Give it a private constructor, and a public static final * ClassName instance member to hold the singleton instance. * {@link Access} is a simple example of this.

    */ public class EnumeratedValues implements Cloneable { /** Map symbol names to values */ private Map valuesByName = new LinkedHashMap(); /** the smallest ordinal value */ private int min = Integer.MAX_VALUE; /** the largest ordinal value */ private int max = Integer.MIN_VALUE; // the variables below are only set AFTER makeImmutable() has been called /** An array mapping ordinals to {@link Value}s. It is biased by the * min value. It is built by {@link #makeImmutable}. */ private Value[] ordinalToValueMap; private static final String[] emptyStringArray = new String[0]; /** * Creates a new empty, mutable enumeration. */ public EnumeratedValues() { } /** * Creates an enumeration, with an array of values, and freezes it. */ public EnumeratedValues(V[] values) { for (V value : values) { register(value); } makeImmutable(); } /** * Creates an enumeration, initialize it with an array of strings, and * freezes it. */ public EnumeratedValues(String[] names) { for (int i = 0; i < names.length; i++) { register((V) new BasicValue(names[i], i, names[i])); } makeImmutable(); } /** * Create an enumeration, initializes it with arrays of code/name pairs, * and freezes it. */ public EnumeratedValues(String[] names, int[] codes) { for (int i = 0; i < names.length; i++) { register((V) new BasicValue(names[i], codes[i], names[i])); } makeImmutable(); } /** * Create an enumeration, initializes it with arrays of code/name pairs, * and freezes it. */ public EnumeratedValues(String[] names, int[] codes, String[] descriptions) { for (int i = 0; i < names.length; i++) { register((V) new BasicValue(names[i], codes[i], descriptions[i])); } makeImmutable(); } public EnumeratedValues(Class clazz) { throw new UnsupportedOperationException(); } public EnumeratedValues clone() { EnumeratedValues clone; try { clone = (EnumeratedValues) super.clone(); } catch (CloneNotSupportedException ex) { throw Util.newInternal(ex, "error while cloning " + this); } clone.valuesByName = new HashMap(valuesByName); clone.ordinalToValueMap = null; return clone; } /** * Creates a mutable enumeration from an existing enumeration, which may * already be immutable. */ public EnumeratedValues getMutableClone() { return clone(); } /** * Associates a symbolic name with an ordinal value. * * @pre value != null * @pre !isImmutable() * @pre value.getName() != null */ public void register(V value) { assert value != null : "pre: value != null"; Util.assertPrecondition(!isImmutable(), "isImmutable()"); final String name = value.getName(); Util.assertPrecondition(name != null, "value.getName() != null"); Value old = valuesByName.put(name, value); if (old != null) { throw Util.newInternal( "Enumeration already contained a value '" + old.getName() + "'"); } final int ordinal = value.getOrdinal(); min = Math.min(min, ordinal); max = Math.max(max, ordinal); } /** * Freezes the enumeration, preventing it from being further modified. */ public void makeImmutable() { ordinalToValueMap = new Value[1 + max - min]; for (Value value : valuesByName.values()) { final int index = value.getOrdinal() - min; if (ordinalToValueMap[index] != null) { throw Util.newInternal( "Enumeration has more than one value with ordinal " + value.getOrdinal()); } ordinalToValueMap[index] = value; } } public final boolean isImmutable() { return (ordinalToValueMap != null); } /** * Returns the smallest ordinal defined by this enumeration. */ public final int getMin() { return min; } /** * Returns the largest ordinal defined by this enumeration. */ public final int getMax() { return max; } /** * Returns whether ordinal is valid for this enumeration. * This method is particularly useful in pre- and post-conditions, for * example *
    *
    @param axisCode Axis code, must be a {@link AxisCode} value
         * @pre AxisCode.instance.isValid(axisCode)
    *
    * * @param ordinal Suspected ordinal from this enumeration. * @return Whether ordinal is valid. */ public final boolean isValid(int ordinal) { if ((ordinal < min) || (ordinal > max)) { return false; } if (getName(ordinal) == null) { return false; } return true; } /** * Returns the name associated with an ordinal; the return value * is null if the ordinal is not a member of the enumeration. * * @pre isImmutable() */ public final V getValue(int ordinal) { Util.assertPrecondition(isImmutable()); return (V) ordinalToValueMap[ordinal - min]; } /** * Returns the name associated with an ordinal; the return value * is null if the ordinal is not a member of the enumeration. * * @pre isImmutable() */ public final String getName(int ordinal) { Util.assertPrecondition(isImmutable()); final Value value = ordinalToValueMap[ordinal - min]; return (value == null) ? null : value.getName(); } /** * Returns the description associated with an ordinal; the return value * is null if the ordinal is not a member of the enumeration. * * @pre isImmutable() */ public final String getDescription(int ordinal) { Util.assertPrecondition(isImmutable()); final Value value = ordinalToValueMap[ordinal - min]; return (value == null) ? null : value.getDescription(); } /** * Returns the ordinal associated with a name * * @throws Error if the name is not a member of the enumeration */ public final int getOrdinal(String name) { return getValue(name, true).getOrdinal(); } /** * Returns the value associated with a name. * * @param name Name of enumerated value * @param fail Whether to throw if not found * @throws Error if the name is not a member of the enumeration and * fail is true */ public V getValue(String name, final boolean fail) { final V value = valuesByName.get(name); if (value == null && fail) { throw new Error("Unknown enum name: " + name); } return value; } /** * Returns the names in this enumeration, in declaration order. */ public String[] getNames() { return valuesByName.keySet().toArray(emptyStringArray); } /** * Returns the members of this enumeration, sorted by name. */ public List getValuesSortedByName() { List list = new ArrayList(); final String[] names = getNames(); Arrays.sort(names); for (String name : names) { list.add(getValue(name, true)); } return list; } /** * Returns an error indicating that the value is illegal. (The client needs * to throw the error.) */ public RuntimeException badValue(int ordinal) { return Util.newInternal( "bad value " + ordinal + "(" + getName(ordinal) + ") for enumeration '" + getClass().getName() + "'"); } /** * Returns an exception indicating that we didn't expect to find this value * here. */ public RuntimeException unexpected(V value) { return Util.newInternal( "Was not expecting value '" + value + "' for enumeration '" + getClass().getName() + "' in this context"); } /** * A Value represents a member of an enumerated type. If an * enumerated type is not based upon an explicit array of values, an * array of {@link BasicValue}s will implicitly be created. */ public interface Value { String getName(); int getOrdinal(); String getDescription(); } /** * BasicValue is an obvious implementation of {@link * EnumeratedValues.Value}. */ public static class BasicValue implements Value { public final String name; public final int ordinal; public final String description; /** * @pre name != null */ public BasicValue(String name, int ordinal, String description) { Util.assertPrecondition(name != null, "name != null"); this.name = name; this.ordinal = ordinal; this.description = description; } public String getName() { return name; } public int getOrdinal() { return ordinal; } public String getDescription() { return description; } /** * Returns the value's name. */ public String toString() { return name; } /** * Returns whether this value is equal to a given string. * * @deprecated I bet you meant to write * value.name_.equals(s) rather than * value.equals(s), didn't you? */ public boolean equals(String s) { return super.equals(s); } /** * Returns an error indicating that we did not expect to find this * value in this context. Typical use is in a switch * statement: * *
             * switch (fruit) {
             * case Fruit.AppleORDINAL:
             *     return 1;
             * case Fruir.OrangeORDINAL:
             *     return 2;
             * default:
             *     throw fruit.unexpected();
             * }
    */ public RuntimeException unexpected() { return Util.newInternal( "Value " + name + " of class " + getClass() + " unexpected here"); } } } // End EnumeratedValues.java mondrian-3.4.1/src/main/mondrian/olap/MemberProperty.java0000644000175000017500000000345611735330606023332 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2000-2005 Julian Hyde // Copyright (C) 2005-2006 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import java.io.PrintWriter; /** * Member property or solve order specification. * * @author jhyde, 1 March, 2000 */ public class MemberProperty extends QueryPart { private final String name; private Exp exp; public MemberProperty(String name, Exp exp) { this.name = name; this.exp = exp; } protected Object clone() { return new MemberProperty(name, (Exp) exp.clone()); } static MemberProperty[] cloneArray(MemberProperty[] x) { MemberProperty[] x2 = new MemberProperty[x.length]; for (int i = 0; i < x.length; i++) { x2[i] = (MemberProperty) x[i].clone(); } return x2; } void resolve(Validator validator) { exp = validator.validate(exp, false); } public Exp getExp() { return exp; } public String getName() { return name; } public Object[] getChildren() { return new Exp[] {exp}; } public void unparse(PrintWriter pw) { pw.print(name + " = "); exp.unparse(pw); } /** * Retrieves a property by name from an array. */ static Exp get(MemberProperty[] a, String name) { // TODO: Linear search may be a performance problem. for (int i = 0; i < a.length; i++) { if (Util.equalName(a[i].name, name)) { return a[i].exp; } } return null; } } // End MemberProperty.java mondrian-3.4.1/src/main/mondrian/olap/MemoryLimitExceededException.java0000644000175000017500000000174511735330606026132 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * Exception which indicates some resource limit was exceeded. * When a client receives a MemoryLimitExceededException the state * of the objects associated with the query execution can NOT be * counted on being correct - specifically data structures could be * in an inconsistent state or missing entirely. No attempt should be * make to access or use the result objects. */ public class MemoryLimitExceededException extends ResultLimitExceededException { public MemoryLimitExceededException(String message) { super(message); } } // End MemoryLimitExceededException.java mondrian-3.4.1/src/main/mondrian/olap/Member.java0000644000175000017500000001271511735330606021563 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1999-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import java.util.List; /** * A Member is a 'point' on a dimension of a cube. Examples are * [Time].[1997].[January], * [Customer].[All Customers], * [Customer].[USA].[CA], * [Measures].[Unit Sales]. * *

    Every member belongs to a {@link Level} of a {@link Hierarchy}. Members * except the root member have a parent, and members not at the leaf level * have one or more children. * *

    Measures are a special kind of member. They belong to their own * dimension, [Measures]. * *

    There are also special members representing the 'All' value of a * hierarchy, the null value, and the error value. * *

    Members can have member properties. Their {@link Level#getProperties} * defines which are allowed. * * @author jhyde, 2 March, 1999 */ public interface Member extends OlapElement, Comparable, Annotated { /** * Returns this member's parent, or null (not the 'null member', as * returned by {@link Hierarchy#getNullMember}) if it has no parent. * *

    In an access-control context, a member may have no visible * parents, so use {@link SchemaReader#getMemberParent}. */ Member getParentMember(); Level getLevel(); Hierarchy getHierarchy(); /** * Returns name of parent member, or empty string (not null) if we are the * root. */ String getParentUniqueName(); /** * Returns the type of member. */ MemberType getMemberType(); boolean isParentChildLeaf(); enum MemberType { UNKNOWN, REGULAR, // adMemberRegular ALL, MEASURE, FORMULA, /** * This member is its hierarchy's NULL member (such as is returned by * [Gender].[All Gender].PrevMember, for example). */ NULL } /** * Only allowable if the member is part of the WITH clause of * a query. */ void setName(String name); /** Returns whether this is the 'all' member. */ boolean isAll(); /** Returns whether this is a member of the measures dimension. */ boolean isMeasure(); /** Returns whether this is the 'null member'. */ boolean isNull(); /** * Returns whether member is equal to, a child, or a * descendent of this Member. */ boolean isChildOrEqualTo(Member member); /** Returns whether this member is computed using either a with * member clause in an mdx query or a calculated member defined in * cube. */ boolean isCalculated(); /** * Returns whether this member should be evaluated within the Evaluator. * *

    Normally {@link #isCalculated} and {@link #isEvaluated} should return * the same value, but in situations where mondrian would like to treat the * two concepts separately such in role based security, these values may * differ. * * @return true if evaluated */ boolean isEvaluated(); int getSolveOrder(); Exp getExpression(); /** * Returns a list of the ancestor members of this member. * * @deprecated Use * {@link SchemaReader#getMemberAncestors(Member, java.util.List)}. */ List getAncestorMembers(); /** * Returns whether this member is computed from a {@code WITH MEMBER} * clause in an MDX query. */ boolean isCalculatedInQuery(); /** * Returns the value of the property named propertyName. * Name match is case-sensitive. */ Object getPropertyValue(String propertyName); /** * Returns the value of the property named propertyName, * matching according to the required case-sensitivity. */ Object getPropertyValue(String propertyName, boolean matchCase); /** * Returns the formatted value of the property named * propertyName. */ String getPropertyFormattedValue(String propertyName); /** * Sets a property of this member to a given value. */ void setProperty(String name, Object value); /** * Returns the definitions of the properties this member may have. */ Property[] getProperties(); /** * Returns the ordinal of the member. */ int getOrdinal(); /** * Returns the order key of the member (relative to its siblings); * null if undefined or unavailable. */ Comparable getOrderKey(); /** * Returns whether this member is 'hidden', as per the rules which define * a ragged hierarchy. */ boolean isHidden(); /** * returns the depth of this member, which is not the level's depth * in case of parent child dimensions * @return depth */ int getDepth(); /** * Returns the system-generated data member that is associated with a * nonleaf member of a dimension. * *

    Returns this member if this member is a leaf member, or if the * nonleaf member does not have an associated data member.

    */ Member getDataMember(); } // End Member.java mondrian-3.4.1/src/main/mondrian/olap/AxisOrdinal.java0000644000175000017500000000515711735330606022573 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho // All Rights Reserved. */ package mondrian.olap; /** * AxisOrdinal describes the allowable values for an axis code. * * @author jhyde * @since Feb 21, 2003 */ public interface AxisOrdinal { /** * Returns the name of this axis, e.g. "COLUMNS", "SLICER", "AXIS(17)". * * @return Name of the axis */ String name(); /** * Returns the ordinal of this axis. * {@link StandardAxisOrdinal#COLUMNS} = 0, * {@link StandardAxisOrdinal#ROWS} = 1, etc. * * @return ordinal of this axis */ int logicalOrdinal(); /** * Returns whether this is the filter (slicer) axis. * * @return whether this is the filter axis */ boolean isFilter(); public enum StandardAxisOrdinal implements AxisOrdinal { /** No axis.*/ NONE, /** Slicer axis. */ SLICER, /** Columns axis (also known as X axis), logical ordinal = 0. */ COLUMNS, /** Rows axis (also known as Y axis), logical ordinal = 1. */ ROWS, /** Pages axis, logical ordinal = 2. */ PAGES, /** Chapters axis, logical ordinal = 3. */ CHAPTERS, /** Sections axis, logical ordinal = 4. */ SECTIONS; /** * Returns an axis with a given number. * *

    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 boolean isFilter() { return false; } }; } else { return values()[ordinal + 2]; } } public int logicalOrdinal() { return ordinal() - 2; } public boolean isFilter() { return this == SLICER; } } } // End AxisOrdinal.java mondrian-3.4.1/src/main/mondrian/olap/CubeBase.java0000644000175000017500000001600211735330606022016 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.resource.MondrianResource; import java.util.List; /** * CubeBase is an abstract implementation of {@link Cube}. * * @author jhyde * @since 6 August, 2001 */ public abstract class CubeBase extends OlapElementBase implements Cube { /** constraints indexes for adSchemaMembers * * http://msdn.microsoft.com/library/psdk/dasdk/mdx8h4k.htm * check "Restrictions in the MEMBER Rowset" under MEMBER Rowset section */ public static final int CATALOG_NAME = 0; public static final int SCHEMA_NAME = 1; public static final int CUBE_NAME = 2; public static final int DIMENSION_UNIQUE_NAME = 3; public static final int HIERARCHY_UNIQUE_NAME = 4; public static final int LEVEL_UNIQUE_NAME = 5; public static final int LEVEL_NUMBER = 6; public static final int MEMBER_NAME = 7; public static final int MEMBER_UNIQUE_NAME = 8; public static final int MEMBER_CAPTION = 9; public static final int MEMBER_TYPE = 10; public static final int Tree_Operator = 11; public static final int maxNofConstraintsForAdSchemaMember = 12; public static final int MDTREEOP_SELF = 0; public static final int MDTREEOP_CHILDREN = 1; public static final int MDPROP_USERDEFINED0 = 19; protected final String name; private final String uniqueName; private final String description; protected Dimension[] dimensions; /** * Creates a CubeBase. * * @param name Name * @param caption Caption * @param description Description * @param dimensions List of dimensions */ protected CubeBase( String name, String caption, boolean visible, String description, Dimension[] dimensions) { this.name = name; this.caption = caption; this.visible = visible; this.description = description; this.dimensions = dimensions; this.uniqueName = Util.quoteMdxIdentifier(name); } // implement OlapElement public String getName() { return name; } public String getUniqueName() { // return e.g. '[Sales Ragged]' return uniqueName; } public String getQualifiedName() { return MondrianResource.instance().MdxCubeName.str(getName()); } public Dimension getDimension() { return null; } public Hierarchy getHierarchy() { return null; } public String getDescription() { return description; } public Dimension[] getDimensions() { return dimensions; } public Hierarchy lookupHierarchy(Id.Segment s, boolean unique) { for (Dimension dimension : dimensions) { Hierarchy[] hierarchies = dimension.getHierarchies(); for (Hierarchy hierarchy : hierarchies) { String name = unique ? hierarchy.getUniqueName() : hierarchy.getName(); if (name.equals(s.name)) { return hierarchy; } } } return null; } public OlapElement lookupChild( SchemaReader schemaReader, Id.Segment s, MatchType matchType) { Dimension mdxDimension = lookupDimension(s); if (mdxDimension != null) { return mdxDimension; } final List 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; } } return null; } /** * Looks up a dimension in this cube based on a component of its name. * * @param s Name segment * @return Dimension, or null if not found */ public Dimension lookupDimension(Id.Segment s) { for (Dimension dimension : dimensions) { if (Util.equalName(dimension.getName(), s.name)) { return dimension; } } return null; } // ------------------------------------------------------------------------ /** * Returns the first level of a given type in this cube. * * @param levelType Level type * @return First level of given type, or null */ private Level getTimeLevel(LevelType levelType) { for (Dimension dimension : dimensions) { if (dimension.getDimensionType() == DimensionType.TimeDimension) { Hierarchy[] hierarchies = dimension.getHierarchies(); for (Hierarchy hierarchy : hierarchies) { Level[] levels = hierarchy.getLevels(); for (Level level : levels) { if (level.getLevelType() == levelType) { return level; } } } } } return null; } public Level getYearLevel() { return getTimeLevel(LevelType.TimeYears); } public Level getQuarterLevel() { return getTimeLevel(LevelType.TimeQuarters); } public Level getMonthLevel() { return getTimeLevel(LevelType.TimeMonths); } public Level getWeekLevel() { return getTimeLevel(LevelType.TimeWeeks); } } // End CubeBase.java mondrian-3.4.1/src/main/mondrian/olap/HierarchyBase.java0000644000175000017500000001312211735330606023056 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.resource.MondrianResource; /** * Skeleton implementation for {@link Hierarchy}. * * @author jhyde * @since 6 August, 2001 */ public abstract class HierarchyBase extends OlapElementBase implements Hierarchy { protected final Dimension dimension; /** * name and subName are the name of the * hierarchy, respectively containing and not containing dimension * name. For example: * * * * *
    uniqueName name subName
    [Time.Weekly] Time.Weekly Weekly
    [Customers] Customers null
    * *

    If {@link mondrian.olap.MondrianProperties#SsasCompatibleNaming} is * true, name and subName have the same value. */ protected final String subName; protected final String name; protected final String uniqueName; protected String description; protected Level[] levels; protected final boolean hasAll; protected String allMemberName; protected String allLevelName; protected HierarchyBase( Dimension dimension, String subName, String caption, boolean visible, String description, boolean hasAll) { this.dimension = dimension; this.hasAll = hasAll; if (caption != null) { this.caption = caption; } else if (subName == null) { this.caption = dimension.getCaption(); } else { this.caption = subName; } this.description = description; this.visible = visible; String name = dimension.getName(); if (MondrianProperties.instance().SsasCompatibleNaming.get()) { if (subName == null) { // e.g. "Time" subName = name; } this.subName = subName; this.name = subName; // e.g. "[Time].[Weekly]" for dimension "Time", hierarchy "Weekly"; // "[Time]" for dimension "Time", hierarchy "Time". this.uniqueName = subName.equals(name) ? dimension.getUniqueName() : Util.makeFqName(dimension, this.name); } else { this.subName = subName; if (this.subName != null) { // e.g. "Time.Weekly" this.name = name + "." + subName; if (this.subName.equals(name)) { this.uniqueName = dimension.getUniqueName(); } else { // e.g. "[Time.Weekly]" this.uniqueName = Util.makeFqName(this.name); } } else { // e.g. "Time" this.name = name; // e.g. "[Time]" this.uniqueName = dimension.getUniqueName(); } } } /** * Returns the name of the hierarchy sans dimension name. * * @return name of hierarchy sans dimension name */ public String getSubName() { return subName; } // implement MdxElement public String getUniqueName() { return uniqueName; } public String getUniqueNameSsas() { return Util.makeFqName(dimension, name); } public String getName() { return name; } public String getQualifiedName() { return MondrianResource.instance().MdxHierarchyName.str( getUniqueName()); } public abstract boolean isRagged(); public String getDescription() { return description; } public Dimension getDimension() { return dimension; } public Level[] getLevels() { return levels; } public Hierarchy getHierarchy() { return this; } public boolean hasAll() { return hasAll; } public boolean equals(OlapElement mdxElement) { // Use object identity, because a private hierarchy can have the same // name as a public hierarchy. return (this == mdxElement); } public OlapElement lookupChild( SchemaReader schemaReader, Id.Segment s, MatchType matchType) { OlapElement oe = Util.lookupHierarchyLevel(this, s.name); if (oe == null) { oe = Util.lookupHierarchyRootMember( schemaReader, this, s, matchType); } if (getLogger().isDebugEnabled()) { StringBuilder buf = new StringBuilder(64); buf.append("HierarchyBase.lookupChild: "); buf.append("name="); buf.append(getName()); buf.append(", childname="); buf.append(s); if (oe == null) { buf.append(" returning null"); } else { buf.append(" returning elementname=").append(oe.getName()); } getLogger().debug(buf.toString()); } return oe; } public String getAllMemberName() { return allMemberName; } /** * Returns the name of the 'all' level in this hierarchy. * * @return name of the 'all' level */ public String getAllLevelName() { return allLevelName; } } // End HierarchyBase.java mondrian-3.4.1/src/main/mondrian/olap/Annotation.java0000644000175000017500000000144411735330606022463 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * User-defined property on a metadata element. * * @see mondrian.olap.Annotated * * @author jhyde */ public interface Annotation { /** * Returns the name of this annotation. Must be unique within its element. * * @return Annotation name */ String getName(); /** * Returns the value of this annotation. Usually a string. * * @return Annotation value */ Object getValue(); } // End Annotation.java mondrian-3.4.1/src/main/mondrian/olap/ExpCacheDescriptor.java0000644000175000017500000000617311735330606024074 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho // All Rights Reserved. */ package mondrian.olap; import mondrian.calc.*; import mondrian.calc.impl.BetterExpCompiler; import java.util.ArrayList; import java.util.List; /** * Holds information necessary to add an expression to the expression result * cache (see {@link Evaluator#getCachedResult(ExpCacheDescriptor)}). * * @author jhyde * @since Aug 16, 2005 */ public class ExpCacheDescriptor { private final Exp exp; private int[] dependentHierarchyOrdinals; private final Calc calc; /** * Creates a descriptor with a given compiled expression. * * @param exp Expression * @param calc Compiled expression * @param evaluator Evaluator */ public ExpCacheDescriptor(Exp exp, Calc calc, Evaluator evaluator) { this.calc = calc; this.exp = exp; computeDepends(calc, evaluator); } /** * Creates a descriptor. * * @param exp Expression * @param evaluator Evaluator */ public ExpCacheDescriptor(Exp exp, Evaluator evaluator) { this(exp, new BetterExpCompiler(evaluator, null)); } /** * Creates a descriptor. * * @param exp Expression * @param compiler Compiler */ public ExpCacheDescriptor(Exp exp, ExpCompiler compiler) { this.exp = exp; // Compile expression. Calc calc = compiler.compile(exp); if (calc == null) { // now allow conversions calc = compiler.compileAs(exp, null, ResultStyle.ANY_ONLY); } this.calc = calc; // Compute list of dependent dimensions. computeDepends(calc, compiler.getEvaluator()); } private void computeDepends(Calc calc, Evaluator evaluator) { final List ordinalList = new ArrayList(); final Member[] members = evaluator.getMembers(); for (int i = 0; i < members.length; i++) { Hierarchy hierarchy = members[i].getHierarchy(); if (calc.dependsOn(hierarchy)) { ordinalList.add(i); } } dependentHierarchyOrdinals = new int[ordinalList.size()]; for (int i = 0; i < dependentHierarchyOrdinals.length; i++) { dependentHierarchyOrdinals[i] = ordinalList.get(i); } } public Exp getExp() { return exp; } public Calc getCalc() { return calc; } public Object evaluate(Evaluator evaluator) { return calc.evaluate(evaluator); } /** * Returns the ordinals of the hierarchies which this expression is * dependent upon. When the cache descriptor is used to generate a cache * key, the key will consist of a member from each of these hierarchies. */ public int[] getDependentHierarchyOrdinals() { return dependentHierarchyOrdinals; } } // End ExpCacheDescriptor.java mondrian-3.4.1/src/main/mondrian/olap/MemberFormatter.java0000644000175000017500000000136511735330606023446 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * An SPI to redefine the caption displayed for members. * * @deprecated Use {@link mondrian.spi.MemberFormatter}. This interface * exists for temporary backwards compatibility and will be removed * in mondrian-4.0. * * @author hhaas * @since 6 October, 2004 */ public interface MemberFormatter extends mondrian.spi.MemberFormatter { } // End MemberFormatter.java mondrian-3.4.1/src/main/mondrian/olap/NameResolver.java0000644000175000017500000002136311735330606022755 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2011 Pentaho // All Rights Reserved. */ package mondrian.olap; import org.olap4j.mdx.IdentifierNode; import org.olap4j.mdx.IdentifierSegment; import java.util.List; /** * Resolves a list of segments (a parsed identifier) to an OLAP element. */ public final class NameResolver { /** * Creates a NameResolver. */ public NameResolver() { } /** * Resolves a list of segments (a parsed identifier) to an OLAP element. * * @param parent Parent element to search in, usually a cube * @param segments Exploded compound name, such as {"Products", * "Product Department", "Produce"} * @param failIfNotFound If the element is not found, determines whether * to return null or throw an error * @param category Type of returned element, a {@link Category} value; * {@link Category#Unknown} if it doesn't matter. * @param matchType Match type * @param namespaces Namespaces wherein to find child element at each step * @return OLAP element with given name, or null if not found */ public OlapElement resolve( OlapElement parent, List segments, boolean failIfNotFound, int category, MatchType matchType, List namespaces) { OlapElement element; if (matchType == MatchType.EXACT) { element = resolveExact( parent, segments, namespaces); } else { element = resolveInexact( parent, segments, matchType, namespaces); } if (element != null) { element = nullify(category, element); } if (element == null && failIfNotFound) { throw Util.newElementNotFoundException( category, new IdentifierNode(segments)); } return element; } private OlapElement resolveInexact( OlapElement parent, List segments, MatchType matchType, List namespaces) { OlapElement element = parent; for (final IdentifierSegment segment : segments) { assert element != null; OlapElement child = null; for (Namespace namespace : namespaces) { child = namespace.lookupChild(element, segment, matchType); if (child != null) { switch (matchType) { case EXACT: case EXACT_SCHEMA: break; case BEFORE: if (!Util.matches(segment, child.getName())) { matchType = MatchType.LAST; } break; case AFTER: if (!Util.matches(segment, child.getName())) { matchType = MatchType.FIRST; } break; } break; } } if (child == null) { return null; } element = child; } return element; } // same logic as resolveInexact, pared down for common case // matchType == EXACT private OlapElement resolveExact( OlapElement parent, List segments, List namespaces) { OlapElement element = parent; for (final IdentifierSegment segment : segments) { assert element != null; OlapElement child = null; for (Namespace namespace : namespaces) { child = namespace.lookupChild(element, segment); if (child != null) { break; } } if (child == null) { return null; } element = child; } return element; } /** * Converts an element to the required type, converting if possible, * returning null if it is not of the required type and cannot be converted. * * @param category Desired category of element * @param element Element * @return Element of the desired category, or null */ private OlapElement nullify(int category, OlapElement element) { switch (category) { case Category.Unknown: return element; case Category.Member: return element instanceof Member ? element : null; case Category.Level: return element instanceof Level ? element : null; case Category.Hierarchy: if (element instanceof Hierarchy) { return element; } else if (element instanceof Dimension) { final Dimension dimension = (Dimension) element; final Hierarchy[] hierarchies = dimension.getHierarchies(); if (hierarchies.length == 1) { return hierarchies[0]; } return null; } else { return null; } case Category.Dimension: return element instanceof Dimension ? element : null; case Category.Set: return element instanceof NamedSet ? element : null; default: throw Util.newInternal("unexpected: " + category); } } /** * Returns whether a formula (representing a calculated member or named * set) matches a given parent and name segment. * * @param formula Formula * @param parent Parent element * @param segment Name segment * @return Whether formula matches */ public static boolean matches( Formula formula, OlapElement parent, IdentifierSegment segment) { if (!Util.matches(segment, formula.getName())) { return false; } if (formula.isMember()) { final Member formulaMember = formula.getMdxMember(); if (formulaMember.getParentMember() != null) { if (parent instanceof Member) { // SSAS matches calc members very loosely. For example, // [Foo].[Z] will match calc member [Foo].[X].[Y].[Z]. return formulaMember.getParentMember().isChildOrEqualTo( (Member) parent); } else if (parent instanceof Hierarchy) { return formulaMember.getParentMember().getHierarchy() .equals(parent); } else { return parent.getUniqueName().equals( formulaMember.getParentMember().getUniqueName()); } } else { // If parent is not a member, member must be a root member. return parent.equals(formulaMember.getHierarchy()) || parent.equals(formulaMember.getDimension()); } } else { return parent instanceof Cube; } } /** * Naming context within which elements are defined. * *

    Elements' names are hierarchical, so elements are resolved one * name segment at a time. It is possible for an element to be defined * in a different namespace than its parent: for example, stored member * [Dim].[Hier].[X].[Y] might have a child [Dim].[Hier].[X].[Y].[Z] which * is a calculated member defined using a WITH MEMBER clause.

    */ public interface Namespace { /** * Looks up a child element, using a match type for inexact matching. * *

    If {@code matchType} is {@link MatchType#EXACT}, effect is * identical to calling * {@link #lookupChild(OlapElement, org.olap4j.mdx.IdentifierSegment)}.

    * *

    Match type is ignored except when searching for members.

    * * @param parent Parent element * @param segment Name segment * @param matchType Match type * @return Olap element, or null */ OlapElement lookupChild( OlapElement parent, IdentifierSegment segment, MatchType matchType); /** * Looks up a child element. * * @param parent Parent element * @param segment Name segment * @return Olap element, or null */ OlapElement lookupChild( OlapElement parent, IdentifierSegment segment); } } // End NameResolver.java mondrian-3.4.1/src/main/mondrian/olap/QueryTimeoutException.java0000644000175000017500000000145011735330606024701 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2005-2007 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * Exception which indicates that a query executed for longer than its allowed * time and was automatically canceled. */ public class QueryTimeoutException extends ResultLimitExceededException { /** * Creates a QueryTimeoutException. * * @param message Localized error message */ public QueryTimeoutException(String message) { super(message); } } // End QueryTimeoutException.java mondrian-3.4.1/src/main/mondrian/olap/ValidatorImpl.java0000644000175000017500000003236311735330606023124 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2009-2009 Pentaho // All Rights Reserved. */ package mondrian.olap; import mondrian.mdx.*; import mondrian.olap.fun.Resolver; import mondrian.olap.type.Type; import mondrian.olap.type.TypeUtil; import mondrian.resource.MondrianResource; import mondrian.util.ArrayStack; import java.util.*; /** * Default implementation of {@link mondrian.olap.Validator}. * *

    Uses a stack to help us guess the type of our parent expression * before we've completely resolved our children -- necessary, * unfortunately, when figuring out whether the "*" operator denotes * multiplication or crossjoin. * *

    Keeps track of which nodes have already been resolved, so we don't * try to resolve nodes which have already been resolved. (That would not * be wrong, but can cause resolution to be an O(2^N) * operation.) * *

    The concrete implementing class needs to implement * {@link #getQuery()} and {@link #defineParameter(Parameter)}. * * @author jhyde */ abstract class ValidatorImpl implements Validator { protected final ArrayStack stack = new ArrayStack(); private final FunTable funTable; private final Map resolvedNodes = new HashMap(); private final QueryPart placeHolder = Literal.zero; private final Map> scopeExprs = new HashMap>(); /** * Creates a ValidatorImpl. * * @param funTable Function table * * @pre funTable != null */ protected ValidatorImpl(FunTable funTable) { Util.assertPrecondition(funTable != null, "funTable != null"); this.funTable = funTable; } public Exp validate(Exp exp, boolean scalar) { Exp resolved; try { resolved = (Exp) resolvedNodes.get(exp); } catch (ClassCastException e) { // A classcast exception will occur if there is a String // placeholder in the map. This is an internal error -- should // not occur for any query, valid or invalid. throw Util.newInternal( e, "Infinite recursion encountered while validating '" + Util.unparse(exp) + "'"); } if (resolved == null) { try { stack.push((QueryPart) exp); // To prevent recursion, put in a placeholder while we're // resolving. resolvedNodes.put((QueryPart) exp, placeHolder); resolved = exp.accept(this); Util.assertTrue(resolved != null); resolvedNodes.put((QueryPart) exp, (QueryPart) resolved); } finally { stack.pop(); } } if (scalar) { final Type type = resolved.getType(); if (!TypeUtil.canEvaluate(type)) { String exprString = Util.unparse(resolved); throw MondrianResource.instance().MdxMemberExpIsSet.ex( exprString); } } return resolved; } public void validate(ParameterExpr parameterExpr) { ParameterExpr resolved = (ParameterExpr) resolvedNodes.get(parameterExpr); if (resolved != null) { return; // already resolved } try { stack.push(parameterExpr); resolvedNodes.put(parameterExpr, placeHolder); resolved = (ParameterExpr) parameterExpr.accept(this); assert resolved != null; resolvedNodes.put(parameterExpr, resolved); } finally { stack.pop(); } } public void validate(MemberProperty memberProperty) { MemberProperty resolved = (MemberProperty) resolvedNodes.get(memberProperty); if (resolved != null) { return; // already resolved } try { stack.push(memberProperty); resolvedNodes.put(memberProperty, placeHolder); memberProperty.resolve(this); resolvedNodes.put(memberProperty, memberProperty); } finally { stack.pop(); } } public void validate(QueryAxis axis) { final QueryAxis resolved = (QueryAxis) resolvedNodes.get(axis); if (resolved != null) { return; // already resolved } try { stack.push(axis); resolvedNodes.put(axis, placeHolder); axis.resolve(this); resolvedNodes.put(axis, axis); } finally { stack.pop(); } } public void validate(Formula formula) { final Formula resolved = (Formula) resolvedNodes.get(formula); if (resolved != null) { return; // already resolved } try { stack.push(formula); resolvedNodes.put(formula, placeHolder); formula.accept(this); resolvedNodes.put(formula, formula); } finally { stack.pop(); } } public FunDef getDef( Exp[] args, String funName, Syntax syntax) { // Compute signature first. It makes debugging easier. final String signature = syntax.getSignature( funName, Category.Unknown, ExpBase.getTypes(args)); // Resolve function by its upper-case name first. If there is only one // function with that name, stop immediately. If there is more than // function, use some custom method, which generally involves looking // at the type of one of its arguments. List resolvers = funTable.getResolvers(funName, syntax); assert resolvers != null; final List conversionList = new ArrayList(); int minConversionCost = Integer.MAX_VALUE; List matchDefs = new ArrayList(); List matchConversionList = null; for (Resolver resolver : resolvers) { conversionList.clear(); FunDef def = resolver.resolve(args, this, conversionList); if (def != null) { int conversionCost = sumConversionCost(conversionList); if (conversionCost < minConversionCost) { minConversionCost = conversionCost; matchDefs.clear(); matchDefs.add(def); matchConversionList = new ArrayList(conversionList); } else if (conversionCost == minConversionCost) { matchDefs.add(def); } else { // ignore this match -- it required more coercions than // other overloadings we've seen } } } switch (matchDefs.size()) { case 0: throw MondrianResource.instance().NoFunctionMatchesSignature.ex( signature); case 1: break; default: final StringBuilder buf = new StringBuilder(); for (FunDef matchDef : matchDefs) { if (buf.length() > 0) { buf.append(", "); } buf.append(matchDef.getSignature()); } throw MondrianResource.instance() .MoreThanOneFunctionMatchesSignature.ex( signature, buf.toString()); } final FunDef matchDef = matchDefs.get(0); for (Resolver.Conversion conversion : matchConversionList) { conversion.checkValid(); conversion.apply(this, Arrays.asList(args)); } return matchDef; } public boolean alwaysResolveFunDef() { return false; } private int sumConversionCost( List conversionList) { int cost = 0; for (Resolver.Conversion conversion : conversionList) { cost += conversion.getCost(); } return cost; } public boolean canConvert( int ordinal, Exp fromExp, int to, List conversions) { return TypeUtil.canConvert( ordinal, fromExp.getType(), to, conversions); } public boolean requiresExpression() { return requiresExpression(stack.size() - 1); } private boolean requiresExpression(int n) { if (n < 1) { return false; } final Object parent = stack.get(n - 1); if (parent instanceof Formula) { return ((Formula) parent).isMember(); } else if (parent instanceof ResolvedFunCall) { final ResolvedFunCall funCall = (ResolvedFunCall) parent; if (funCall.getFunDef().getSyntax() == Syntax.Parentheses) { return requiresExpression(n - 1); } else { int k = whichArg(funCall, (Exp) stack.get(n)); if (k < 0) { // Arguments of call have mutated since call was placed // on stack. Presumably the call has already been // resolved correctly, so the answer we give here is // irrelevant. return false; } final FunDef funDef = funCall.getFunDef(); final int[] parameterTypes = funDef.getParameterCategories(); return parameterTypes[k] != Category.Set; } } else if (parent instanceof UnresolvedFunCall) { final UnresolvedFunCall funCall = (UnresolvedFunCall) parent; if (funCall.getSyntax() == Syntax.Parentheses || funCall.getFunName().equals("*")) { return requiresExpression(n - 1); } else { int k = whichArg(funCall, (Exp) stack.get(n)); if (k < 0) { // Arguments of call have mutated since call was placed // on stack. Presumably the call has already been // resolved correctly, so the answer we give here is // irrelevant. return false; } return requiresExpression(funCall, k); } } else { return false; } } /** * Returns whether the kth argument to a function call * has to be an expression. */ boolean requiresExpression( UnresolvedFunCall funCall, int k) { // The function call has not been resolved yet. In fact, this method // may have been invoked while resolving the child. Consider this: // CrossJoin([Measures].[Unit Sales] * [Measures].[Store Sales]) // // In order to know whether to resolve '*' to the multiplication // operator (which returns a scalar) or the crossjoin operator // (which returns a set) we have to know what kind of expression is // expected. List resolvers = funTable.getResolvers( funCall.getFunName(), funCall.getSyntax()); for (Resolver resolver2 : resolvers) { if (!resolver2.requiresExpression(k)) { // This resolver accepts a set in this argument position, // therefore we don't REQUIRE a scalar expression. return false; } } return true; } public FunTable getFunTable() { return funTable; } public Parameter createOrLookupParam( boolean definition, String name, Type type, Exp defaultExp, String description) { final SchemaReader schemaReader = getQuery().getSchemaReader(false); Parameter param = schemaReader.getParameter(name); if (definition) { if (param != null) { if (param.getScope() == Parameter.Scope.Statement) { ParameterImpl paramImpl = (ParameterImpl) param; paramImpl.setDescription(description); paramImpl.setDefaultExp(defaultExp); paramImpl.setType(type); } return param; } param = new ParameterImpl( name, defaultExp, description, type); // Append it to the list of known parameters. defineParameter(param); return param; } else { if (param != null) { return param; } throw MondrianResource.instance().UnknownParameter.ex(name); } } private int whichArg(final FunCall node, final Exp arg) { final Exp[] children = node.getArgs(); for (int i = 0; i < children.length; i++) { if (children[i] == arg) { return i; } } return -1; } /** * Defines a parameter. * * @param param Parameter */ protected abstract void defineParameter(Parameter param); } // End ValidatorImpl.java mondrian-3.4.1/src/main/mondrian/olap/UnionRoleImpl.java0000644000175000017500000001642011735330606023105 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2007-2009 Pentaho // All Rights Reserved. */ package mondrian.olap; import java.util.ArrayList; import java.util.List; /** * Implementation of {@link Role} which combines the privileges of several * roles and has the superset of their privileges. * * @see mondrian.olap.RoleImpl#union(java.util.List) * * @author jhyde * @since Nov 26, 2007 */ class UnionRoleImpl implements Role { private final List roleList; /** * Creates a UnionRoleImpl. * * @param roleList List of constituent roles */ UnionRoleImpl(List roleList) { this.roleList = new ArrayList(roleList); } public Access getAccess(Schema schema) { Access access = Access.NONE; for (Role role : roleList) { access = max(access, role.getAccess(schema)); if (access == Access.ALL) { break; } } return access; } /** * Returns the larger of two enum values. Useful if the enums are sorted * so that more permissive values come after less permissive values. * * @param t1 First value * @param t2 Second value * @return larger of the two values */ private static > T max(T t1, T t2) { if (t1.ordinal() > t2.ordinal()) { return t1; } else { return t2; } } public Access getAccess(Cube cube) { Access access = Access.NONE; for (Role role : roleList) { access = max(access, role.getAccess(cube)); if (access == Access.ALL) { break; } } return access; } public Access getAccess(Dimension dimension) { Access access = Access.NONE; for (Role role : roleList) { access = max(access, role.getAccess(dimension)); if (access == Access.ALL) { break; } } return access; } public Access getAccess(Hierarchy hierarchy) { Access access = Access.NONE; for (Role role : roleList) { access = max(access, role.getAccess(hierarchy)); if (access == Access.ALL) { break; } } return access; } public HierarchyAccess getAccessDetails(final Hierarchy hierarchy) { List list = new ArrayList(); for (Role role : roleList) { final HierarchyAccess accessDetails = role.getAccessDetails(hierarchy); if (accessDetails != null) { list.add(accessDetails); } } // If none of the roles call out access details, we shouldn't either. if (list.isEmpty()) { return null; } HierarchyAccess hierarchyAccess = new UnionHierarchyAccessImpl(hierarchy, list); if (list.size() > 5) { hierarchyAccess = new RoleImpl.CachingHierarchyAccess(hierarchyAccess); } return hierarchyAccess; } public Access getAccess(Level level) { Access access = Access.NONE; for (Role role : roleList) { access = max(access, role.getAccess(level)); if (access == Access.ALL) { break; } } return access; } public Access getAccess(Member member) { assert member != null; HierarchyAccess hierarchyAccess = getAccessDetails(member.getHierarchy()); if (hierarchyAccess != null) { return hierarchyAccess.getAccess(member); } return getAccess(member.getDimension()); } public Access getAccess(NamedSet set) { Access access = Access.NONE; for (Role role : roleList) { access = max(access, role.getAccess(set)); if (access == Access.ALL) { break; } } return access; } public boolean canAccess(OlapElement olapElement) { for (Role role : roleList) { if (role.canAccess(olapElement)) { return true; } } return false; } /** * Implementation of {@link mondrian.olap.Role.HierarchyAccess} that * gives access to an object if any one of the constituent hierarchy * accesses has access to that object. */ private class UnionHierarchyAccessImpl implements HierarchyAccess { private final List list; /** * Creates a UnionHierarchyAccessImpl. * * @param hierarchy Hierarchy * @param list List of underlying hierarchy accesses */ UnionHierarchyAccessImpl( Hierarchy hierarchy, List list) { Util.discard(hierarchy); this.list = list; } public Access getAccess(Member member) { Access access = Access.NONE; final int roleCount = roleList.size(); for (int i = 0; i < roleCount; i++) { Role role = roleList.get(i); access = max(access, role.getAccess(member)); if (access == Access.ALL) { break; } } return access; } public int getTopLevelDepth() { int access = Integer.MAX_VALUE; for (HierarchyAccess hierarchyAccess : list) { access = Math.min( access, hierarchyAccess.getTopLevelDepth()); if (access == 0) { break; } } return access; } public int getBottomLevelDepth() { int access = -1; for (HierarchyAccess hierarchyAccess : list) { access = Math.max( access, hierarchyAccess.getBottomLevelDepth()); } return access; } public RollupPolicy getRollupPolicy() { RollupPolicy rollupPolicy = RollupPolicy.HIDDEN; for (HierarchyAccess hierarchyAccess : list) { rollupPolicy = max( rollupPolicy, hierarchyAccess.getRollupPolicy()); if (rollupPolicy == RollupPolicy.FULL) { break; } } return rollupPolicy; } public boolean hasInaccessibleDescendants(Member member) { for (HierarchyAccess hierarchyAccess : list) { switch (hierarchyAccess.getAccess(member)) { case NONE: continue; case CUSTOM: return true; case ALL: if (!hierarchyAccess.hasInaccessibleDescendants(member)) { return false; } } } return true; } } } // End UnionRoleImpl.java mondrian-3.4.1/src/main/mondrian/olap/Parameter.java0000644000175000017500000000565111735330606022275 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2010 Pentaho // All Rights Reserved. */ package mondrian.olap; import mondrian.olap.type.Type; /** * Parameter to a Query. * *

    A parameter is not an expression; see {@link mondrian.mdx.ParameterExpr}. * * @author jhyde * @since Jul 22, 2006 */ public interface Parameter { /** * Returns the scope where this parameter is defined. * * @return Scope of the parameter */ Scope getScope(); /** * Returns the type of this Parameter. * * @return Type of the parameter */ Type getType(); /** * Returns the expression which provides the default value for this * Parameter. Never null. * * @return Default value expression of the parameter */ Exp getDefaultExp(); /** * Returns the name of this Parameter. * * @return Name of the parameter */ String getName(); /** * Returns the description of this Parameter. * * @return Description of the parameter */ String getDescription(); /** * Returns whether the value of this Parameter can be modified in a query. * * @return Whether parameter is modifiable */ boolean isModifiable(); /** * Returns the value of this parameter. * *

    If {@link #setValue(Object)} has not been called, returns the default * value of this parameter. * *

    The type of the value is (depending on the type of the parameter) * a {@link String}, {@link Number}, or {@link Member}. * * @return The value of this parameter */ Object getValue(); /** * Sets the value of this parameter. * * @param value Value of the parameter; must be a {@link String}, * a {@link Double}, or a {@link mondrian.olap.Member} */ void setValue(Object value); /** * Returns whether the value of this parameter has been set. * *

    If the value has not been set, this parameter will return its default * value. * *

    Setting a parameter to {@code null} is not equivalent to unsetting it. * To unset a parameter, call {@link #unsetValue}. * * @return Whether this parameter has been assigned a value */ boolean isSet(); /** * Unsets the value of this parameter. * *

    After calling this method, the parameter will revert to its default * value, as if {@link #setValue(Object)} had not been called, and * {@link #isSet()} will return {@code false}. */ void unsetValue(); /** * Scope where a parameter is defined. */ enum Scope { System, Schema, Connection, Statement } } // End Parameter.java mondrian-3.4.1/src/main/mondrian/olap/Position.java0000644000175000017500000000122411735330606022151 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2007 Pentaho and others // All Rights Reserved. // // jhyde, 6 August, 2001 */ package mondrian.olap; import java.util.List; /** * A Position is an item on an {@link Axis}. It contains * one or more {@link Member}s. * * @author jhyde * @since 6 August, 2001 */ public interface Position extends List { } // End Position.java mondrian-3.4.1/src/main/mondrian/olap/LevelBase.java0000644000175000017500000000627411735330606022221 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.resource.MondrianResource; import mondrian.spi.MemberFormatter; /** * Skeleton implementation of {@link Level}. * * @author jhyde * @since 6 August, 2001 */ public abstract class LevelBase extends OlapElementBase implements Level { protected final Hierarchy hierarchy; protected final String name; protected final String uniqueName; protected final String description; protected final int depth; protected final LevelType levelType; protected MemberFormatter memberFormatter; protected int approxRowCount; protected LevelBase( Hierarchy hierarchy, String name, String caption, boolean visible, String description, int depth, LevelType levelType) { this.hierarchy = hierarchy; this.name = name; this.caption = caption; this.visible = visible; this.description = description; this.uniqueName = Util.makeFqName(hierarchy, name); this.depth = depth; this.levelType = levelType; } /** * Sets the approximate number of members in this Level. * @see #getApproxRowCount() */ public void setApproxRowCount(int approxRowCount) { this.approxRowCount = approxRowCount; } // from Element public String getQualifiedName() { return MondrianResource.instance().MdxLevelName.str(getUniqueName()); } public LevelType getLevelType() { return levelType; } public String getUniqueName() { return uniqueName; } public String getName() { return name; } public String getDescription() { return description; } public Hierarchy getHierarchy() { return hierarchy; } public Dimension getDimension() { return hierarchy.getDimension(); } public int getDepth() { return depth; } public Level getChildLevel() { int childDepth = depth + 1; Level[] levels = hierarchy.getLevels(); return (childDepth < levels.length) ? levels[childDepth] : null; } public Level getParentLevel() { int parentDepth = depth - 1; Level[] levels = hierarchy.getLevels(); return (parentDepth >= 0) ? levels[parentDepth] : null; } public abstract boolean isAll(); public boolean isMeasure() { return hierarchy.getName().equals("Measures"); } public OlapElement lookupChild( SchemaReader schemaReader, Id.Segment s, MatchType matchType) { return areMembersUnique() ? Util.lookupHierarchyRootMember( schemaReader, hierarchy, s, matchType) : null; } public MemberFormatter getMemberFormatter() { return memberFormatter; } } // End LevelBase.java mondrian-3.4.1/src/main/mondrian/olap/Axis.java0000644000175000017500000000117511735330606021256 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2007 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import java.util.List; /** * A Axis is a component of a {@link Result}. * It contains a list of {@link Position}s. * * @author jhyde * @since 6 August, 2001 */ public interface Axis { List getPositions(); } // End Axis.java mondrian-3.4.1/src/main/mondrian/olap/Result.java0000644000175000017500000000212411735330606021623 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import java.io.PrintWriter; /** * A Result is the result of running an MDX query. See {@link * Connection#execute}. * * @author jhyde * @since 6 August, 2001 */ public interface Result { /** Returns the query which generated this result. */ Query getQuery(); /** Returns the non-slicer axes. */ Axis[] getAxes(); /** Returns the slicer axis. */ Axis getSlicerAxis(); /** Returns the cell at a given set of coordinates. For example, in a result * with 4 columns and 6 rows, the top-left cell has coordinates [0, 0], * and the bottom-right cell has coordinates [3, 5]. */ Cell getCell(int[] pos); void print(PrintWriter pw); void close(); } // End Result.java mondrian-3.4.1/src/main/mondrian/olap/Access.java0000644000175000017500000000142311735330606021547 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2006 Pentaho // All Rights Reserved. */ package mondrian.olap; /** * Access enumerates the allowable access rights. * * @author jhyde * @since Feb 21, 2003 */ public enum Access { /** No access to an object. */ NONE, /** Custom access to an object (described by other parameters). */ CUSTOM, /** Access to all shared dimensions (applies to schema grant). */ ALL_DIMENSIONS, /** All access to an object. */ ALL; } // End Access.javamondrian-3.4.1/src/main/mondrian/olap/FunCall.java0000644000175000017500000000301311735330606021667 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1998-2005 Julian Hyde // Copyright (C) 2005-2006 Pentaho and others // All Rights Reserved. */ package mondrian.olap; /** * A FunCall is a function applied to a list of operands. * *

    The parser creates function calls as an * {@link mondrian.mdx.UnresolvedFunCall unresolved function call}. * The validator converts it to a * {@link mondrian.mdx.ResolvedFunCall resolved function call}, * which has a {@link FunDef function definition} and extra type information. * * @author jhyde * @since Jan 6, 2006 */ public interface FunCall extends Exp { /** * Returns the indexth argument to this function * call. * * @param index Ordinal of the argument * @return indexth argument to this function call */ Exp getArg(int index); /** * Returns the arguments to this function. * * @return array of arguments */ Exp[] getArgs(); /** * Returns the number of arguments to this function. * * @return number of arguments */ int getArgCount(); /** * Returns the name of the function. */ String getFunName(); /** * Returns the syntax of the call. */ Syntax getSyntax(); } // End FunCall.java mondrian-3.4.1/src/main/mondrian/olap/MondrianPropertiesBase.java0000644000175000017500000002133111735330606024765 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import org.apache.log4j.Logger; import org.eigenbase.util.property.TriggerableProperties; import java.io.*; import java.net.*; import java.util.Enumeration; /** * MondrianProperties contains the properties which determine the * behavior of a mondrian instance. * *

    There is a method for property valid in a * mondrian.properties file. Although it is possible to retrieve * properties using the inherited {@link java.util.Properties#getProperty(String)} * method, we recommend that you use methods in this class. * *

    Note to developers

    * * If you add a property, you must:
      * *
    • Add a property definition to MondrianProperties.xml.
    • * *
    • Re-generate MondrianProperties.java using PropertyUtil.
    • * *
    • Modify the default mondrian.properties file checked into * source control, with a description of the property and its default * value.
    • * *
    • Modify the * * Configuration Specification.
    • *
    * *

    Similarly if you update or delete a property. * * @author jhyde * @since 22 December, 2002 */ public abstract class MondrianPropertiesBase extends TriggerableProperties { private final PropertySource propertySource; private int populateCount; private static final Logger LOGGER = Logger.getLogger(MondrianProperties.class); protected static final String mondrianDotProperties = "mondrian.properties"; protected MondrianPropertiesBase(PropertySource propertySource) { this.propertySource = propertySource; } public boolean triggersAreEnabled() { return ((MondrianProperties) this).EnableTriggers.get(); } /** * Represents a place that properties can be read from, and remembers the * timestamp that we last read them. */ public interface PropertySource { /** * Opens an input stream from the source. * *

    Also checks the 'last modified' time, which will determine whether * {@link #isStale()} returns true. * * @return input stream */ InputStream openStream(); /** * Returns true if the source exists and has been modified since last * time we called {@link #openStream()}. * * @return whether source has changed since it was last read */ boolean isStale(); /** * Returns the description of this source, such as a filename or URL. * * @return description of this PropertySource */ String getDescription(); } /** * Implementation of {@link PropertySource} which reads from a * {@link java.io.File}. */ static class FilePropertySource implements PropertySource { private final File file; private long lastModified; FilePropertySource(File file) { this.file = file; this.lastModified = 0; } public InputStream openStream() { try { this.lastModified = file.lastModified(); return new FileInputStream(file); } catch (FileNotFoundException e) { throw Util.newInternal( e, "Error while opening properties file '" + file + "'"); } } public boolean isStale() { return file.exists() && file.lastModified() > this.lastModified; } public String getDescription() { return "file=" + file.getAbsolutePath() + " (exists=" + file.exists() + ")"; } } /** * Implementation of {@link PropertySource} which reads from a * {@link java.net.URL}. */ static class UrlPropertySource implements PropertySource { private final URL url; private long lastModified; UrlPropertySource(URL url) { this.url = url; } private URLConnection getConnection() { try { return url.openConnection(); } catch (IOException e) { throw Util.newInternal( e, "Error while opening properties file '" + url + "'"); } } public InputStream openStream() { try { final URLConnection connection = getConnection(); this.lastModified = connection.getLastModified(); return connection.getInputStream(); } catch (IOException e) { throw Util.newInternal( e, "Error while opening properties file '" + url + "'"); } } public boolean isStale() { final long lastModified = getConnection().getLastModified(); return lastModified > this.lastModified; } public String getDescription() { return url.toExternalForm(); } } /** * Loads this property set from: the file "$PWD/mondrian.properties" (if it * exists); the "mondrian.properties" in the CLASSPATH; and from the system * properties. */ public void populate() { // Read properties file "mondrian.properties", if it exists. If we have // read the file before, only read it if it is newer. loadIfStale(propertySource); URL url = null; File file = new File(mondrianDotProperties); if (file.exists() && file.isFile()) { // Read properties file "mondrian.properties" from PWD, if it // exists. try { url = file.toURI().toURL(); } catch (MalformedURLException e) { LOGGER.warn( "Mondrian: file '" + file.getAbsolutePath() + "' could not be loaded", e); } } else { // Then try load it from classloader url = MondrianPropertiesBase.class.getClassLoader().getResource( mondrianDotProperties); } if (url != null) { load(new UrlPropertySource(url)); } else { LOGGER.warn( "mondrian.properties can't be found under '" + new File(".").getAbsolutePath() + "' or classloader"); } // copy in all system properties which start with "mondrian." int count = 0; for (Enumeration keys = System.getProperties().keys(); keys.hasMoreElements();) { String key = (String) keys.nextElement(); String value = System.getProperty(key); if (key.startsWith("mondrian.")) { // NOTE: the super allows us to bybase calling triggers // Is this the correct behavior? if (LOGGER.isDebugEnabled()) { LOGGER.debug("populate: key=" + key + ", value=" + value); } super.setProperty(key, value); count++; } } if (populateCount++ == 0) { LOGGER.info( "Mondrian: loaded " + count + " system properties"); } } /** * Reads properties from a source. * If the source does not exist, or has not changed since we last read it, * does nothing. * * @param source Source of properties */ private void loadIfStale(PropertySource source) { if (source.isStale()) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Mondrian: loading " + source.getDescription()); } load(source); } } /** * Tries to load properties from a URL. Does not fail, just prints success * or failure to log. * * @param source Source to read properties from */ private void load(final PropertySource source) { try { load(source.openStream()); if (populateCount == 0) { LOGGER.info( "Mondrian: properties loaded from '" + source.getDescription() + "'"); } } catch (IOException e) { LOGGER.error( "Mondrian: error while loading properties " + "from '" + source.getDescription() + "' (" + e + ")"); } } } // End MondrianPropertiesBase.java mondrian-3.4.1/src/main/mondrian/olap/QueryTiming.java0000644000175000017500000001542111735330606022626 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import java.util.*; /** * Provides hooks for recording timing information of components of Query * execution. * *

    NOTE: This class is experimental and subject to change/removal * without notice. * *

    Code that executes as part of a Query can call * {@link QueryTiming#markStart(String)} * before executing, and {@link QueryTiming#markEnd(String)} afterwards, or can * track execution times manually and call * {@link QueryTiming#markFull(String, long)}. * *

    To read timing information, add a handler to the statement using * {@link mondrian.server.Statement#enableProfiling} and implement the * {@link mondrian.spi.ProfileHandler#explain(String, QueryTiming)} method. * * @author jbarnett */ public class QueryTiming { private boolean enabled; private final Stack currentTimings = new Stack(); private final Map> timings = new HashMap>(); private final Map fullTimings = new HashMap(); /** * Initializes (or re-initializes) a query timing, also setting whether * enabled. All previous stats are removed. * * @param enabled Whether to collect stats in future */ public void init(boolean enabled) { this.enabled = enabled; currentTimings.clear(); timings.clear(); fullTimings.clear(); } public void done() { } /** * Marks the start of a Query component's execution. * * @param name Name of the component */ public final void markStart(String name) { if (enabled) { markStartInternal(name); } } /** * Marks the end of a Query component's execution. * * @param name Name of the component */ public final void markEnd(String name) { if (enabled) { long tstamp = System.currentTimeMillis(); markEndInternal(name, tstamp); } } /** * Marks the duration of a Query component's execution. * * @param name Name of the component * @param duration Duration of the execution */ public final void markFull(String name, long duration) { if (enabled) { markFullInternal(name, duration); } } private void markStartInternal(String name) { currentTimings.push(new TimingInfo(name)); } private void markEndInternal(String name, long tstamp) { if (currentTimings == null || currentTimings.isEmpty() || !currentTimings.peek().name.equals(name)) { throw new IllegalStateException("end but no start for " + name); } TimingInfo finished = currentTimings.pop(); assert finished.name.equals(name); finished.markEnd(tstamp); List timingList = timings.get(finished.name); if (timingList == null) { timingList = new ArrayList(); timings.put(finished.name, timingList); } timingList.add(new StartEnd(finished.startTime, finished.endTime)); } private void markFullInternal(String name, long duration) { DurationCount p = fullTimings.get(name); if (p == null) { p = new DurationCount(); fullTimings.put(name, p); } p.count++; p.duration += duration; } public String toString() { StringBuilder sb = new StringBuilder(); for (Map.Entry> entry : timings.entrySet()) { if (sb.length() > 0) { sb.append(Util.nl); } long total = 0; for (StartEnd durection : entry.getValue()) { total += (durection.endTime - durection.startTime); } int count = entry.getValue().size(); sb.append(entry.getKey()) .append(" invoked ") .append(count) .append(" times for total of ") .append(total) .append("ms. (Avg. ") .append(total / count) .append("ms/invocation)"); } for (Map.Entry entry : fullTimings.entrySet()) { if (sb.length() > 0) { sb.append(Util.nl); } sb.append(entry.getKey()) .append(" invoked ") .append(entry.getValue().count) .append(" times for total of ") .append(entry.getValue().duration) .append("ms. (Avg. ") .append(entry.getValue().duration / entry.getValue().count) .append("ms/invocation)"); } return sb.toString(); } /** * @return a collection of all Query component names */ public Collection getTimingKeys() { Set keys = new HashSet(); keys.addAll(timings.keySet()); keys.addAll(fullTimings.keySet()); return keys; } /** * @param key Name of the Query component to get timing information on * @return a List of durations */ public List getTimings(String key) { List timingList = new ArrayList(); List regTime = timings.get(key); if (regTime != null) { for (StartEnd timing : regTime) { timingList.add(timing.endTime - timing.startTime); } } DurationCount qTime = fullTimings.get(key); if (qTime != null) { final Long duration = qTime.duration; for (int i = 0; i < qTime.count; i++) { timingList.add(duration); } } return timingList; } private static class TimingInfo { private final String name; private final long startTime; private long endTime; private TimingInfo(String name) { this.name = name; this.startTime = System.currentTimeMillis(); } private void markEnd(long tstamp) { this.endTime = tstamp; } } private static class StartEnd { final long startTime; final long endTime; public StartEnd(long startTime, long endTime) { this.startTime = startTime; this.endTime = endTime; } } private static class DurationCount { long duration; long count; } } // End QueryTiming.java mondrian-3.4.1/src/main/mondrian/olap/Cell.java0000644000175000017500000001106611735330606021231 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import org.olap4j.AllocationPolicy; import org.olap4j.Scenario; import java.util.List; /** * A Cell is an item in the grid of a {@link Result}. It is * returned by {@link Result#getCell}. * * @author jhyde * @since 6 August, 2001 */ public interface Cell { /** * Returns the coordinates of this Cell in its {@link Result}. * * @return Coordinates of this Cell */ List getCoordinateList(); /** * Returns the cell's raw value. This is useful for sending to further data * processing, such as plotting a chart. * *

    The value is never null. It may have various types:

      *
    • if the cell is null, the value is {@link Util#nullValue};
    • *
    • if the cell contains an error, the value is an instance of * {@link Throwable};
    • *
    • otherwise, the type of this value depends upon the type of * measure: possible types include {@link java.math.BigDecimal}, * {@link Double}, {@link Integer} and {@link String}.
    • *
    * * @post return != null * @post (return instanceof Throwable) == isError() * @post (return instanceof Util.NullCellValue) == isNull() */ Object getValue(); /** * Return the cached formatted string, that survives an aggregate cache * clear. */ String getCachedFormatString(); /** * Returns the cell's value formatted according to the current format * string, and locale-specific settings such as currency symbol. The * current format string may itself be derived via an expression. For more * information about format strings, see {@link mondrian.util.Format}. */ String getFormattedValue(); /** * Returns whether the cell's value is null. */ boolean isNull(); /** * Returns whether the cell's calculation returned an error. */ boolean isError(); /** * Returns a SQL query that, when executed, returns drill through data * for this Cell. * *

    If the parameter {@code 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 {@code 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(boolean extendedContext); /** * Returns true if drill through is possible for this Cell. * Returns false if the Cell is based on a calculated measure. * * @return Whether can drill through on this cell */ boolean canDrillThrough(); /** * Returns the number of fact table rows which contributed to this Cell. */ int getDrillThroughCount(); /** * Returns the value of a property. * * @param propertyName Case-sensitive property name * @return Value of property */ Object getPropertyValue(String propertyName); /** * Returns the context member for a particular dimension. * * The member is defined as follows (note that there is always a * member):

      * *
    • If the dimension appears on one of the visible axes, the context * member is simply the member on the current row or column. * *
    • If the dimension appears in the slicer, the context member is the * member of that dimension in the slier. * *
    • Otherwise, the context member is the default member of that * dimension (usually the 'all' member).
    * * @param hierarchy Hierarchy * @return current member of given hierarchy */ Member getContextMember(Hierarchy hierarchy); /** * Helper method to implement {@link org.olap4j.Cell#setValue}. * * @param scenario Scenario * @param newValue New value * @param allocationPolicy Allocation policy * @param allocationArgs Arguments for allocation policy */ void setValue( Scenario scenario, Object newValue, AllocationPolicy allocationPolicy, Object... allocationArgs); } // End Cell.java mondrian-3.4.1/src/main/mondrian/olap/DelegatingSchemaReader.java0000644000175000017500000002053111735330606024656 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // Copyright (C) 2004-2005 TONBELLER AG // All Rights Reserved. */ package mondrian.olap; import mondrian.calc.Calc; import mondrian.rolap.RolapSchema; import mondrian.rolap.RolapUtil; import java.util.List; import javax.sql.DataSource; /** * DelegatingSchemaReader implements {@link SchemaReader} by * delegating all methods to an underlying {@link SchemaReader}. * *

    It is a convenient base class if you want to override just a few of * {@link SchemaReader}'s methods.

    * * @author jhyde * @since Feb 26, 2003 */ public abstract class DelegatingSchemaReader implements SchemaReader { protected final SchemaReader schemaReader; /** * Creates a DelegatingSchemaReader. * * @param schemaReader Parent reader to delegate unhandled calls to */ protected DelegatingSchemaReader(SchemaReader schemaReader) { this.schemaReader = schemaReader; } public RolapSchema getSchema() { return schemaReader.getSchema(); } public Role getRole() { return schemaReader.getRole(); } public Cube getCube() { return schemaReader.getCube(); } public List getCubeDimensions(Cube cube) { return schemaReader.getCubeDimensions(cube); } public List getDimensionHierarchies(Dimension dimension) { return schemaReader.getDimensionHierarchies(dimension); } public List getHierarchyRootMembers(Hierarchy hierarchy) { return schemaReader.getHierarchyRootMembers(hierarchy); } public Member getMemberParent(Member member) { return schemaReader.getMemberParent(member); } public Member substitute(Member member) { return schemaReader.substitute(member); } public List getMemberChildren(Member member) { return schemaReader.getMemberChildren(member); } public List getMemberChildren(List members) { return schemaReader.getMemberChildren(members); } public void getParentChildContributingChildren( Member dataMember, Hierarchy hierarchy, List list) { schemaReader.getParentChildContributingChildren( dataMember, hierarchy, list); } public int getMemberDepth(Member member) { return schemaReader.getMemberDepth(member); } public final Member getMemberByUniqueName( List uniqueNameParts, boolean failIfNotFound) { return getMemberByUniqueName( uniqueNameParts, failIfNotFound, MatchType.EXACT); } public Member getMemberByUniqueName( List uniqueNameParts, boolean failIfNotFound, MatchType matchType) { return schemaReader.getMemberByUniqueName( uniqueNameParts, failIfNotFound, matchType); } public final OlapElement lookupCompound( OlapElement parent, List names, boolean failIfNotFound, int category) { return lookupCompound( parent, names, failIfNotFound, category, MatchType.EXACT); } public final OlapElement lookupCompound( OlapElement parent, List names, boolean failIfNotFound, int category, MatchType matchType) { if (MondrianProperties.instance().SsasCompatibleNaming.get()) { return new NameResolver().resolve( parent, Util.toOlap4j(names), failIfNotFound, category, matchType, getNamespaces()); } return lookupCompoundInternal( parent, names, failIfNotFound, category, matchType); } public List getNamespaces() { return schemaReader.getNamespaces(); } public OlapElement lookupCompoundInternal( OlapElement parent, List names, boolean failIfNotFound, int category, MatchType matchType) { return schemaReader.lookupCompound( parent, names, failIfNotFound, category, matchType); } public Member getCalculatedMember(List nameParts) { return schemaReader.getCalculatedMember(nameParts); } public NamedSet getNamedSet(List nameParts) { return schemaReader.getNamedSet(nameParts); } public void getMemberRange( Level level, Member startMember, Member endMember, List list) { schemaReader.getMemberRange(level, startMember, endMember, list); } public Member getLeadMember(Member member, int n) { return schemaReader.getLeadMember(member, n); } public int compareMembersHierarchically(Member m1, Member m2) { return schemaReader.compareMembersHierarchically(m1, m2); } public OlapElement getElementChild(OlapElement parent, Id.Segment name) { return getElementChild(parent, name, MatchType.EXACT); } public OlapElement getElementChild( OlapElement parent, Id.Segment name, MatchType matchType) { return schemaReader.getElementChild(parent, name, matchType); } public List getLevelMembers( Level level, boolean includeCalculated) { return schemaReader.getLevelMembers(level, includeCalculated); } public List getHierarchyLevels(Hierarchy hierarchy) { return schemaReader.getHierarchyLevels(hierarchy); } public Member getHierarchyDefaultMember(Hierarchy hierarchy) { return schemaReader.getHierarchyDefaultMember(hierarchy); } public boolean isDrillable(Member member) { return schemaReader.isDrillable(member); } public boolean isVisible(Member member) { return schemaReader.isVisible(member); } public Cube[] getCubes() { return schemaReader.getCubes(); } public List getCalculatedMembers(Hierarchy hierarchy) { return schemaReader.getCalculatedMembers(hierarchy); } public List getCalculatedMembers(Level level) { return schemaReader.getCalculatedMembers(level); } public List getCalculatedMembers() { return schemaReader.getCalculatedMembers(); } public int getChildrenCountFromCache(Member member) { return schemaReader.getChildrenCountFromCache(member); } public int getLevelCardinality( Level level, boolean approximate, boolean materialize) { return schemaReader.getLevelCardinality( level, approximate, materialize); } public List getLevelMembers(Level level, Evaluator context) { return schemaReader.getLevelMembers(level, context); } public List getMemberChildren(Member member, Evaluator context) { return schemaReader.getMemberChildren(member, context); } public List getMemberChildren( List members, Evaluator context) { return schemaReader.getMemberChildren(members, context); } public void getMemberAncestors(Member member, List ancestorList) { schemaReader.getMemberAncestors(member, ancestorList); } public Member lookupMemberChildByName( Member member, Id.Segment memberName, MatchType matchType) { return schemaReader.lookupMemberChildByName( member, memberName, matchType); } public NativeEvaluator getNativeSetEvaluator( FunDef fun, Exp[] args, Evaluator evaluator, Calc calc) { return schemaReader.getNativeSetEvaluator(fun, args, evaluator, calc); } public Parameter getParameter(String name) { return schemaReader.getParameter(name); } public DataSource getDataSource() { return schemaReader.getDataSource(); } public SchemaReader withoutAccessControl() { return schemaReader.withoutAccessControl(); } public SchemaReader withLocus() { return RolapUtil.locusSchemaReader( schemaReader.getSchema().getInternalConnection(), this); } } // End DelegatingSchemaReader.java mondrian-3.4.1/src/main/mondrian/olap/Parser.cup0000644000175000017500000015346211735330606021463 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1999-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. // // Grammar condensed from OLE DB reference // (http://www.microsoft.com/data/reference/oledb2.htm) by jhyde on 990120. */ import java_cup.runtime.*; import java.util.*; import java.math.BigDecimal; import mondrian.mdx.*; import mondrian.parser.MdxParserValidator; import mondrian.resource.MondrianResource; import mondrian.server.Statement; // Preliminaries to set up and use the scanner. // action code {: ... :}; parser code {: // Generated from Parser.cup. private Scanner scanner; private String queryString; Statement statement; private FunTable funTable; boolean strictValidation; MdxParserValidator.QueryPartFactory factory; /** * Recursively parses an expression. */ Exp recursivelyParseExp(String s) { return new Parser().parseExpression( factory, statement, s, false, funTable); } /** * Parses a string to create a {@link Query}. * Called only by {@link ConnectionBase#parseQuery}. */ public QueryPart parseInternal( MdxParserValidator.QueryPartFactory factory, Statement statement, String queryString, boolean debug, FunTable funTable, boolean strictValidation) { Symbol parse_tree; this.factory = factory; this.scanner = new StringScanner(queryString, debug); this.statement = statement; this.queryString = queryString; this.funTable = funTable; this.strictValidation = strictValidation; try { if (debug) { parse_tree = debug_parse(); } else { parse_tree = parse(); } return (QueryPart) parse_tree.value; } catch (Exception e) { // "Error while parsing MDX statement '%1'" throw MondrianResource.instance().WhileParsingMdx.ex( queryString, e); } finally { this.scanner = null; this.statement = null; this.queryString = null; this.funTable = null; } } /** * Parses a string to create an {@link Exp}. * Called only by {@link ConnectionBase#parseExpression}. */ public Exp parseExpression( MdxParserValidator.QueryPartFactory factory, Statement statement, String queryString, boolean debug, FunTable funTable) { this.factory = factory; Symbol parse_tree = null; this.scanner = new PrefixScanner( debug, new StringScanner(queryString, debug), new int[] {ParserSym._VALUE_EXPRESSION}); this.statement = statement; this.queryString = queryString; this.funTable = funTable; try { if (debug) { parse_tree = debug_parse(); } else { parse_tree = parse(); } return (Exp) parse_tree.value; } catch (Exception e) { // "Syntax error while parsing MDX expression '%1'" throw MondrianResource.instance().WhileParsingMdxExpression.ex( queryString, e); } finally { this.scanner = null; this.statement = null; this.queryString = null; this.funTable = null; } } /** * Scanner which returns a list of pre-programmed tokens, then switches * to a parent scanner. */ private static class PrefixScanner extends Scanner { private final Scanner parent; private final int tokens[]; private int i; PrefixScanner(boolean debug, Scanner parent, int[] tokens) { super(debug); this.parent = parent; this.tokens = tokens; } public void init() throws java.io.IOException { i = 0; parent.init(); } public Symbol next_token() throws java.io.IOException { if (i < tokens.length) { return new Symbol(tokens[i++], 0, 0, null); } return parent.next_token(); } void getLocation(Symbol symbol, int[] loc) { parent.getLocation(symbol, loc); } } public static class FactoryImpl implements MdxParserValidator.QueryPartFactory { public Query makeQuery( Statement statement, Formula[] formulae, QueryAxis[] axes, String cube, Exp slicer, QueryPart[] cellProps, boolean strictValidation) { final QueryAxis slicerAxis = slicer == null ? null : new QueryAxis( false, slicer, AxisOrdinal.StandardAxisOrdinal.SLICER, QueryAxis.SubtotalVisibility.Undefined, new Id[0]); return new Query( statement, formulae, axes, cube, slicerAxis, cellProps, strictValidation); } public DrillThrough makeDrillThrough( Query query, int maxRowCount, int firstRowOrdinal, List returnList) { return new DrillThrough( query, maxRowCount, firstRowOrdinal, returnList); } /** * Creates an {@link Explain} object. */ public Explain makeExplain( QueryPart query) { return new Explain(query); } } // Override lr_parser methods for NLS. With this error handling scheme, // all errors are fatal. public void report_fatal_error( String message, Object info) throws java.lang.Exception { done_parsing(); try { report_error(message, info); } catch (Throwable e) { // "MDX parser cannot recover from previous error(s)" throw MondrianResource.instance().MdxFatalError.ex(e); } } // override lr_parser method public void report_error(String message, Object info) { // "Error: %1" throw MondrianResource.instance().MdxError.ex(message); } // override lr_parser method public void syntax_error(Symbol cur_token) { String s = cur_token.value.toString(); if (cur_token.left != -1) { int loc[] = new int[2]; scanner.getLocation(cur_token, loc); // "Syntax error at line %2, column %3, token '%1'" throw MondrianResource.instance().MdxSyntaxErrorAt.ex( s, Integer.toString(loc[0] + 1), Integer.toString(loc[1] + 1)); } else { // "Syntax error at token '%1'" throw MondrianResource.instance().MdxSyntaxError.ex(s); } } public void unrecovered_syntax_error(Symbol cur_token) throws java.lang.Exception { // "Couldn't repair and continue parse" String sFatalSyntaxError = MondrianResource.instance().MdxFatalSyntaxError.str(); report_fatal_error(sFatalSyntaxError, cur_token); } /** * Returns whether the given identifier can possibly the name of an operator * with property syntax. * *

    For example, isFunCall("ORDINAL") * returns true because there is a "<Level>.Ordinal" property.

    */ protected boolean isFunCall(String s) { return funTable.isProperty(s); } static Id[] toIdArray(List idList) { if (idList == null || idList.size() == 0) { return EmptyIdArray; } else { return idList.toArray(new Id[idList.size()]); } } static Exp[] toExpArray(List expList) { if (expList == null || expList.size() == 0) { return EmptyExpArray; } else { return expList.toArray(new Exp[expList.size()]); } } static Formula[] toFormulaArray(List formulaList) { if (formulaList == null || formulaList.size() == 0) { return EmptyFormulaArray; } else { return formulaList.toArray(new Formula[formulaList.size()]); } } static MemberProperty[] toMemberPropertyArray(List mpList) { if (mpList == null || mpList.size() == 0) { return EmptyMemberPropertyArray; } else { return mpList.toArray(new MemberProperty[mpList.size()]); } } static QueryPart[] toQueryPartArray(List qpList) { if (qpList == null || qpList.size() == 0) { return EmptyQueryPartArray; } else { return qpList.toArray(new QueryPart[qpList.size()]); } } static QueryAxis[] toQueryAxisArray(List qpList) { if (qpList == null || qpList.size() == 0) { return EmptyQueryAxisArray; } else { return qpList.toArray(new QueryAxis[qpList.size()]); } } private static final MemberProperty[] EmptyMemberPropertyArray = new MemberProperty[0]; private static final Exp[] EmptyExpArray = new Exp[0]; private static final Formula[] EmptyFormulaArray = new Formula[0]; private static final Id[] EmptyIdArray = new Id[0]; private static final QueryPart[] EmptyQueryPartArray = new QueryPart[0]; private static final QueryAxis[] EmptyQueryAxisArray = new QueryAxis[0]; :}; init with {: scanner.init(); :}; scan with {: return scanner.next_token(); :}; // Terminals (tokens returned by the scanner). // a. Keywords. terminal AND, AS, AXIS, CASE, CAST, CELL, CHAPTERS, COLUMNS, DIMENSION, DRILLTHROUGH, ELSE, EMPTY, END, EXPLAIN, FIRSTROWSET, FOR, FROM, IN, IS, MATCHES, MAXROWS, MEMBER, NON, NOT, NULL, ON, OR, PAGES, PLAN, PROPERTIES, RETURN, ROWS, SECTIONS, SELECT, SET, THEN, WHEN, WHERE, XOR, WITH, _VALUE_EXPRESSION; // b. Symbols terminal ASTERISK, // * BANG, // ! COLON, // : COMMA, // , CONCAT, // || DOT, // . EQ, // = GE, // >= GT, // > LBRACE, // { LE, // <= LPAREN, // ( LT, // < MINUS, // - NE, // <> PLUS, // + RBRACE, // } RPAREN, //) SOLIDUS; // / // c. Typed terminals terminal BigDecimal NUMBER; terminal String ID; terminal String QUOTED_ID; terminal String AMP_QUOTED_ID; terminal String STRING; terminal String FORMULA_STRING; terminal String UNKNOWN; // a token the lexer doesn't like! // Non terminals non terminal QueryAxis axis_specification; non terminal UnresolvedFunCall aliasedExpression; non terminal Exp case_expression, else_clause_opt, expression, expression_or_empty, factor, slicer_specification, term, term2, term3, term4, term5, return_item, value_expression, value_expression_opt, value_expression_primary, where_clause_opt; non terminal Query select_statement; non terminal QueryPart statement, select_or_drillthrough_statement, drillthrough_statement, explain_statement; non terminal Id bang_compound_id, compound_id, cube_name, cube_specification, member_name, set_name; non terminal AxisOrdinal.StandardAxisOrdinal axis_name; non terminal String comp_op, keyword; non terminal Id.Segment identifier, quoted_identifier, unquoted_identifier, amp_quoted_identifier; non terminal Formula member_specification, set_specification, single_formula_specification; non terminal MemberProperty member_property_definition; non terminal cell_opt, cell_property, dimension_opt, property; non terminal Boolean non_empty_opt; non terminal List axis_specification_list, axis_specification_list_opt, cell_props, cell_props_opt, comma_member_property_def_list_opt, dim_props, dim_props_opt, exp_list, exp_list_opt, formula_specification, member_property_def_list, property_list, cell_property_list, return_opt, return_item_list, when_list, with_formula_specification_opt; non terminal Exp[] when_clause; non terminal BigDecimal axis_number; non terminal Number maxrows_opt, firstrowset_opt; // Start symbol start with statement; // ---------------------------------------------------------------------------- // Elements // // // ::= | quoted_identifier ::= QUOTED_ID:i {: RESULT = new Id.Segment(i, Id.Quoting.QUOTED); :} ; amp_quoted_identifier ::= AMP_QUOTED_ID:i {: RESULT = new Id.Segment(i, Id.Quoting.KEY); :} ; unquoted_identifier ::= ID:i {: RESULT = new Id.Segment(i, Id.Quoting.UNQUOTED); :} | keyword:i {: RESULT = new Id.Segment(i, Id.Quoting.UNQUOTED); :} ; identifier ::= unquoted_identifier | quoted_identifier ; // a keyword (unlike a reserved word) can be converted back into an // identifier in some contexts keyword ::= DIMENSION {: RESULT = "Dimension"; :} | PROPERTIES {: RESULT = "Properties"; :} ; compound_id ::= identifier:i {: RESULT = new Id(i); :} | compound_id:hd DOT identifier:tl {: RESULT = hd.append(tl); :} ; bang_compound_id ::= identifier:i {: RESULT = new Id(i); :} | bang_compound_id:hd BANG identifier:tl {: RESULT = hd.append(tl); :} ; // // ::= [{ | // | }...] // // ::= // { | } // [{ | }...] // // // ::= // // ::= // // ::= end_delimiter> // // ::= !! // // ::= [ [ [ ] ] [].] // cube_name ::= compound_id ; // // ::= // // ::= // // ::= // // ::= [.] // | [[.]< dimension_name>.] // // jhyde: Need more lookahead for this to work... just use id in place of // dim_hier. // dim_hier ::= id; // // ::= // | .DIMENSION // | .DIMENSION // | .DIMENSION // // ::= // | < member>.HIERARCHY // | .HIERARCHY // // ::= [.]< identifier> // | .LEVELS() // | .LEVEL // // Note: The first production is for the case when named levels are // supported. The second production is for the case when named levels are not // supported. // // // ::= [.] // | . // | . // | // // Note: The . recognizes the fact that members may // sometimes need to be qualified by their parent names. For example, // "Portland" is a city in Oregon, and also in Maine. So a reference to // Portland will be either Oregon.Portland or Maine.Portland. // // // ::= | // // ::= CATALOG_NAME // | SCHEMA_NAME // | CUBE_NAME // | DIMENSION_UNIQUE_NAME // | HIERARCHY_UNIQUE_NAME // | LEVEL_UNIQUE_NAME // | LEVEL_NUMBER // | MEMBER_UNIQUE_NAME // | MEMBER_NAME // | MEMBER_TYPE // | MEMBER_GUID // | MEMBER_CAPTION // | MEMBER_ORDINAL // | CHILDREN_CARDINALITY // | PARENT_LEVEL // | PARENT_UNIQUE_NAME // | PARENT_COUNT // | DESCRIPTION // // ::= . // | . // | . // // Note: The three productions recognize the fact that a property can apply to // all the members of a dimension, or all the members of a level, or just to a // member. // // // ::= // | ( [, ...]) // | // // Note: Each member must be from a different dimension or from a different // hierarchy. // // // ::= : // // Note: Each member must be from the same hierarchy and the same level. // // // | // | [| [, |...]] // // Note: Duplicates (if any) are always retained when specifying sets in this // fashion. // // // | () // // ::= { // // ::= } // // ::= [ // // ::= ] // // ::= _ // // ::= a | b | c | ...| z | A | B | C | ... | Z // // ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 // // Leveling Rules for Elements // // The ability to qualify a cube name by one or more of , // , or is optional. Consumers can check the value // of the property MDPROP_MDX_OBJQUALIFICATION to see whether a provider // supports cube qualification. // // // The ability to qualify a dimension name by a cube name is // optional. Consumers can check the value of the property // MDPROP_MDX_OBJQUALIFICATION to see whether a provider supports dimension // qualification. // // // The ability to qualify a hierarchy name by a dimension name or by cube name // and dimension name is optional. Consumers can check the value of the // property MDPROP_MDX_OBJQUALIFICATION to see whether a provider supports // hierarchy qualification. // // // The provider need support only one of the two productions for . If it // supports // // ::= [.] // // then the ability to qualify by is optional. // // Consumers can check the value of the property MDPROP_NAMED_LEVELS to see if // the provider supports named levels. If it does, then the consumer can check // MDPROP_MDX_OBJQUALIFICATION to see whether named levels can be qualified by // . // // The ability to qualify a member by a level, a member, or is // optional. Consumers can check the value of the property // MDPROP_MDX_OBJQUALIFICATION to see whether a provider supports member // qualification. // // Note: Several leveling rules above make it optional to qualify // multidimensional schema object names. However, this does not imply that the // ability to generate unique names for members, levels, dimensions, and // hierarchies is optional. Providers are required to furnish unique names in // the schema rowsets for these objects. If providers generate unique names by // means other than qualification, then the ability to qualify is optional. For // more information, see 'Provider Implementation Considerations for Unique // Names' in Chapter 2. // // // ---------------------------------------------------------------------------- // // // Expressions // // Note: The syntax of is generally the same as SQL-92, // subclause 6.11, . Differences are: // // [.VALUE], [.VALUE], and are new // values for . // // // There are new values for , mainly for statistical // analysis. // // // The BNF for , , and // have been shortened by eliminating several // intermediate nonterminals. // // ::= // | // // ::= // | { | } value_expression ::= term5 | value_expression:x OR term5:y {: RESULT = new UnresolvedFunCall("OR", Syntax.Infix, new Exp[] {x, y}); :} | value_expression:x XOR term5:y {: RESULT = new UnresolvedFunCall("XOR", Syntax.Infix, new Exp[] {x, y}); :} ; term5 ::= term4 | term5:x AND term4:y {: RESULT = new UnresolvedFunCall("AND", Syntax.Infix, new Exp[] {x, y}); :} ; term4 ::= term3 | NOT term4:p {: RESULT = new UnresolvedFunCall("NOT", Syntax.Prefix, new Exp[] {p}); :} ; term3 ::= term2 | term3:x comp_op:op term2:y {: // e.g. "1 < 5" RESULT = new UnresolvedFunCall(op, Syntax.Infix, new Exp[] {x, y}); :} | // We expect a shift-reduce conflict here, because NULL is a literal and // so is a valid argument to the IS operator. JavaCUP resolves the // conflict by shifting, which is what we want. Compile with expect=61 // to ignore the conflicts and continue. term3:x IS NULL {: RESULT = new UnresolvedFunCall("IS NULL", Syntax.Postfix, new Exp[] {x}); :} | term3:x IS term2:y {: // e.g. "x IS y"; but "x IS NULL" is handled elsewhere RESULT = new UnresolvedFunCall("IS", Syntax.Infix, new Exp[] {x, y}); :} | term3:x IS EMPTY {: RESULT = new UnresolvedFunCall( "IS EMPTY", Syntax.Postfix, new Exp[] {x}); :} | term3:x MATCHES term2:y {: RESULT = new UnresolvedFunCall( "MATCHES", Syntax.Infix, new Exp[] {x, y}); :} | term3:x NOT MATCHES term2:y {: RESULT = new UnresolvedFunCall( "NOT", Syntax.Prefix, new Exp[] { new UnresolvedFunCall( "MATCHES", Syntax.Infix, new Exp[] {x, y})}); :} | term3:x IN term2:y {: RESULT = new UnresolvedFunCall("IN", Syntax.Infix, new Exp[] {x, y}); :} | term3:x NOT IN term2:y {: RESULT = new UnresolvedFunCall( "NOT", Syntax.Prefix, new Exp[] { new UnresolvedFunCall( "IN", Syntax.Infix, new Exp[] {x, y})}); :} ; term2 ::= term | term2:x PLUS term:y {: RESULT = new UnresolvedFunCall("+", Syntax.Infix, new Exp[] {x, y}); :} | term2:x MINUS term:y {: RESULT = new UnresolvedFunCall("-", Syntax.Infix, new Exp[] {x, y}); :} | term2:x CONCAT term:y {: RESULT = new UnresolvedFunCall("||", Syntax.Infix, new Exp[] {x, y}); :} ; // // ::= | { | } term ::= factor | term:x ASTERISK factor:y {: RESULT = new UnresolvedFunCall("*", Syntax.Infix, new Exp[] {x, y}); :} | term:x SOLIDUS factor:y {: RESULT = new UnresolvedFunCall("/", Syntax.Infix, new Exp[] {x, y}); :} ; // // ::= [] // factor ::= value_expression_primary | PLUS value_expression_primary:p {: RESULT = p; :} | MINUS value_expression_primary:p {: RESULT = new UnresolvedFunCall("-", Syntax.Prefix, new Exp[] {p}); :} ; // ::= + | - // // ::= + // // ::= - // // ::= * // // ::= / // // ::= // | // // Note: The data type of in the above production // shall be numeric. // // // ::= // | () // | // | [.][.VALUE] // | [.VALUE] // | value_expression_primary ::= STRING:s {: RESULT = Literal.createString(s); :} | NUMBER:d {: RESULT = Literal.create(d); :} | identifier:i {: RESULT = new Id(i); :} | value_expression_primary:i DOT unquoted_identifier:j {: if (i instanceof Id && !parser.isFunCall(j.name)) { RESULT = ((Id) i).append(j); } else { RESULT = new UnresolvedFunCall( j.name, Syntax.Property, new Exp[] {i}); } :} | value_expression_primary:i DOT quoted_identifier:j {: if (i instanceof Id) { RESULT = ((Id) i).append(j); } else { RESULT = new UnresolvedFunCall( j.name, Syntax.QuotedProperty, new Exp[] {i}); } :} | value_expression_primary:i DOT amp_quoted_identifier:j {: if (i instanceof Id) { RESULT = ((Id) i).append(j); } else { RESULT = new UnresolvedFunCall( j.name, Syntax.AmpersandQuotedProperty, new Exp[] {i}); } :} | value_expression_primary:i DOT identifier:j LPAREN exp_list_opt:lis RPAREN {: lis.add(0, i); RESULT = new UnresolvedFunCall( j.name, Syntax.Method, Parser.toExpArray(lis)); :} | bang_compound_id:i LPAREN exp_list_opt:lis RPAREN {: RESULT = new UnresolvedFunCall( i.getSegments().get(i.getSegments().size() - 1).name, Syntax.Function, Parser.toExpArray(lis)); :} | CAST LPAREN aliasedExpression:ae RPAREN {: assert ae.getArgCount() == 2; Exp e = ae.getArg(0); Id.Segment t = ((Id) ae.getArg(1)).getSegments().get(0); RESULT = new UnresolvedFunCall( "CAST", Syntax.Cast, new Exp[] { e, Literal.createSymbol(t.name)}); :} | LPAREN exp_list:lis RPAREN {: // Whereas ([Sales],[Time]) and () are tuples, ([Sales]) and (5) // are just expressions. RESULT = new UnresolvedFunCall( "()", Syntax.Parentheses, Parser.toExpArray(lis)); :} | LBRACE exp_list_opt:lis RBRACE {: // set built from sets/tuples RESULT = new UnresolvedFunCall( "{}", Syntax.Braces, Parser.toExpArray(lis)); :} | NULL {: RESULT = Literal.nullValue; :} | case_expression ; case_expression ::= CASE value_expression_opt:x when_list:y else_clause_opt:z END {: List v = new ArrayList(); if (x != null) { v.add(x); } for (int i = 0; i < y.size(); i++) { Exp[] exps = (Exp[]) y.get(i); Util.assertTrue(exps.length == 2); v.add(exps[0]); v.add(exps[1]); } if (z != null) { v.add(z); } if (x == null) { RESULT = new UnresolvedFunCall( "_CaseTest", Syntax.Case, Parser.toExpArray(v)); } else { RESULT = new UnresolvedFunCall( "_CaseMatch", Syntax.Case, Parser.toExpArray(v)); } :} ; value_expression_opt ::= /* empty */ | value_expression ; when_list ::= /* empty */ {: RESULT = new ArrayList(); :} | when_list:x when_clause:y {: RESULT = x; x.add(y); :} ; when_clause ::= WHEN value_expression:x THEN value_expression:y {: RESULT = new Exp[] {x, y}; :} ; else_clause_opt ::= /* empty */ | ELSE value_expression:x {: RESULT = x; :} ; // // ::= | // // ::= iif(, , ) // // ::= // // ::= // // ::= | | // // ::= CASE // ... // [] // END // // ::= CASE // ... // [] // END // // ::= WHEN THEN // // ::= WHEN THEN // // ::= ELSE // // ::= // // ::= // // ::= // // ::= COALESCEEMPTY ( // {, }...) // // ::= [] // // ::= // | // // ::= [.] // | . // | . // // ::= {}... // // ::= E // // ::= < exact_numeric_literal> // // ::= [] // // ::= // | // // // // Note: The data type of in the above production // shall be a character string. // // // ::= [...] // // // ::= | // // ::= !! // // // ::= // // ::= ' // // ::= || // // Leveling Rules for Expressions // // The following productions for are optional: // // The ability to qualify [.VALUE] by in a value expression // primary is optional. Consumers can check the value of the property // MDPROP_MDX_OUTERREFERENCE to see whether a provider supports this feature. // // // [.VALUE]. Consumers can check the value of the property // MDPROP_MDX_QUERYBYPROPERTY to see whether a provider supports this feature. // // // , . Consumers can check the value of the // property MDPROP_MDX_CASESUPPORT to see whether a provider supports this // feature. // // ---------------------------------------------------------------------------- // Search Condition // // ::= // | {OR | XOR} // // ::= | AND // // ::= [NOT] // // ::= // | ISEMPTY() // | IS EMPTY // | () // ::= // | // | // | // | // | // "IS" is not a comp_op because of conflict with " IS EMPTY" comp_op ::= EQ {: RESULT = "="; :} | NE {: RESULT = "<>"; :} | LT {: RESULT = "<"; :} | GT {: RESULT = ">"; :} | LE {: RESULT = "<="; :} | GE {: RESULT = ">="; :} ; // // ::= = // // ::= <> // // ::= > // // ::= < // // ::= >= // // ::= <= // // Leveling Rules for Search Condition // // If in a value is a string value // expression, then support for values other than // and is optional. Consumers can check the value of the // property MDPROP_MDX_STRING_COMPOP to see whether a provider supports this // feature. // ---------------------------------------------------------------------------- // Set Value Expression // // ::= // // Note: denotes an integer argument. If an arbitrary // appears here, then it is truncated to the nearest // integer. // // // ::= // // ::= .MEMBERS // | .MEMBERS // | .CHILDREN // | BOTTOMCOUNT(, // [, ]) // | BOTTOMPERCENT(, , // ) // | BOTTOMSUM(, , // ) // | CROSSJOIN(, ) // | DESCENDANTS(, [,]) // // Note: In the absence of explicit specification, SELF is the // default. // // | DISTINCT() // | DRILLDOWNLEVEL( [, ]]) // | DRILLDOWNLEVELBOTTOM(, // [,[] [, ]]) // | DRILLDOWNLEVELTOP(, [, [] // [, ]]) // | DRILLDOWNMEMBER(, [, RECURSIVE]) // | DRILLDOWNMEMBERBOTTOM(, , // [, ][, RECURSIVE]]) // | DRILLDOWNMEMBERTOP(, , // [, [][, RECURSIVE]]) // | DRILLUPLEVEL([, ]]) // | DRILLUPMEMBER(, ) // | EXCEPT(, [, ALL]) // | EXTRACT(, [, ...]) // | FILTER(, ) // | GENERATE(, [, ALL]) // | HIERARCHIZE() // | INTERSECT(, [, ALL]) // | LASTPERIODS( [, ]) // | MTD([]) // | ORDER(, // [, ASC | DESC | BASC | BDESC]) // // Note: In the absence of explicit specification, ASC is the default. // // // | PERIODSTODATE([[, ]]) // | QTD([]) // | TOGGLEDRILLSTATE(, [, RECURSIVE]) // // Note: With the exception of CROSSJOIN, all set functions that take more than // one argument require that the two set arguments have tuples of the // same dimensionality. // // // | TOPCOUNT(, // [, ]) // | TOPPERCENT(, , // ) // | TOPSUM(, , // ) // | UNION(, [, ALL]) // | WTD([]) // | YTD() // // ::= SELF // | AFTER // | BEFORE // | BEFORE_AND_AFTER // | SELF_AND_AFTER // | SELF_AND_BEFORE // | SELF_BEFORE_AFTER // // ---------------------------------------------------------------------------- // Member Value Expression // // ::= .{PARENT | FIRSTCHILD | LASTCHILD // | PREVMEMBER | NEXTMEMBER} // | .LEAD() // | .LAG() // // Note: LAG() is the same as LEAD(-) // // // | .{FIRSTSIBLING | LASTSIBLING} // | .[CURRENTMEMBER] // | .DEFAULTMEMBER // | .DEFAULTMEMBER // | ANCESTOR(, ) // | CLOSINGPERIOD([[, ]) // | COUSIN(, ) // | OPENINGPERIOD([[, ]) // | PARALLELPERIOD([[, // [, ]]]) expression ::= expression:x COLON value_expression:y {: // range yields set RESULT = new UnresolvedFunCall(":", Syntax.Infix, new Exp[] {x, y}); :} | aliasedExpression | value_expression ; expression_or_empty ::= expression | /* empty */ {: RESULT = new UnresolvedFunCall("", Syntax.Empty, new Exp[] {}); :} ; exp_list_opt ::= /* empty */ {: RESULT = new LinkedList(); :} | exp_list ; exp_list ::= expression:e {: RESULT = new LinkedList(); RESULT.add(e); :} | expression_or_empty:e COMMA exp_list:list {: list.add(0, e); RESULT = list; :} ; aliasedExpression ::= expression:x AS identifier:i {: Id id = new Id(i); RESULT = new UnresolvedFunCall("AS", Syntax.Infix, new Exp[] {x, id}); :} ; // // Leveling Rules for Member Value Expression // // The following member functions are optional: COUSIN, PARALLELPERIOD, // OPENINGPERIOD, CLOSINGPERIOD. Consumers can check the value of the property // MDPROP_MDX_MEMBER_FUNCTIONS to see whether a provider supports this feature. // // // * Tuple Value Expression // // ::= .CURRENTMEMBER // | [.ITEM]( // [, ...] | ) // // // * Numeric Value Function // // ::= // AGGREGATE( [, ]) // | AVG([, ]) // | CORRELATION( [, ] // [, ]) // | COVARIANCE([, // [, ]) // | COUNT([, INCLUDEEMPTY]) // | LINREGINTERCEPT([, // // // Leveling Rules for Numeric Value Function // // The following numeric functions are optional: MEDIAN, VAR, STDEV, RANK, // AGGREGATE, COVARIANCE, CORRELATION, LINREGSLOPE, LINREGVARIANCE, LINREGR2, // LINREGPOINT. Consumers can check the value of the property // MDPROP_MDX_NUMERIC_FUNCTIONS to see whether a provider supports this // feature. // // ---------------------------------------------------------------------------- // MDX Statement // // ::= // | // | // | // | // // ::= [WITH ] // SELECT [ // [, ...]] // FROM [] // [WHERE ] // [] // // ::= // DRILLTHROUGH // [ MAXROWS ] // [ FIRSTROWSET ] // // [ RETURN [, ...] ] // // ::= // EXPLAIN PLAN FOR // ( | ) statement ::= select_statement | drillthrough_statement | explain_statement | _VALUE_EXPRESSION expression:e {: RESULT = (QueryPart) e; :} ; select_statement ::= with_formula_specification_opt:f SELECT axis_specification_list_opt:a FROM cube_specification:c where_clause_opt:w cell_props_opt:cp {: Parser parser = (Parser) CUP$Parser$parser; // We want 'Sales', not '[Sales]', and can't handle 'Schema.Sales' // yet. String cubeName = c.getElement(0).name; RESULT = parser.factory.makeQuery( parser.statement, Parser.toFormulaArray(f), Parser.toQueryAxisArray(a), cubeName, w, Parser.toQueryPartArray(cp), parser.strictValidation); :}; with_formula_specification_opt ::= /* empty */ {: RESULT = new LinkedList(); :} | WITH formula_specification:f {: RESULT = f; :} ; axis_specification_list_opt ::= /* empty */ {: RESULT = new LinkedList(); :} | axis_specification_list ; axis_specification_list ::= axis_specification:i {: RESULT = new LinkedList(); RESULT.add(i); :} | axis_specification:e COMMA axis_specification_list:list {: list.add(0, e); RESULT = list; :} ; where_clause_opt ::= /* empty */ | WHERE slicer_specification:s {: RESULT = s; :} ; cell_props_opt ::= /* empty */ {: RESULT = new LinkedList(); :} | cell_props; // // ::= // [...] // formula_specification ::= single_formula_specification:e {: RESULT = new LinkedList(); RESULT.add(e); :} | single_formula_specification:hd formula_specification:tl {: tl.add(0, hd); RESULT = tl; :} ; // ::= // | // single_formula_specification ::= member_specification | set_specification ; // // ::= MEMBER AS // [, ] // [, ...] member_specification ::= MEMBER member_name:m AS FORMULA_STRING:s comma_member_property_def_list_opt:l {: Exp e = parser.recursivelyParseExp(s); RESULT = new Formula( m, e, Parser.toMemberPropertyArray(l)); :} | MEMBER member_name:m AS value_expression:e comma_member_property_def_list_opt:l {: RESULT = new Formula( m, e, Parser.toMemberPropertyArray(l)); :} ; comma_member_property_def_list_opt ::= /* empty */ {: RESULT = new LinkedList(); :} | COMMA member_property_def_list:l {: RESULT = l; :} ; member_property_def_list ::= member_property_definition:m {: RESULT = new LinkedList(); RESULT.add(m); :} | member_property_definition:hd COMMA member_property_def_list:tl {: RESULT = tl; RESULT.add(0, hd); :} ; // // ::= . // | .. // member_name ::= compound_id; // // Note: // // The identifier defines a new member. The qualification member has enough // information to specify the dimension, and the level in the dimension that // this new member should be on. // // // If is part of a member specification that appears in a create // formula statement or is part of a drop formula statement, then it must be // qualified by a cube name, as in the second production above. // // ::= SOLVE_ORDER = // // ::= = member_property_definition ::= identifier:id EQ value_expression:e {: RESULT = new MemberProperty(id.name, e); :} ; // // Note: Since the property definition appears in the context of a member // definition, there is enough information to associate the identifier (which // is the property name) in the above production with a member. // // // ::= SET AS set_specification ::= SET set_name:n AS FORMULA_STRING:s {: Exp e = parser.recursivelyParseExp(s); RESULT = new Formula(n, e); :} | SET set_name:n AS expression:e {: RESULT = new Formula(n, e); :} ; // // ::= | . set_name ::= compound_id ; // // Note: If is part of a set specification that appears in a create // formula statement or is part of a drop formula statement, then it must be // qualified by a cube name, as in the second production above. // // // ::= [NON EMPTY] [] ON axis_specification ::= non_empty_opt:b expression:s dim_props_opt:dp ON axis_name:a {: RESULT = new QueryAxis( b.booleanValue(), s, a, QueryAxis.SubtotalVisibility.Undefined, Parser.toIdArray(dp)); :} | non_empty_opt:b expression:s dim_props_opt:dp ON axis_number:n {: double d = n.doubleValue(); int index = n.intValue(); // AxisOrdinal values go from -2 to 4 for standard axis, but higher // ordinals are allowed. The negative values represent // special cases, so are ignored. if (index < 0 || index != d) { throw MondrianResource.instance().InvalidAxis.ex( d); } AxisOrdinal axis = AxisOrdinal.StandardAxisOrdinal.forLogicalOrdinal(index); RESULT = new QueryAxis( b.booleanValue(), s, axis, QueryAxis.SubtotalVisibility.Undefined, Parser.toIdArray(dp)); :} ; non_empty_opt ::= /* empty */ {: RESULT = Boolean.FALSE; :} | NON EMPTY {: RESULT = Boolean.TRUE; :} ; dim_props_opt ::= /* empty */ | dim_props ; // // ::= COLUMNS // | ROWS // | PAGES // | CHAPTERS // | SECTIONS // | AXIS() axis_name ::= COLUMNS {: RESULT = AxisOrdinal.StandardAxisOrdinal.COLUMNS; :} | ROWS {: RESULT = AxisOrdinal.StandardAxisOrdinal.ROWS; :} | PAGES {: RESULT = AxisOrdinal.StandardAxisOrdinal.PAGES; :} | SECTIONS {: RESULT = AxisOrdinal.StandardAxisOrdinal.SECTIONS; :} | CHAPTERS {: RESULT = AxisOrdinal.StandardAxisOrdinal.CHAPTERS; :} ; axis_number ::= NUMBER | AXIS LPAREN NUMBER:n RPAREN {: RESULT = n; :} ; // // ::= [DIMENSION] PROPERTIES [, ...] dim_props ::= dimension_opt PROPERTIES property_list:pl {: RESULT = pl; :} ; dimension_opt ::= /* empty */ | DIMENSION ; property_list ::= property:p {: RESULT = new LinkedList(); RESULT.add(p); :} | property:p COMMA property_list:pl {: pl.add(0, p); RESULT = pl; :} ; property ::= compound_id ; // // ::= [] [, ] // jhyde: In this implementation, you must supply EXACTLY one cube. cube_specification ::= cube_name; // // ::= { | } slicer_specification ::= expression; // // ::= [CELL] PROPERTIES [, ...] cell_props ::= cell_opt PROPERTIES cell_property_list:p1 {: RESULT = p1; :} ; cell_opt ::= /* empty */ | CELL ; cell_property_list ::= cell_property:p {: RESULT = new LinkedList(); RESULT.add(new CellProperty(p)); :} | cell_property:p COMMA cell_property_list:p1 {: p1.add(0, new CellProperty(p)); RESULT = p1; :} ; // ::= FORMAT_STRING // | FORMATTED_VALUE // | FORE_COLOR // | BACK_COLOR // | FONT_NAME // | FONT_SIZE // | FONT_FLAGS // | CELL_ORDINAL // | VALUE cell_property ::= compound_id; drillthrough_statement ::= DRILLTHROUGH maxrows_opt:m firstrowset_opt:f select_statement:s return_opt:r {: RESULT = parser.factory.makeDrillThrough( s, m == null ? 0 : m.intValue(), f == null ? 0 : f.intValue(), r); :} ; maxrows_opt ::= MAXROWS NUMBER:n {: RESULT = n; :} | {: RESULT = null; :} ; firstrowset_opt ::= FIRSTROWSET NUMBER:n {: RESULT = n; :} | {: RESULT = null; :} ; return_opt ::= RETURN return_item_list:rl {: RESULT = rl; :} | {: RESULT = null; :} ; return_item_list ::= return_item:i {: RESULT = new LinkedList(); RESULT.add(i); :} | return_item:i COMMA return_item_list:list {: list.add(0, i); RESULT = list; :} ; return_item ::= compound_id:i // TODO: allow NAME(id) etc. ; select_or_drillthrough_statement ::= select_statement | drillthrough_statement ; explain_statement ::= EXPLAIN PLAN FOR select_or_drillthrough_statement:s {: RESULT = parser.factory.makeExplain( s); :} ; // // ::= CREATE [] // // ::= // | // // ::= DROP MEMBER // [, ...] // // ::= DROP SET [, ...] // // := GLOBAL | SESSION // // Leveling Rules for MDX Statement // // Support for is optional. Consumers can check the // value of the property MDPROP_MDX_FORMULAS to see whether a provider supports // this feature. // // // Support for in is optional. Consumers can check // the value of the property MDPROP_MDX_SLICER to see whether a provider // supports this feature. // // // Support for more than one cube name in is // optional. Support for having no cube name in the FROM clause (that is, the // cube is implicitly defined by the axis and slicer dimensions) is also // optional. Consumers can check the value of the property MDPROP_MDX_JOINCUBES // to see whether a provider supports this feature. // // // The axis names CHAPTERS and SECTIONS are optional. Consumers can check the // value of the property MDPROP_AXES to see whether a provider supports this // feature. // // // Support for > 2 in the AXIS() function is optional. Consumers // can check the value of the property MDPROP_AXES to see whether a provider // supports this feature. // // // Support for is optional. Consumers can check the // value of the property MDPROP_MDX_FORMULAS to see whether a provider supports // this feature. // // // Support for of GLOBAL is optional. Consumers can check the value of // the property MDPROP_MDX_FORMULAS to see whether a provider supports this // feature. // // End Parser.cup mondrian-3.4.1/src/main/mondrian/olap/Id.java0000644000175000017500000001606611735330606020713 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1998-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.mdx.MdxVisitor; import mondrian.olap.type.Type; import java.io.PrintWriter; import java.util.*; /** * Multi-part identifier. * * author jhyde, 21 January, 1999 */ public class Id extends ExpBase implements Cloneable { private final List segments; /** * Creates an identifier containing a single part. * * @param segment Segment, consisting of a name and quoting style */ public Id(Segment segment) { segments = Collections.singletonList(segment); } public Id(List segments) { this.segments = segments; } public Id clone() { // This is immutable, so no need to clone. return this; } public int getCategory() { return Category.Unknown; } public Type getType() { // Can't give the type until we have resolved. throw new UnsupportedOperationException(); } public String toString() { final StringBuilder buf = new StringBuilder(); Util.quoteMdxIdentifier(segments, buf); return buf.toString(); } public String[] toStringArray() { String[] names = new String[segments.size()]; int k = 0; for (Segment segment : segments) { names[k++] = segment.name; } return names; } public List getSegments() { return Collections.unmodifiableList(this.segments); } public Id.Segment getElement(int i) { return segments.get(i); } /** * Returns a new Identifier consisting of this one with another segment * appended. Does not modify this Identifier. * * @param segment Name of segment * @return New identifier */ public Id append(Segment segment) { List newSegments = new ArrayList(segments); newSegments.add(segment); return new Id(newSegments); } public Exp accept(Validator validator) { if (segments.size() == 1) { final Segment s = segments.get(0); if (s.quoting == Quoting.UNQUOTED && validator.getFunTable().isReserved(s.name)) { return Literal.createSymbol(s.name.toUpperCase()); } } final Exp element = Util.lookup( validator.getQuery(), validator.getSchemaReader().withLocus(), segments, true); if (element == null) { return null; } return element.accept(validator); } public Object accept(MdxVisitor visitor) { return visitor.visit(this); } public void unparse(PrintWriter pw) { int k = 0; for (Segment s : segments) { if (k++ > 0) { pw.print("."); } switch (s.quoting) { case UNQUOTED: pw.print(s.name); break; case KEY: pw.print("&[" + Util.mdxEncodeString(s.name) + "]"); break; case QUOTED: pw.print("[" + Util.mdxEncodeString(s.name) + "]"); break; } } } /** * Component in a compound identifier. It is described by its name and how * the name is quoted. * *

    For example, the identifier * [Store].USA.[New Mexico].&[45] has four segments:

      *
    • "Store", {@link mondrian.olap.Id.Quoting#QUOTED}
    • *
    • "USA", {@link mondrian.olap.Id.Quoting#UNQUOTED}
    • *
    • "New Mexico", {@link mondrian.olap.Id.Quoting#QUOTED}
    • *
    • "45", {@link mondrian.olap.Id.Quoting#KEY}
    • *
    */ public static class Segment { public final String name; public final Quoting quoting; public Segment(String name, Quoting quoting) { this.name = name; this.quoting = quoting; } public String toString() { switch (quoting) { case UNQUOTED: //return name; Disabled to pass old tests... case QUOTED: return "[" + name + "]"; case KEY: return "&[" + name + "]"; default: return "UNKNOWN:" + name; } } /** * Appends this segment to a StringBuffer * * @param buf StringBuffer */ public void toString(StringBuilder buf) { switch (quoting) { case UNQUOTED: buf.append(name); return; case QUOTED: Util.quoteMdxIdentifier(name, buf); return; case KEY: buf.append('&'); Util.quoteMdxIdentifier(name, buf); return; default: throw Util.unexpected(quoting); } } public boolean equals(final Object o) { if (o instanceof Segment) { Segment that = (Segment) o; return that.name.equals(this.name) && that.quoting == this.quoting; } else { return false; } } public int hashCode() { return name.hashCode(); } /** * Converts an array of names to a list of segments. * * @param nameParts Array of names * @return List of segments */ public static List toList(String... nameParts) { final List segments = new ArrayList(nameParts.length); for (String namePart : nameParts) { segments.add(new Segment(namePart, Id.Quoting.QUOTED)); } return segments; } /** * Returns whether this segment matches a given name according to * the rules of case-sensitivity and quoting. * * @param name Name to match * @return Whether matches */ public boolean matches(String name) { switch (quoting) { case UNQUOTED: return Util.equalName(this.name, name); case QUOTED: return Util.equalName(this.name, name); default: return false; } } } public enum Quoting { /** * Unquoted identifier, for example "Measures". */ UNQUOTED, /** * Quoted identifier, for example "[Measures]". */ QUOTED, /** * Identifier quoted with an ampersand to indicate a key value, for * example the second segment in "[Employees].&[89]". */ KEY } } // End Id.java mondrian-3.4.1/src/main/mondrian/olap/MondrianServer.java0000644000175000017500000002111511735330606023304 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2010 Pentaho // All Rights Reserved. */ package mondrian.olap; import mondrian.rolap.RolapConnection; import mondrian.rolap.RolapResultShepherd; import mondrian.rolap.agg.AggregationManager; import mondrian.server.*; import mondrian.server.monitor.Monitor; import mondrian.spi.CatalogLocator; import mondrian.util.LockBox; import org.olap4j.OlapConnection; import java.sql.SQLException; import java.util.*; /** * Interface by which to control an instance of Mondrian. * *

    Typically, there is only one instance of Mondrian per JVM. However, you * access a MondrianServer via the {@link #forConnection} method for future * expansion. * * @author jhyde * @since Jun 25, 2006 */ public abstract class MondrianServer { /** * Returns the MondrianServer that hosts a given connection. * * @param connection Connection (not null) * @return server this connection belongs to (not null) */ public static MondrianServer forConnection(Connection connection) { return ((RolapConnection) connection).getServer(); } /** * Creates a server. * *

    When creating a server, the calling code must call the * {@link MondrianServer#shutdown()} method to dispose of it. * * @param contentFinder Repository content finder * @param catalogLocator Catalog locator * @return Server that reads from the given repository */ public static MondrianServer createWithRepository( RepositoryContentFinder contentFinder, CatalogLocator catalogLocator) { return MondrianServerRegistry.INSTANCE.createWithRepository( contentFinder, catalogLocator); } /** * Returns the server with the given id. * *

    If id is null, returns the catalog-less server. (The catalog-less * server can also be acquired using its id.)

    * *

    If server is not found, returns null.

    * * @param instanceId Server instance id * @return Server, or null if no server with this id */ public static MondrianServer forId(String instanceId) { return MondrianServerRegistry.INSTANCE.serverForId(instanceId); } /** * Disposes of a server and cleans up everything. * * @param instanceId The instance ID of the server * to shutdown gracefully. */ public static void dispose(String instanceId) { final MondrianServer server = forId(instanceId); if (server != null) { server.shutdown(); } } /** * Returns an integer uniquely identifying this server within its JVM. * * @return Server's unique identifier */ public abstract int getId(); /** * Returns the version of this MondrianServer. * * @return Server's version */ public MondrianVersion getVersion() { return MondrianServerRegistry.INSTANCE.getOrLoadVersion(); } /** * Returns a list of MDX keywords. * @return list of MDX keywords */ public abstract List getKeywords(); public abstract RolapResultShepherd getResultShepherd(); /** * Returns the lock box that can be used to pass objects via their string * key. * * @return Lock box for this server */ public abstract LockBox getLockBox(); /** * Gets a Connection given a catalog (and implicitly the catalog's data * source) and the name of a user role. * *

    If you want to pass in a role object, and you are making the call * within the same JVM (i.e. not RPC), register the role using * {@link MondrianServer#getLockBox()} and pass in the moniker * for the generated lock box entry. The server will retrieve the role from * the moniker. * * @param catalogName Catalog name * @param schemaName Schema name * @param roleName User role name * @return Connection * @throws SQLException If error occurs * @throws SecurityException If security error occurs */ public abstract OlapConnection getConnection( String catalogName, String schemaName, String roleName) throws SQLException, SecurityException; /** * Extended version of * {@link MondrianServer#getConnection(String, String, String)} * taking a list of properties to pass down to the native connection. * *

    Gets a Connection given a catalog (and implicitly the catalog's data * source) and the name of a user role. * *

    If you want to pass in a role object, and you are making the call * within the same JVM (i.e. not RPC), register the role using * {@link MondrianServer#getLockBox()} and pass in the moniker * for the generated lock box entry. The server will retrieve the role from * the moniker. * * @param catalogName Catalog name * @param schemaName Schema name * @param roleName User role name * @param props Properties to pass down to the native driver. * @return Connection * @throws SQLException If error occurs * @throws SecurityException If security error occurs */ public abstract OlapConnection getConnection( String catalogName, String schemaName, String roleName, Properties props) throws SQLException, SecurityException; /** * Returns a list of the databases in this server. One element * per database, each element a map whose keys are the XMLA fields * describing a data source: "DataSourceName", "DataSourceDescription", * "URL", etc. Unrecognized fields are ignored. * * @return List of data source definitions * @param connection Connection */ public abstract List> getDatabases( RolapConnection connection); public abstract CatalogLocator getCatalogLocator(); /** * Called when the server must terminate all background tasks * and cleanup all potential memory leaks. */ public abstract void shutdown(); /** * Called just after a connection has been created. * * @param connection Connection */ public abstract void addConnection(RolapConnection connection); /** * Called when a connection is closed. * * @param connection Connection */ public abstract void removeConnection(RolapConnection connection); /** * Retrieves a connection. * * @param connectionId Connection id, per * {@link mondrian.rolap.RolapConnection#getId()} * * @return Connection, or null if connection is not registered */ public abstract RolapConnection getConnection(int connectionId); /** * Called just after a statement has been created. * * @param statement Statement */ public abstract void addStatement(Statement statement); /** * Called when a statement is closed. * * @param statement Statement */ public abstract void removeStatement(Statement statement); public abstract Monitor getMonitor(); public abstract AggregationManager getAggregationManager(); /** * Description of the version of the server. */ public interface MondrianVersion { /** * Returns the version string, for example "2.3.0". * * @see java.sql.DatabaseMetaData#getDatabaseProductVersion() * @return Version of this server */ String getVersionString(); /** * Returns the major part of the version number. * *

    For example, if the full version string is "2.3.0", the major * version is 2. * * @return major part of the version number * @see java.sql.DatabaseMetaData#getDatabaseMajorVersion() */ int getMajorVersion(); /** * Returns the minor part of the version number. * *

    For example, if the full version string is "2.3.0", the minor * version is 3. * * @return minor part of the version number * * @see java.sql.DatabaseMetaData#getDatabaseProductVersion() */ int getMinorVersion(); /** * Retrieves the name of this database product. * * @return database product name * @see java.sql.DatabaseMetaData#getDatabaseProductName() */ String getProductName(); } } // End MondrianServer.java mondrian-3.4.1/src/main/mondrian/olap/ResultStyleException.java0000644000175000017500000000412711735330606024530 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2004-2005 TONBELLER AG // Copyright (C) 2006-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.calc.ResultStyle; import java.util.List; /** * Exception that indicates a compiler could not implement an expression in any * of the result styles requested by the client. * * @author Richard Emberson */ public class ResultStyleException extends MondrianException { public static ResultStyleException generate( List producer, List consumer) { StringBuilder buf = new StringBuilder(); buf.append("Producer expected ResultStyles: "); buf.append('{'); for (int i = 0; i < producer.size(); i++) { if (i > 0) { buf.append(','); } buf.append(producer.get(i)); } buf.append('}'); buf.append(" but Consumer wanted: "); buf.append('{'); for (int i = 0; i < consumer.size(); i++) { if (i > 0) { buf.append(','); } buf.append(consumer.get(i)); } buf.append('}'); throw new ResultStyleException(buf.toString()); } public static ResultStyleException generateBadType( List wanted, ResultStyle got) { StringBuilder buf = new StringBuilder(); buf.append("Wanted ResultStyles: "); buf.append('{'); for (int i = 0; i < wanted.size(); i++) { if (i > 0) { buf.append(','); } buf.append(wanted.get(i)); } buf.append('}'); buf.append(" but got: "); buf.append(got); return new ResultStyleException(buf.toString()); } public ResultStyleException(String message) { super(message); } } // End ResultStyleException.java mondrian-3.4.1/src/main/mondrian/olap/FunDef.java0000644000175000017500000000450411735330606021520 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1999-2005 Julian Hyde // Copyright (C) 2005-2006 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.calc.Calc; import mondrian.calc.ExpCompiler; import mondrian.mdx.ResolvedFunCall; import java.io.PrintWriter; /** * Definition of an MDX function. See also {@link FunTable}. * * @author jhyde, 21 April, 1999 */ public interface FunDef { /** * Returns the syntactic type of the function. */ Syntax getSyntax(); /** * Returns the name of this function. */ String getName(); /** * Returns the description of this function. */ String getDescription(); /** * Returns the {@link Category} code of the value returned by this * function. */ int getReturnCategory(); /** * Returns the types of the arguments of this function. Values are the same * as those returned by {@link Exp#getCategory()}. The 0th * argument of methods and properties are the object they are applied * to. Infix operators have two arguments, and prefix operators have one * argument. */ int[] getParameterCategories(); /** * Creates an expression which represents a call to this function with * a given set of arguments. The result is usually a {@link ResolvedFunCall} but * not always. */ Exp createCall(Validator validator, Exp[] args); /** * Returns an English description of the signature of the function, for * example "<Numeric Expression> / <Numeric Expression>". */ String getSignature(); /** * Converts a function call into MDX source code. */ void unparse(Exp[] args, PrintWriter pw); /** * Converts a call to this function into executable objects. * *

    The result must implement the appropriate interface for the result * type. For example, a function which returns an integer must return * an object which implements {@link mondrian.calc.IntegerCalc}. */ Calc compileCall(ResolvedFunCall call, ExpCompiler compiler); } // End FunDef.java mondrian-3.4.1/src/main/mondrian/olap/Scanner.java0000644000175000017500000007424411735330606021752 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1998-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import org.apache.log4j.Logger; import java_cup.runtime.Symbol; import java.io.IOException; import java.math.BigDecimal; import java.util.*; /** * Lexical analyzer for MDX. * * @author jhyde, 20 January, 1999 */ public class Scanner { private static final Logger LOGGER = Logger.getLogger(Scanner.class); /** single lookahead character */ protected int nextChar; /** next lookahead character */ private int lookaheadChars[] = new int[16]; private int firstLookaheadChar = 0; private int lastLookaheadChar = 0; private Hashtable m_resWordsTable; private int iMaxResword; private String m_aResWords[]; protected boolean debug; /** lines[x] is the start of the x'th line */ private List lines; /** number of times advance() has been called */ private int iChar; /** end of previous token */ private int iPrevChar; /** previous symbol returned */ private int previousSymbol; private boolean inFormula; /** * Comment delimiters. Modify this list to support other comment styles. */ private static final String[][] commentDelim = { new String[] {"//", null}, new String[] {"--", null}, new String[] {"/*", "*/"} }; /** * Whether to allow nested comments. */ private static final boolean allowNestedComments = true; /** * The {@link java.math.BigDecimal} value 0. * Note that BigDecimal.ZERO does not exist until JDK 1.5. */ private static final BigDecimal BigDecimalZero = BigDecimal.valueOf(0); /** * Creates a Scanner. * * @param debug Whether to emit debug messages. */ Scanner(boolean debug) { this.debug = debug; } /** * Returns the current nested comments state. */ public static boolean getNestedCommentsState() { return allowNestedComments; } /** * Returns the list of comment delimiters. */ public static String[][] getCommentDelimiters() { return commentDelim; } /** * Advance input by one character, setting {@link #nextChar}. */ private void advance() throws IOException { if (firstLookaheadChar == lastLookaheadChar) { // We have nothing in the lookahead buffer. nextChar = getChar(); } else { // We have called lookahead(); advance to the next character it got. nextChar = lookaheadChars[firstLookaheadChar++]; if (firstLookaheadChar == lastLookaheadChar) { firstLookaheadChar = 0; lastLookaheadChar = 0; } } if (nextChar == '\012') { lines.add(iChar); } iChar++; } /** Peek at the character after {@link #nextChar} without advancing. */ private int lookahead() throws IOException { return lookahead(1); } /** * Peeks at the character n after {@link #nextChar} without advancing. * *

    lookahead(0) returns the current char (nextChar). * lookahead(1) returns the next char (was lookaheadChar, same as * lookahead()); */ private int lookahead(int n) throws IOException { if (n == 0) { return nextChar; } else { // if the desired character not in lookahead buffer, read it in if (n > lastLookaheadChar - firstLookaheadChar) { int len = lastLookaheadChar - firstLookaheadChar; int t[]; // make sure we do not go off the end of the buffer if (n + firstLookaheadChar > lookaheadChars.length) { if (n > lookaheadChars.length) { // the array is too small; make it bigger and shift // everything to the beginning. t = new int[n * 2]; } else { // the array is big enough, so just shift everything // to the beginning of it. t = lookaheadChars; } System.arraycopy( lookaheadChars, firstLookaheadChar, t, 0, len); lookaheadChars = t; firstLookaheadChar = 0; lastLookaheadChar = len; } // read ahead enough while (n > lastLookaheadChar - firstLookaheadChar) { lookaheadChars[lastLookaheadChar++] = getChar(); } } return lookaheadChars[n - 1 + firstLookaheadChar]; } } /** Read a character from input, returning -1 if end of input. */ protected int getChar() throws IOException { return System.in.read(); } /** Initialize the scanner */ public void init() throws IOException { initReswords(); lines = new ArrayList(); iChar = iPrevChar = 0; advance(); } /** * Deduces the line and column (0-based) of a symbol. * Called by {@link Parser#syntax_error}. */ void getLocation(Symbol symbol, int[] loc) { int iTarget = symbol.left; int iLine = -1; int iLineEnd = 0; int iLineStart; do { iLine++; iLineStart = iLineEnd; iLineEnd = Integer.MAX_VALUE; if (iLine < lines.size()) { iLineEnd = lines.get(iLine); } } while (iLineEnd < iTarget); loc[0] = iLine; // line loc[1] = iTarget - iLineStart; // column } private Symbol trace(Symbol s) { if (debug) { String name = null; if (s.sym < m_aResWords.length) { name = m_aResWords[s.sym]; } LOGGER.error( "Scanner returns #" + s.sym + (name == null ? "" : ":" + name) + (s.value == null ? "" : "(" + s.value.toString() + ")")); } return s; } private void initResword(int id, String s) { m_resWordsTable.put(s, id); if (id > iMaxResword) { iMaxResword = id; } } /** * Initializes the table of reserved words. */ private void initReswords() { // This list generated by piping the 'terminal' declaration in mdx.cup // through: // grep -list // | // sed -e 's/,//' | // awk '{printf "initResword(%20s,%c%s%c);",$1,34,$1,34}' m_resWordsTable = new Hashtable(); iMaxResword = 0; // initResword(ParserSym.ALL, "ALL"); initResword(ParserSym.AND, "AND"); initResword(ParserSym.AS, "AS"); // initResword(ParserSym.ASC, "ASC"); initResword(ParserSym.AXIS, "AXIS"); // initResword(ParserSym.BACK_COLOR, "BACK_COLOR"); // initResword(ParserSym.BASC, "BASC"); // initResword(ParserSym.BDESC, "BDESC"); // CAST is a mondrian extension initResword(ParserSym.CAST, "CAST"); initResword(ParserSym.CASE, "CASE"); initResword(ParserSym.CELL, "CELL"); // initResword(ParserSym.CELL_ORDINAL, "CELL_ORDINAL"); initResword(ParserSym.CHAPTERS, "CHAPTERS"); // initResword(ParserSym.CHILDREN, "CHILDREN"); initResword(ParserSym.COLUMNS, "COLUMNS"); // initResword(ParserSym.DESC, "DESC"); initResword(ParserSym.DIMENSION, "DIMENSION"); initResword(ParserSym.DRILLTHROUGH, "DRILLTHROUGH"); initResword(ParserSym.ELSE, "ELSE"); initResword(ParserSym.EMPTY, "EMPTY"); initResword(ParserSym.END, "END"); initResword(ParserSym.EXPLAIN, "EXPLAIN"); // initResword(ParserSym.FIRSTCHILD, "FIRSTCHILD"); initResword(ParserSym.FIRSTROWSET, "FIRSTROWSET"); // initResword(ParserSym.FIRSTSIBLING, "FIRSTSIBLING"); // initResword(ParserSym.FONT_FLAGS, "FONT_FLAGS"); // initResword(ParserSym.FONT_NAME, "FONT_NAME"); // initResword(ParserSym.FONT_SIZE, "FONT_SIZE"); // initResword(ParserSym.FORE_COLOR, "FORE_COLOR"); // initResword(ParserSym.FORMATTED_VALUE, "FORMATTED_VALUE"); // initResword(ParserSym.FORMAT_STRING, "FORMAT_STRING"); initResword(ParserSym.FOR, "FOR"); initResword(ParserSym.FROM, "FROM"); initResword(ParserSym.IS, "IS"); initResword(ParserSym.IN, "IN"); // initResword(ParserSym.LAG, "LAG"); // initResword(ParserSym.LASTCHILD, "LASTCHILD"); // initResword(ParserSym.LASTSIBLING, "LASTSIBLING"); // initResword(ParserSym.LEAD, "LEAD"); initResword(ParserSym.MATCHES, "MATCHES"); initResword(ParserSym.MAXROWS, "MAXROWS"); initResword(ParserSym.MEMBER, "MEMBER"); // initResword(ParserSym.MEMBERS, "MEMBERS"); // initResword(ParserSym.NEXTMEMBER, "NEXTMEMBER"); initResword(ParserSym.NON, "NON"); initResword(ParserSym.NOT, "NOT"); initResword(ParserSym.NULL, "NULL"); initResword(ParserSym.ON, "ON"); initResword(ParserSym.OR, "OR"); initResword(ParserSym.PAGES, "PAGES"); // initResword(ParserSym.PARENT, "PARENT"); initResword(ParserSym.PLAN, "PLAN"); // initResword(ParserSym.PREVMEMBER, "PREVMEMBER"); initResword(ParserSym.PROPERTIES, "PROPERTIES"); // initResword(ParserSym.RECURSIVE, "RECURSIVE"); initResword(ParserSym.RETURN, "RETURN"); initResword(ParserSym.ROWS, "ROWS"); initResword(ParserSym.SECTIONS, "SECTIONS"); initResword(ParserSym.SELECT, "SELECT"); initResword(ParserSym.SET, "SET"); // initResword(ParserSym.SOLVE_ORDER, "SOLVE_ORDER"); initResword(ParserSym.THEN, "THEN"); // initResword(ParserSym.VALUE, "VALUE"); initResword(ParserSym.WHEN, "WHEN"); initResword(ParserSym.WHERE, "WHERE"); initResword(ParserSym.WITH, "WITH"); initResword(ParserSym.XOR, "XOR"); m_aResWords = new String[iMaxResword + 1]; Enumeration e = m_resWordsTable.keys(); while (e.hasMoreElements()) { Object o = e.nextElement(); String s = (String) o; int i = (m_resWordsTable.get(s)).intValue(); m_aResWords[i] = s; } } /** return the name of the reserved word whose token code is "i" */ public String lookupReserved(int i) { return m_aResWords[i]; } private Symbol makeSymbol(int id, Object o) { int iPrevPrevChar = iPrevChar; this.iPrevChar = iChar; this.previousSymbol = id; return trace(new Symbol(id, iPrevPrevChar, iChar, o)); } /** * Creates a token representing a numeric literal. * * @param mantissa The digits of the number * @param exponent The base-10 exponent of the number * @return number literal token */ private Symbol makeNumber(BigDecimal mantissa, int exponent) { BigDecimal d = mantissa.movePointRight(exponent); return makeSymbol(ParserSym.NUMBER, d); } private Symbol makeId(String s, boolean quoted, boolean ampersand) { return makeSymbol( quoted && ampersand ? ParserSym.AMP_QUOTED_ID : quoted ? ParserSym.QUOTED_ID : ParserSym.ID, s); } /** * Creates a token representing a reserved word. * * @param i Token code * @return Token */ private Symbol makeRes(int i) { return makeSymbol(i, m_aResWords[i]); } /** * Creates a token. * * @param i Token code * @param s Text of the token * @return Token */ private Symbol makeToken(int i, String s) { return makeSymbol(i, s); } /** * Creates a token representing a string literal. * * @param s String * @return String token */ private Symbol makeString(String s) { if (inFormula) { inFormula = false; return makeSymbol(ParserSym.FORMULA_STRING, s); } else { return makeSymbol(ParserSym.STRING, s); } } /** * Discards all characters until the end of the current line. */ private void skipToEOL() throws IOException { while (nextChar != -1 && nextChar != '\012') { advance(); } } /** * Eats a delimited comment. * The type of delimiters are kept in commentDelim. The current * comment type is indicated by commentType. * end of file terminates a comment without error. */ private void skipComment( final String startDelim, final String endDelim) throws IOException { int depth = 1; // skip the starting delimiter for (int x = 0; x < startDelim.length(); x++) { advance(); } for (;;) { if (nextChar == -1) { return; } else if (checkForSymbol(endDelim)) { // eat the end delimiter for (int x = 0; x < endDelim.length(); x++) { advance(); } if (--depth == 0) { return; } } else if (allowNestedComments && checkForSymbol(startDelim)) { // eat the nested start delimiter for (int x = 0; x < startDelim.length(); x++) { advance(); } depth++; } else { advance(); } } } /** * If the next tokens are comments, skip over them. */ private void searchForComments() throws IOException { // eat all following comments boolean foundComment; do { foundComment = false; for (String[] aCommentDelim : commentDelim) { if (checkForSymbol(aCommentDelim[0])) { if (aCommentDelim[1] == null) { foundComment = true; skipToEOL(); } else { foundComment = true; skipComment(aCommentDelim[0], aCommentDelim[1]); } } } } while (foundComment); } /** * Checks if the next symbol is the supplied string */ private boolean checkForSymbol(final String symb) throws IOException { for (int x = 0; x < symb.length(); x++) { if (symb.charAt(x) != lookahead(x)) { return false; } } return true; } /** * Recognizes and returns the next complete token. */ public Symbol next_token() throws IOException { StringBuilder id; boolean ampersandId = false; for (;;) { searchForComments(); switch (nextChar) { case '.': switch (lookahead()) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // We're looking at the '.' on the start of a number, // e.g. .1; fall through to parse a number. break; default: advance(); return makeToken(ParserSym.DOT, "."); } // fall through case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': // Parse a number. Valid examples include 1, 1.2, 0.1, .1, // 1e2, 1E2, 1e-2, 1e+2. Invalid examples include e2, 1.2.3, // 1e2e3, 1e2.3. // // Signs preceding numbers (e.g. -1, + 1E-5) are valid, but are // handled by the parser. // BigDecimal n = BigDecimalZero; int digitCount = 0, exponent = 0; boolean positive = true; BigDecimal mantissa = BigDecimalZero; State state = State.leftOfPoint; for (;;) { switch (nextChar) { case '.': switch (state) { case leftOfPoint: state = State.rightOfPoint; mantissa = n; n = BigDecimalZero; digitCount = 0; positive = true; advance(); break; // Error: we are seeing a point in the exponent // (e.g. 1E2.3 or 1.2E3.4) or a second point in the // mantissa (e.g. 1.2.3). Return what we've got // and let the parser raise the error. case rightOfPoint: mantissa = mantissa.add( n.movePointRight(-digitCount)); return makeNumber(mantissa, exponent); case inExponent: if (!positive) { n = n.negate(); } exponent = n.intValue(); return makeNumber(mantissa, exponent); } break; case 'E': case 'e': switch (state) { case inExponent: // Error: we are seeing an 'e' in the exponent // (e.g. 1.2e3e4). Return what we've got and let // the parser raise the error. if (!positive) { n = n.negate(); } exponent = n.intValue(); return makeNumber(mantissa, exponent); case leftOfPoint: mantissa = n; break; default: mantissa = mantissa.add( n.movePointRight(-digitCount)); break; } digitCount = 0; n = BigDecimalZero; positive = true; advance(); state = State.inExponent; break; case'0': case'1': case'2': case'3': case'4': case'5': case'6': case'7': case'8': case'9': n = n.movePointRight(1); n = n.add(BigDecimal.valueOf(nextChar - '0')); digitCount++; advance(); break; case '+': case '-': if (state == State.inExponent && digitCount == 0) { // We're looking at the sign after the 'e'. positive = !positive; advance(); break; } // fall through - end of number default: // Reached end of number. switch (state) { case leftOfPoint: mantissa = n; break; case rightOfPoint: mantissa = mantissa.add( n.movePointRight(-digitCount)); break; default: if (!positive) { n = n.negate(); } exponent = n.intValue(); break; } return makeNumber(mantissa, exponent); } } case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': case '$': /* parse an identifier */ id = new StringBuilder(); for (;;) { id.append((char)nextChar); advance(); switch (nextChar) { case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '_': case '$': break; default: String strId = id.toString(); Integer i = m_resWordsTable.get( strId.toUpperCase()); if (i == null) { // identifier return makeId(strId, false, false); } else { // reserved word return makeRes(i); } } } case '&': advance(); if (nextChar == '[') { ampersandId = true; // fall through } else { return makeToken(ParserSym.UNKNOWN, "&"); } case '[': /* parse a delimited identifier */ id = new StringBuilder(); for (;;) { advance(); switch (nextChar) { case ']': advance(); if (nextChar == ']') { // ] escaped with ] - just take one id.append(']'); break; } else { // end of identifier if (ampersandId) { ampersandId = false; return makeId(id.toString(), true, true); } else { return makeId(id.toString(), true, false); } } case -1: if (ampersandId) { ampersandId = false; return makeId(id.toString(), true, true); } else { return makeId(id.toString(), true, false); } default: id.append((char)nextChar); } } case ':': advance(); return makeToken(ParserSym.COLON, ":"); case ',': advance(); return makeToken(ParserSym.COMMA, ","); case '=': advance(); return makeToken(ParserSym.EQ, "="); case '<': advance(); switch (nextChar) { case '>': advance(); return makeToken(ParserSym.NE, "<>"); case '=': advance(); return makeToken(ParserSym.LE, "<="); default: return makeToken(ParserSym.LT, "<"); } case '>': advance(); switch (nextChar) { case '=': advance(); return makeToken(ParserSym.GE, ">="); default: return makeToken(ParserSym.GT, ">"); } case '{': advance(); return makeToken(ParserSym.LBRACE, "{"); case '(': advance(); return makeToken(ParserSym.LPAREN, "("); case '}': advance(); return makeToken(ParserSym.RBRACE, "}"); case ')': advance(); return makeToken(ParserSym.RPAREN, ")"); case '+': advance(); return makeToken(ParserSym.PLUS, "+"); case '-': advance(); return makeToken(ParserSym.MINUS, "-"); case '*': advance(); return makeToken(ParserSym.ASTERISK, "*"); case '/': advance(); return makeToken(ParserSym.SOLIDUS, "/"); case '!': advance(); return makeToken(ParserSym.BANG, "!"); case '|': advance(); switch (nextChar) { case '|': advance(); return makeToken(ParserSym.CONCAT, "||"); default: return makeToken(ParserSym.UNKNOWN, "|"); } case '"': /* parse a double-quoted string */ id = new StringBuilder(); for (;;) { advance(); switch (nextChar) { case '"': advance(); if (nextChar == '"') { // " escaped with " id.append('"'); break; } else { // end of string return makeString(id.toString()); } case -1: return makeString(id.toString()); default: id.append((char)nextChar); } } case '\'': if (previousSymbol == ParserSym.AS) { inFormula = true; } /* parse a single-quoted string */ id = new StringBuilder(); for (;;) { advance(); switch (nextChar) { case '\'': advance(); if (nextChar == '\'') { // " escaped with " id.append('\''); break; } else { // end of string return makeString(id.toString()); } case -1: return makeString(id.toString()); default: id.append((char)nextChar); } } case -1: // we're done return makeToken(ParserSym.EOF, "EOF"); default: // If it's whitespace, skip over it. // (When we switch to JDK 1.5, use Character.isWhitespace(int); // til then, there's just Character.isWhitespace(char).) if (nextChar <= Character.MAX_VALUE && Character.isWhitespace((char) nextChar)) { // fall through } else { // everything else is an error throw new RuntimeException( "Unexpected character '" + (char) nextChar + "'"); } case ' ': case '\t': case '\n': case '\r': // whitespace can be ignored iPrevChar = iChar; advance(); break; } } } private enum State { leftOfPoint, rightOfPoint, inExponent, } } // End Scanner.java mondrian-3.4.1/src/main/mondrian/olap/MemberBase.java0000644000175000017500000002071011735330606022350 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.resource.MondrianResource; import mondrian.spi.MemberFormatter; import mondrian.util.Bug; import java.util.ArrayList; import java.util.List; /** * MemberBase is a partial implementation of {@link Member}. * * @author jhyde * @since 6 August, 2001 */ public abstract class MemberBase extends OlapElementBase implements Member { protected Member parentMember; protected final Level level; protected String uniqueName; /** * Combines member type and other properties, such as whether the member * is the 'all' or 'null' member of its hierarchy and whether it is a * measure or is calculated, into an integer field. * *

    The fields are:

      *
    • bits 0, 1, 2 ({@link #FLAG_TYPE_MASK}) are member type; *
    • bit 3 ({@link #FLAG_HIDDEN}) is set if the member is hidden; *
    • bit 4 ({@link #FLAG_ALL}) is set if this is the all member of its * hierarchy; *
    • bit 5 ({@link #FLAG_NULL}) is set if this is the null member of its * hierarchy; *
    • bit 6 ({@link #FLAG_CALCULATED}) is set if this is a calculated * member. *
    • bit 7 ({@link #FLAG_MEASURE}) is set if this is a measure. *
    * * NOTE: jhyde, 2007/8/10. It is necessary to cache whether the member is * 'all', 'calculated' or 'null' in the member's state, because these * properties are used so often. If we used a virtual method call - say we * made each subclass implement 'boolean isNull()' - it would be slower. * We use one flags field rather than 4 boolean fields to save space. */ protected final int flags; private static final int FLAG_TYPE_MASK = 0x07; private static final int FLAG_HIDDEN = 0x08; private static final int FLAG_ALL = 0x10; private static final int FLAG_NULL = 0x20; private static final int FLAG_CALCULATED = 0x40; private static final int FLAG_MEASURE = 0x80; /** * Cached values of {@link mondrian.olap.Member.MemberType} enumeration. * Without caching, get excessive calls to {@link Object#clone}. */ private static final MemberType[] MEMBER_TYPE_VALUES = MemberType.values(); protected MemberBase( Member parentMember, Level level, MemberType memberType) { this.parentMember = parentMember; this.level = level; this.flags = memberType.ordinal() | (memberType == MemberType.ALL ? FLAG_ALL : 0) | (memberType == MemberType.NULL ? FLAG_NULL : 0) | (computeCalculated(memberType) ? FLAG_CALCULATED : 0) | (level.getHierarchy().getDimension().isMeasures() ? FLAG_MEASURE : 0); } protected MemberBase() { this.flags = 0; this.level = null; } public String getQualifiedName() { return MondrianResource.instance().MdxMemberName.str(getUniqueName()); } public abstract String getName(); public String getUniqueName() { return uniqueName; } public String getCaption() { // if there is a member formatter for the members level, // we will call this interface to provide the display string MemberFormatter mf = getLevel().getMemberFormatter(); if (mf != null) { return mf.formatMember(this); } final String caption = super.getCaption(); return (caption != null) ? caption : getName(); } public String getParentUniqueName() { return parentMember == null ? null : parentMember.getUniqueName(); } public Dimension getDimension() { return level.getDimension(); } public Hierarchy getHierarchy() { return level.getHierarchy(); } public Level getLevel() { return level; } public MemberType getMemberType() { return MEMBER_TYPE_VALUES[flags & FLAG_TYPE_MASK]; } public String getDescription() { return (String) getPropertyValue(Property.DESCRIPTION.name); } public boolean isMeasure() { return (flags & FLAG_MEASURE) != 0; } public boolean isAll() { return (flags & FLAG_ALL) != 0; } public boolean isNull() { return (flags & FLAG_NULL) != 0; } public boolean isCalculated() { return (flags & FLAG_CALCULATED) != 0; } public boolean isEvaluated() { // should just call isCalculated(), but called in tight loops // and too many subclass implementations for jit to inline properly? return (flags & FLAG_CALCULATED) != 0; } public OlapElement lookupChild( SchemaReader schemaReader, Id.Segment childName, MatchType matchType) { return schemaReader.lookupMemberChildByName( this, childName, matchType); } // implement Member public Member getParentMember() { return parentMember; } // implement Member public boolean isChildOrEqualTo(Member member) { // REVIEW: Using uniqueName to calculate ancestry seems inefficient, // because we can't afford to store every member's unique name, so // we want to compute it on the fly assert !Bug.BugSegregateRolapCubeMemberFixed; return (member != null) && isChildOrEqualTo(member.getUniqueName()); } /** * Returns whether this Member's unique name is equal to, a * child of, or a descendent of a member whose unique name is * uniqueName. */ public boolean isChildOrEqualTo(String uniqueName) { if (uniqueName == null) { return false; } return isChildOrEqualTo(this, uniqueName); } private static boolean isChildOrEqualTo(Member member, String uniqueName) { while (true) { String thisUniqueName = member.getUniqueName(); if (thisUniqueName.equals(uniqueName)) { // found a match return true; } // try candidate's parentMember member = member.getParentMember(); if (member == null) { // have reached root return false; } } } /** * Computes the value to be returned by {@link #isCalculated()}, so it can * be cached in a variable. * * @param memberType Member type * @return Whether this member is calculated */ protected boolean computeCalculated(final MemberType memberType) { // If the member is not created from the "with member ..." MDX, the // calculated will be null. But it may be still a calculated measure // stored in the cube. return isCalculatedInQuery() || memberType == MemberType.FORMULA; } public int getSolveOrder() { return -1; } /** * Returns the expression by which this member is calculated. The expression * is not null if and only if the member is not calculated. * * @post (return != null) == (isCalculated()) */ public Exp getExpression() { return null; } // implement Member public List getAncestorMembers() { final SchemaReader schemaReader = getDimension().getSchema().getSchemaReader(); final ArrayList ancestorList = new ArrayList(); schemaReader.getMemberAncestors(this, ancestorList); return ancestorList; } /** * Returns the ordinal of this member within its hierarchy. * The default implementation returns -1. */ public int getOrdinal() { return -1; } /** * Returns the order key of this member among its siblings. * The default implementation returns null. */ public Comparable getOrderKey() { return null; } public boolean isHidden() { return false; } public Member getDataMember() { return null; } public String getPropertyFormattedValue(String propertyName) { return getPropertyValue(propertyName).toString(); } public boolean isParentChildLeaf() { return false; } } // End MemberBase.java mondrian-3.4.1/src/main/mondrian/olap/QueryPart.java0000644000175000017500000000251411735330606022304 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 1998-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import java.io.PrintWriter; /** * Component of an MDX query (derived classes include Query, Axis, Exp, Level). * * @author jhyde, 23 January, 1999 */ public abstract class QueryPart implements Walkable { /** * Creates a QueryPart. */ QueryPart() { } /** * Writes a string representation of this parse tree * node to the given writer. * * @param pw writer */ public void unparse(PrintWriter pw) { pw.print(toString()); } // implement Walkable public Object[] getChildren() { // By default, a QueryPart is atomic (has no children). return null; } /** * Returns the plan that Mondrian intends to use to execute this query. * * @param pw Print writer */ public void explain(PrintWriter pw) { throw new UnsupportedOperationException( "explain not implemented for " + this + " (" + getClass() + ")"); } } // End QueryPart.java mondrian-3.4.1/src/main/mondrian/olap/Evaluator.java0000644000175000017500000004137711735330606022324 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2001-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.olap; import mondrian.calc.ParameterSlot; import mondrian.calc.TupleIterable; import java.util.*; /** * An Evaluator holds the context necessary to evaluate an * expression. * * @author jhyde * @since 27 July, 2001 */ public interface Evaluator { /** * Returns the current cube. */ Cube getCube(); /** * Returns the current query. */ Query getQuery(); /** * Returns the start time of the current query. */ Date getQueryStartTime(); /** * Creates a savepoint encapsulating the current state of the evalutor. * You can restore the evaluator to this state by calling * {@link #restore(int)} with the value returned by this method. * *

    This method is typically called before evaluating an expression which * is known to corrupt the evaluation context. * *

    Multiple savepoints may be active at the same time for the same * evaluator. And, it is allowable to restore to the save savepoint more * than once (or not at all). However, when you have rolled back to a * particular savepoint you may not restore to a later savepoint. * * @return Evaluator with each given member overriding the state of the * current Evaluator for its hierarchy */ int savepoint(); /** * Creates a new Evaluator with each given member overriding the context of * the current Evaluator for its hierarchy. Other hierarchies retain the * same context as this Evaluator. * *

    In mondrian-3.3 and later, a more efficient way to save the state of * an evaluator is to call {@link #savepoint} followed by * {@link #restore(int)}. We recommend using those methods. * * @param members Array of members to add to the context * @return Evaluator with each given member overriding the state of the * current Evaluator for its hierarchy * * @deprecated Use {@link #savepoint()} followed by * {@link #setContext(Member[])}; will be removed in mondrian-4 */ Evaluator push(Member[] members); /** * Creates a new Evaluator with the same context as this evaluator. * *

    This method is typically called before evaluating an expression which * may corrupt the evaluation context. * *

    In mondrian-3.3 and later, a more efficient way to save the state of * an evaluator is to call {@link #savepoint} followed by * {@link #restore(int)}. We recommend using those methods most of the time. * *

    However, it makes sense to use this method in the constructor of an * iterator. It allows the iterator to modify its evaluation context without * affecting the evaluation context of the calling code. This behavior * cannot be achieved using {@code savepoint}. * * @return Evaluator with each given member overriding the state of the * current Evaluator for its hierarchy */ Evaluator push(); /** * Creates a new Evaluator with the same context except for one member. * *

    This method is typically called before evaluating an expression which * may corrupt the evaluation context. * *

    In mondrian-3.3 and later, a more efficient way to save the state of * an evaluator is to call {@link #savepoint} followed by * {@link #restore(int)}. We recommend using those methods. * * @param member Member to add to the context * @return Evaluator with each given member overriding the state of the * current Evaluator for its hierarchy * * @deprecated Use {@link #savepoint()} followed by * {@link #setContext(Member)}; will be removed in mondrian-4 */ Evaluator push(Member member); /** * Creates a new evaluator with the same state except nonEmpty property * *

    In mondrian-3.3 and later, a more efficient way to save the state of * an evaluator is to call {@link #savepoint} followed by * {@link #restore(int)}. We recommend using those methods. * * @deprecated Use {@link #savepoint()} followed by * {@link #setNonEmpty(boolean)}; will be removed in mondrian-4 */ Evaluator push(boolean nonEmpty); /** * Creates a new evaluator with the same state except nonEmpty * and nativeEnabled properties. * *

    In mondrian-3.3 and later, a more efficient way to save the state of * an evaluator is to call {@link #savepoint} followed by * {@link #restore(int)}. We recommend using those methods. * * @deprecated Use {@link #savepoint()} followed by * {@link #setNonEmpty(boolean)} and * {@link #setNativeEnabled(boolean)}; will be removed in mondrian-4. */ Evaluator push(boolean nonEmpty, boolean nativeEnabled); /** * Restores previous evaluator. * * @param savepoint Savepoint returned by {@link #savepoint()} */ void restore(int savepoint); /** * Makes member the current member of its hierarchy. * * @param member New member * * @return Previous member of this hierarchy */ Member setContext(Member member); /** * Makes member the current member of its hierarchy. * *

    If {@code safe}, checks whether this is the first time that * a member of this hierarchy has been changed since {@link #savepoint()} * was called. If so, saves the previous member. If {@code safe} is false, * never saves the previous member. * *

    Use {@code safe = false} only if you are sure that the context has * been set before. For example, * *

    * int n = 0;
    * for (Member member : members) {
    *   evaluator.setContext(member, n++ > 0);
    * }
    * * @param member New member * @param safe Whether to store the member of this hierarchy that was * current last time that {@link #savepoint()} was called. */ void setContext(Member member, boolean safe); /** * Sets the context to a list of members. * *

    Equivalent to * *

    for (Member member : memberList) {
    *   setContext(member);
    * }
    * * @param memberList List of members */ void setContext(List memberList); /** * Sets the context to a list of members, optionally skipping the check * whether it is necessary to store the previous member of each hierarchy. * *

    Equivalent to * *

    for (Member member : memberList) {
    *   setContext(member, safe);
    * }
    * * @param memberList List of members * @param safe Whether to store the member of each hierarchy that was * current last time that {@link #savepoint()} was called. */ void setContext(List memberList, boolean safe); /** * Sets the context to an array of members. * *

    Equivalent to * *

    for (Member member : memberList) {
    *   setContext(member);
    * }
    * * @param members Array of members */ void setContext(Member[] members); /** * Sets the context to an array of members, optionally skipping the check * whether it is necessary to store the previous member of each hierarchy. * *

    Equivalent to * *

    for (Member member : memberList) {
    *   setContext(member, safe);
    * }
    * * @param members Array of members * @param safe Whether to store the member of each hierarchy that was * current last time that {@link #savepoint()} was called. */ void setContext(Member[] members, boolean safe); Member getContext(Hierarchy hierarchy); /** * Calculates and returns the value of the cell at the current context. */ Object evaluateCurrent(); /** * Returns the format string for this cell. This is computed by evaluating * the format expression in the current context, and therefore different * cells may have different format strings. */ public String getFormatString(); /** * Formats a value as a string according to the current context's * format. */ String format(Object o); /** * Formats a value as a string according to the current context's * format, using a given format string. */ String format(Object o, String formatString); /** * Obsolete method. * * @deprecated Will be removed in mondrian-4 */ int getDepth(); /** * Returns parent evaluator. * * @deprecated Will be removed in mondrian-4 */ Evaluator getParent(); /** * Returns the connection's locale. */ Locale getConnectionLocale(); /** * Retrieves the value of property name. If more than one * member in the current context defines that property, the one with the * highest solve order has precedence. * *

    If the property is not defined, default value is returned. */ Object getProperty(String name, Object defaultValue); /** * Returns a {@link SchemaReader} appropriate for the current * access-control context. */ SchemaReader getSchemaReader(); /** * Simple caching of the result of an Exp. The * key for the cache consists of all members of the current * context that exp depends on. Members of * independent hierarchies are not part of the key. * * @see mondrian.calc.Calc#dependsOn(Hierarchy) */ Object getCachedResult(ExpCacheDescriptor key); /** * Returns true for an axis that is NON EMPTY. * *

    May be used by expression * evaluators to optimize their result. For example, a top-level crossjoin * may be optimized by removing all non-empty set elements before * performing the crossjoin. This is possible because of the identity * *

    nonempty(crossjoin(a, b)) == * nonempty(crossjoin(nonempty(a), nonempty(b));
    */ boolean isNonEmpty(); /** * Sets whether an expression evaluation should filter out empty cells. * Allows expressions to modify non empty flag to evaluate their children. */ void setNonEmpty(boolean nonEmpty); /** * Creates an exception which indicates that an error has occurred during * the runtime evaluation of a function. The caller should then throw that * exception. */ RuntimeException newEvalException(Object context, String s); /** * Returns an evaluator for a named set. * * @param namedSet Named set * @param create Whether to create evaluator if not found * @return Evaluator of named set */ NamedSetEvaluator getNamedSetEvaluator(NamedSet namedSet, boolean create); /** * Returns an array of the members which make up the current context. */ Member[] getMembers(); /** * Returns an array of the non-All members which make up the current * context. * *

    Notes:

      *
    • The 0th element is a measure, but otherwise the order of the * members is unspecified. *
    • No hierarchy occurs more than once. *
    • In rare circumstances, some of the members may be an 'All' member. *
    • The list may contain calculated members. *
    */ Member[] getNonAllMembers(); /** * Returns the number of times that this evaluator has told a lie when * retrieving cell values. */ int getMissCount(); /** * Returns the value of a parameter, evaluating its default value if it is * not set. */ Object getParameterValue(ParameterSlot slot); /** * @return the iteration length of the current context */ int getIterationLength(); /** * Sets the iteration length for the current evaluator context * * @param length length to be set */ void setIterationLength(int length); /** * @return true if evaluating axes */ boolean isEvalAxes(); /** * Indicate whether the evaluator is evaluating the axes * * @param evalAxes true if evaluating axes */ void setEvalAxes(boolean evalAxes); /** * Returns a new Aggregator whose aggregation context adds a given list of * tuples, and whose evaluation context is the same as this * Aggregator. * * @param list List of tuples * @return Aggregator with list added to its aggregation * context */ Evaluator pushAggregation(List> list); /** * Returns whether hierarchies unrelated to the measure in the current * context should be ignored. * * @return whether hierarchies unrelated to the measure in the current * context should be ignored */ boolean shouldIgnoreUnrelatedDimensions(); /** * Returns the base (non-virtual) cube that the current measure in the * context belongs to. * @return Cube */ Cube getMeasureCube(); /** * Returns whether it is necessary to check whether to return null for * an unrelated dimension. If false, we never need to check: we can assume * that {@link #needToReturnNullForUnrelatedDimension(mondrian.olap.Member[])} * will always return false. * * @return whether it is necessary to check whether to return null for * an unrelated dimension */ boolean mightReturnNullForUnrelatedDimension(); /** * If IgnoreMeasureForNonJoiningDimension is set to true and one or more * members are on unrelated dimension for the measure in current context * then returns true. * *

    You must not call this method unless * {@link #mightReturnNullForUnrelatedDimension()} has returned true. * * @param members Dimensions for the members need to be checked whether * related or unrelated * * @return boolean */ boolean needToReturnNullForUnrelatedDimension(Member[] members); /** * Returns whether native evaluation is enabled in this context. * * @return whether native evaluation is enabled in this context */ boolean nativeEnabled(); /** * Sets whether native evaluation should be used. * * @param nativeEnabled Whether native evaluation should be used */ void setNativeEnabled(boolean nativeEnabled); /** * Returns whether the current context is an empty cell. * * @return Whether the current context is an empty cell */ boolean currentIsEmpty(); /** * Returns the member that was the current evaluation context for a * particular hierarchy before the most recent change in context. * * @param hierarchy Hierarchy * @return Previous context member for given hierarchy */ Member getPreviousContext(Hierarchy hierarchy); /** * Returns the query timing context for this execution. * * @return query timing context */ QueryTiming getTiming(); /** * Interface for evaluating a particular named set. */ interface NamedSetEvaluator { /** * Returns an iterator over the tuples of the named set. Applicable if * the named set is a set of tuples. * *

    The iterator from this iterable maintains the current ordinal * property required for the methods {@link #currentOrdinal()} and * {@link #currentTuple()}. * * @return Iterable over the tuples of the set */ TupleIterable evaluateTupleIterable(); /** * Returns the ordinal of the current member or tuple in the named set. * * @return Ordinal of the current member or tuple in the named set */ int currentOrdinal(); /** * Returns the current member in the named set. * *

    Applicable if the named set is a set of members. * * @return Current member */ Member currentMember(); /** * Returns the current tuple in the named set. * *

    Applicable if the named set is a set of tuples. * * @return Current tuple. */ Member[] currentTuple(); } } // End Evaluator.java mondrian-3.4.1/src/main/mondrian/xmla/0000755000175000017500000000000011744246550017515 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/xmla/PropertyDefinition.java0000644000175000017500000002674111735330606024223 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.xmla; import mondrian.olap.MondrianServer; import org.olap4j.impl.Olap4jUtil; import org.olap4j.metadata.XmlaConstants; import java.util.Set; /** * Defines an XML for Analysis Property. * * @author jhyde * @since May 2, 2003 */ public enum PropertyDefinition { AxisFormat( RowsetDefinition.Type.Enumeration, Olap4jUtil.enumSetAllOf(XmlaConstants.AxisFormat.class), XmlaConstants.Access.Write, "", XmlaConstants.Method.EXECUTE, "Determines the format used within an MDDataSet result set to describe the axes of the multidimensional dataset. This property can have the values listed in the following table: TupleFormat (default), ClusterFormat, CustomFormat."), BeginRange( RowsetDefinition.Type.Integer, null, XmlaConstants.Access.Write, "-1", XmlaConstants.Method.EXECUTE, "Contains a zero-based integer value corresponding to a CellOrdinal attribute value. (The CellOrdinal attribute is part of the Cell element in the CellData section of MDDataSet.)\n" + "Used together with the EndRange property, the client application can use this property to restrict an OLAP dataset returned by a command to a specific range of cells. If -1 is specified, all cells up to the cell specified in the EndRange property are returned.\n" + "The default value for this property is -1."), Catalog( RowsetDefinition.Type.String, null, XmlaConstants.Access.ReadWrite, "", XmlaConstants.Method.DISCOVER_AND_EXECUTE, "When establishing a session with an Analysis Services instance to send an XMLA command, this property is equivalent to the OLE DB property, DBPROP_INIT_CATALOG.\n" + "When you set this property during a session to change the current database for the session, this property is equivalent to the OLE DB property, DBPROP_CURRENTCATALOG.\n" + "The default value for this property is an empty string."), Content( RowsetDefinition.Type.EnumString, Olap4jUtil.enumSetAllOf(XmlaConstants.Content.class), XmlaConstants.Access.Write, XmlaConstants.Content.DEFAULT.name(), XmlaConstants.Method.DISCOVER_AND_EXECUTE, "An enumerator that specifies what type of data is returned in the result set.\n" + "None: Allows the structure of the command to be verified, but not executed. Analogous to using Prepare to check syntax, and so on.\n" + "Schema: Contains the XML schema (which indicates column information, and so on) that relates to the requested query.\n" + "Data: Contains only the data that was requested.\n" + "SchemaData: Returns both the schema information as well as the data."), Cube( RowsetDefinition.Type.String, null, XmlaConstants.Access.ReadWrite, "", XmlaConstants.Method.EXECUTE, "The cube context for the Command parameter. If the command contains a cube name (such as an MDX FROM clause) the setting of this property is ignored."), DataSourceInfo( RowsetDefinition.Type.String, null, XmlaConstants.Access.ReadWrite, "", XmlaConstants.Method.DISCOVER_AND_EXECUTE, "A string containing provider specific information, required to access the data source."), // Mondrian-specific extension to XMLA. Deep( RowsetDefinition.Type.Boolean, null, XmlaConstants.Access.ReadWrite, "", XmlaConstants.Method.DISCOVER, "In an MDSCHEMA_CUBES request, whether to include sub-elements " + "(dimensions, hierarchies, levels, measures, named sets) of each " + "cube."), // Mondrian-specific extension to XMLA. EmitInvisibleMembers( RowsetDefinition.Type.Boolean, null, XmlaConstants.Access.ReadWrite, "", XmlaConstants.Method.DISCOVER, "Whether to include members whose VISIBLE property is false, or " + "measures whose MEASURE_IS_VISIBLE property is false."), EndRange( RowsetDefinition.Type.Integer, null, XmlaConstants.Access.Write, "-1", XmlaConstants.Method.EXECUTE, "An integer value corresponding to a CellOrdinal used to restrict an MDDataSet returned by a command to a specific range of cells. Used in conjunction with the BeginRange property. If unspecified, all cells are returned in the rowset. The value -1 means unspecified."), Format( RowsetDefinition.Type.EnumString, Olap4jUtil.enumSetAllOf(XmlaConstants.Format.class), XmlaConstants.Access.Write, "Native", XmlaConstants.Method.DISCOVER_AND_EXECUTE, "Enumerator that determines the format of the returned result set. Values include:\n" + "Tabular: a flat or hierarchical rowset. Similar to the XML RAW format in SQL. The Format property should be set to Tabular for OLE DB for Data Mining commands.\n" + "Multidimensional: Indicates that the result set will use the MDDataSet format (Execute method only).\n" + "Native: The client does not request a specific format, so the provider may return the format appropriate to the query. (The actual result type is identified by namespace of the result.)"), LocaleIdentifier( RowsetDefinition.Type.UnsignedInteger, null, XmlaConstants.Access.ReadWrite, "None", XmlaConstants.Method.DISCOVER_AND_EXECUTE, "Use this to read or set the numeric locale identifier for this request. The default is provider-specific.\n" + "For the complete hexadecimal list of language identifiers, search on \"Language Identifiers\" in the MSDN Library at http://www.msdn.microsoft.com.\n" + "As an extension to the XMLA standard, Mondrian also allows locale codes as specified by ISO-639 and ISO-3166 and as used by Java; for example 'en-US'.\n"), MDXSupport( RowsetDefinition.Type.EnumString, Olap4jUtil.enumSetAllOf(XmlaConstants.MdxSupport.class), XmlaConstants.Access.Read, "Core", XmlaConstants.Method.DISCOVER, "Enumeration that describes the degree of MDX support. At initial release Core is the only value in the enumeration. In future releases, other values will be defined for this enumeration."), Password( RowsetDefinition.Type.String, null, org.olap4j.metadata.XmlaConstants.Access.Read, "", XmlaConstants.Method.DISCOVER_AND_EXECUTE, "This property is deprecated in XMLA 1.1. To support legacy applications, the provider accepts but ignores the Password property setting when it is used with the Discover and Execute method"), ProviderName( RowsetDefinition.Type.String, null, XmlaConstants.Access.Read, "Mondrian XML for Analysis Provider", XmlaConstants.Method.DISCOVER, "The XML for Analysis Provider name."), ProviderVersion( RowsetDefinition.Type.String, null, XmlaConstants.Access.Read, MondrianServer.forId(null).getVersion().getVersionString(), XmlaConstants.Method.DISCOVER, "The version of the Mondrian XMLA Provider"), // Mondrian-specific extension to XMLA. /** * @see Enumeration.ResponseMimeType */ ResponseMimeType( RowsetDefinition.Type.String, null, XmlaConstants.Access.ReadWrite, "None", XmlaConstants.Method.DISCOVER_AND_EXECUTE, "Accepted mime type for RPC response; accepted are 'text/xml' " + "(default), 'application/xml' (equivalent to 'text/xml'), or " + "'application/json'. If not specified, value in the 'Accept' header " + "of the HTTP request is used."), StateSupport( RowsetDefinition.Type.EnumString, Olap4jUtil.enumSetAllOf(XmlaConstants.StateSupport.class), XmlaConstants.Access.Read, "None", XmlaConstants.Method.DISCOVER, "Property that specifies the degree of support in the provider for state. For information about state in XML for Analysis, see \"Support for Statefulness in XML for Analysis.\" Minimum enumeration values are as follows:\n" + "None - No support for sessions or stateful operations.\n" + "Sessions - Provider supports sessions."), Timeout( RowsetDefinition.Type.UnsignedInteger, null, XmlaConstants.Access.ReadWrite, "Undefined", XmlaConstants.Method.DISCOVER_AND_EXECUTE, "A numeric time-out specifying in seconds the amount of time to wait for a request to be successful."), UserName( RowsetDefinition.Type.String, null, XmlaConstants.Access.Read, "", XmlaConstants.Method.DISCOVER_AND_EXECUTE, "Returns the UserName the server associates with the command.\n" + "This property is deprecated as writeable in XMLA 1.1. To support legacy applications, servers accept but ignore the password setting when it is used with the Execute method."), VisualMode( RowsetDefinition.Type.Enumeration, Olap4jUtil.enumSetAllOf(XmlaConstants.VisualMode.class), XmlaConstants.Access.Write, Integer.toString(XmlaConstants.VisualMode.VISUAL.ordinal()), XmlaConstants.Method.DISCOVER_AND_EXECUTE, "This property is equivalent to the OLE DB property, MDPROP_VISUALMODE.\n" + "The default value for this property is zero (0), equivalent to DBPROPVAL_VISUAL_MODE_DEFAULT."), // mondrian-specific property for advanced drill-through TableFields( RowsetDefinition.Type.String, null, XmlaConstants.Access.Read, "", XmlaConstants.Method.DISCOVER_AND_EXECUTE, "List of fields to return for drill-through.\n" + "The default value of this property is the empty string," + "in which case, all fields are returned."), // mondrian-specific property for advanced drill-through AdvancedFlag( RowsetDefinition.Type.Boolean, null, XmlaConstants.Access.Read, "false", XmlaConstants.Method.DISCOVER_AND_EXECUTE, ""); final RowsetDefinition.Type type; final Set enumSet; final XmlaConstants.Access access; final XmlaConstants.Method usage; final String value; final String description; PropertyDefinition( RowsetDefinition.Type type, Set enumSet, XmlaConstants.Access access, String value, XmlaConstants.Method usage, String description) { // Line endings must be UNIX style (LF) not Windows style (LF+CR). // Thus the client will receive the same XML, regardless // of the server O/S. assert description.indexOf('\r') == -1; assert value.indexOf('\r') == -1; assert (enumSet != null) == type.isEnum(); this.type = type; this.enumSet = enumSet; this.access = access; this.usage = usage; this.value = value; this.description = description; } /** * Returns the description of this PropertyDefinition. * * @return description */ public String getDescription() { return description; } } // End PropertyDefinition.java mondrian-3.4.1/src/main/mondrian/xmla/XmlaResponse.java0000644000175000017500000000121511735330606022773 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2006 Pentaho and others // All Rights Reserved. */ package mondrian.xmla; /** * XML/A response interface. * * @author Gang Chen */ public interface XmlaResponse { /** * Report XML/A error (not SOAP fault). */ public void error(Throwable t); /** * Get helper for writing XML document. */ public SaxWriter getWriter(); } // End XmlaResponse.java mondrian-3.4.1/src/main/mondrian/xmla/package.html0000644000175000017500000000014711735330606021774 0ustar drazzibdrazzib Implements the XML for Analysis API. mondrian-3.4.1/src/main/mondrian/xmla/RowsetDefinition.java0000644000175000017500000073362111735330606023664 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.xmla; import mondrian.olap.*; import mondrian.util.Composite; import org.olap4j.OlapConnection; import org.olap4j.OlapException; import org.olap4j.impl.ArrayNamedListImpl; import org.olap4j.impl.Olap4jUtil; import org.olap4j.mdx.IdentifierNode; import org.olap4j.mdx.IdentifierSegment; import org.olap4j.metadata.*; import org.olap4j.metadata.Cube; import org.olap4j.metadata.Dimension; import org.olap4j.metadata.Hierarchy; import org.olap4j.metadata.Level; import org.olap4j.metadata.Member; import org.olap4j.metadata.Member.TreeOp; import org.olap4j.metadata.NamedSet; import org.olap4j.metadata.Property; import org.olap4j.metadata.Schema; import org.olap4j.metadata.XmlaConstants; import java.lang.reflect.*; import java.sql.SQLException; import java.text.Format; import java.text.SimpleDateFormat; import java.util.*; import static mondrian.olap.Util.filter; import static mondrian.xmla.XmlaConstants.*; import static mondrian.xmla.XmlaHandler.getExtra; /** * RowsetDefinition defines a rowset, including the columns it * should contain. * *

    See "XML for Analysis Rowsets", page 38 of the XML for Analysis * Specification, version 1.1. * * @author jhyde */ public enum RowsetDefinition { /** * Returns a list of XML for Analysis data sources * available on the server or Web Service. (For an * example of how these may be published, see * "XML for Analysis Implementation Walkthrough" * in the XML for Analysis specification.) * * http://msdn2.microsoft.com/en-us/library/ms126129(SQL.90).aspx * * * restrictions * * Not supported */ DISCOVER_DATASOURCES( 0, "Returns a list of XML for Analysis data sources available on the " + "server or Web Service.", new Column[] { DiscoverDatasourcesRowset.DataSourceName, DiscoverDatasourcesRowset.DataSourceDescription, DiscoverDatasourcesRowset.URL, DiscoverDatasourcesRowset.DataSourceInfo, DiscoverDatasourcesRowset.ProviderName, DiscoverDatasourcesRowset.ProviderType, DiscoverDatasourcesRowset.AuthenticationMode, }, // XMLA does not specify a sort order, but olap4j does. new Column[] { DiscoverDatasourcesRowset.DataSourceName, }) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new DiscoverDatasourcesRowset(request, handler); } }, /** * Note that SQL Server also returns the data-mining columns. * * * restrictions * * Not supported */ DISCOVER_SCHEMA_ROWSETS( 2, "Returns the names, values, and other information of all supported " + "RequestType enumeration values.", new Column[] { DiscoverSchemaRowsetsRowset.SchemaName, DiscoverSchemaRowsetsRowset.SchemaGuid, DiscoverSchemaRowsetsRowset.Restrictions, DiscoverSchemaRowsetsRowset.Description, }, null /* not sorted */) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new DiscoverSchemaRowsetsRowset(request, handler); } protected void writeRowsetXmlSchemaRowDef(SaxWriter writer) { writer.startElement( "xsd:complexType", "name", "row"); writer.startElement("xsd:sequence"); for (Column column : columnDefinitions) { final String name = XmlaUtil.ElementNameEncoder.INSTANCE.encode(column.name); if (column == DiscoverSchemaRowsetsRowset.Restrictions) { writer.startElement( "xsd:element", "sql:field", column.name, "name", name, "minOccurs", 0, "maxOccurs", "unbounded"); writer.startElement("xsd:complexType"); writer.startElement("xsd:sequence"); writer.element( "xsd:element", "name", "Name", "type", "xsd:string", "sql:field", "Name"); writer.element( "xsd:element", "name", "Type", "type", "xsd:string", "sql:field", "Type"); writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType writer.endElement(); // xsd:element } else { final String xsdType = column.type.columnType; Object[] attrs; if (column.nullable) { if (column.unbounded) { attrs = new Object[]{ "sql:field", column.name, "name", name, "type", xsdType, "minOccurs", 0, "maxOccurs", "unbounded" }; } else { attrs = new Object[]{ "sql:field", column.name, "name", name, "type", xsdType, "minOccurs", 0 }; } } else { if (column.unbounded) { attrs = new Object[]{ "sql:field", column.name, "name", name, "type", xsdType, "maxOccurs", "unbounded" }; } else { attrs = new Object[]{ "sql:field", column.name, "name", name, "type", xsdType }; } } writer.element("xsd:element", attrs); } } writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType } }, /** * * * * restrictions * * Not supported */ DISCOVER_ENUMERATORS( 3, "Returns a list of names, data types, and enumeration values for " + "enumerators supported by the provider of a specific data source.", new Column[] { DiscoverEnumeratorsRowset.EnumName, DiscoverEnumeratorsRowset.EnumDescription, DiscoverEnumeratorsRowset.EnumType, DiscoverEnumeratorsRowset.ElementName, DiscoverEnumeratorsRowset.ElementDescription, DiscoverEnumeratorsRowset.ElementValue, }, null /* not sorted */) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new DiscoverEnumeratorsRowset(request, handler); } }, /** * * * * restrictions * * Not supported */ DISCOVER_PROPERTIES( 1, "Returns a list of information and values about the requested " + "properties that are supported by the specified data source " + "provider.", new Column[] { DiscoverPropertiesRowset.PropertyName, DiscoverPropertiesRowset.PropertyDescription, DiscoverPropertiesRowset.PropertyType, DiscoverPropertiesRowset.PropertyAccessType, DiscoverPropertiesRowset.IsRequired, DiscoverPropertiesRowset.Value, }, null /* not sorted */) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new DiscoverPropertiesRowset(request, handler); } }, /** * * * * restrictions * * Not supported */ DISCOVER_KEYWORDS( 4, "Returns an XML list of keywords reserved by the provider.", new Column[] { DiscoverKeywordsRowset.Keyword, }, null /* not sorted */) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new DiscoverKeywordsRowset(request, handler); } }, /** * * * * restrictions * * Not supported */ DISCOVER_LITERALS( 5, "Returns information about literals supported by the provider.", new Column[] { DiscoverLiteralsRowset.LiteralName, DiscoverLiteralsRowset.LiteralValue, DiscoverLiteralsRowset.LiteralInvalidChars, DiscoverLiteralsRowset.LiteralInvalidStartingChars, DiscoverLiteralsRowset.LiteralMaxLength, }, null /* not sorted */) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new DiscoverLiteralsRowset(request, handler); } }, /** * * * * restrictions * * Not supported */ DBSCHEMA_CATALOGS( 6, "Identifies the physical attributes associated with catalogs " + "accessible from the provider.", new Column[] { DbschemaCatalogsRowset.CatalogName, DbschemaCatalogsRowset.Description, DbschemaCatalogsRowset.Roles, DbschemaCatalogsRowset.DateModified, }, new Column[] { DbschemaCatalogsRowset.CatalogName, }) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new DbschemaCatalogsRowset(request, handler); } }, /** * * * * restrictions * * Not supported * COLUMN_OLAP_TYPE */ DBSCHEMA_COLUMNS( 7, null, new Column[] { DbschemaColumnsRowset.TableCatalog, DbschemaColumnsRowset.TableSchema, DbschemaColumnsRowset.TableName, DbschemaColumnsRowset.ColumnName, DbschemaColumnsRowset.OrdinalPosition, DbschemaColumnsRowset.ColumnHasDefault, DbschemaColumnsRowset.ColumnFlags, DbschemaColumnsRowset.IsNullable, DbschemaColumnsRowset.DataType, DbschemaColumnsRowset.CharacterMaximumLength, DbschemaColumnsRowset.CharacterOctetLength, DbschemaColumnsRowset.NumericPrecision, DbschemaColumnsRowset.NumericScale, }, new Column[] { DbschemaColumnsRowset.TableCatalog, DbschemaColumnsRowset.TableSchema, DbschemaColumnsRowset.TableName, }) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new DbschemaColumnsRowset(request, handler); } }, /** * * * * restrictions * * Not supported */ DBSCHEMA_PROVIDER_TYPES( 8, null, new Column[] { DbschemaProviderTypesRowset.TypeName, DbschemaProviderTypesRowset.DataType, DbschemaProviderTypesRowset.ColumnSize, DbschemaProviderTypesRowset.LiteralPrefix, DbschemaProviderTypesRowset.LiteralSuffix, DbschemaProviderTypesRowset.IsNullable, DbschemaProviderTypesRowset.CaseSensitive, DbschemaProviderTypesRowset.Searchable, DbschemaProviderTypesRowset.UnsignedAttribute, DbschemaProviderTypesRowset.FixedPrecScale, DbschemaProviderTypesRowset.AutoUniqueValue, DbschemaProviderTypesRowset.IsLong, DbschemaProviderTypesRowset.BestMatch, }, new Column[] { DbschemaProviderTypesRowset.DataType, }) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new DbschemaProviderTypesRowset(request, handler); } }, DBSCHEMA_SCHEMATA( 8, null, new Column[] { DbschemaSchemataRowset.CatalogName, DbschemaSchemataRowset.SchemaName, DbschemaSchemataRowset.SchemaOwner, }, new Column[] { DbschemaSchemataRowset.CatalogName, DbschemaSchemataRowset.SchemaName, DbschemaSchemataRowset.SchemaOwner, }) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new DbschemaSchemataRowset(request, handler); } }, /** * http://msdn2.microsoft.com/en-us/library/ms126299(SQL.90).aspx * * restrictions: * TABLE_CATALOG Optional * TABLE_SCHEMA Optional * TABLE_NAME Optional * TABLE_TYPE Optional * TABLE_OLAP_TYPE Optional * * Not supported */ DBSCHEMA_TABLES( 9, null, new Column[] { DbschemaTablesRowset.TableCatalog, DbschemaTablesRowset.TableSchema, DbschemaTablesRowset.TableName, DbschemaTablesRowset.TableType, DbschemaTablesRowset.TableGuid, DbschemaTablesRowset.Description, DbschemaTablesRowset.TablePropId, DbschemaTablesRowset.DateCreated, DbschemaTablesRowset.DateModified, //TableOlapType, }, new Column[] { DbschemaTablesRowset.TableType, DbschemaTablesRowset.TableCatalog, DbschemaTablesRowset.TableSchema, DbschemaTablesRowset.TableName, }) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new DbschemaTablesRowset(request, handler); } }, /** * http://msdn.microsoft.com/library/en-us/oledb/htm/ * oledbtables_info_rowset.asp * * * restrictions * * Not supported */ DBSCHEMA_TABLES_INFO( 10, null, new Column[] { DbschemaTablesInfoRowset.TableCatalog, DbschemaTablesInfoRowset.TableSchema, DbschemaTablesInfoRowset.TableName, DbschemaTablesInfoRowset.TableType, DbschemaTablesInfoRowset.TableGuid, DbschemaTablesInfoRowset.Bookmarks, DbschemaTablesInfoRowset.BookmarkType, DbschemaTablesInfoRowset.BookmarkDataType, DbschemaTablesInfoRowset.BookmarkMaximumLength, DbschemaTablesInfoRowset.BookmarkInformation, DbschemaTablesInfoRowset.TableVersion, DbschemaTablesInfoRowset.Cardinality, DbschemaTablesInfoRowset.Description, DbschemaTablesInfoRowset.TablePropId, }, null /* cannot find doc -- presume unsorted */) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new DbschemaTablesInfoRowset(request, handler); } }, /** * http://msdn2.microsoft.com/en-us/library/ms126032(SQL.90).aspx * * restrictions * CATALOG_NAME Optional * SCHEMA_NAME Optional * CUBE_NAME Mandatory * ACTION_NAME Optional * ACTION_TYPE Optional * COORDINATE Mandatory * COORDINATE_TYPE Mandatory * INVOCATION * (Optional) The INVOCATION restriction column defaults to the * value of MDACTION_INVOCATION_INTERACTIVE. To retrieve all * actions, use the MDACTION_INVOCATION_ALL value in the * INVOCATION restriction column. * CUBE_SOURCE * (Optional) A bitmap with one of the following valid values: * * 1 CUBE * 2 DIMENSION * * Default restriction is a value of 1. * * Not supported */ MDSCHEMA_ACTIONS( 11, null, new Column[] { MdschemaActionsRowset.CatalogName, MdschemaActionsRowset.SchemaName, MdschemaActionsRowset.CubeName, MdschemaActionsRowset.ActionName, MdschemaActionsRowset.Coordinate, MdschemaActionsRowset.CoordinateType, }, new Column[] { // Spec says sort on CATALOG_NAME, SCHEMA_NAME, CUBE_NAME, // ACTION_NAME. MdschemaActionsRowset.CatalogName, MdschemaActionsRowset.SchemaName, MdschemaActionsRowset.CubeName, MdschemaActionsRowset.ActionName, }) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new MdschemaActionsRowset(request, handler); } }, /** * http://msdn2.microsoft.com/en-us/library/ms126271(SQL.90).aspx * * restrictions * CATALOG_NAME Optional. * SCHEMA_NAME Optional. * CUBE_NAME Optional. * CUBE_TYPE * (Optional) A bitmap with one of these valid values: * 1 CUBE * 2 DIMENSION * Default restriction is a value of 1. * BASE_CUBE_NAME Optional. * * Not supported * CREATED_ON * LAST_SCHEMA_UPDATE * SCHEMA_UPDATED_BY * LAST_DATA_UPDATE * DATA_UPDATED_BY * ANNOTATIONS */ MDSCHEMA_CUBES( 12, null, new Column[] { MdschemaCubesRowset.CatalogName, MdschemaCubesRowset.SchemaName, MdschemaCubesRowset.CubeName, MdschemaCubesRowset.CubeType, MdschemaCubesRowset.CubeGuid, MdschemaCubesRowset.CreatedOn, MdschemaCubesRowset.LastSchemaUpdate, MdschemaCubesRowset.SchemaUpdatedBy, MdschemaCubesRowset.LastDataUpdate, MdschemaCubesRowset.DataUpdatedBy, MdschemaCubesRowset.IsDrillthroughEnabled, MdschemaCubesRowset.IsWriteEnabled, MdschemaCubesRowset.IsLinkable, MdschemaCubesRowset.IsSqlEnabled, MdschemaCubesRowset.CubeCaption, MdschemaCubesRowset.Description, MdschemaCubesRowset.Dimensions, MdschemaCubesRowset.Sets, MdschemaCubesRowset.Measures }, new Column[] { MdschemaCubesRowset.CatalogName, MdschemaCubesRowset.SchemaName, MdschemaCubesRowset.CubeName, }) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new MdschemaCubesRowset(request, handler); } }, /** * http://msdn2.microsoft.com/en-us/library/ms126180(SQL.90).aspx * http://msdn2.microsoft.com/en-us/library/ms126180.aspx * * restrictions * CATALOG_NAME Optional. * SCHEMA_NAME Optional. * CUBE_NAME Optional. * DIMENSION_NAME Optional. * DIMENSION_UNIQUE_NAME Optional. * CUBE_SOURCE (Optional) A bitmap with one of the following valid * values: * 1 CUBE * 2 DIMENSION * Default restriction is a value of 1. * * DIMENSION_VISIBILITY (Optional) A bitmap with one of the following * valid values: * 1 Visible * 2 Not visible * Default restriction is a value of 1. */ MDSCHEMA_DIMENSIONS( 13, null, new Column[] { MdschemaDimensionsRowset.CatalogName, MdschemaDimensionsRowset.SchemaName, MdschemaDimensionsRowset.CubeName, MdschemaDimensionsRowset.DimensionName, MdschemaDimensionsRowset.DimensionUniqueName, MdschemaDimensionsRowset.DimensionGuid, MdschemaDimensionsRowset.DimensionCaption, MdschemaDimensionsRowset.DimensionOrdinal, MdschemaDimensionsRowset.DimensionType, MdschemaDimensionsRowset.DimensionCardinality, MdschemaDimensionsRowset.DefaultHierarchy, MdschemaDimensionsRowset.Description, MdschemaDimensionsRowset.IsVirtual, MdschemaDimensionsRowset.IsReadWrite, MdschemaDimensionsRowset.DimensionUniqueSettings, MdschemaDimensionsRowset.DimensionMasterUniqueName, MdschemaDimensionsRowset.DimensionIsVisible, MdschemaDimensionsRowset.Hierarchies, }, new Column[] { MdschemaDimensionsRowset.CatalogName, MdschemaDimensionsRowset.SchemaName, MdschemaDimensionsRowset.CubeName, MdschemaDimensionsRowset.DimensionName, }) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new MdschemaDimensionsRowset(request, handler); } }, /** * http://msdn2.microsoft.com/en-us/library/ms126257(SQL.90).aspx * * restrictions * LIBRARY_NAME Optional. * INTERFACE_NAME Optional. * FUNCTION_NAME Optional. * ORIGIN Optional. * * Not supported * DLL_NAME * Optional * HELP_FILE * Optional * HELP_CONTEXT * Optional * - SQL Server xml schema says that this must be present * OBJECT * Optional * CAPTION The display caption for the function. */ MDSCHEMA_FUNCTIONS( 14, null, new Column[] { MdschemaFunctionsRowset.FunctionName, MdschemaFunctionsRowset.Description, MdschemaFunctionsRowset.ParameterList, MdschemaFunctionsRowset.ReturnType, MdschemaFunctionsRowset.Origin, MdschemaFunctionsRowset.InterfaceName, MdschemaFunctionsRowset.LibraryName, MdschemaFunctionsRowset.Caption, }, new Column[] { MdschemaFunctionsRowset.LibraryName, MdschemaFunctionsRowset.InterfaceName, MdschemaFunctionsRowset.FunctionName, MdschemaFunctionsRowset.Origin, }) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new MdschemaFunctionsRowset(request, handler); } }, /** * http://msdn2.microsoft.com/en-us/library/ms126062(SQL.90).aspx * * restrictions * CATALOG_NAME Optional. * SCHEMA_NAME Optional. * CUBE_NAME Optional. * DIMENSION_UNIQUE_NAME Optional. * HIERARCHY_NAME Optional. * HIERARCHY_UNIQUE_NAME Optional. * HIERARCHY_ORIGIN * (Optional) A default restriction is in effect * on MD_USER_DEFINED and MD_SYSTEM_ENABLED. * CUBE_SOURCE * (Optional) A bitmap with one of the following valid values: * 1 CUBE * 2 DIMENSION * Default restriction is a value of 1. * HIERARCHY_VISIBILITY * (Optional) A bitmap with one of the following valid values: * 1 Visible * 2 Not visible * Default restriction is a value of 1. * * Not supported * HIERARCHY_ORIGIN * HIERARCHY_DISPLAY_FOLDER * INSTANCE_SELECTION */ MDSCHEMA_HIERARCHIES( 15, null, new Column[] { MdschemaHierarchiesRowset.CatalogName, MdschemaHierarchiesRowset.SchemaName, MdschemaHierarchiesRowset.CubeName, MdschemaHierarchiesRowset.DimensionUniqueName, MdschemaHierarchiesRowset.HierarchyName, MdschemaHierarchiesRowset.HierarchyUniqueName, MdschemaHierarchiesRowset.HierarchyGuid, MdschemaHierarchiesRowset.HierarchyCaption, MdschemaHierarchiesRowset.DimensionType, MdschemaHierarchiesRowset.HierarchyCardinality, MdschemaHierarchiesRowset.DefaultMember, MdschemaHierarchiesRowset.AllMember, MdschemaHierarchiesRowset.Description, MdschemaHierarchiesRowset.Structure, MdschemaHierarchiesRowset.IsVirtual, MdschemaHierarchiesRowset.IsReadWrite, MdschemaHierarchiesRowset.DimensionUniqueSettings, MdschemaHierarchiesRowset.DimensionIsVisible, MdschemaHierarchiesRowset.HierarchyIsVisible, MdschemaHierarchiesRowset.HierarchyOrdinal, MdschemaHierarchiesRowset.DimensionIsShared, MdschemaHierarchiesRowset.ParentChild, MdschemaHierarchiesRowset.Levels, }, new Column[] { MdschemaHierarchiesRowset.CatalogName, MdschemaHierarchiesRowset.SchemaName, MdschemaHierarchiesRowset.CubeName, MdschemaHierarchiesRowset.DimensionUniqueName, MdschemaHierarchiesRowset.HierarchyName, }) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new MdschemaHierarchiesRowset(request, handler); } }, /** * http://msdn2.microsoft.com/en-us/library/ms126038(SQL.90).aspx * * restriction * CATALOG_NAME Optional. * SCHEMA_NAME Optional. * CUBE_NAME Optional. * DIMENSION_UNIQUE_NAME Optional. * HIERARCHY_UNIQUE_NAME Optional. * LEVEL_NAME Optional. * LEVEL_UNIQUE_NAME Optional. * LEVEL_ORIGIN * (Optional) A default restriction is in effect * on MD_USER_DEFINED and MD_SYSTEM_ENABLED * CUBE_SOURCE * (Optional) A bitmap with one of the following valid values: * 1 CUBE * 2 DIMENSION * Default restriction is a value of 1. * LEVEL_VISIBILITY * (Optional) A bitmap with one of the following values: * 1 Visible * 2 Not visible * Default restriction is a value of 1. * * Not supported * CUSTOM_ROLLUP_SETTINGS * LEVEL_UNIQUE_SETTINGS * LEVEL_ORDERING_PROPERTY * LEVEL_DBTYPE * LEVEL_MASTER_UNIQUE_NAME * LEVEL_NAME_SQL_COLUMN_NAME Customers:(All)!NAME * LEVEL_KEY_SQL_COLUMN_NAME Customers:(All)!KEY * LEVEL_UNIQUE_NAME_SQL_COLUMN_NAME Customers:(All)!UNIQUE_NAME * LEVEL_ATTRIBUTE_HIERARCHY_NAME * LEVEL_KEY_CARDINALITY * LEVEL_ORIGIN * */ MDSCHEMA_LEVELS( 16, null, new Column[] { MdschemaLevelsRowset.CatalogName, MdschemaLevelsRowset.SchemaName, MdschemaLevelsRowset.CubeName, MdschemaLevelsRowset.DimensionUniqueName, MdschemaLevelsRowset.HierarchyUniqueName, MdschemaLevelsRowset.LevelName, MdschemaLevelsRowset.LevelUniqueName, MdschemaLevelsRowset.LevelGuid, MdschemaLevelsRowset.LevelCaption, MdschemaLevelsRowset.LevelNumber, MdschemaLevelsRowset.LevelCardinality, MdschemaLevelsRowset.LevelType, MdschemaLevelsRowset.CustomRollupSettings, MdschemaLevelsRowset.LevelUniqueSettings, MdschemaLevelsRowset.LevelIsVisible, MdschemaLevelsRowset.Description, }, new Column[] { MdschemaLevelsRowset.CatalogName, MdschemaLevelsRowset.SchemaName, MdschemaLevelsRowset.CubeName, MdschemaLevelsRowset.DimensionUniqueName, MdschemaLevelsRowset.HierarchyUniqueName, MdschemaLevelsRowset.LevelNumber, }) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new MdschemaLevelsRowset(request, handler); } }, /** * http://msdn2.microsoft.com/en-us/library/ms126250(SQL.90).aspx * * restrictions * CATALOG_NAME Optional. * SCHEMA_NAME Optional. * CUBE_NAME Optional. * MEASURE_NAME Optional. * MEASURE_UNIQUE_NAME Optional. * CUBE_SOURCE * (Optional) A bitmap with one of the following valid values: * 1 CUBE * 2 DIMENSION * Default restriction is a value of 1. * MEASURE_VISIBILITY * (Optional) A bitmap with one of the following valid values: * 1 Visible * 2 Not Visible * Default restriction is a value of 1. * * Not supported * MEASURE_GUID * NUMERIC_PRECISION * NUMERIC_SCALE * MEASURE_UNITS * EXPRESSION * MEASURE_NAME_SQL_COLUMN_NAME * MEASURE_UNQUALIFIED_CAPTION * MEASUREGROUP_NAME * MEASURE_DISPLAY_FOLDER * DEFAULT_FORMAT_STRING */ MDSCHEMA_MEASURES( 17, null, new Column[] { MdschemaMeasuresRowset.CatalogName, MdschemaMeasuresRowset.SchemaName, MdschemaMeasuresRowset.CubeName, MdschemaMeasuresRowset.MeasureName, MdschemaMeasuresRowset.MeasureUniqueName, MdschemaMeasuresRowset.MeasureCaption, MdschemaMeasuresRowset.MeasureGuid, MdschemaMeasuresRowset.MeasureAggregator, MdschemaMeasuresRowset.DataType, MdschemaMeasuresRowset.MeasureIsVisible, MdschemaMeasuresRowset.LevelsList, MdschemaMeasuresRowset.Description, MdschemaMeasuresRowset.FormatString, }, new Column[] { MdschemaMeasuresRowset.CatalogName, MdschemaMeasuresRowset.SchemaName, MdschemaMeasuresRowset.CubeName, MdschemaMeasuresRowset.MeasureName, }) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new MdschemaMeasuresRowset(request, handler); } }, /** * * http://msdn2.microsoft.com/es-es/library/ms126046.aspx * * * restrictions * CATALOG_NAME Optional. * SCHEMA_NAME Optional. * CUBE_NAME Optional. * DIMENSION_UNIQUE_NAME Optional. * HIERARCHY_UNIQUE_NAME Optional. * LEVEL_UNIQUE_NAME Optional. * LEVEL_NUMBER Optional. * MEMBER_NAME Optional. * MEMBER_UNIQUE_NAME Optional. * MEMBER_CAPTION Optional. * MEMBER_TYPE Optional. * TREE_OP (Optional) Only applies to a single member: * MDTREEOP_ANCESTORS (0x20) returns all of the ancestors. * MDTREEOP_CHILDREN (0x01) returns only the immediate children. * MDTREEOP_SIBLINGS (0x02) returns members on the same level. * MDTREEOP_PARENT (0x04) returns only the immediate parent. * MDTREEOP_SELF (0x08) returns itself in the list of * returned rows. * MDTREEOP_DESCENDANTS (0x10) returns all of the descendants. * CUBE_SOURCE (Optional) A bitmap with one of the * following valid values: * 1 CUBE * 2 DIMENSION * Default restriction is a value of 1. * * Not supported */ MDSCHEMA_MEMBERS( 18, null, new Column[] { MdschemaMembersRowset.CatalogName, MdschemaMembersRowset.SchemaName, MdschemaMembersRowset.CubeName, MdschemaMembersRowset.DimensionUniqueName, MdschemaMembersRowset.HierarchyUniqueName, MdschemaMembersRowset.LevelUniqueName, MdschemaMembersRowset.LevelNumber, MdschemaMembersRowset.MemberOrdinal, MdschemaMembersRowset.MemberName, MdschemaMembersRowset.MemberUniqueName, MdschemaMembersRowset.MemberType, MdschemaMembersRowset.MemberGuid, MdschemaMembersRowset.MemberCaption, MdschemaMembersRowset.ChildrenCardinality, MdschemaMembersRowset.ParentLevel, MdschemaMembersRowset.ParentUniqueName, MdschemaMembersRowset.ParentCount, MdschemaMembersRowset.TreeOp_, MdschemaMembersRowset.Depth, }, new Column[] { MdschemaMembersRowset.CatalogName, MdschemaMembersRowset.SchemaName, MdschemaMembersRowset.CubeName, MdschemaMembersRowset.DimensionUniqueName, MdschemaMembersRowset.HierarchyUniqueName, MdschemaMembersRowset.LevelUniqueName, MdschemaMembersRowset.LevelNumber, MdschemaMembersRowset.MemberOrdinal, }) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new MdschemaMembersRowset(request, handler); } }, /** * http://msdn2.microsoft.com/en-us/library/ms126309(SQL.90).aspx * * restrictions * CATALOG_NAME Mandatory * SCHEMA_NAME Optional * CUBE_NAME Optional * DIMENSION_UNIQUE_NAME Optional * HIERARCHY_UNIQUE_NAME Optional * LEVEL_UNIQUE_NAME Optional * * MEMBER_UNIQUE_NAME Optional * PROPERTY_NAME Optional * PROPERTY_TYPE Optional * PROPERTY_CONTENT_TYPE * (Optional) A default restriction is in place on MDPROP_MEMBER * OR MDPROP_CELL. * PROPERTY_ORIGIN * (Optional) A default restriction is in place on MD_USER_DEFINED * OR MD_SYSTEM_ENABLED * CUBE_SOURCE * (Optional) A bitmap with one of the following valid values: * 1 CUBE * 2 DIMENSION * Default restriction is a value of 1. * PROPERTY_VISIBILITY * (Optional) A bitmap with one of the following valid values: * 1 Visible * 2 Not visible * Default restriction is a value of 1. * * Not supported * PROPERTY_ORIGIN * CUBE_SOURCE * PROPERTY_VISIBILITY * CHARACTER_MAXIMUM_LENGTH * CHARACTER_OCTET_LENGTH * NUMERIC_PRECISION * NUMERIC_SCALE * DESCRIPTION * SQL_COLUMN_NAME * LANGUAGE * PROPERTY_ATTRIBUTE_HIERARCHY_NAME * PROPERTY_CARDINALITY * MIME_TYPE * PROPERTY_IS_VISIBLE */ MDSCHEMA_PROPERTIES( 19, null, new Column[] { MdschemaPropertiesRowset.CatalogName, MdschemaPropertiesRowset.SchemaName, MdschemaPropertiesRowset.CubeName, MdschemaPropertiesRowset.DimensionUniqueName, MdschemaPropertiesRowset.HierarchyUniqueName, MdschemaPropertiesRowset.LevelUniqueName, MdschemaPropertiesRowset.MemberUniqueName, MdschemaPropertiesRowset.PropertyName, MdschemaPropertiesRowset.PropertyCaption, MdschemaPropertiesRowset.PropertyType, MdschemaPropertiesRowset.DataType, MdschemaPropertiesRowset.PropertyContentType, MdschemaPropertiesRowset.Description }, null /* not sorted */) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new MdschemaPropertiesRowset(request, handler); } }, /** * http://msdn2.microsoft.com/en-us/library/ms126290(SQL.90).aspx * * restrictions * CATALOG_NAME Optional. * SCHEMA_NAME Optional. * CUBE_NAME Optional. * SET_NAME Optional. * SCOPE Optional. * HIERARCHY_UNIQUE_NAME Optional. * CUBE_SOURCE Optional. * Note: Only one hierarchy can be included, and only those named * sets whose hierarchies exactly match the restriction are * returned. * * Not supported * EXPRESSION * DIMENSIONS * SET_DISPLAY_FOLDER */ MDSCHEMA_SETS( 20, null, new Column[] { MdschemaSetsRowset.CatalogName, MdschemaSetsRowset.SchemaName, MdschemaSetsRowset.CubeName, MdschemaSetsRowset.SetName, MdschemaSetsRowset.Scope, }, new Column[] { MdschemaSetsRowset.CatalogName, MdschemaSetsRowset.SchemaName, MdschemaSetsRowset.CubeName, }) { public Rowset getRowset(XmlaRequest request, XmlaHandler handler) { return new MdschemaSetsRowset(request, handler); } }; transient final Column[] columnDefinitions; transient final Column[] sortColumnDefinitions; /** * Date the schema was last modified. * *

    TODO: currently schema grammar does not support modify date * so we return just some date for now. */ private static final String dateModified = "2005-01-25T17:35:32"; private final String description; static final String UUID_PATTERN = "[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-" + "[0-9a-fA-F]{12}"; /** * Creates a rowset definition. * * @param ordinal Rowset ordinal, per OLE DB for OLAP * @param description Description * @param columnDefinitions List of column definitions * @param sortColumnDefinitions List of column definitions to sort on, */ RowsetDefinition( int ordinal, String description, Column[] columnDefinitions, Column[] sortColumnDefinitions) { Util.discard(ordinal); this.description = description; this.columnDefinitions = columnDefinitions; this.sortColumnDefinitions = sortColumnDefinitions; } public abstract Rowset getRowset(XmlaRequest request, XmlaHandler handler); public Column lookupColumn(String name) { for (Column columnDefinition : columnDefinitions) { if (columnDefinition.name.equals(name)) { return columnDefinition; } } return null; } /** * Returns a comparator with which to sort rows of this rowset definition. * The sort order is defined by the {@link #sortColumnDefinitions} field. * If the rowset is not sorted, returns null. */ Comparator getComparator() { if (sortColumnDefinitions == null) { return null; } return new Comparator() { public int compare(Rowset.Row row1, Rowset.Row row2) { // A faster implementation is welcome. for (Column sortColumn : sortColumnDefinitions) { Comparable val1 = (Comparable) row1.get(sortColumn.name); Comparable val2 = (Comparable) row2.get(sortColumn.name); if ((val1 == null) && (val2 == null)) { // columns can be optional, compare next column continue; } else if (val1 == null) { return -1; } else if (val2 == null) { return 1; } else if (val1 instanceof String && val2 instanceof String) { int v = ((String) val1).compareToIgnoreCase((String) val2); // if equal (= 0), compare next column if (v != 0) { return v; } } else { int v = val1.compareTo(val2); // if equal (= 0), compare next column if (v != 0) { return v; } } } return 0; } }; } /** * Generates an XML schema description to the writer. * This is broken into top, Row definition and bottom so that on a * case by case basis a RowsetDefinition can redefine the Row * definition output. The default assumes a flat set of elements, but * for example, SchemaRowsets has a element with child elements. * * @param writer SAX writer * @see XmlaHandler#writeDatasetXmlSchema(SaxWriter, mondrian.xmla.XmlaHandler.SetType) */ void writeRowsetXmlSchema(SaxWriter writer) { writeRowsetXmlSchemaTop(writer); writeRowsetXmlSchemaRowDef(writer); writeRowsetXmlSchemaBottom(writer); } protected void writeRowsetXmlSchemaTop(SaxWriter writer) { writer.startElement( "xsd:schema", "xmlns:xsd", NS_XSD, "xmlns", NS_XMLA_ROWSET, "xmlns:xsi", NS_XSI, "xmlns:sql", "urn:schemas-microsoft-com:xml-sql", "targetNamespace", NS_XMLA_ROWSET, "elementFormDefault", "qualified"); writer.startElement( "xsd:element", "name", "root"); writer.startElement("xsd:complexType"); writer.startElement("xsd:sequence"); writer.element( "xsd:element", "name", "row", "type", "row", "minOccurs", 0, "maxOccurs", "unbounded"); writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType writer.endElement(); // xsd:element // MS SQL includes this in its schema section even thought // its not need for most queries. writer.startElement( "xsd:simpleType", "name", "uuid"); writer.startElement( "xsd:restriction", "base", "xsd:string"); writer.element( "xsd:pattern", "value", UUID_PATTERN); writer.endElement(); // xsd:restriction writer.endElement(); // xsd:simpleType } protected void writeRowsetXmlSchemaRowDef(SaxWriter writer) { writer.startElement( "xsd:complexType", "name", "row"); writer.startElement("xsd:sequence"); for (Column column : columnDefinitions) { final String name = XmlaUtil.ElementNameEncoder.INSTANCE.encode(column.name); final String xsdType = column.type.columnType; Object[] attrs; if (column.nullable) { if (column.unbounded) { attrs = new Object[]{ "sql:field", column.name, "name", name, "type", xsdType, "minOccurs", 0, "maxOccurs", "unbounded" }; } else { attrs = new Object[]{ "sql:field", column.name, "name", name, "type", xsdType, "minOccurs", 0 }; } } else { if (column.unbounded) { attrs = new Object[]{ "sql:field", column.name, "name", name, "type", xsdType, "maxOccurs", "unbounded" }; } else { attrs = new Object[]{ "sql:field", column.name, "name", name, "type", xsdType }; } } writer.element("xsd:element", attrs); } writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType } protected void writeRowsetXmlSchemaBottom(SaxWriter writer) { writer.endElement(); // xsd:schema } enum Type { String("xsd:string"), StringArray("xsd:string"), Array("xsd:string"), Enumeration("xsd:string"), EnumerationArray("xsd:string"), EnumString("xsd:string"), Boolean("xsd:boolean"), StringSometimesArray("xsd:string"), Integer("xsd:int"), UnsignedInteger("xsd:unsignedInt"), DateTime("xsd:dateTime"), Rowset(null), Short("xsd:short"), UUID("uuid"), UnsignedShort("xsd:unsignedShort"), Long("xsd:long"), UnsignedLong("xsd:unsignedLong"); public final String columnType; Type(String columnType) { this.columnType = columnType; } boolean isEnum() { return this == Enumeration || this == EnumerationArray || this == EnumString; } String getName() { return this == String ? "string" : name(); } } private static XmlaConstants.DBType getDBTypeFromProperty(Property prop) { switch (prop.getDatatype()) { case STRING: return XmlaConstants.DBType.WSTR; case INTEGER: case UNSIGNED_INTEGER: case DOUBLE: return XmlaConstants.DBType.R8; case BOOLEAN: return XmlaConstants.DBType.BOOL; default: // TODO: what type is it really, its not a string return XmlaConstants.DBType.WSTR; } } static class Column { /** * This is used as the true value for the restriction parameter. */ static final boolean RESTRICTION = true; /** * This is used as the false value for the restriction parameter. */ static final boolean NOT_RESTRICTION = false; /** * This is used as the false value for the nullable parameter. */ static final boolean REQUIRED = false; /** * This is used as the true value for the nullable parameter. */ static final boolean OPTIONAL = true; /** * This is used as the false value for the unbounded parameter. */ static final boolean ONE_MAX = false; /** * This is used as the true value for the unbounded parameter. */ static final boolean UNBOUNDED = true; final String name; final Type type; final Enumeration enumeration; final String description; final boolean restriction; final boolean nullable; final boolean unbounded; /** * Creates a column. * * @param name Name of column * @param type A {@link mondrian.xmla.RowsetDefinition.Type} value * @param enumeratedType Must be specified for enumeration or array * of enumerations * @param description Description of column * @param restriction Whether column can be used as a filter on its * rowset * @param nullable Whether column can contain null values * @pre type != null * @pre (type == Type.Enumeration * || type == Type.EnumerationArray * || type == Type.EnumString) * == (enumeratedType != null) * @pre description == null || description.indexOf('\r') == -1 */ Column( String name, Type type, Enumeration enumeratedType, boolean restriction, boolean nullable, String description) { this( name, type, enumeratedType, restriction, nullable, ONE_MAX, description); } Column( String name, Type type, Enumeration enumeratedType, boolean restriction, boolean nullable, boolean unbounded, String description) { assert type != null; assert (type == Type.Enumeration || type == Type.EnumerationArray || type == Type.EnumString) == (enumeratedType != null); // Line endings must be UNIX style (LF) not Windows style (LF+CR). // Thus the client will receive the same XML, regardless // of the server O/S. assert description == null || description.indexOf('\r') == -1; this.name = name; this.type = type; this.enumeration = enumeratedType; this.description = description; this.restriction = restriction; this.nullable = nullable; this.unbounded = unbounded; } /** * Retrieves a value of this column from a row. The base implementation * uses reflection to call an accessor method; a derived class may * provide a different implementation. * * @param row Row */ protected Object get(Object row) { return getFromAccessor(row); } /** * Retrieves the value of this column "MyColumn" from a field called * "myColumn". * * @param row Current row * @return Value of given this property of the given row */ protected final Object getFromField(Object row) { try { String javaFieldName = name.substring(0, 1).toLowerCase() + name.substring(1); Field field = row.getClass().getField(javaFieldName); return field.get(row); } catch (NoSuchFieldException e) { throw Util.newInternal( e, "Error while accessing rowset column " + name); } catch (SecurityException e) { throw Util.newInternal( e, "Error while accessing rowset column " + name); } catch (IllegalAccessException e) { throw Util.newInternal( e, "Error while accessing rowset column " + name); } } /** * Retrieves the value of this column "MyColumn" by calling a method * called "getMyColumn()". * * @param row Current row * @return Value of given this property of the given row */ protected final Object getFromAccessor(Object row) { try { String javaMethodName = "get" + name; Method method = row.getClass().getMethod(javaMethodName); return method.invoke(row); } catch (SecurityException e) { throw Util.newInternal( e, "Error while accessing rowset column " + name); } catch (IllegalAccessException e) { throw Util.newInternal( e, "Error while accessing rowset column " + name); } catch (NoSuchMethodException e) { throw Util.newInternal( e, "Error while accessing rowset column " + name); } catch (InvocationTargetException e) { throw Util.newInternal( e, "Error while accessing rowset column " + name); } } public String getColumnType() { if (type.isEnum()) { return enumeration.type.columnType; } return type.columnType; } } // ------------------------------------------------------------------------- // From this point on, just rowset classess. static class DiscoverDatasourcesRowset extends Rowset { private static final Column DataSourceName = new Column( "DataSourceName", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the data source, such as FoodMart 2000."); private static final Column DataSourceDescription = new Column( "DataSourceDescription", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A description of the data source, as entered by the " + "publisher."); private static final Column URL = new Column( "URL", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The unique path that shows where to invoke the XML for " + "Analysis methods for that data source."); private static final Column DataSourceInfo = new Column( "DataSourceInfo", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A string containing any additional information required to " + "connect to the data source. This can include the Initial " + "Catalog property or other information for the provider.\n" + "Example: \"Provider=MSOLAP;Data Source=Local;\""); private static final Column ProviderName = new Column( "ProviderName", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the provider behind the data source.\n" + "Example: \"MSDASQL\""); private static final Column ProviderType = new Column( "ProviderType", Type.EnumerationArray, Enumeration.PROVIDER_TYPE, Column.RESTRICTION, Column.REQUIRED, Column.UNBOUNDED, "The types of data supported by the provider. May include one " + "or more of the following types. Example follows this " + "table.\n" + "TDP: tabular data provider.\n" + "MDP: multidimensional data provider.\n" + "DMP: data mining provider. A DMP provider implements the " + "OLE DB for Data Mining specification."); private static final Column AuthenticationMode = new Column( "AuthenticationMode", Type.EnumString, Enumeration.AUTHENTICATION_MODE, Column.RESTRICTION, Column.REQUIRED, "Specification of what type of security mode the data source " + "uses. Values can be one of the following:\n" + "Unauthenticated: no user ID or password needs to be sent.\n" + "Authenticated: User ID and Password must be included in the " + "information required for the connection.\n" + "Integrated: the data source uses the underlying security to " + "determine authorization, such as Integrated Security " + "provided by Microsoft Internet Information Services (IIS)."); public DiscoverDatasourcesRowset( XmlaRequest request, XmlaHandler handler) { super(DISCOVER_DATASOURCES, request, handler); } private static final Column[] columns = { DataSourceName, DataSourceDescription, URL, DataSourceInfo, ProviderName, ProviderType, AuthenticationMode, }; public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException, SQLException { if (needConnection()) { final XmlaHandler.XmlaExtra extra = getExtra(connection); for (Map ds : extra.getDataSources(connection)) { Row row = new Row(); for (Column column : columns) { row.set(column.name, ds.get(column.name)); } addRow(row, rows); } } else { // using pre-configured discover datasources response Row row = new Row(); Map map = this.handler.connectionFactory .getPreConfiguredDiscoverDatasourcesResponse(); for (Column column : columns) { row.set(column.name, map.get(column.name)); } addRow(row, rows); } } @Override protected boolean needConnection() { // If the olap connection factory has a pre configured response, // we don't need to connect to find metadata. This is good. return this.handler.connectionFactory .getPreConfiguredDiscoverDatasourcesResponse() == null; } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } static class DiscoverSchemaRowsetsRowset extends Rowset { private static final Column SchemaName = new Column( "SchemaName", Type.StringArray, null, Column.RESTRICTION, Column.REQUIRED, "The name of the schema/request. This returns the values in " + "the RequestTypes enumeration, plus any additional types " + "supported by the provider. The provider defines rowset " + "structures for the additional types"); private static final Column SchemaGuid = new Column( "SchemaGuid", Type.UUID, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The GUID of the schema."); private static final Column Restrictions = new Column( "Restrictions", Type.Array, null, Column.NOT_RESTRICTION, Column.REQUIRED, "An array of the restrictions suppoted by provider. An example " + "follows this table."); private static final Column Description = new Column( "Description", Type.String, null, Column.NOT_RESTRICTION, Column.REQUIRED, "A localizable description of the schema"); public DiscoverSchemaRowsetsRowset( XmlaRequest request, XmlaHandler handler) { super(DISCOVER_SCHEMA_ROWSETS, request, handler); } public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException { RowsetDefinition[] rowsetDefinitions = RowsetDefinition.class.getEnumConstants().clone(); Arrays.sort( rowsetDefinitions, new Comparator() { public int compare( RowsetDefinition o1, RowsetDefinition o2) { return o1.name().compareTo(o2.name()); } }); for (RowsetDefinition rowsetDefinition : rowsetDefinitions) { Row row = new Row(); row.set(SchemaName.name, rowsetDefinition.name()); // TODO: If we have a SchemaGuid output here //row.set(SchemaGuid.name, ""); row.set(Restrictions.name, getRestrictions(rowsetDefinition)); String desc = rowsetDefinition.getDescription(); row.set(Description.name, (desc == null) ? "" : desc); addRow(row, rows); } } private List getRestrictions( RowsetDefinition rowsetDefinition) { List restrictionList = new ArrayList(); final Column[] columns = rowsetDefinition.columnDefinitions; for (Column column : columns) { if (column.restriction) { restrictionList.add( new XmlElement( Restrictions.name, null, new XmlElement[]{ new XmlElement("Name", null, column.name), new XmlElement( "Type", null, column.getColumnType())})); } } return restrictionList; } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } public String getDescription() { return description; } static class DiscoverPropertiesRowset extends Rowset { private final Util.Functor1 propNameCond; DiscoverPropertiesRowset(XmlaRequest request, XmlaHandler handler) { super(DISCOVER_PROPERTIES, request, handler); propNameCond = makeCondition(PROPDEF_NAME_GETTER, PropertyName); } private static final Column PropertyName = new Column( "PropertyName", Type.StringSometimesArray, null, Column.RESTRICTION, Column.REQUIRED, "The name of the property."); private static final Column PropertyDescription = new Column( "PropertyDescription", Type.String, null, Column.NOT_RESTRICTION, Column.REQUIRED, "A localizable text description of the property."); private static final Column PropertyType = new Column( "PropertyType", Type.String, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The XML data type of the property."); private static final Column PropertyAccessType = new Column( "PropertyAccessType", Type.EnumString, Enumeration.ACCESS, Column.NOT_RESTRICTION, Column.REQUIRED, "Access for the property. The value can be Read, Write, or " + "ReadWrite."); private static final Column IsRequired = new Column( "IsRequired", Type.Boolean, null, Column.NOT_RESTRICTION, Column.REQUIRED, "True if a property is required, false if it is not required."); private static final Column Value = new Column( "Value", Type.String, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The current value of the property."); protected boolean needConnection() { return false; } public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException { for (PropertyDefinition propertyDefinition : PropertyDefinition.class.getEnumConstants()) { if (!propNameCond.apply(propertyDefinition)) { continue; } Row row = new Row(); row.set(PropertyName.name, propertyDefinition.name()); row.set( PropertyDescription.name, propertyDefinition.description); row.set(PropertyType.name, propertyDefinition.type.getName()); row.set(PropertyAccessType.name, propertyDefinition.access); row.set(IsRequired.name, false); row.set(Value.name, propertyDefinition.value); addRow(row, rows); } } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } static class DiscoverEnumeratorsRowset extends Rowset { DiscoverEnumeratorsRowset(XmlaRequest request, XmlaHandler handler) { super(DISCOVER_ENUMERATORS, request, handler); } private static final Column EnumName = new Column( "EnumName", Type.StringArray, null, Column.RESTRICTION, Column.REQUIRED, "The name of the enumerator that contains a set of values."); private static final Column EnumDescription = new Column( "EnumDescription", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A localizable description of the enumerator."); private static final Column EnumType = new Column( "EnumType", Type.String, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The data type of the Enum values."); private static final Column ElementName = new Column( "ElementName", Type.String, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The name of one of the value elements in the enumerator set.\n" + "Example: TDP"); private static final Column ElementDescription = new Column( "ElementDescription", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A localizable description of the element (optional)."); private static final Column ElementValue = new Column( "ElementValue", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The value of the element.\n" + "Example: 01"); public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException { List enumerators = getEnumerators(); for (Enumeration enumerator : enumerators) { final List values = enumerator.getValues(); for (Enum value : values) { Row row = new Row(); row.set(EnumName.name, enumerator.name); row.set(EnumDescription.name, enumerator.description); // Note: SQL Server always has EnumType string // Need type of element of array, not the array // it self. row.set(EnumType.name, "string"); final String name = (value instanceof XmlaConstant) ? ((XmlaConstant) value).xmlaName() : value.name(); row.set(ElementName.name, name); final String description = (value instanceof XmlaConstant) ? ((XmlaConstant) value).getDescription() : (value instanceof XmlaConstants.EnumWithDesc) ? ((XmlaConstants.EnumWithDesc) value).getDescription() : null; if (description != null) { row.set( ElementDescription.name, description); } switch (enumerator.type) { case String: case StringArray: // these don't have ordinals break; default: final int ordinal = (value instanceof XmlaConstant && ((XmlaConstant) value).xmlaOrdinal() != -1) ? ((XmlaConstant) value).xmlaOrdinal() : value.ordinal(); row.set(ElementValue.name, ordinal); break; } addRow(row, rows); } } } private static List getEnumerators() { // Build a set because we need to eliminate duplicates. SortedSet enumeratorSet = new TreeSet( new Comparator() { public int compare(Enumeration o1, Enumeration o2) { return o1.name.compareTo(o2.name); } } ); for (RowsetDefinition rowsetDefinition : RowsetDefinition.class.getEnumConstants()) { for (Column column : rowsetDefinition.columnDefinitions) { if (column.enumeration != null) { enumeratorSet.add(column.enumeration); } } } return new ArrayList(enumeratorSet); } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } static class DiscoverKeywordsRowset extends Rowset { DiscoverKeywordsRowset(XmlaRequest request, XmlaHandler handler) { super(DISCOVER_KEYWORDS, request, handler); } private static final Column Keyword = new Column( "Keyword", Type.StringSometimesArray, null, Column.RESTRICTION, Column.REQUIRED, "A list of all the keywords reserved by a provider.\n" + "Example: AND"); public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException { MondrianServer mondrianServer = MondrianServer.forId(null); for (String keyword : mondrianServer.getKeywords()) { Row row = new Row(); row.set(Keyword.name, keyword); addRow(row, rows); } } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } static class DiscoverLiteralsRowset extends Rowset { DiscoverLiteralsRowset(XmlaRequest request, XmlaHandler handler) { super(DISCOVER_LITERALS, request, handler); } private static final Column LiteralName = new Column( "LiteralName", Type.StringSometimesArray, null, Column.RESTRICTION, Column.REQUIRED, "The name of the literal described in the row.\n" + "Example: DBLITERAL_LIKE_PERCENT"); private static final Column LiteralValue = new Column( "LiteralValue", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Contains the actual literal value.\n" + "Example, if LiteralName is DBLITERAL_LIKE_PERCENT and the " + "percent character (%) is used to match zero or more characters " + "in a LIKE clause, this column's value would be \"%\"."); private static final Column LiteralInvalidChars = new Column( "LiteralInvalidChars", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The characters, in the literal, that are not valid.\n" + "For example, if table names can contain anything other than a " + "numeric character, this string would be \"0123456789\"."); private static final Column LiteralInvalidStartingChars = new Column( "LiteralInvalidStartingChars", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The characters that are not valid as the first character of the " + "literal. If the literal can start with any valid character, " + "this is null."); private static final Column LiteralMaxLength = new Column( "LiteralMaxLength", Type.Integer, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The maximum number of characters in the literal. If there is no " + "maximum or the maximum is unknown, the value is ?1."); public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException { populate( XmlaConstants.Literal.class, rows, new Comparator() { public int compare( XmlaConstants.Literal o1, XmlaConstants.Literal o2) { return o1.name().compareTo(o2.name()); } }); } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } static class DbschemaCatalogsRowset extends Rowset { private final Util.Functor1 catalogNameCond; DbschemaCatalogsRowset(XmlaRequest request, XmlaHandler handler) { super(DBSCHEMA_CATALOGS, request, handler); catalogNameCond = makeCondition(CATALOG_NAME_GETTER, CatalogName); } private static final Column CatalogName = new Column( "CATALOG_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "Catalog name. Cannot be NULL."); private static final Column Description = new Column( "DESCRIPTION", Type.String, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Human-readable description of the catalog."); private static final Column Roles = new Column( "ROLES", Type.String, null, Column.NOT_RESTRICTION, Column.REQUIRED, "A comma delimited list of roles to which the current user " + "belongs. An asterisk (*) is included as a role if the " + "current user is a server or database administrator. " + "Username is appended to ROLES if one of the roles uses " + "dynamic security."); private static final Column DateModified = new Column( "DATE_MODIFIED", Type.DateTime, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The date that the catalog was last modified."); public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException, SQLException { for (Catalog catalog : catIter(connection, catalogNameCond)) { for (Schema schema : catalog.getSchemas()) { Row row = new Row(); row.set(CatalogName.name, catalog.getName()); // TODO: currently schema grammar does not support a // description row.set(Description.name, "No description available"); // get Role names StringBuilder buf = new StringBuilder(100); List roleNames = getExtra(connection).getSchemaRoleNames(schema); serialize(buf, roleNames); row.set(Roles.name, buf.toString()); // TODO: currently schema grammar does not support modify // date so we return just some date for now. if (false) { row.set(DateModified.name, dateModified); } addRow(row, rows); } } } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } static class DbschemaColumnsRowset extends Rowset { private final Util.Functor1 tableCatalogCond; private final Util.Functor1 tableNameCond; private final Util.Functor1 columnNameCond; DbschemaColumnsRowset(XmlaRequest request, XmlaHandler handler) { super(DBSCHEMA_COLUMNS, request, handler); tableCatalogCond = makeCondition(CATALOG_NAME_GETTER, TableCatalog); tableNameCond = makeCondition(ELEMENT_NAME_GETTER, TableName); columnNameCond = makeCondition(ColumnName); } private static final Column TableCatalog = new Column( "TABLE_CATALOG", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the Database."); private static final Column TableSchema = new Column( "TABLE_SCHEMA", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, null); private static final Column TableName = new Column( "TABLE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the cube."); private static final Column ColumnName = new Column( "COLUMN_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the attribute hierarchy or measure."); private static final Column OrdinalPosition = new Column( "ORDINAL_POSITION", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The position of the column, beginning with 1."); private static final Column ColumnHasDefault = new Column( "COLUMN_HAS_DEFAULT", Type.Boolean, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Not supported."); /* * A bitmask indicating the information stored in * DBCOLUMNFLAGS in OLE DB. * 1 = Bookmark * 2 = Fixed length * 4 = Nullable * 8 = Row versioning * 16 = Updateable column * * And, of course, MS SQL Server sometimes has the value of 80!! */ private static final Column ColumnFlags = new Column( "COLUMN_FLAGS", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.REQUIRED, "A DBCOLUMNFLAGS bitmask indicating column properties."); private static final Column IsNullable = new Column( "IS_NULLABLE", Type.Boolean, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Always returns false."); private static final Column DataType = new Column( "DATA_TYPE", Type.UnsignedShort, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The data type of the column. Returns a string for dimension " + "columns and a variant for measures."); private static final Column CharacterMaximumLength = new Column( "CHARACTER_MAXIMUM_LENGTH", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The maximum possible length of a value within the column."); private static final Column CharacterOctetLength = new Column( "CHARACTER_OCTET_LENGTH", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The maximum possible length of a value within the column, in " + "bytes, for character or binary columns."); private static final Column NumericPrecision = new Column( "NUMERIC_PRECISION", Type.UnsignedShort, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The maximum precision of the column for numeric data types " + "other than DBTYPE_VARNUMERIC."); private static final Column NumericScale = new Column( "NUMERIC_SCALE", Type.Short, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The number of digits to the right of the decimal point for " + "DBTYPE_DECIMAL, DBTYPE_NUMERIC, DBTYPE_VARNUMERIC. " + "Otherwise, this is NULL."); public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException, OlapException { for (Catalog catalog : catIter(connection, tableCatalogCond)) { // By definition, mondrian catalogs have only one // schema. It is safe to use get(0) final Schema schema = catalog.getSchemas().get(0); final boolean emitInvisibleMembers = XmlaUtil.shouldEmitInvisibleMembers(request); int ordinalPosition = 1; Row row; for (Cube cube : filter(sortedCubes(schema), tableNameCond)) { for (Dimension dimension : cube.getDimensions()) { for (Hierarchy hierarchy : dimension.getHierarchies()) { ordinalPosition = populateHierarchy( cube, hierarchy, ordinalPosition, rows); } } List rms = cube.getMeasures(); for (int k = 1; k < rms.size(); k++) { Measure member = rms.get(k); // null == true for regular cubes // virtual cubes do not set the visible property // on its measures so it might be null. Boolean visible = (Boolean) member.getPropertyValue( Property.StandardMemberProperty.$visible); if (visible == null) { visible = true; } if (!emitInvisibleMembers && !visible) { continue; } String memberName = member.getName(); final String columnName = "Measures:" + memberName; if (!columnNameCond.apply(columnName)) { continue; } row = new Row(); row.set(TableCatalog.name, catalog.getName()); row.set(TableName.name, cube.getName()); row.set(ColumnName.name, columnName); row.set(OrdinalPosition.name, ordinalPosition++); row.set(ColumnHasDefault.name, false); row.set(ColumnFlags.name, 0); row.set(IsNullable.name, false); // TODO: here is where one tries to determine the // type of the column - since these are all // Measures, aggregate Measures??, maybe they // are all numeric? (or currency) row.set( DataType.name, XmlaConstants.DBType.R8.xmlaOrdinal()); // TODO: 16/255 seems to be what MS SQL Server // always returns. row.set(NumericPrecision.name, 16); row.set(NumericScale.name, 255); addRow(row, rows); } } } } private int populateHierarchy( Cube cube, Hierarchy hierarchy, int ordinalPosition, List rows) { String schemaName = cube.getSchema().getName(); String cubeName = cube.getName(); String hierarchyName = hierarchy.getName(); if (hierarchy.hasAll()) { Row row = new Row(); row.set(TableCatalog.name, schemaName); row.set(TableName.name, cubeName); row.set(ColumnName.name, hierarchyName + ":(All)!NAME"); row.set(OrdinalPosition.name, ordinalPosition++); row.set(ColumnHasDefault.name, false); row.set(ColumnFlags.name, 0); row.set(IsNullable.name, false); // names are always WSTR row.set(DataType.name, XmlaConstants.DBType.WSTR.xmlaOrdinal()); row.set(CharacterMaximumLength.name, 0); row.set(CharacterOctetLength.name, 0); addRow(row, rows); row = new Row(); row.set(TableCatalog.name, schemaName); row.set(TableName.name, cubeName); row.set(ColumnName.name, hierarchyName + ":(All)!UNIQUE_NAME"); row.set(OrdinalPosition.name, ordinalPosition++); row.set(ColumnHasDefault.name, false); row.set(ColumnFlags.name, 0); row.set(IsNullable.name, false); // names are always WSTR row.set(DataType.name, XmlaConstants.DBType.WSTR.xmlaOrdinal()); row.set(CharacterMaximumLength.name, 0); row.set(CharacterOctetLength.name, 0); addRow(row, rows); if (false) { // TODO: SQLServer outputs this hasall KEY column name - // don't know what it's for row = new Row(); row.set(TableCatalog.name, schemaName); row.set(TableName.name, cubeName); row.set(ColumnName.name, hierarchyName + ":(All)!KEY"); row.set(OrdinalPosition.name, ordinalPosition++); row.set(ColumnHasDefault.name, false); row.set(ColumnFlags.name, 0); row.set(IsNullable.name, false); // names are always BOOL row.set( DataType.name, XmlaConstants.DBType.BOOL.xmlaOrdinal()); row.set(NumericPrecision.name, 255); row.set(NumericScale.name, 255); addRow(row, rows); } } for (Level level : hierarchy.getLevels()) { ordinalPosition = populateLevel( cube, hierarchy, level, ordinalPosition, rows); } return ordinalPosition; } private int populateLevel( Cube cube, Hierarchy hierarchy, Level level, int ordinalPosition, List rows) { String schemaName = cube.getSchema().getName(); String cubeName = cube.getName(); String hierarchyName = hierarchy.getName(); String levelName = level.getName(); Row row = new Row(); row.set(TableCatalog.name, schemaName); row.set(TableName.name, cubeName); row.set( ColumnName.name, hierarchyName + ':' + levelName + "!NAME"); row.set(OrdinalPosition.name, ordinalPosition++); row.set(ColumnHasDefault.name, false); row.set(ColumnFlags.name, 0); row.set(IsNullable.name, false); // names are always WSTR row.set(DataType.name, XmlaConstants.DBType.WSTR.xmlaOrdinal()); row.set(CharacterMaximumLength.name, 0); row.set(CharacterOctetLength.name, 0); addRow(row, rows); row = new Row(); row.set(TableCatalog.name, schemaName); row.set(TableName.name, cubeName); row.set( ColumnName.name, hierarchyName + ':' + levelName + "!UNIQUE_NAME"); row.set(OrdinalPosition.name, ordinalPosition++); row.set(ColumnHasDefault.name, false); row.set(ColumnFlags.name, 0); row.set(IsNullable.name, false); // names are always WSTR row.set(DataType.name, XmlaConstants.DBType.WSTR.xmlaOrdinal()); row.set(CharacterMaximumLength.name, 0); row.set(CharacterOctetLength.name, 0); addRow(row, rows); /* TODO: see above row = new Row(); row.set(TableCatalog.name, schemaName); row.set(TableName.name, cubeName); row.set(ColumnName.name, hierarchyName + ":" + levelName + "!KEY"); row.set(OrdinalPosition.name, ordinalPosition++); row.set(ColumnHasDefault.name, false); row.set(ColumnFlags.name, 0); row.set(IsNullable.name, false); // names are always BOOL row.set(DataType.name, DBType.BOOL.ordinal()); row.set(NumericPrecision.name, 255); row.set(NumericScale.name, 255); addRow(row, rows); */ NamedList props = level.getProperties(); for (Property prop : props) { String propName = prop.getName(); row = new Row(); row.set(TableCatalog.name, schemaName); row.set(TableName.name, cubeName); row.set( ColumnName.name, hierarchyName + ':' + levelName + '!' + propName); row.set(OrdinalPosition.name, ordinalPosition++); row.set(ColumnHasDefault.name, false); row.set(ColumnFlags.name, 0); row.set(IsNullable.name, false); XmlaConstants.DBType dbType = getDBTypeFromProperty(prop); row.set(DataType.name, dbType.xmlaOrdinal()); switch (prop.getDatatype()) { case STRING: row.set(CharacterMaximumLength.name, 0); row.set(CharacterOctetLength.name, 0); break; case INTEGER: case UNSIGNED_INTEGER: case DOUBLE: // TODO: 16/255 seems to be what MS SQL Server // always returns. row.set(NumericPrecision.name, 16); row.set(NumericScale.name, 255); break; case BOOLEAN: row.set(NumericPrecision.name, 255); row.set(NumericScale.name, 255); break; default: // TODO: what type is it really, its // not a string row.set(CharacterMaximumLength.name, 0); row.set(CharacterOctetLength.name, 0); break; } addRow(row, rows); } return ordinalPosition; } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } static class DbschemaProviderTypesRowset extends Rowset { private final Util.Functor1 dataTypeCond; DbschemaProviderTypesRowset(XmlaRequest request, XmlaHandler handler) { super(DBSCHEMA_PROVIDER_TYPES, request, handler); dataTypeCond = makeCondition(DataType); } /* DATA_TYPE DBTYPE_UI2 BEST_MATCH DBTYPE_BOOL Column(String name, Type type, Enumeration enumeratedType, boolean restriction, boolean nullable, String description) */ /* * These are the columns returned by SQL Server. */ private static final Column TypeName = new Column( "TYPE_NAME", Type.String, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The provider-specific data type name."); private static final Column DataType = new Column( "DATA_TYPE", Type.UnsignedShort, null, Column.RESTRICTION, Column.REQUIRED, "The indicator of the data type."); private static final Column ColumnSize = new Column( "COLUMN_SIZE", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The length of a non-numeric column. If the data type is " + "numeric, this is the upper bound on the maximum precision " + "of the data type."); private static final Column LiteralPrefix = new Column( "LITERAL_PREFIX", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The character or characters used to prefix a literal of this " + "type in a text command."); private static final Column LiteralSuffix = new Column( "LITERAL_SUFFIX", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The character or characters used to suffix a literal of this " + "type in a text command."); private static final Column IsNullable = new Column( "IS_NULLABLE", Type.Boolean, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A Boolean that indicates whether the data type is nullable. " + "NULL-- indicates that it is not known whether the data type " + "is nullable."); private static final Column CaseSensitive = new Column( "CASE_SENSITIVE", Type.Boolean, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A Boolean that indicates whether the data type is a " + "characters type and case-sensitive."); private static final Column Searchable = new Column( "SEARCHABLE", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "An integer indicating how the data type can be used in " + "searches if the provider supports ICommandText; otherwise, " + "NULL."); private static final Column UnsignedAttribute = new Column( "UNSIGNED_ATTRIBUTE", Type.Boolean, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A Boolean that indicates whether the data type is unsigned."); private static final Column FixedPrecScale = new Column( "FIXED_PREC_SCALE", Type.Boolean, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A Boolean that indicates whether the data type has a fixed " + "precision and scale."); private static final Column AutoUniqueValue = new Column( "AUTO_UNIQUE_VALUE", Type.Boolean, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A Boolean that indicates whether the data type is " + "autoincrementing."); private static final Column IsLong = new Column( "IS_LONG", Type.Boolean, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A Boolean that indicates whether the data type is a binary " + "large object (BLOB) and has very long data."); private static final Column BestMatch = new Column( "BEST_MATCH", Type.Boolean, null, Column.RESTRICTION, Column.OPTIONAL, "A Boolean that indicates whether the data type is a best " + "match."); @Override protected boolean needConnection() { return false; } public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException { // Identifies the (base) data types supported by the data provider. Row row; // i4 Integer dt = XmlaConstants.DBType.I4.xmlaOrdinal(); if (dataTypeCond.apply(dt)) { row = new Row(); row.set(TypeName.name, XmlaConstants.DBType.I4.userName); row.set(DataType.name, dt); row.set(ColumnSize.name, 8); row.set(IsNullable.name, true); row.set(Searchable.name, null); row.set(UnsignedAttribute.name, false); row.set(FixedPrecScale.name, false); row.set(AutoUniqueValue.name, false); row.set(IsLong.name, false); row.set(BestMatch.name, true); addRow(row, rows); } // R8 dt = XmlaConstants.DBType.R8.xmlaOrdinal(); if (dataTypeCond.apply(dt)) { row = new Row(); row.set(TypeName.name, XmlaConstants.DBType.R8.userName); row.set(DataType.name, dt); row.set(ColumnSize.name, 16); row.set(IsNullable.name, true); row.set(Searchable.name, null); row.set(UnsignedAttribute.name, false); row.set(FixedPrecScale.name, false); row.set(AutoUniqueValue.name, false); row.set(IsLong.name, false); row.set(BestMatch.name, true); addRow(row, rows); } // CY dt = XmlaConstants.DBType.CY.xmlaOrdinal(); if (dataTypeCond.apply(dt)) { row = new Row(); row.set(TypeName.name, XmlaConstants.DBType.CY.userName); row.set(DataType.name, dt); row.set(ColumnSize.name, 8); row.set(IsNullable.name, true); row.set(Searchable.name, null); row.set(UnsignedAttribute.name, false); row.set(FixedPrecScale.name, false); row.set(AutoUniqueValue.name, false); row.set(IsLong.name, false); row.set(BestMatch.name, true); addRow(row, rows); } // BOOL dt = XmlaConstants.DBType.BOOL.xmlaOrdinal(); if (dataTypeCond.apply(dt)) { row = new Row(); row.set(TypeName.name, XmlaConstants.DBType.BOOL.userName); row.set(DataType.name, dt); row.set(ColumnSize.name, 1); row.set(IsNullable.name, true); row.set(Searchable.name, null); row.set(UnsignedAttribute.name, false); row.set(FixedPrecScale.name, false); row.set(AutoUniqueValue.name, false); row.set(IsLong.name, false); row.set(BestMatch.name, true); addRow(row, rows); } // I8 dt = XmlaConstants.DBType.I8.xmlaOrdinal(); if (dataTypeCond.apply(dt)) { row = new Row(); row.set(TypeName.name, XmlaConstants.DBType.I8.userName); row.set(DataType.name, dt); row.set(ColumnSize.name, 16); row.set(IsNullable.name, true); row.set(Searchable.name, null); row.set(UnsignedAttribute.name, false); row.set(FixedPrecScale.name, false); row.set(AutoUniqueValue.name, false); row.set(IsLong.name, false); row.set(BestMatch.name, true); addRow(row, rows); } // WSTR dt = XmlaConstants.DBType.WSTR.xmlaOrdinal(); if (dataTypeCond.apply(dt)) { row = new Row(); row.set(TypeName.name, XmlaConstants.DBType.WSTR.userName); row.set(DataType.name, dt); // how big are the string columns in the db row.set(ColumnSize.name, 255); row.set(LiteralPrefix.name, "\""); row.set(LiteralSuffix.name, "\""); row.set(IsNullable.name, true); row.set(CaseSensitive.name, false); row.set(Searchable.name, null); row.set(FixedPrecScale.name, false); row.set(AutoUniqueValue.name, false); row.set(IsLong.name, false); row.set(BestMatch.name, true); addRow(row, rows); } } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } static class DbschemaSchemataRowset extends Rowset { private final Util.Functor1 catalogNameCond; DbschemaSchemataRowset(XmlaRequest request, XmlaHandler handler) { super(DBSCHEMA_SCHEMATA, request, handler); catalogNameCond = makeCondition(CATALOG_NAME_GETTER, CatalogName); } /* * These are the columns returned by SQL Server. */ private static final Column CatalogName = new Column( "CATALOG_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The provider-specific data type name."); private static final Column SchemaName = new Column( "SCHEMA_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The indicator of the data type."); private static final Column SchemaOwner = new Column( "SCHEMA_OWNER", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The length of a non-numeric column. If the data type is " + "numeric, this is the upper bound on the maximum precision " + "of the data type."); public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException, OlapException { for (Catalog catalog : catIter(connection, catalogNameCond)) { for (Schema schema : catalog.getSchemas()) { Row row = new Row(); row.set(CatalogName.name, catalog.getName()); row.set(SchemaName.name, schema.getName()); row.set(SchemaOwner.name, ""); addRow(row, rows); } } } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } static class DbschemaTablesRowset extends Rowset { private final Util.Functor1 tableCatalogCond; private final Util.Functor1 tableNameCond; private final Util.Functor1 tableTypeCond; DbschemaTablesRowset(XmlaRequest request, XmlaHandler handler) { super(DBSCHEMA_TABLES, request, handler); tableCatalogCond = makeCondition(CATALOG_NAME_GETTER, TableCatalog); tableNameCond = makeCondition(ELEMENT_NAME_GETTER, TableName); tableTypeCond = makeCondition(TableType); } private static final Column TableCatalog = new Column( "TABLE_CATALOG", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the catalog to which this object belongs."); private static final Column TableSchema = new Column( "TABLE_SCHEMA", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the cube to which this object belongs."); private static final Column TableName = new Column( "TABLE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the object, if TABLE_TYPE is TABLE."); private static final Column TableType = new Column( "TABLE_TYPE", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The type of the table. TABLE indicates the object is a " + "measure group. SYSTEM TABLE indicates the object is a " + "dimension."); private static final Column TableGuid = new Column( "TABLE_GUID", Type.UUID, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Not supported."); private static final Column Description = new Column( "DESCRIPTION", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A human-readable description of the object."); private static final Column TablePropId = new Column( "TABLE_PROPID", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Not supported."); private static final Column DateCreated = new Column( "DATE_CREATED", Type.DateTime, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Not supported."); private static final Column DateModified = new Column( "DATE_MODIFIED", Type.DateTime, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The date the object was last modified."); /* private static final Column TableOlapType = new Column( "TABLE_OLAP_TYPE", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The OLAP type of the object. MEASURE_GROUP indicates the " + "object is a measure group. CUBE_DIMENSION indicated the " + "object is a dimension."); */ public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException, OlapException { for (Catalog catalog : catIter(connection, catNameCond(), tableCatalogCond)) { // By definition, mondrian catalogs have only one // schema. It is safe to use get(0) final Schema schema = catalog.getSchemas().get(0); Row row; for (Cube cube : filter(sortedCubes(schema), tableNameCond)) { String desc = cube.getDescription(); if (desc == null) { //TODO: currently this is always null desc = catalog.getName() + " - " + cube.getName() + " Cube"; } if (tableTypeCond.apply("TABLE")) { row = new Row(); row.set(TableCatalog.name, catalog.getName()); row.set(TableName.name, cube.getName()); row.set(TableType.name, "TABLE"); row.set(Description.name, desc); if (false) { row.set(DateModified.name, dateModified); } addRow(row, rows); } if (tableTypeCond.apply("SYSTEM TABLE")) { for (Dimension dimension : cube.getDimensions()) { if (dimension.getDimensionType() == Dimension.Type.MEASURE) { continue; } for (Hierarchy hierarchy : dimension.getHierarchies()) { populateHierarchy( cube, hierarchy, rows); } } } } } } private void populateHierarchy( Cube cube, Hierarchy hierarchy, List rows) { /* String schemaName = cube.getSchema().getName(); String cubeName = cube.getName(); String hierarchyName = hierarchy.getName(); String desc = hierarchy.getDescription(); if (desc == null) { //TODO: currently this is always null desc = schemaName + " - " + cubeName + " Cube - " + hierarchyName + " Hierarchy"; } if (hierarchy.hasAll()) { String tableName = cubeName + ':' + hierarchyName + ':' + "(All)"; Row row = new Row(); row.set(TableCatalog.name, schemaName); row.set(TableName.name, tableName); row.set(TableType.name, "SYSTEM TABLE"); row.set(Description.name, desc); row.set(DateModified.name, dateModified); addRow(row, rows); } */ for (Level level : hierarchy.getLevels()) { populateLevel(cube, hierarchy, level, rows); } } private void populateLevel( Cube cube, Hierarchy hierarchy, Level level, List rows) { String schemaName = cube.getSchema().getName(); String cubeName = cube.getName(); String hierarchyName = getHierarchyName(hierarchy); String levelName = level.getName(); String tableName = cubeName + ':' + hierarchyName + ':' + levelName; String desc = level.getDescription(); if (desc == null) { //TODO: currently this is always null desc = schemaName + " - " + cubeName + " Cube - " + hierarchyName + " Hierarchy - " + levelName + " Level"; } Row row = new Row(); row.set(TableCatalog.name, schemaName); row.set(TableName.name, tableName); row.set(TableType.name, "SYSTEM TABLE"); row.set(Description.name, desc); if (false) { row.set(DateModified.name, dateModified); } addRow(row, rows); } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } // TODO: Is this needed???? static class DbschemaTablesInfoRowset extends Rowset { DbschemaTablesInfoRowset(XmlaRequest request, XmlaHandler handler) { super(DBSCHEMA_TABLES_INFO, request, handler); } private static final Column TableCatalog = new Column( "TABLE_CATALOG", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "Catalog name. NULL if the provider does not support " + "catalogs."); private static final Column TableSchema = new Column( "TABLE_SCHEMA", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "Unqualified schema name. NULL if the provider does not " + "support schemas."); private static final Column TableName = new Column( "TABLE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "Table name."); private static final Column TableType = new Column( "TABLE_TYPE", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "Table type. One of the following or a provider-specific " + "value: ALIAS, TABLE, SYNONYM, SYSTEM TABLE, VIEW, GLOBAL " + "TEMPORARY, LOCAL TEMPORARY, EXTERNAL TABLE, SYSTEM VIEW"); private static final Column TableGuid = new Column( "TABLE_GUID", Type.UUID, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "GUID that uniquely identifies the table. Providers that do " + "not use GUIDs to identify tables should return NULL in this " + "column."); private static final Column Bookmarks = new Column( "BOOKMARKS", Type.Boolean, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Whether this table supports bookmarks. Allways is false."); private static final Column BookmarkType = new Column( "BOOKMARK_TYPE", Type.Integer, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Default bookmark type supported on this table."); private static final Column BookmarkDataType = new Column( "BOOKMARK_DATATYPE", Type.UnsignedShort, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The indicator of the bookmark's native data type."); private static final Column BookmarkMaximumLength = new Column( "BOOKMARK_MAXIMUM_LENGTH", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Maximum length of the bookmark in bytes."); private static final Column BookmarkInformation = new Column( "BOOKMARK_INFORMATION", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A bitmask specifying additional information about bookmarks " + "over the rowset. "); private static final Column TableVersion = new Column( "TABLE_VERSION", Type.Long, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Version number for this table or NULL if the provider does " + "not support returning table version information."); private static final Column Cardinality = new Column( "CARDINALITY", Type.UnsignedLong, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Cardinality (number of rows) of the table."); private static final Column Description = new Column( "DESCRIPTION", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Human-readable description of the table."); private static final Column TablePropId = new Column( "TABLE_PROPID", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Property ID of the table. Return null."); public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException, OlapException { for (Catalog catalog : catIter(connection, catNameCond())) { // By definition, mondrian catalogs have only one // schema. It is safe to use get(0) final Schema schema = catalog.getSchemas().get(0); //TODO: Is this cubes or tables? SQL Server returns what // in foodmart are cube names for TABLE_NAME for (Cube cube : sortedCubes(schema)) { String cubeName = cube.getName(); String desc = cube.getDescription(); if (desc == null) { //TODO: currently this is always null desc = catalog.getName() + " - " + cubeName + " Cube"; } //TODO: SQL Server returns 1000000 for all tables int cardinality = 1000000; String version = "null"; Row row = new Row(); row.set(TableCatalog.name, catalog.getName()); row.set(TableName.name, cubeName); row.set(TableType.name, "TABLE"); row.set(Bookmarks.name, false); row.set(TableVersion.name, version); row.set(Cardinality.name, cardinality); row.set(Description.name, desc); addRow(row, rows); } } } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } static class MdschemaActionsRowset extends Rowset { MdschemaActionsRowset(XmlaRequest request, XmlaHandler handler) { super(MDSCHEMA_ACTIONS, request, handler); } private static final Column CatalogName = new Column( "CATALOG_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the catalog to which this action belongs."); private static final Column SchemaName = new Column( "SCHEMA_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the schema to which this action belongs."); private static final Column CubeName = new Column( "CUBE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the cube to which this action belongs."); private static final Column ActionName = new Column( "ACTION_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the action."); private static final Column Coordinate = new Column( "COORDINATE", Type.String, null, Column.RESTRICTION, Column.REQUIRED, null); private static final Column CoordinateType = new Column( "COORDINATE_TYPE", Type.Integer, null, Column.RESTRICTION, Column.REQUIRED, null); /* TODO: optional columns ACTION_TYPE INVOCATION CUBE_SOURCE */ public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException { // mondrian doesn't support actions. It's not an error to ask for // them, there just aren't any } } public static class MdschemaCubesRowset extends Rowset { private final Util.Functor1 catalogNameCond; private final Util.Functor1 schemaNameCond; private final Util.Functor1 cubeNameCond; MdschemaCubesRowset(XmlaRequest request, XmlaHandler handler) { super(MDSCHEMA_CUBES, request, handler); catalogNameCond = makeCondition(CATALOG_NAME_GETTER, CatalogName); schemaNameCond = makeCondition(SCHEMA_NAME_GETTER, SchemaName); cubeNameCond = makeCondition(ELEMENT_NAME_GETTER, CubeName); } public static final String MD_CUBTYPE_CUBE = "CUBE"; public static final String MD_CUBTYPE_VIRTUAL_CUBE = "VIRTUAL CUBE"; private static final Column CatalogName = new Column( "CATALOG_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the catalog to which this cube belongs."); private static final Column SchemaName = new Column( "SCHEMA_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the schema to which this cube belongs."); private static final Column CubeName = new Column( "CUBE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "Name of the cube."); private static final Column CubeType = new Column( "CUBE_TYPE", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "Cube type."); private static final Column CubeGuid = new Column( "CUBE_GUID", Type.UUID, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Cube type."); private static final Column CreatedOn = new Column( "CREATED_ON", Type.DateTime, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Date and time of cube creation."); private static final Column LastSchemaUpdate = new Column( "LAST_SCHEMA_UPDATE", Type.DateTime, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Date and time of last schema update."); private static final Column SchemaUpdatedBy = new Column( "SCHEMA_UPDATED_BY", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "User ID of the person who last updated the schema."); private static final Column LastDataUpdate = new Column( "LAST_DATA_UPDATE", Type.DateTime, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Date and time of last data update."); private static final Column DataUpdatedBy = new Column( "DATA_UPDATED_BY", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "User ID of the person who last updated the data."); private static final Column IsDrillthroughEnabled = new Column( "IS_DRILLTHROUGH_ENABLED", Type.Boolean, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Describes whether DRILLTHROUGH can be performed on the " + "members of a cube"); private static final Column IsWriteEnabled = new Column( "IS_WRITE_ENABLED", Type.Boolean, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Describes whether a cube is write-enabled"); private static final Column IsLinkable = new Column( "IS_LINKABLE", Type.Boolean, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Describes whether a cube can be used in a linked cube"); private static final Column IsSqlEnabled = new Column( "IS_SQL_ENABLED", Type.Boolean, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Describes whether or not SQL can be used on the cube"); private static final Column CubeCaption = new Column( "CUBE_CAPTION", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The caption of the cube."); private static final Column Description = new Column( "DESCRIPTION", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A user-friendly description of the dimension."); private static final Column Dimensions = new Column( "DIMENSIONS", Type.Rowset, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Dimensions in this cube."); private static final Column Sets = new Column( "SETS", Type.Rowset, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Sets in this cube."); private static final Column Measures = new Column( "MEASURES", Type.Rowset, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Measures in this cube."); public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException, SQLException { for (Catalog catalog : catIter(connection, catNameCond(), catalogNameCond)) { for (Schema schema : filter(catalog.getSchemas(), schemaNameCond)) { for (Cube cube : filter(sortedCubes(schema), cubeNameCond)) { String desc = cube.getDescription(); if (desc == null) { desc = catalog.getName() + " Schema - " + cube.getName() + " Cube"; } Row row = new Row(); row.set(CatalogName.name, catalog.getName()); row.set(SchemaName.name, schema.getName()); row.set(CubeName.name, cube.getName()); final XmlaHandler.XmlaExtra extra = getExtra(connection); row.set(CubeType.name, extra.getCubeType(cube)); //row.set(CubeGuid.name, ""); //row.set(CreatedOn.name, ""); //row.set(LastSchemaUpdate.name, ""); //row.set(SchemaUpdatedBy.name, ""); //row.set(LastDataUpdate.name, ""); //row.set(DataUpdatedBy.name, ""); row.set(IsDrillthroughEnabled.name, true); row.set(IsWriteEnabled.name, false); row.set(IsLinkable.name, false); row.set(IsSqlEnabled.name, false); row.set(CubeCaption.name, cube.getCaption()); row.set(Description.name, desc); Format formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); String formattedDate = formatter.format( extra.getSchemaLoadDate(schema)); row.set(LastSchemaUpdate.name, formattedDate); if (deep) { row.set( Dimensions.name, new MdschemaDimensionsRowset( wrapRequest( request, Olap4jUtil.mapOf( MdschemaDimensionsRowset .CatalogName, catalog.getName(), MdschemaDimensionsRowset.SchemaName, schema.getName(), MdschemaDimensionsRowset.CubeName, cube.getName())), handler)); row.set( Sets.name, new MdschemaSetsRowset( wrapRequest( request, Olap4jUtil.mapOf( MdschemaSetsRowset.CatalogName, catalog.getName(), MdschemaSetsRowset.SchemaName, schema.getName(), MdschemaSetsRowset.CubeName, cube.getName())), handler)); row.set( Measures.name, new MdschemaMeasuresRowset( wrapRequest( request, Olap4jUtil.mapOf( MdschemaMeasuresRowset.CatalogName, catalog.getName(), MdschemaMeasuresRowset.SchemaName, schema.getName(), MdschemaMeasuresRowset.CubeName, cube.getName())), handler)); } addRow(row, rows); } } } } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } static class MdschemaDimensionsRowset extends Rowset { private final Util.Functor1 catalogNameCond; private final Util.Functor1 schemaNameCond; private final Util.Functor1 cubeNameCond; private final Util.Functor1 dimensionUnameCond; private final Util.Functor1 dimensionNameCond; MdschemaDimensionsRowset(XmlaRequest request, XmlaHandler handler) { super(MDSCHEMA_DIMENSIONS, request, handler); catalogNameCond = makeCondition(CATALOG_NAME_GETTER, CatalogName); schemaNameCond = makeCondition(SCHEMA_NAME_GETTER, SchemaName); cubeNameCond = makeCondition(ELEMENT_NAME_GETTER, CubeName); dimensionUnameCond = makeCondition(ELEMENT_UNAME_GETTER, DimensionUniqueName); dimensionNameCond = makeCondition(ELEMENT_NAME_GETTER, DimensionName); } public static final int MD_DIMTYPE_OTHER = 3; public static final int MD_DIMTYPE_MEASURE = 2; public static final int MD_DIMTYPE_TIME = 1; private static final Column CatalogName = new Column( "CATALOG_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the database."); private static final Column SchemaName = new Column( "SCHEMA_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "Not supported."); private static final Column CubeName = new Column( "CUBE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the cube."); private static final Column DimensionName = new Column( "DIMENSION_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the dimension."); private static final Column DimensionUniqueName = new Column( "DIMENSION_UNIQUE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The unique name of the dimension."); private static final Column DimensionGuid = new Column( "DIMENSION_GUID", Type.UUID, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Not supported."); private static final Column DimensionCaption = new Column( "DIMENSION_CAPTION", Type.String, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The caption of the dimension."); private static final Column DimensionOrdinal = new Column( "DIMENSION_ORDINAL", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The position of the dimension within the cube."); /* * SQL Server returns values: * MD_DIMTYPE_TIME (1) * MD_DIMTYPE_MEASURE (2) * MD_DIMTYPE_OTHER (3) */ private static final Column DimensionType = new Column( "DIMENSION_TYPE", Type.Short, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The type of the dimension."); private static final Column DimensionCardinality = new Column( "DIMENSION_CARDINALITY", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The number of members in the key attribute."); private static final Column DefaultHierarchy = new Column( "DEFAULT_HIERARCHY", Type.String, null, Column.NOT_RESTRICTION, Column.REQUIRED, "A hierarchy from the dimension. Preserved for backwards " + "compatibility."); private static final Column Description = new Column( "DESCRIPTION", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A user-friendly description of the dimension."); private static final Column IsVirtual = new Column( "IS_VIRTUAL", Type.Boolean, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Always FALSE."); private static final Column IsReadWrite = new Column( "IS_READWRITE", Type.Boolean, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A Boolean that indicates whether the dimension is " + "write-enabled."); /* * SQL Server returns values: 0 or 1 */ private static final Column DimensionUniqueSettings = new Column( "DIMENSION_UNIQUE_SETTINGS", Type.Integer, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A bitmap that specifies which columns contain unique values " + "if the dimension contains only members with unique names."); private static final Column DimensionMasterUniqueName = new Column( "DIMENSION_MASTER_UNIQUE_NAME", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Always NULL."); private static final Column DimensionIsVisible = new Column( "DIMENSION_IS_VISIBLE", Type.Boolean, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Always TRUE."); private static final Column Hierarchies = new Column( "HIERARCHIES", Type.Rowset, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Hierarchies in this dimension."); public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException, SQLException { for (Catalog catalog : catIter(connection, catNameCond(), catalogNameCond)) { populateCatalog(connection, catalog, rows); } } protected void populateCatalog( OlapConnection connection, Catalog catalog, List rows) throws XmlaException, SQLException { for (Schema schema : filter(catalog.getSchemas(), schemaNameCond)) { for (Cube cube : filteredCubes(schema, cubeNameCond)) { populateCube(connection, catalog, cube, rows); } } } protected void populateCube( OlapConnection connection, Catalog catalog, Cube cube, List rows) throws XmlaException, SQLException { for (Dimension dimension : filter( cube.getDimensions(), dimensionNameCond, dimensionUnameCond)) { populateDimension( connection, catalog, cube, dimension, rows); } } protected void populateDimension( OlapConnection connection, Catalog catalog, Cube cube, Dimension dimension, List rows) throws XmlaException, SQLException { String desc = dimension.getDescription(); if (desc == null) { desc = cube.getName() + " Cube - " + dimension.getName() + " Dimension"; } Row row = new Row(); row.set(CatalogName.name, catalog.getName()); row.set(SchemaName.name, cube.getSchema().getName()); row.set(CubeName.name, cube.getName()); row.set(DimensionName.name, dimension.getName()); row.set(DimensionUniqueName.name, dimension.getUniqueName()); row.set(DimensionCaption.name, dimension.getCaption()); row.set( DimensionOrdinal.name, cube.getDimensions().indexOf(dimension)); row.set(DimensionType.name, getDimensionType(dimension)); //Is this the number of primaryKey members there are?? // According to microsoft this is: // "The number of members in the key attribute." // There may be a better way of doing this but // this is what I came up with. Note that I need to // add '1' to the number inorder for it to match // match what microsoft SQL Server is producing. // The '1' might have to do with whether or not the // hierarchy has a 'all' member or not - don't know yet. // large data set total for Orders cube 0m42.923s Hierarchy firstHierarchy = dimension.getHierarchies().get(0); NamedList levels = firstHierarchy.getLevels(); Level lastLevel = levels.get(levels.size() - 1); /* if override config setting is set if approxRowCount has a value use it else do default */ // Added by TWI to returned cached row numbers int n = getExtra(connection).getLevelCardinality(lastLevel); row.set(DimensionCardinality.name, n + 1); // TODO: I think that this is just the dimension name row.set(DefaultHierarchy.name, dimension.getUniqueName()); row.set(Description.name, desc); row.set(IsVirtual.name, false); // SQL Server always returns false row.set(IsReadWrite.name, false); // TODO: don't know what to do here // Are these the levels with uniqueMembers == true? // How are they mapped to specific column numbers? row.set(DimensionUniqueSettings.name, 0); row.set(DimensionIsVisible.name, dimension.isVisible()); if (deep) { row.set( Hierarchies.name, new MdschemaHierarchiesRowset( wrapRequest( request, Olap4jUtil.mapOf( MdschemaHierarchiesRowset.CatalogName, catalog.getName(), MdschemaHierarchiesRowset.SchemaName, cube.getSchema().getName(), MdschemaHierarchiesRowset.CubeName, cube.getName(), MdschemaHierarchiesRowset.DimensionUniqueName, dimension.getUniqueName())), handler)); } addRow(row, rows); } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } static int getDimensionType(Dimension dim) throws OlapException { switch (dim.getDimensionType()) { case MEASURE: return MdschemaDimensionsRowset.MD_DIMTYPE_MEASURE; case TIME: return MdschemaDimensionsRowset.MD_DIMTYPE_TIME; default: return MdschemaDimensionsRowset.MD_DIMTYPE_OTHER; } } public static class MdschemaFunctionsRowset extends Rowset { /** * http://www.csidata.com/custserv/onlinehelp/VBSdocs/vbs57.htm */ public enum VarType { Empty("Uninitialized (default)"), Null("Contains no valid data"), Integer("Integer subtype"), Long("Long subtype"), Single("Single subtype"), Double("Double subtype"), Currency("Currency subtype"), Date("Date subtype"), String("String subtype"), Object("Object subtype"), Error("Error subtype"), Boolean("Boolean subtype"), Variant("Variant subtype"), DataObject("DataObject subtype"), Decimal("Decimal subtype"), Byte("Byte subtype"), Array("Array subtype"); public static VarType forCategory(int category) { switch (category) { case Category.Unknown: // expression == unknown ??? // case Category.Expression: return Empty; case Category.Array: return Array; case Category.Dimension: case Category.Hierarchy: case Category.Level: case Category.Member: case Category.Set: case Category.Tuple: case Category.Cube: case Category.Value: return Variant; case Category.Logical: return Boolean; case Category.Numeric: return Double; case Category.String: case Category.Symbol: case Category.Constant: return String; case Category.DateTime: return Date; case Category.Integer: case Category.Mask: return Integer; } // NOTE: this should never happen return Empty; } VarType(String description) { Util.discard(description); } } private final Util.Functor1 functionNameCond; MdschemaFunctionsRowset(XmlaRequest request, XmlaHandler handler) { super(MDSCHEMA_FUNCTIONS, request, handler); functionNameCond = makeCondition(FunctionName); } private static final Column FunctionName = new Column( "FUNCTION_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the function."); private static final Column Description = new Column( "DESCRIPTION", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A description of the function."); private static final Column ParameterList = new Column( "PARAMETER_LIST", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A comma delimited list of parameters."); private static final Column ReturnType = new Column( "RETURN_TYPE", Type.Integer, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The VARTYPE of the return data type of the function."); private static final Column Origin = new Column( "ORIGIN", Type.Integer, null, Column.RESTRICTION, Column.REQUIRED, "The origin of the function: 1 for MDX functions. 2 for " + "user-defined functions."); private static final Column InterfaceName = new Column( "INTERFACE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the interface for user-defined functions"); private static final Column LibraryName = new Column( "LIBRARY_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the type library for user-defined functions. " + "NULL for MDX functions."); private static final Column Caption = new Column( "CAPTION", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The display caption for the function."); public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException, SQLException { final XmlaHandler.XmlaExtra extra = getExtra(connection); for (Catalog catalog : catIter(connection, catNameCond())) { // By definition, mondrian catalogs have only one // schema. It is safe to use get(0) final Schema schema = catalog.getSchemas().get(0); List funDefs = new ArrayList(); // olap4j does not support describing functions. Call an // auxiliary method. extra.getSchemaFunctionList( funDefs, schema, functionNameCond); for (XmlaHandler.XmlaExtra.FunctionDefinition funDef : funDefs) { Row row = new Row(); row.set(FunctionName.name, funDef.functionName); row.set(Description.name, funDef.description); row.set(ParameterList.name, funDef.parameterList); row.set(ReturnType.name, funDef.returnType); row.set(Origin.name, funDef.origin); //row.set(LibraryName.name, ""); row.set(InterfaceName.name, funDef.interfaceName); row.set(Caption.name, funDef.caption); addRow(row, rows); } } } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } static class MdschemaHierarchiesRowset extends Rowset { private final Util.Functor1 catalogCond; private final Util.Functor1 schemaNameCond; private final Util.Functor1 cubeNameCond; private final Util.Functor1 dimensionUnameCond; private final Util.Functor1 hierarchyUnameCond; private final Util.Functor1 hierarchyNameCond; MdschemaHierarchiesRowset(XmlaRequest request, XmlaHandler handler) { super(MDSCHEMA_HIERARCHIES, request, handler); catalogCond = makeCondition(CATALOG_NAME_GETTER, CatalogName); schemaNameCond = makeCondition(SCHEMA_NAME_GETTER, SchemaName); cubeNameCond = makeCondition(ELEMENT_NAME_GETTER, CubeName); dimensionUnameCond = makeCondition(ELEMENT_UNAME_GETTER, DimensionUniqueName); hierarchyUnameCond = makeCondition(ELEMENT_UNAME_GETTER, HierarchyUniqueName); hierarchyNameCond = makeCondition(ELEMENT_NAME_GETTER, HierarchyName); } private static final Column CatalogName = new Column( "CATALOG_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the catalog to which this hierarchy belongs."); private static final Column SchemaName = new Column( "SCHEMA_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "Not supported"); private static final Column CubeName = new Column( "CUBE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the cube to which this hierarchy belongs."); private static final Column DimensionUniqueName = new Column( "DIMENSION_UNIQUE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The unique name of the dimension to which this hierarchy " + "belongs."); private static final Column HierarchyName = new Column( "HIERARCHY_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the hierarchy. Blank if there is only a single " + "hierarchy in the dimension."); private static final Column HierarchyUniqueName = new Column( "HIERARCHY_UNIQUE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The unique name of the hierarchy."); private static final Column HierarchyGuid = new Column( "HIERARCHY_GUID", Type.UUID, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Hierarchy GUID."); private static final Column HierarchyCaption = new Column( "HIERARCHY_CAPTION", Type.String, null, Column.NOT_RESTRICTION, Column.REQUIRED, "A label or a caption associated with the hierarchy."); private static final Column DimensionType = new Column( "DIMENSION_TYPE", Type.Short, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The type of the dimension."); private static final Column HierarchyCardinality = new Column( "HIERARCHY_CARDINALITY", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The number of members in the hierarchy."); private static final Column DefaultMember = new Column( "DEFAULT_MEMBER", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The default member for this hierarchy."); private static final Column AllMember = new Column( "ALL_MEMBER", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The member at the highest level of rollup in the hierarchy."); private static final Column Description = new Column( "DESCRIPTION", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A human-readable description of the hierarchy. NULL if no " + "description exists."); private static final Column Structure = new Column( "STRUCTURE", Type.Short, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The structure of the hierarchy."); private static final Column IsVirtual = new Column( "IS_VIRTUAL", Type.Boolean, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Always returns False."); private static final Column IsReadWrite = new Column( "IS_READWRITE", Type.Boolean, null, Column.NOT_RESTRICTION, Column.REQUIRED, "A Boolean that indicates whether the Write Back to dimension " + "column is enabled."); private static final Column DimensionUniqueSettings = new Column( "DIMENSION_UNIQUE_SETTINGS", Type.Integer, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Always returns MDDIMENSIONS_MEMBER_KEY_UNIQUE (1)."); private static final Column DimensionIsVisible = new Column( "DIMENSION_IS_VISIBLE", Type.Boolean, null, Column.NOT_RESTRICTION, Column.REQUIRED, "A Boolean that indicates whether the parent dimension is visible."); private static final Column HierarchyIsVisible = new Column( "HIERARCHY_IS_VISIBLE", Type.Boolean, null, Column.NOT_RESTRICTION, Column.REQUIRED, "A Boolean that indicates whether the hieararchy is visible."); private static final Column HierarchyOrdinal = new Column( "HIERARCHY_ORDINAL", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The ordinal number of the hierarchy across all hierarchies of " + "the cube."); private static final Column DimensionIsShared = new Column( "DIMENSION_IS_SHARED", Type.Boolean, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Always returns true."); private static final Column Levels = new Column( "LEVELS", Type.Rowset, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Levels in this hierarchy."); /* * NOTE: This is non-standard, where did it come from? */ private static final Column ParentChild = new Column( "PARENT_CHILD", Type.Boolean, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Is hierarchy a parent."); public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException, SQLException { for (Catalog catalog : catIter(connection, catNameCond(), catalogCond)) { populateCatalog(connection, catalog, rows); } } protected void populateCatalog( OlapConnection connection, Catalog catalog, List rows) throws XmlaException, SQLException { for (Schema schema : filter(catalog.getSchemas(), schemaNameCond)) { for (Cube cube : filteredCubes(schema, cubeNameCond)) { populateCube(connection, catalog, cube, rows); } } } protected void populateCube( OlapConnection connection, Catalog catalog, Cube cube, List rows) throws XmlaException, SQLException { int ordinal = 0; for (Dimension dimension : cube.getDimensions()) { // Must increment ordinal for all dimensions but // only output some of them. boolean genOutput = dimensionUnameCond.apply(dimension); if (genOutput) { populateDimension( connection, catalog, cube, dimension, ordinal, rows); } ordinal += dimension.getHierarchies().size(); } } protected void populateDimension( OlapConnection connection, Catalog catalog, Cube cube, Dimension dimension, int ordinal, List rows) throws XmlaException, SQLException { final NamedList hierarchies = dimension.getHierarchies(); for (Hierarchy hierarchy : filter(hierarchies, hierarchyNameCond, hierarchyUnameCond)) { populateHierarchy( connection, catalog, cube, dimension, hierarchy, ordinal + hierarchies.indexOf(hierarchy), rows); } } protected void populateHierarchy( OlapConnection connection, Catalog catalog, Cube cube, Dimension dimension, Hierarchy hierarchy, int ordinal, List rows) throws XmlaException, SQLException { final XmlaHandler.XmlaExtra extra = getExtra(connection); String desc = hierarchy.getDescription(); if (desc == null) { desc = cube.getName() + " Cube - " + getHierarchyName(hierarchy) + " Hierarchy"; } Row row = new Row(); row.set(CatalogName.name, catalog.getName()); row.set(SchemaName.name, cube.getSchema().getName()); row.set(CubeName.name, cube.getName()); row.set(DimensionUniqueName.name, dimension.getUniqueName()); row.set(HierarchyName.name, hierarchy.getName()); row.set(HierarchyUniqueName.name, hierarchy.getUniqueName()); //row.set(HierarchyGuid.name, ""); row.set(HierarchyCaption.name, hierarchy.getCaption()); row.set(DimensionType.name, getDimensionType(dimension)); // The number of members in the hierarchy. Because // of the presence of multiple hierarchies, this number // might not be the same as DIMENSION_CARDINALITY. This // value can be an approximation of the real // cardinality. Consumers should not assume that this // value is accurate. int cardinality = extra.getHierarchyCardinality(hierarchy); row.set(HierarchyCardinality.name, cardinality); row.set( DefaultMember.name, hierarchy.getDefaultMember().getUniqueName()); if (hierarchy.hasAll()) { row.set( AllMember.name, hierarchy.getRootMembers().get(0).getUniqueName()); } row.set(Description.name, desc); //TODO: only support: // MD_STRUCTURE_FULLYBALANCED (0) // MD_STRUCTURE_RAGGEDBALANCED (1) row.set(Structure.name, extra.getHierarchyStructure(hierarchy)); row.set(IsVirtual.name, false); row.set(IsReadWrite.name, false); // NOTE that SQL Server returns '0' not '1'. row.set(DimensionUniqueSettings.name, 0); row.set(DimensionIsVisible.name, dimension.isVisible()); row.set(HierarchyIsVisible.name, hierarchy.isVisible()); row.set(HierarchyOrdinal.name, ordinal); // always true row.set(DimensionIsShared.name, true); row.set(ParentChild.name, extra.isHierarchyParentChild(hierarchy)); if (deep) { row.set( Levels.name, new MdschemaLevelsRowset( wrapRequest( request, Olap4jUtil.mapOf( MdschemaLevelsRowset.CatalogName, catalog.getName(), MdschemaLevelsRowset.SchemaName, cube.getSchema().getName(), MdschemaLevelsRowset.CubeName, cube.getName(), MdschemaLevelsRowset.DimensionUniqueName, dimension.getUniqueName(), MdschemaLevelsRowset.HierarchyUniqueName, hierarchy.getUniqueName())), handler)); } addRow(row, rows); } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } static class MdschemaLevelsRowset extends Rowset { private final Util.Functor1 catalogCond; private final Util.Functor1 schemaNameCond; private final Util.Functor1 cubeNameCond; private final Util.Functor1 dimensionUnameCond; private final Util.Functor1 hierarchyUnameCond; private final Util.Functor1 levelUnameCond; private final Util.Functor1 levelNameCond; MdschemaLevelsRowset(XmlaRequest request, XmlaHandler handler) { super(MDSCHEMA_LEVELS, request, handler); catalogCond = makeCondition(CATALOG_NAME_GETTER, CatalogName); schemaNameCond = makeCondition(SCHEMA_NAME_GETTER, SchemaName); cubeNameCond = makeCondition(ELEMENT_NAME_GETTER, CubeName); dimensionUnameCond = makeCondition(ELEMENT_UNAME_GETTER, DimensionUniqueName); hierarchyUnameCond = makeCondition(ELEMENT_UNAME_GETTER, HierarchyUniqueName); levelUnameCond = makeCondition(ELEMENT_UNAME_GETTER, LevelUniqueName); levelNameCond = makeCondition(ELEMENT_NAME_GETTER, LevelName); } public static final int MDLEVEL_TYPE_UNKNOWN = 0x0000; public static final int MDLEVEL_TYPE_REGULAR = 0x0000; public static final int MDLEVEL_TYPE_ALL = 0x0001; public static final int MDLEVEL_TYPE_CALCULATED = 0x0002; public static final int MDLEVEL_TYPE_TIME = 0x0004; public static final int MDLEVEL_TYPE_RESERVED1 = 0x0008; public static final int MDLEVEL_TYPE_TIME_YEARS = 0x0014; public static final int MDLEVEL_TYPE_TIME_HALF_YEAR = 0x0024; public static final int MDLEVEL_TYPE_TIME_QUARTERS = 0x0044; public static final int MDLEVEL_TYPE_TIME_MONTHS = 0x0084; public static final int MDLEVEL_TYPE_TIME_WEEKS = 0x0104; public static final int MDLEVEL_TYPE_TIME_DAYS = 0x0204; public static final int MDLEVEL_TYPE_TIME_HOURS = 0x0304; public static final int MDLEVEL_TYPE_TIME_MINUTES = 0x0404; public static final int MDLEVEL_TYPE_TIME_SECONDS = 0x0804; public static final int MDLEVEL_TYPE_TIME_UNDEFINED = 0x1004; private static final Column CatalogName = new Column( "CATALOG_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the catalog to which this level belongs."); private static final Column SchemaName = new Column( "SCHEMA_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the schema to which this level belongs."); private static final Column CubeName = new Column( "CUBE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the cube to which this level belongs."); private static final Column DimensionUniqueName = new Column( "DIMENSION_UNIQUE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The unique name of the dimension to which this level " + "belongs."); private static final Column HierarchyUniqueName = new Column( "HIERARCHY_UNIQUE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The unique name of the hierarchy."); private static final Column LevelName = new Column( "LEVEL_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the level."); private static final Column LevelUniqueName = new Column( "LEVEL_UNIQUE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The properly escaped unique name of the level."); private static final Column LevelGuid = new Column( "LEVEL_GUID", Type.UUID, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Level GUID."); private static final Column LevelCaption = new Column( "LEVEL_CAPTION", Type.String, null, Column.NOT_RESTRICTION, Column.REQUIRED, "A label or caption associated with the hierarchy."); private static final Column LevelNumber = new Column( "LEVEL_NUMBER", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The distance of the level from the root of the hierarchy. " + "Root level is zero (0)."); private static final Column LevelCardinality = new Column( "LEVEL_CARDINALITY", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The number of members in the level. This value can be an " + "approximation of the real cardinality."); private static final Column LevelType = new Column( "LEVEL_TYPE", Type.Integer, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Type of the level"); private static final Column CustomRollupSettings = new Column( "CUSTOM_ROLLUP_SETTINGS", Type.Integer, null, Column.NOT_RESTRICTION, Column.REQUIRED, "A bitmap that specifies the custom rollup options."); private static final Column LevelUniqueSettings = new Column( "LEVEL_UNIQUE_SETTINGS", Type.Integer, null, Column.NOT_RESTRICTION, Column.REQUIRED, "A bitmap that specifies which columns contain unique values, " + "if the level only has members with unique names or keys."); private static final Column LevelIsVisible = new Column( "LEVEL_IS_VISIBLE", Type.Boolean, null, Column.NOT_RESTRICTION, Column.REQUIRED, "A Boolean that indicates whether the level is visible."); private static final Column Description = new Column( "DESCRIPTION", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A human-readable description of the level. NULL if no " + "description exists."); public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException, SQLException { for (Catalog catalog : catIter(connection, catNameCond(), catalogCond)) { populateCatalog(connection, catalog, rows); } } protected void populateCatalog( OlapConnection connection, Catalog catalog, List rows) throws XmlaException, SQLException { for (Schema schema : filter(catalog.getSchemas(), schemaNameCond)) { for (Cube cube : filteredCubes(schema, cubeNameCond)) { populateCube(connection, catalog, cube, rows); } } } protected void populateCube( OlapConnection connection, Catalog catalog, Cube cube, List rows) throws XmlaException, SQLException { for (Dimension dimension : filter(cube.getDimensions(), dimensionUnameCond)) { populateDimension( connection, catalog, cube, dimension, rows); } } protected void populateDimension( OlapConnection connection, Catalog catalog, Cube cube, Dimension dimension, List rows) throws XmlaException, SQLException { for (Hierarchy hierarchy : filter(dimension.getHierarchies(), hierarchyUnameCond)) { populateHierarchy( connection, catalog, cube, hierarchy, rows); } } protected void populateHierarchy( OlapConnection connection, Catalog catalog, Cube cube, Hierarchy hierarchy, List rows) throws XmlaException, SQLException { for (Level level : filter(hierarchy.getLevels(), levelUnameCond, levelNameCond)) { outputLevel( connection, catalog, cube, hierarchy, level, rows); } } /** * Outputs a level. * * @param catalog Catalog name * @param cube Cube definition * @param hierarchy Hierarchy * @param level Level * @param rows List of rows to output to * @return whether the level is visible * @throws XmlaException If error occurs */ protected boolean outputLevel( OlapConnection connection, Catalog catalog, Cube cube, Hierarchy hierarchy, Level level, List rows) throws XmlaException, SQLException { final XmlaHandler.XmlaExtra extra = getExtra(connection); String desc = level.getDescription(); if (desc == null) { desc = cube.getName() + " Cube - " + getHierarchyName(hierarchy) + " Hierarchy - " + level.getName() + " Level"; } Row row = new Row(); row.set(CatalogName.name, catalog.getName()); row.set(SchemaName.name, cube.getSchema().getName()); row.set(CubeName.name, cube.getName()); row.set( DimensionUniqueName.name, hierarchy.getDimension().getUniqueName()); row.set(HierarchyUniqueName.name, hierarchy.getUniqueName()); row.set(LevelName.name, level.getName()); row.set(LevelUniqueName.name, level.getUniqueName()); //row.set(LevelGuid.name, ""); row.set(LevelCaption.name, level.getCaption()); // see notes on this #getDepth() row.set(LevelNumber.name, level.getDepth()); // Get level cardinality // According to microsoft this is: // "The number of members in the level." int n = extra.getLevelCardinality(level); row.set(LevelCardinality.name, n); row.set(LevelType.name, getLevelType(level)); // TODO: most of the time this is correct row.set(CustomRollupSettings.name, 0); int uniqueSettings = 0; if (level.getLevelType() == Level.Type.ALL) { uniqueSettings |= 2; } if (extra.isLevelUnique(level)) { uniqueSettings |= 1; } row.set(LevelUniqueSettings.name, uniqueSettings); row.set(LevelIsVisible.name, level.isVisible()); row.set(Description.name, desc); addRow(row, rows); return true; } private int getLevelType(Level lev) { int ret = 0; switch (lev.getLevelType()) { case ALL: ret |= MDLEVEL_TYPE_ALL; break; case REGULAR: ret |= MDLEVEL_TYPE_REGULAR; break; case TIME_YEARS: ret |= MDLEVEL_TYPE_TIME_YEARS; break; case TIME_HALF_YEAR: ret |= MDLEVEL_TYPE_TIME_HALF_YEAR; break; case TIME_QUARTERS: ret |= MDLEVEL_TYPE_TIME_QUARTERS; break; case TIME_MONTHS: ret |= MDLEVEL_TYPE_TIME_MONTHS; break; case TIME_WEEKS: ret |= MDLEVEL_TYPE_TIME_WEEKS; break; case TIME_DAYS: ret |= MDLEVEL_TYPE_TIME_DAYS; break; case TIME_HOURS: ret |= MDLEVEL_TYPE_TIME_HOURS; break; case TIME_MINUTES: ret |= MDLEVEL_TYPE_TIME_MINUTES; break; case TIME_SECONDS: ret |= MDLEVEL_TYPE_TIME_SECONDS; break; case TIME_UNDEFINED: ret |= MDLEVEL_TYPE_TIME_UNDEFINED; break; default: ret |= MDLEVEL_TYPE_UNKNOWN; } return ret; } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } public static class MdschemaMeasuresRowset extends Rowset { public static final int MDMEASURE_AGGR_UNKNOWN = 0; public static final int MDMEASURE_AGGR_SUM = 1; public static final int MDMEASURE_AGGR_COUNT = 2; public static final int MDMEASURE_AGGR_MIN = 3; public static final int MDMEASURE_AGGR_MAX = 4; public static final int MDMEASURE_AGGR_AVG = 5; public static final int MDMEASURE_AGGR_VAR = 6; public static final int MDMEASURE_AGGR_STD = 7; public static final int MDMEASURE_AGGR_CALCULATED = 127; private final Util.Functor1 catalogCond; private final Util.Functor1 schemaNameCond; private final Util.Functor1 cubeNameCond; private final Util.Functor1 measureUnameCond; private final Util.Functor1 measureNameCond; MdschemaMeasuresRowset(XmlaRequest request, XmlaHandler handler) { super(MDSCHEMA_MEASURES, request, handler); catalogCond = makeCondition(CATALOG_NAME_GETTER, CatalogName); schemaNameCond = makeCondition(SCHEMA_NAME_GETTER, SchemaName); cubeNameCond = makeCondition(ELEMENT_NAME_GETTER, CubeName); measureNameCond = makeCondition(ELEMENT_NAME_GETTER, MeasureName); measureUnameCond = makeCondition(ELEMENT_UNAME_GETTER, MeasureUniqueName); } private static final Column CatalogName = new Column( "CATALOG_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the catalog to which this measure belongs."); private static final Column SchemaName = new Column( "SCHEMA_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the schema to which this measure belongs."); private static final Column CubeName = new Column( "CUBE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the cube to which this measure belongs."); private static final Column MeasureName = new Column( "MEASURE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The name of the measure."); private static final Column MeasureUniqueName = new Column( "MEASURE_UNIQUE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "The Unique name of the measure."); private static final Column MeasureCaption = new Column( "MEASURE_CAPTION", Type.String, null, Column.NOT_RESTRICTION, Column.REQUIRED, "A label or caption associated with the measure."); private static final Column MeasureGuid = new Column( "MEASURE_GUID", Type.UUID, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Measure GUID."); private static final Column MeasureAggregator = new Column( "MEASURE_AGGREGATOR", Type.Integer, null, Column.NOT_RESTRICTION, Column.REQUIRED, "How a measure was derived."); private static final Column DataType = new Column( "DATA_TYPE", Type.UnsignedShort, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Data type of the measure."); private static final Column MeasureIsVisible = new Column( "MEASURE_IS_VISIBLE", Type.Boolean, null, Column.NOT_RESTRICTION, Column.REQUIRED, "A Boolean that always returns True. If the measure is not " + "visible, it will not be included in the schema rowset."); private static final Column LevelsList = new Column( "LEVELS_LIST", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A string that always returns NULL. EXCEPT that SQL Server " + "returns non-null values!!!"); private static final Column Description = new Column( "DESCRIPTION", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A human-readable description of the measure."); private static final Column FormatString = new Column( "DEFAULT_FORMAT_STRING", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "The default format string for the measure."); public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException, SQLException { for (Catalog catalog : catIter(connection, catNameCond(), catalogCond)) { populateCatalog(connection, catalog, rows); } } protected void populateCatalog( OlapConnection connection, Catalog catalog, List rows) throws XmlaException, SQLException { // SQL Server actually includes the LEVELS_LIST row StringBuilder buf = new StringBuilder(100); for (Schema schema : filter(catalog.getSchemas(), schemaNameCond)) { for (Cube cube : filteredCubes(schema, cubeNameCond)) { buf.setLength(0); int j = 0; for (Dimension dimension : cube.getDimensions()) { if (dimension.getDimensionType() == Dimension.Type.MEASURE) { continue; } for (Hierarchy hierarchy : dimension.getHierarchies()) { NamedList levels = hierarchy.getLevels(); Level lastLevel = levels.get(levels.size() - 1); if (j++ > 0) { buf.append(','); } buf.append(lastLevel.getUniqueName()); } } String levelListStr = buf.toString(); List calcMembers = new ArrayList(); for (Measure measure : filter( cube.getMeasures(), measureNameCond, measureUnameCond)) { if (measure.isCalculated()) { // Output calculated measures after stored // measures. calcMembers.add(measure); } else { populateMember( connection, catalog, measure, cube, levelListStr, rows); } } for (Member member : calcMembers) { populateMember( connection, catalog, member, cube, null, rows); } } } } private void populateMember( OlapConnection connection, Catalog catalog, Member member, Cube cube, String levelListStr, List rows) throws SQLException { Boolean visible = (Boolean) member.getPropertyValue( Property.StandardMemberProperty.$visible); if (visible == null) { visible = true; } if (!visible && !XmlaUtil.shouldEmitInvisibleMembers(request)) { return; } //TODO: currently this is always null String desc = member.getDescription(); if (desc == null) { desc = cube.getName() + " Cube - " + member.getName() + " Member"; } final String formatString = (String) member.getPropertyValue( Property.StandardCellProperty.FORMAT_STRING); Row row = new Row(); row.set(CatalogName.name, catalog.getName()); row.set(SchemaName.name, cube.getSchema().getName()); row.set(CubeName.name, cube.getName()); row.set(MeasureName.name, member.getName()); row.set(MeasureUniqueName.name, member.getUniqueName()); row.set(MeasureCaption.name, member.getCaption()); //row.set(MeasureGuid.name, ""); final XmlaHandler.XmlaExtra extra = getExtra(connection); row.set(MeasureAggregator.name, extra.getMeasureAggregator(member)); // DATA_TYPE DBType best guess is string XmlaConstants.DBType dbType = XmlaConstants.DBType.WSTR; String datatype = (String) member.getPropertyValue(Property.StandardCellProperty.DATATYPE); if (datatype != null) { if (datatype.equals("Integer")) { dbType = XmlaConstants.DBType.I4; } else if (datatype.equals("Numeric")) { dbType = XmlaConstants.DBType.R8; } else { dbType = XmlaConstants.DBType.WSTR; } } row.set(DataType.name, dbType.xmlaOrdinal()); row.set(MeasureIsVisible.name, visible); if (levelListStr != null) { row.set(LevelsList.name, levelListStr); } row.set(Description.name, desc); row.set(FormatString.name, formatString); addRow(row, rows); } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } static class MdschemaMembersRowset extends Rowset { private final Util.Functor1 catalogCond; private final Util.Functor1 schemaNameCond; private final Util.Functor1 cubeNameCond; private final Util.Functor1 dimensionUnameCond; private final Util.Functor1 hierarchyUnameCond; private final Util.Functor1 memberNameCond; private final Util.Functor1 memberUnameCond; private final Util.Functor1 memberTypeCond; MdschemaMembersRowset(XmlaRequest request, XmlaHandler handler) { super(MDSCHEMA_MEMBERS, request, handler); catalogCond = makeCondition(CATALOG_NAME_GETTER, CatalogName); schemaNameCond = makeCondition(SCHEMA_NAME_GETTER, SchemaName); cubeNameCond = makeCondition(ELEMENT_NAME_GETTER, CubeName); dimensionUnameCond = makeCondition(ELEMENT_UNAME_GETTER, DimensionUniqueName); hierarchyUnameCond = makeCondition(ELEMENT_UNAME_GETTER, HierarchyUniqueName); memberNameCond = makeCondition(ELEMENT_NAME_GETTER, MemberName); memberUnameCond = makeCondition(ELEMENT_UNAME_GETTER, MemberUniqueName); memberTypeCond = makeCondition(MEMBER_TYPE_GETTER, MemberType); } private static final Column CatalogName = new Column( "CATALOG_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the catalog to which this member belongs."); private static final Column SchemaName = new Column( "SCHEMA_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the schema to which this member belongs."); private static final Column CubeName = new Column( "CUBE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "Name of the cube to which this member belongs."); private static final Column DimensionUniqueName = new Column( "DIMENSION_UNIQUE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "Unique name of the dimension to which this member belongs."); private static final Column HierarchyUniqueName = new Column( "HIERARCHY_UNIQUE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "Unique name of the hierarchy. If the member belongs to more " + "than one hierarchy, there is one row for each hierarchy to " + "which it belongs."); private static final Column LevelUniqueName = new Column( "LEVEL_UNIQUE_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, " Unique name of the level to which the member belongs."); private static final Column LevelNumber = new Column( "LEVEL_NUMBER", Type.UnsignedInteger, null, Column.RESTRICTION, Column.REQUIRED, "The distance of the member from the root of the hierarchy."); private static final Column MemberOrdinal = new Column( "MEMBER_ORDINAL", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Ordinal number of the member. Sort rank of the member when " + "members of this dimension are sorted in their natural sort " + "order. If providers do not have the concept of natural " + "ordering, this should be the rank when sorted by " + "MEMBER_NAME."); private static final Column MemberName = new Column( "MEMBER_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "Name of the member."); private static final Column MemberUniqueName = new Column( "MEMBER_UNIQUE_NAME", Type.StringSometimesArray, null, Column.RESTRICTION, Column.REQUIRED, " Unique name of the member."); private static final Column MemberType = new Column( "MEMBER_TYPE", Type.Integer, null, Column.RESTRICTION, Column.REQUIRED, "Type of the member."); private static final Column MemberGuid = new Column( "MEMBER_GUID", Type.UUID, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Memeber GUID."); private static final Column MemberCaption = new Column( "MEMBER_CAPTION", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "A label or caption associated with the member."); private static final Column ChildrenCardinality = new Column( "CHILDREN_CARDINALITY", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Number of children that the member has."); private static final Column ParentLevel = new Column( "PARENT_LEVEL", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.REQUIRED, "The distance of the member's parent from the root level of " + "the hierarchy."); private static final Column ParentUniqueName = new Column( "PARENT_UNIQUE_NAME", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "Unique name of the member's parent."); private static final Column ParentCount = new Column( "PARENT_COUNT", Type.UnsignedInteger, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Number of parents that this member has."); private static final Column TreeOp_ = new Column( "TREE_OP", Type.Enumeration, Enumeration.TREE_OP, Column.RESTRICTION, Column.OPTIONAL, "Tree Operation"); /* Mondrian specified member properties. */ private static final Column Depth = new Column( "DEPTH", Type.Integer, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "depth"); public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException, SQLException { for (Catalog catalog : catIter(connection, catNameCond(), catalogCond)) { populateCatalog(connection, catalog, rows); } } protected void populateCatalog( OlapConnection connection, Catalog catalog, List rows) throws XmlaException, SQLException { for (Schema schema : filter(catalog.getSchemas(), schemaNameCond)) { for (Cube cube : filteredCubes(schema, cubeNameCond)) { if (isRestricted(MemberUniqueName)) { // NOTE: it is believed that if MEMBER_UNIQUE_NAME is // a restriction, then none of the remaining possible // restrictions other than TREE_OP are relevant // (or allowed??). outputUniqueMemberName( connection, catalog, cube, rows); } else { populateCube(connection, catalog, cube, rows); } } } } protected void populateCube( OlapConnection connection, Catalog catalog, Cube cube, List rows) throws XmlaException, SQLException { if (isRestricted(LevelUniqueName)) { // Note: If the LEVEL_UNIQUE_NAME has been specified, then // the dimension and hierarchy are specified implicitly. String levelUniqueName = getRestrictionValueAsString(LevelUniqueName); if (levelUniqueName == null) { // The query specified two or more unique names // which means that nothing will match. return; } Level level = lookupLevel(cube, levelUniqueName); if (level != null) { // Get members of this level, without access control, but // including calculated members. List members = level.getMembers(); outputMembers(connection, members, catalog, cube, rows); } } else { for (Dimension dimension : filter(cube.getDimensions(), dimensionUnameCond)) { populateDimension( connection, catalog, cube, dimension, rows); } } } protected void populateDimension( OlapConnection connection, Catalog catalog, Cube cube, Dimension dimension, List rows) throws XmlaException, SQLException { for (Hierarchy hierarchy : filter(dimension.getHierarchies(), hierarchyUnameCond)) { populateHierarchy( connection, catalog, cube, hierarchy, rows); } } protected void populateHierarchy( OlapConnection connection, Catalog catalog, Cube cube, Hierarchy hierarchy, List rows) throws XmlaException, SQLException { if (isRestricted(LevelNumber)) { int levelNumber = getRestrictionValueAsInt(LevelNumber); if (levelNumber == -1) { LOGGER.warn( "RowsetDefinition.populateHierarchy: " + "LevelNumber invalid"); return; } NamedList levels = hierarchy.getLevels(); if (levelNumber >= levels.size()) { LOGGER.warn( "RowsetDefinition.populateHierarchy: " + "LevelNumber (" + levelNumber + ") is greater than number of levels (" + levels.size() + ") for hierarchy \"" + hierarchy.getUniqueName() + "\""); return; } Level level = levels.get(levelNumber); List members = level.getMembers(); outputMembers(connection, members, catalog, cube, rows); } else { // At this point we get ALL of the members associated with // the Hierarchy (rather than getting them one at a time). // The value returned is not used at this point but they are // now cached in the SchemaReader. for (Level level : hierarchy.getLevels()) { outputMembers( connection, level.getMembers(), catalog, cube, rows); } } } /** * Returns whether a value contains all of the bits in a mask. */ private static boolean mask(int value, int mask) { return (value & mask) == mask; } /** * Adds a member to a result list and, depending upon the * treeOp parameter, other relatives of the member. This * method recursively invokes itself to walk up, down, or across the * hierarchy. */ private void populateMember( OlapConnection connection, Catalog catalog, Cube cube, Member member, int treeOp, List rows) throws SQLException { // Visit node itself. if (mask(treeOp, TreeOp.SELF.xmlaOrdinal())) { outputMember(connection, member, catalog, cube, rows); } // Visit node's siblings (not including itself). if (mask(treeOp, TreeOp.SIBLINGS.xmlaOrdinal())) { final List siblings; final Member parent = member.getParentMember(); if (parent == null) { siblings = member.getHierarchy().getRootMembers(); } else { siblings = Olap4jUtil.cast(parent.getChildMembers()); } for (Member sibling : siblings) { if (sibling.equals(member)) { continue; } populateMember( connection, catalog, cube, sibling, TreeOp.SELF.xmlaOrdinal(), rows); } } // Visit node's descendants or its immediate children, but not both. if (mask(treeOp, TreeOp.DESCENDANTS.xmlaOrdinal())) { for (Member child : member.getChildMembers()) { populateMember( connection, catalog, cube, child, TreeOp.SELF.xmlaOrdinal() | TreeOp.DESCENDANTS.xmlaOrdinal(), rows); } } else if (mask( treeOp, TreeOp.CHILDREN.xmlaOrdinal())) { for (Member child : member.getChildMembers()) { populateMember( connection, catalog, cube, child, TreeOp.SELF.xmlaOrdinal(), rows); } } // Visit node's ancestors or its immediate parent, but not both. if (mask(treeOp, TreeOp.ANCESTORS.xmlaOrdinal())) { final Member parent = member.getParentMember(); if (parent != null) { populateMember( connection, catalog, cube, parent, TreeOp.SELF.xmlaOrdinal() | TreeOp.ANCESTORS.xmlaOrdinal(), rows); } } else if (mask(treeOp, TreeOp.PARENT.xmlaOrdinal())) { final Member parent = member.getParentMember(); if (parent != null) { populateMember( connection, catalog, cube, parent, TreeOp.SELF.xmlaOrdinal(), rows); } } } protected ArrayList pruneRestrictions(ArrayList list) { // If they've restricted TreeOp, we don't want to literally filter // the result on TreeOp (because it's not an output column) or // on MemberUniqueName (because TreeOp will have caused us to // generate other members than the one asked for). if (list.contains(TreeOp_)) { list.remove(TreeOp_); list.remove(MemberUniqueName); } return list; } private void outputMembers( OlapConnection connection, List members, final Catalog catalog, Cube cube, List rows) throws SQLException { for (Member member : members) { outputMember(connection, member, catalog, cube, rows); } } private void outputUniqueMemberName( final OlapConnection connection, final Catalog catalog, Cube cube, List rows) throws SQLException { final Object unameRestrictions = restrictions.get(MemberUniqueName.name); List list; if (unameRestrictions instanceof String) { list = Collections.singletonList((String) unameRestrictions); } else { list = (List) unameRestrictions; } for (String memberUniqueName : list) { final IdentifierNode identifierNode = IdentifierNode.parseIdentifier(memberUniqueName); Member member = cube.lookupMember(identifierNode.getSegmentList()); if (member == null) { return; } if (isRestricted(TreeOp_)) { int treeOp = getRestrictionValueAsInt(TreeOp_); if (treeOp == -1) { return; } populateMember( connection, catalog, cube, member, treeOp, rows); } else { outputMember(connection, member, catalog, cube, rows); } } } private void outputMember( OlapConnection connection, Member member, final Catalog catalog, Cube cube, List rows) throws SQLException { if (!memberNameCond.apply(member)) { return; } if (!memberTypeCond.apply(member)) { return; } getExtra(connection).checkMemberOrdinal(member); // Check whether the member is visible, otherwise do not dump. Boolean visible = (Boolean) member.getPropertyValue( Property.StandardMemberProperty.$visible); if (visible == null) { visible = true; } if (!visible && !XmlaUtil.shouldEmitInvisibleMembers(request)) { return; } final Level level = member.getLevel(); final Hierarchy hierarchy = level.getHierarchy(); final Dimension dimension = hierarchy.getDimension(); int adjustedLevelDepth = level.getDepth(); Row row = new Row(); row.set(CatalogName.name, catalog.getName()); row.set(SchemaName.name, cube.getSchema().getName()); row.set(CubeName.name, cube.getName()); row.set(DimensionUniqueName.name, dimension.getUniqueName()); row.set(HierarchyUniqueName.name, hierarchy.getUniqueName()); row.set(LevelUniqueName.name, level.getUniqueName()); row.set(LevelNumber.name, adjustedLevelDepth); row.set(MemberOrdinal.name, member.getOrdinal()); row.set(MemberName.name, member.getName()); row.set(MemberUniqueName.name, member.getUniqueName()); row.set(MemberType.name, member.getMemberType().ordinal()); //row.set(MemberGuid.name, ""); row.set(MemberCaption.name, member.getCaption()); row.set( ChildrenCardinality.name, member.getPropertyValue( Property.StandardMemberProperty.CHILDREN_CARDINALITY)); row.set(ChildrenCardinality.name, 100); if (adjustedLevelDepth == 0) { row.set(ParentLevel.name, 0); } else { row.set(ParentLevel.name, adjustedLevelDepth - 1); final Member parentMember = member.getParentMember(); if (parentMember != null) { row.set( ParentUniqueName.name, parentMember.getUniqueName()); } } row.set(ParentCount.name, member.getParentMember() == null ? 0 : 1); row.set(Depth.name, member.getDepth()); addRow(row, rows); } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } static class MdschemaSetsRowset extends Rowset { private final Util.Functor1 catalogCond; private final Util.Functor1 schemaNameCond; private final Util.Functor1 cubeNameCond; private final Util.Functor1 setUnameCond; private static final String GLOBAL_SCOPE = "1"; MdschemaSetsRowset(XmlaRequest request, XmlaHandler handler) { super(MDSCHEMA_SETS, request, handler); catalogCond = makeCondition(CATALOG_NAME_GETTER, CatalogName); schemaNameCond = makeCondition(SCHEMA_NAME_GETTER, SchemaName); cubeNameCond = makeCondition(ELEMENT_NAME_GETTER, CubeName); setUnameCond = makeCondition(ELEMENT_UNAME_GETTER, SetName); } private static final Column CatalogName = new Column( "CATALOG_NAME", Type.String, null, true, true, null); private static final Column SchemaName = new Column( "SCHEMA_NAME", Type.String, null, true, true, null); private static final Column CubeName = new Column( "CUBE_NAME", Type.String, null, true, false, null); private static final Column SetName = new Column( "SET_NAME", Type.String, null, true, false, null); private static final Column SetCaption = new Column( "SET_CAPTION", Type.String, null, true, true, null); private static final Column Scope = new Column( "SCOPE", Type.Integer, null, true, false, null); private static final Column Description = new Column( "DESCRIPTION", Type.String, null, false, true, "A human-readable description of the measure."); public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException, OlapException { for (Catalog catalog : catIter(connection, catNameCond(), catalogCond)) { processCatalog(connection, catalog, rows); } } private void processCatalog( OlapConnection connection, Catalog catalog, List rows) throws OlapException { for (Schema schema : filter(catalog.getSchemas(), schemaNameCond)) { for (Cube cube : filter(sortedCubes(schema), cubeNameCond)) { populateNamedSets(cube, catalog, rows); } } } private void populateNamedSets( Cube cube, Catalog catalog, List rows) { for (NamedSet namedSet : filter(cube.getSets(), setUnameCond)) { Row row = new Row(); row.set(CatalogName.name, catalog.getName()); row.set(SchemaName.name, cube.getSchema().getName()); row.set(CubeName.name, cube.getName()); row.set(SetName.name, namedSet.getUniqueName()); row.set(Scope.name, GLOBAL_SCOPE); row.set(Description.name, namedSet.getDescription()); addRow(row, rows); } } } static class MdschemaPropertiesRowset extends Rowset { private final Util.Functor1 catalogCond; private final Util.Functor1 schemaNameCond; private final Util.Functor1 cubeNameCond; private final Util.Functor1 dimensionUnameCond; private final Util.Functor1 hierarchyUnameCond; private final Util.Functor1 propertyNameCond; MdschemaPropertiesRowset(XmlaRequest request, XmlaHandler handler) { super(MDSCHEMA_PROPERTIES, request, handler); catalogCond = makeCondition(CATALOG_NAME_GETTER, CatalogName); schemaNameCond = makeCondition(SCHEMA_NAME_GETTER, SchemaName); cubeNameCond = makeCondition(ELEMENT_NAME_GETTER, CubeName); dimensionUnameCond = makeCondition(ELEMENT_UNAME_GETTER, DimensionUniqueName); hierarchyUnameCond = makeCondition(ELEMENT_UNAME_GETTER, HierarchyUniqueName); propertyNameCond = makeCondition(ELEMENT_NAME_GETTER, PropertyName); } private static final Column CatalogName = new Column( "CATALOG_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the database."); private static final Column SchemaName = new Column( "SCHEMA_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the schema to which this property belongs."); private static final Column CubeName = new Column( "CUBE_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The name of the cube."); private static final Column DimensionUniqueName = new Column( "DIMENSION_UNIQUE_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The unique name of the dimension."); private static final Column HierarchyUniqueName = new Column( "HIERARCHY_UNIQUE_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The unique name of the hierarchy."); private static final Column LevelUniqueName = new Column( "LEVEL_UNIQUE_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The unique name of the level to which this property belongs."); // According to MS this should not be nullable private static final Column MemberUniqueName = new Column( "MEMBER_UNIQUE_NAME", Type.String, null, Column.RESTRICTION, Column.OPTIONAL, "The unique name of the member to which the property belongs."); private static final Column PropertyName = new Column( "PROPERTY_NAME", Type.String, null, Column.RESTRICTION, Column.REQUIRED, "Name of the property."); private static final Column PropertyType = new Column( "PROPERTY_TYPE", Type.Short, null, Column.RESTRICTION, Column.REQUIRED, "A bitmap that specifies the type of the property"); private static final Column PropertyCaption = new Column( "PROPERTY_CAPTION", Type.String, null, Column.NOT_RESTRICTION, Column.REQUIRED, "A label or caption associated with the property, used " + "primarily for display purposes."); private static final Column DataType = new Column( "DATA_TYPE", Type.UnsignedShort, null, Column.NOT_RESTRICTION, Column.REQUIRED, "Data type of the property."); private static final Column PropertyContentType = new Column( "PROPERTY_CONTENT_TYPE", Type.Short, null, Column.RESTRICTION, Column.OPTIONAL, "The type of the property."); private static final Column Description = new Column( "DESCRIPTION", Type.String, null, Column.NOT_RESTRICTION, Column.OPTIONAL, "A human-readable description of the measure."); protected boolean needConnection() { return false; } public void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException, SQLException { // Default PROPERTY_TYPE is MDPROP_MEMBER. @SuppressWarnings({"unchecked"}) final List list = (List) restrictions.get(PropertyType.name); Set typeFlags; if (list == null) { typeFlags = Olap4jUtil.enumSetOf( Property.TypeFlag.MEMBER); } else { typeFlags = Property.TypeFlag.getDictionary().forMask( Integer.valueOf(list.get(0))); } for (Property.TypeFlag typeFlag : typeFlags) { switch (typeFlag) { case MEMBER: populateMember(rows); break; case CELL: populateCell(rows); break; case SYSTEM: case BLOB: default: break; } } } private void populateCell(List rows) { for (Property.StandardCellProperty property : Property.StandardCellProperty.values()) { Row row = new Row(); row.set( PropertyType.name, Property.TypeFlag.getDictionary() .toMask( property.getType())); row.set(PropertyName.name, property.name()); row.set(PropertyCaption.name, property.getCaption()); row.set(DataType.name, property.getDatatype().xmlaOrdinal()); addRow(row, rows); } } private void populateMember(List rows) throws SQLException { OlapConnection connection = handler.getConnection( request, Collections.emptyMap()); for (Catalog catalog : catIter(connection, catNameCond(), catalogCond)) { populateCatalog(catalog, rows); } } protected void populateCatalog( Catalog catalog, List rows) throws XmlaException, SQLException { for (Schema schema : filter(catalog.getSchemas(), schemaNameCond)) { for (Cube cube : filteredCubes(schema, cubeNameCond)) { populateCube(catalog, cube, rows); } } } protected void populateCube( Catalog catalog, Cube cube, List rows) throws XmlaException, SQLException { if (cube instanceof SharedDimensionHolderCube) { return; } if (isRestricted(LevelUniqueName)) { // Note: If the LEVEL_UNIQUE_NAME has been specified, then // the dimension and hierarchy are specified implicitly. String levelUniqueName = getRestrictionValueAsString(LevelUniqueName); if (levelUniqueName == null) { // The query specified two or more unique names // which means that nothing will match. return; } Level level = lookupLevel(cube, levelUniqueName); if (level == null) { return; } populateLevel( catalog, cube, level, rows); } else { for (Dimension dimension : filter(cube.getDimensions(), dimensionUnameCond)) { populateDimension( catalog, cube, dimension, rows); } } } private void populateDimension( Catalog catalog, Cube cube, Dimension dimension, List rows) throws SQLException { for (Hierarchy hierarchy : filter(dimension.getHierarchies(), hierarchyUnameCond)) { populateHierarchy( catalog, cube, hierarchy, rows); } } private void populateHierarchy( Catalog catalog, Cube cube, Hierarchy hierarchy, List rows) throws SQLException { for (Level level : hierarchy.getLevels()) { populateLevel(catalog, cube, level, rows); } } private void populateLevel( Catalog catalog, Cube cube, Level level, List rows) throws SQLException { final XmlaHandler.XmlaExtra extra = getExtra(catalog.getMetaData().getConnection()); for (Property property : filter(extra.getLevelProperties(level), propertyNameCond)) { if (extra.isPropertyInternal(property)) { continue; } outputProperty( property, catalog, cube, level, rows); } } private void outputProperty( Property property, Catalog catalog, Cube cube, Level level, List rows) { Hierarchy hierarchy = level.getHierarchy(); Dimension dimension = hierarchy.getDimension(); String propertyName = property.getName(); Row row = new Row(); row.set(CatalogName.name, catalog.getName()); row.set(SchemaName.name, cube.getSchema().getName()); row.set(CubeName.name, cube.getName()); row.set(DimensionUniqueName.name, dimension.getUniqueName()); row.set(HierarchyUniqueName.name, hierarchy.getUniqueName()); row.set(LevelUniqueName.name, level.getUniqueName()); //TODO: what is the correct value here //row.set(MemberUniqueName.name, ""); row.set(PropertyName.name, propertyName); // Only member properties now row.set( PropertyType.name, Property.TypeFlag.MEMBER.xmlaOrdinal()); row.set( PropertyContentType.name, Property.ContentType.REGULAR.xmlaOrdinal()); row.set(PropertyCaption.name, property.getCaption()); XmlaConstants.DBType dbType = getDBTypeFromProperty(property); row.set(DataType.name, dbType.xmlaOrdinal()); String desc = cube.getName() + " Cube - " + getHierarchyName(hierarchy) + " Hierarchy - " + level.getName() + " Level - " + property.getName() + " Property"; row.set(Description.name, desc); addRow(row, rows); } protected void setProperty( PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Content: break; default: super.setProperty(propertyDef, value); } } } public static final Util.Functor1 CATALOG_NAME_GETTER = new Util.Functor1() { public String apply(Catalog catalog) { return catalog.getName(); } }; public static final Util.Functor1 SCHEMA_NAME_GETTER = new Util.Functor1() { public String apply(Schema schema) { return schema.getName(); } }; public static final Util.Functor1 ELEMENT_NAME_GETTER = new Util.Functor1() { public String apply(MetadataElement element) { return element.getName(); } }; public static final Util.Functor1 ELEMENT_UNAME_GETTER = new Util.Functor1() { public String apply(MetadataElement element) { return element.getUniqueName(); } }; public static final Util.Functor1 MEMBER_TYPE_GETTER = new Util.Functor1() { public Member.Type apply(Member member) { return member.getMemberType(); } }; public static final Util.Functor1 PROPDEF_NAME_GETTER = new Util.Functor1() { public String apply(PropertyDefinition property) { return property.name(); } }; static void serialize(StringBuilder buf, Collection strings) { int k = 0; for (String name : Util.sort(strings)) { if (k++ > 0) { buf.append(','); } buf.append(name); } } private static Level lookupLevel(Cube cube, String levelUniqueName) { for (Dimension dimension : cube.getDimensions()) { for (Hierarchy hierarchy : dimension.getHierarchies()) { for (Level level : hierarchy.getLevels()) { if (level.getUniqueName().equals(levelUniqueName)) { return level; } } } } return null; } static Iterable sortedCubes(Schema schema) throws OlapException { return Util.sort( schema.getCubes(), new Comparator() { public int compare(Cube o1, Cube o2) { return o1.getName().compareTo(o2.getName()); } } ); } static Iterable filteredCubes( final Schema schema, Util.Functor1 cubeNameCond) throws OlapException { final Iterable iterable = filter(sortedCubes(schema), cubeNameCond); if (!cubeNameCond.apply(new SharedDimensionHolderCube(schema))) { return iterable; } return Composite.of( Collections.singletonList( new SharedDimensionHolderCube(schema)), iterable); } private static String getHierarchyName(Hierarchy hierarchy) { String hierarchyName = hierarchy.getName(); if (MondrianProperties.instance().SsasCompatibleNaming.get() && !hierarchyName.equals(hierarchy.getDimension().getName())) { hierarchyName = hierarchy.getDimension().getName() + "." + hierarchyName; } return hierarchyName; } private static XmlaRequest wrapRequest( XmlaRequest request, Map map) { final Map restrictionsMap = new HashMap(request.getRestrictions()); for (Map.Entry entry : map.entrySet()) { restrictionsMap.put( entry.getKey().name, Collections.singletonList(entry.getValue())); } return new DelegatingXmlaRequest(request) { @Override public Map getRestrictions() { return restrictionsMap; } }; } /** * Returns an iterator over the catalogs in a connection, setting the * connection's catalog to each successful catalog in turn. * * @param connection Connection * @param conds Zero or more conditions to be applied to catalogs * @return Iterator over catalogs */ private static Iterable catIter( final OlapConnection connection, final Util.Functor1... conds) { return new Iterable() { public Iterator iterator() { try { return new Iterator() { final Iterator catalogIter = Util.filter( connection.getOlapCatalogs(), conds).iterator(); public boolean hasNext() { return catalogIter.hasNext(); } public Catalog next() { Catalog catalog = catalogIter.next(); try { connection.setCatalog(catalog.getName()); } catch (SQLException e) { throw new RuntimeException(e); } return catalog; } public void remove() { throw new UnsupportedOperationException(); } }; } catch (OlapException e) { throw new RuntimeException( "Failed to obtain a list of catalogs form the connection object.", e); } } }; } private static class DelegatingXmlaRequest implements XmlaRequest { protected final XmlaRequest request; public DelegatingXmlaRequest(XmlaRequest request) { this.request = request; } public XmlaConstants.Method getMethod() { return request.getMethod(); } public Map getProperties() { return request.getProperties(); } public Map getRestrictions() { return request.getRestrictions(); } public String getStatement() { return request.getStatement(); } public String getRoleName() { return request.getRoleName(); } public String getRequestType() { return request.getRequestType(); } public boolean isDrillThrough() { return request.isDrillThrough(); } public String getUsername() { return request.getUsername(); } public String getPassword() { return request.getPassword(); } public String getSessionId() { return request.getSessionId(); } } /** * Dummy implementation of {@link Cube} that holds all shared dimensions * in a given schema. Less error-prone than requiring all generator code * to cope with a null Cube. */ private static class SharedDimensionHolderCube implements Cube { private final Schema schema; public SharedDimensionHolderCube(Schema schema) { this.schema = schema; } public Schema getSchema() { return schema; } public NamedList getDimensions() { try { return schema.getSharedDimensions(); } catch (OlapException e) { throw new RuntimeException(e); } } public NamedList getHierarchies() { final NamedList hierarchyList = new ArrayNamedListImpl() { protected String getName(Hierarchy hierarchy) { return hierarchy.getName(); } }; for (Dimension dimension : getDimensions()) { hierarchyList.addAll(dimension.getHierarchies()); } return hierarchyList; } public List getMeasures() { return Collections.emptyList(); } public NamedList getSets() { throw new UnsupportedOperationException(); } public Collection getSupportedLocales() { throw new UnsupportedOperationException(); } public Member lookupMember(List identifierSegments) throws org.olap4j.OlapException { throw new UnsupportedOperationException(); } public List lookupMembers( Set treeOps, List identifierSegments) throws org.olap4j.OlapException { throw new UnsupportedOperationException(); } public boolean isDrillThroughEnabled() { return false; } public String getName() { return ""; } public String getUniqueName() { return ""; } public String getCaption() { return ""; } public String getDescription() { return ""; } public boolean isVisible() { return false; } } } // End RowsetDefinition.java mondrian-3.4.1/src/main/mondrian/xmla/XmlaServlet.java0000644000175000017500000004230611735330606022627 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.xmla; import org.apache.log4j.Logger; import org.w3c.dom.Element; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.*; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.*; /** * Base XML/A servlet. * * @author Gang Chen * @since December, 2005 */ public abstract class XmlaServlet extends HttpServlet implements XmlaConstants { protected static final Logger LOGGER = Logger.getLogger(XmlaServlet.class); public static final String PARAM_DATASOURCES_CONFIG = "DataSourcesConfig"; public static final String PARAM_OPTIONAL_DATASOURCE_CONFIG = "OptionalDataSourceConfig"; public static final String PARAM_CHAR_ENCODING = "CharacterEncoding"; public static final String PARAM_CALLBACKS = "Callbacks"; protected XmlaHandler xmlaHandler = null; protected String charEncoding = null; private final List callbackList = new ArrayList(); private XmlaHandler.ConnectionFactory connectionFactory; public enum Phase { VALIDATE_HTTP_HEAD, INITIAL_PARSE, CALLBACK_PRE_ACTION, PROCESS_HEADER, PROCESS_BODY, CALLBACK_POST_ACTION, SEND_RESPONSE, SEND_ERROR } /** * Returns true if paramName's value is not null and 'true'. */ public static boolean getBooleanInitParameter( ServletConfig servletConfig, String paramName) { String paramValue = servletConfig.getInitParameter(paramName); return paramValue != null && Boolean.valueOf(paramValue); } public static boolean getParameter( HttpServletRequest req, String paramName) { String paramValue = req.getParameter(paramName); return paramValue != null && Boolean.valueOf(paramValue); } public XmlaServlet() { } /** * Initializes servlet and XML/A handler. * */ public void init(ServletConfig servletConfig) throws ServletException { super.init(servletConfig); // init: charEncoding initCharEncodingHandler(servletConfig); // init: callbacks initCallbacks(servletConfig); this.connectionFactory = createConnectionFactory(servletConfig); } protected abstract XmlaHandler.ConnectionFactory createConnectionFactory( ServletConfig servletConfig) throws ServletException; /** * Gets (creating if needed) the XmlaHandler. * * @return XMLA handler */ protected XmlaHandler getXmlaHandler() { if (this.xmlaHandler == null) { this.xmlaHandler = new XmlaHandler( connectionFactory, "cxmla"); } return this.xmlaHandler; } /** * Registers a callback. */ protected final void addCallback(XmlaRequestCallback callback) { callbackList.add(callback); } /** * Returns the list of callbacks. The list is immutable. * * @return list of callbacks */ protected final List getCallbacks() { return Collections.unmodifiableList(callbackList); } /** * Main entry for HTTP post method * */ protected void doPost( HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // Request Soap Header and Body // header [0] and body [1] Element[] requestSoapParts = new Element[2]; // Response Soap Header and Body // An array allows response parts to be passed into callback // and possible modifications returned. // response header in [0] and response body in [1] byte[][] responseSoapParts = new byte[2][]; Phase phase = Phase.VALIDATE_HTTP_HEAD; Enumeration.ResponseMimeType mimeType = Enumeration.ResponseMimeType.SOAP; try { if (charEncoding != null) { try { request.setCharacterEncoding(charEncoding); response.setCharacterEncoding(charEncoding); } catch (UnsupportedEncodingException uee) { charEncoding = null; LOGGER.warn( "Unsupported character encoding '" + charEncoding + "': Use default character encoding from HTTP client " + "for now"); } } response.setContentType(mimeType.getMimeType()); Map context = new HashMap(); try { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Invoking validate http header callbacks"); } for (XmlaRequestCallback callback : getCallbacks()) { if (!callback.processHttpHeader( request, response, context)) { return; } } } catch (XmlaException xex) { LOGGER.error( "Errors when invoking callbacks validateHttpHeader", xex); handleFault(response, responseSoapParts, phase, xex); phase = Phase.SEND_ERROR; marshallSoapMessage(response, responseSoapParts, mimeType); return; } catch (Exception ex) { LOGGER.error( "Errors when invoking callbacks validateHttpHeader", ex); handleFault( response, responseSoapParts, phase, new XmlaException( SERVER_FAULT_FC, CHH_CODE, CHH_FAULT_FS, ex)); phase = Phase.SEND_ERROR; marshallSoapMessage(response, responseSoapParts, mimeType); return; } phase = Phase.INITIAL_PARSE; try { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Unmarshalling SOAP message"); } // check request's content type String contentType = request.getContentType(); if (contentType == null || !contentType.contains("text/xml")) { throw new IllegalArgumentException( "Only accepts content type 'text/xml', not '" + contentType + "'"); } // are they asking for a JSON response? String accept = request.getHeader("Accept"); if (accept != null) { mimeType = XmlaUtil.chooseResponseMimeType(accept); if (mimeType == null) { throw new IllegalArgumentException( "Accept header '" + accept + "' is not a supported" + " response content type. Allowed values:" + " text/xml, application/xml, application/json."); } if (mimeType != Enumeration.ResponseMimeType.SOAP) { response.setContentType(mimeType.getMimeType()); } } context.put(CONTEXT_MIME_TYPE, mimeType); unmarshallSoapMessage(request, requestSoapParts); } catch (XmlaException xex) { LOGGER.error("Unable to unmarshall SOAP message", xex); handleFault(response, responseSoapParts, phase, xex); phase = Phase.SEND_ERROR; marshallSoapMessage(response, responseSoapParts, mimeType); return; } phase = Phase.PROCESS_HEADER; try { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Handling XML/A message header"); } // process application specified SOAP header here handleSoapHeader( response, requestSoapParts, responseSoapParts, context); } catch (XmlaException xex) { LOGGER.error("Errors when handling XML/A message", xex); handleFault(response, responseSoapParts, phase, xex); phase = Phase.SEND_ERROR; marshallSoapMessage(response, responseSoapParts, mimeType); return; } phase = Phase.CALLBACK_PRE_ACTION; try { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Invoking callbacks preAction"); } for (XmlaRequestCallback callback : getCallbacks()) { callback.preAction(request, requestSoapParts, context); } } catch (XmlaException xex) { LOGGER.error("Errors when invoking callbacks preaction", xex); handleFault(response, responseSoapParts, phase, xex); phase = Phase.SEND_ERROR; marshallSoapMessage(response, responseSoapParts, mimeType); return; } catch (Exception ex) { LOGGER.error("Errors when invoking callbacks preaction", ex); handleFault( response, responseSoapParts, phase, new XmlaException( SERVER_FAULT_FC, CPREA_CODE, CPREA_FAULT_FS, ex)); phase = Phase.SEND_ERROR; marshallSoapMessage(response, responseSoapParts, mimeType); return; } phase = Phase.PROCESS_BODY; try { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Handling XML/A message body"); } // process XML/A request handleSoapBody( response, requestSoapParts, responseSoapParts, context); } catch (XmlaException xex) { LOGGER.error("Errors when handling XML/A message", xex); handleFault(response, responseSoapParts, phase, xex); phase = Phase.SEND_ERROR; marshallSoapMessage(response, responseSoapParts, mimeType); return; } mimeType = (Enumeration.ResponseMimeType) context.get(CONTEXT_MIME_TYPE); phase = Phase.CALLBACK_POST_ACTION; try { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Invoking callbacks postAction"); } for (XmlaRequestCallback callback : getCallbacks()) { callback.postAction( request, response, responseSoapParts, context); } } catch (XmlaException xex) { LOGGER.error("Errors when invoking callbacks postaction", xex); handleFault(response, responseSoapParts, phase, xex); phase = Phase.SEND_ERROR; marshallSoapMessage(response, responseSoapParts, mimeType); return; } catch (Exception ex) { LOGGER.error("Errors when invoking callbacks postaction", ex); handleFault( response, responseSoapParts, phase, new XmlaException( SERVER_FAULT_FC, CPOSTA_CODE, CPOSTA_FAULT_FS, ex)); phase = Phase.SEND_ERROR; marshallSoapMessage(response, responseSoapParts, mimeType); return; } phase = Phase.SEND_RESPONSE; try { response.setStatus(HttpServletResponse.SC_OK); marshallSoapMessage(response, responseSoapParts, mimeType); } catch (XmlaException xex) { LOGGER.error("Errors when handling XML/A message", xex); handleFault(response, responseSoapParts, phase, xex); phase = Phase.SEND_ERROR; marshallSoapMessage(response, responseSoapParts, mimeType); } } catch (Throwable t) { LOGGER.error("Unknown Error when handling XML/A message", t); handleFault(response, responseSoapParts, phase, t); marshallSoapMessage(response, responseSoapParts, mimeType); } } /** * Implement to provide application specified SOAP unmarshalling algorithm. */ protected abstract void unmarshallSoapMessage( HttpServletRequest request, Element[] requestSoapParts) throws XmlaException; /** * Implement to handle application specified SOAP header. */ protected abstract void handleSoapHeader( HttpServletResponse response, Element[] requestSoapParts, byte[][] responseSoapParts, Map context) throws XmlaException; /** * Implement to handle XML/A request. */ protected abstract void handleSoapBody( HttpServletResponse response, Element[] requestSoapParts, byte[][] responseSoapParts, Map context) throws XmlaException; /** * Implement to provide application specified SOAP marshalling algorithm. */ protected abstract void marshallSoapMessage( HttpServletResponse response, byte[][] responseSoapParts, Enumeration.ResponseMimeType responseMimeType) throws XmlaException; /** * Implement to application specified handler of SOAP fualt. */ protected abstract void handleFault( HttpServletResponse response, byte[][] responseSoapParts, Phase phase, Throwable t); /** * Initialize character encoding */ protected void initCharEncodingHandler(ServletConfig servletConfig) { String paramValue = servletConfig.getInitParameter(PARAM_CHAR_ENCODING); if (paramValue != null) { this.charEncoding = paramValue; } else { this.charEncoding = null; LOGGER.warn("Use default character encoding from HTTP client"); } } /** * Registers callbacks configured in web.xml. */ protected void initCallbacks(ServletConfig servletConfig) { String callbacksValue = servletConfig.getInitParameter(PARAM_CALLBACKS); if (callbacksValue != null) { String[] classNames = callbacksValue.split(";"); int count = 0; nextCallback: for (String className1 : classNames) { String className = className1.trim(); try { Class cls = Class.forName(className); if (XmlaRequestCallback.class.isAssignableFrom(cls)) { XmlaRequestCallback callback = (XmlaRequestCallback) cls.newInstance(); try { callback.init(servletConfig); } catch (Exception e) { LOGGER.warn( "Failed to initialize callback '" + className + "'", e); continue nextCallback; } addCallback(callback); count++; if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Register callback '" + className + "'"); } } else { LOGGER.warn( "'" + className + "' is not an implementation of '" + XmlaRequestCallback.class + "'"); } } catch (ClassNotFoundException cnfe) { LOGGER.warn( "Callback class '" + className + "' not found", cnfe); } catch (InstantiationException ie) { LOGGER.warn( "Can't instantiate class '" + className + "'", ie); } catch (IllegalAccessException iae) { LOGGER.warn( "Can't instantiate class '" + className + "'", iae); } } LOGGER.debug( "Registered " + count + " callback" + (count > 1 ? "s" : "")); } } } // End XmlaServlet.java mondrian-3.4.1/src/main/mondrian/xmla/XmlaConstants.java0000644000175000017500000003302411735330606023154 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.xmla; /** * Constants for XML/A. * * @author Gang Chen */ public interface XmlaConstants { /* SOAP 1.1 */ public static final String NS_SOAP_ENV_1_1 = "http://schemas.xmlsoap.org/soap/envelope/"; public static final String NS_SOAP_ENC_1_1 = "http://schemas.xmlsoap.org/soap/encoding/"; /* SOAP 1.2 - currently not supported */ public static final String NS_SOAP_ENV_1_2 = "http://www.w3.org/2003/05/soap-envelope"; public static final String NS_SOAP_ENC_1_2 = "http://www.w3.org/2003/05/soap-encoding"; /* Namespaces for XML */ public static final String NS_XSD = "http://www.w3.org/2001/XMLSchema"; public static final String NS_XSI = "http://www.w3.org/2001/XMLSchema-instance"; /* Namespaces for XML/A */ public static final String NS_XMLA = "urn:schemas-microsoft-com:xml-analysis"; public static final String NS_XMLA_MDDATASET = "urn:schemas-microsoft-com:xml-analysis:mddataset"; public static final String NS_XMLA_EMPTY = "urn:schemas-microsoft-com:xml-analysis:empty"; public static final String NS_XMLA_ROWSET = "urn:schemas-microsoft-com:xml-analysis:rowset"; public static final String NS_SQL = "urn:schemas-microsoft-com:xml-sql"; public static final String NS_XMLA_EX = "urn:schemas-microsoft-com:xml-analysis:exception"; public static final String NS_SOAP_SECEXT = "http://schemas.xmlsoap.org/ws/2002/04/secext"; public static final String SOAP_PREFIX = "SOAP-ENV"; /* * Soap Header mustUnderstand attribute name. */ public static final String SOAP_MUST_UNDERSTAND_ATTR = "mustUnderstand"; /* * Soap XMLA Header elements and attribute names. */ public static final String XMLA_BEGIN_SESSION = "BeginSession"; public static final String XMLA_SESSION = "Session"; public static final String XMLA_END_SESSION = "EndSession"; public static final String XMLA_SESSION_ID = "SessionId"; public static final String XMLA_SECURITY = "Security"; /* * Names of context keys known by both callbacks and Mondrian code. */ // context key for role name storage public static final String CONTEXT_ROLE_NAME = "role_name"; // context key for language (SOAP or JSON) public static final String CONTEXT_MIME_TYPE = "language"; // context key for session id storage public static final String CONTEXT_XMLA_SESSION_ID = "session_id"; // Username and password tokens public static final String CONTEXT_XMLA_USERNAME = "username"; public static final String CONTEXT_XMLA_PASSWORD = "password"; // context key for session state storage public static final String CONTEXT_XMLA_SESSION_STATE = "SessionState"; public static final String CONTEXT_XMLA_SESSION_STATE_BEGIN = "SessionStateBegin"; public static final String CONTEXT_XMLA_SESSION_STATE_WITHIN = "SessionStateWithin"; public static final String CONTEXT_XMLA_SESSION_STATE_END = "SessionStateEnd"; /************************************************************************* * * The following are XMLA exception fault codes used as faultcode entries * in the SOAP Fault element. * * If Mondrian Exceptions actually used the "id" attributes found in the * MondrianResource.xml file, then those would be used as the SOAP Fault * detail error code values, but, alas they do not show up as part of * the generated Exception Java code so, here we simply duplicate * the fault code entry. * * Currently, SOAP 1.2 errors are not supported. * *************************************************************************/ /** * This is the namespace used to qualify the faultcode identifier. */ public static final String MONDRIAN_NAMESPACE = "http://mondrian.sourceforge.net"; public static final String FAULT_NS_PREFIX = "XA"; public static final String FAULT_ACTOR = "Mondrian"; // soap 1.1 default faultcodes public static final String VERSION_MISSMATCH_FAULT_FC = "VersionMismatch"; public static final String MUST_UNDERSTAND_FAULT_FC = "MustUnderstand"; public static final String CLIENT_FAULT_FC = "Client"; public static final String SERVER_FAULT_FC = "Server"; //XA:Mondrian.XML.88BA1202 public static final String FAULT_FC_PREFIX = "Mondrian"; public static final String FAULT_FS_PREFIX = "The Mondrian XML: "; ///////////////////////////////////////////////////////////////////////// // Unmarshall Soap Message : USM ///////////////////////////////////////////////////////////////////////// public static final String USM_REQUEST_STATE_CODE = "00USMA01"; public static final String USM_REQUEST_STATE_FAULT_FS = "Request input method invoked at illegal time"; public static final String USM_REQUEST_INPUT_CODE = "00USMA02"; public static final String USM_REQUEST_INPUT_FAULT_FS = "Request input Exception occurred"; public static final String USM_DOM_FACTORY_CODE = "00USMB01"; public static final String USM_DOM_FACTORY_FAULT_FS = "DocumentBuilder cannot be created which satisfies the configuration " + "requested"; public static final String USM_DOM_PARSE_IO_CODE = "00USMC01"; public static final String USM_DOM_PARSE_IO_FAULT_FS = "DOM parse IO errors occur"; public static final String USM_DOM_PARSE_CODE = "00USMC02"; public static final String USM_DOM_PARSE_FAULT_FS = "DOM parse errors occur"; // unknown error while unmarshalling soap message public static final String USM_UNKNOWN_CODE = "00USMU01"; public static final String USM_UNKNOWN_FAULT_FS = "Unknown error unmarshalling soap message"; ///////////////////////////////////////////////////////////////////////// // Callback http header : CHH ///////////////////////////////////////////////////////////////////////// public static final String CHH_CODE = "00CHHA01"; public static final String CHH_FAULT_FS = "Error in Callback processHttpHeader"; public static final String CHH_AUTHORIZATION_CODE = "00CHHA02"; public static final String CHH_AUTHORIZATION_FAULT_FS = "Error in Callback processHttpHeader Authorization"; ///////////////////////////////////////////////////////////////////////// // Callback Pre-Action : CPREA ///////////////////////////////////////////////////////////////////////// public static final String CPREA_CODE = "00CPREA01"; public static final String CPREA_FAULT_FS = "Error in Callback PreAction"; /* public static final String CPREA_AUTHORIZATION_CODE = "00CPREA02"; public static final String CPREA_AUTHORIZATION_FAULT_FS = "Error Callback PreAction Authorization"; */ ///////////////////////////////////////////////////////////////////////// // Handle Soap Header : HSH ///////////////////////////////////////////////////////////////////////// public static final String HSH_MUST_UNDERSTAND_CODE = "00HSHA01"; public static final String HSH_MUST_UNDERSTAND_FAULT_FS = "SOAP Header must understand element not recognized"; // This is used to signal XMLA clients supporting Soap header session ids // that the client's metadata may no longer be valid. public static final String HSH_BAD_SESSION_ID_CODE = "00HSHB01"; public static final String HSH_BAD_SESSION_ID_FAULT_FS = "Bad Session Id, re-start session"; // unknown error while handle soap header public static final String HSH_UNKNOWN_CODE = "00HSHU01"; public static final String HSH_UNKNOWN_FAULT_FS = "Unknown error handle soap header"; ///////////////////////////////////////////////////////////////////////// // Handle Soap Body : HSB ///////////////////////////////////////////////////////////////////////// public static final String HSB_BAD_SOAP_BODY_CODE = "00HSBA01"; public static final String HSB_BAD_SOAP_BODY_FAULT_FS = "SOAP Body not correctly formed"; public static final String HSB_PROCESS_CODE = "00HSBB01"; public static final String HSB_PROCESS_FAULT_FS = "XMLA SOAP Body processing error"; public static final String HSB_BAD_METHOD_CODE = "00HSBB02"; public static final String HSB_BAD_METHOD_FAULT_FS = "XMLA SOAP bad method"; public static final String HSB_BAD_METHOD_NS_CODE = "00HSBB03"; public static final String HSB_BAD_METHOD_NS_FAULT_FS = "XMLA SOAP bad method namespace"; public static final String HSB_BAD_REQUEST_TYPE_CODE = "00HSBB04"; public static final String HSB_BAD_REQUEST_TYPE_FAULT_FS = "XMLA SOAP bad Discover RequestType element"; public static final String HSB_BAD_RESTRICTIONS_CODE = "00HSBB05"; public static final String HSB_BAD_RESTRICTIONS_FAULT_FS = "XMLA SOAP bad Discover Restrictions element"; public static final String HSB_BAD_PROPERTIES_CODE = "00HSBB06"; public static final String HSB_BAD_PROPERTIES_FAULT_FS = "XMLA SOAP bad Discover or Execute Properties element"; public static final String HSB_BAD_COMMAND_CODE = "00HSBB07"; public static final String HSB_BAD_COMMAND_FAULT_FS = "XMLA SOAP bad Execute Command element"; public static final String HSB_BAD_RESTRICTION_LIST_CODE = "00HSBB08"; public static final String HSB_BAD_RESTRICTION_LIST_FAULT_FS = "XMLA SOAP too many Discover RestrictionList element"; public static final String HSB_BAD_PROPERTIES_LIST_CODE = "00HSBB09"; public static final String HSB_BAD_PROPERTIES_LIST_FAULT_FS = "XMLA SOAP bad Discover or Execute PropertyList element"; public static final String HSB_BAD_STATEMENT_CODE = "00HSBB10"; public static final String HSB_BAD_STATEMENT_FAULT_FS = "XMLA SOAP bad Execute Statement element"; public static final String HSB_BAD_NON_NULLABLE_COLUMN_CODE = "00HSBB16"; public static final String HSB_BAD_NON_NULLABLE_COLUMN_FAULT_FS = "XMLA SOAP non-nullable column"; public static final String HSB_CONNECTION_DATA_SOURCE_CODE = "00HSBC01"; public static final String HSB_CONNECTION_DATA_SOURCE_FAULT_FS = "XMLA connection datasource not found"; public static final String HSB_ACCESS_DENIED_CODE = "00HSBC02"; public static final String HSB_ACCESS_DENIED_FAULT_FS = "XMLA connection with role must be authenticated"; public static final String HSB_PARSE_QUERY_CODE = "00HSBD01"; public static final String HSB_PARSE_QUERY_FAULT_FS = "XMLA MDX parse failed"; public static final String HSB_EXECUTE_QUERY_CODE = "00HSBD02"; public static final String HSB_EXECUTE_QUERY_FAULT_FS = "XMLA MDX execute failed"; public static final String HSB_DISCOVER_FORMAT_CODE = "00HSBE01"; public static final String HSB_DISCOVER_FORMAT_FAULT_FS = "XMLA Discover format error"; public static final String HSB_DRILL_THROUGH_FORMAT_CODE = "00HSBE02"; public static final String HSB_DRILL_THROUGH_FORMAT_FAULT_FS = "XMLA Drill Through format error"; public static final String HSB_DISCOVER_UNPARSE_CODE = "00HSBE02"; public static final String HSB_DISCOVER_UNPARSE_FAULT_FS = "XMLA Discover unparse results error"; public static final String HSB_EXECUTE_UNPARSE_CODE = "00HSBE03"; public static final String HSB_EXECUTE_UNPARSE_FAULT_FS = "XMLA Execute unparse results error"; public static final String HSB_DRILL_THROUGH_NOT_ALLOWED_CODE = "00HSBF01"; public static final String HSB_DRILL_THROUGH_NOT_ALLOWED_FAULT_FS = "XMLA Drill Through not allowed"; public static final String HSB_DRILL_THROUGH_SQL_CODE = "00HSBF02"; public static final String HSB_DRILL_THROUGH_SQL_FAULT_FS = "XMLA Drill Through SQL error"; // unknown error while handle soap body public static final String HSB_UNKNOWN_CODE = "00HSBU01"; public static final String HSB_UNKNOWN_FAULT_FS = "Unknown error handle soap body"; ///////////////////////////////////////////////////////////////////////// // Callback Post-Action : CPOSTA ///////////////////////////////////////////////////////////////////////// public static final String CPOSTA_CODE = "00CPOSTA01"; public static final String CPOSTA_FAULT_FS = "Error in Callback PostAction"; ///////////////////////////////////////////////////////////////////////// // Marshall Soap Message : MSM ///////////////////////////////////////////////////////////////////////// // unknown error while marshalling soap message public static final String MSM_UNKNOWN_CODE = "00MSMU01"; public static final String MSM_UNKNOWN_FAULT_FS = "Unknown error marshalling soap message"; ///////////////////////////////////////////////////////////////////////// // Unknown error : UE ///////////////////////////////////////////////////////////////////////// public static final String UNKNOWN_ERROR_CODE = "00UE001"; // While this is actually "unknown", for users "internal" // is a better term public static final String UNKNOWN_ERROR_FAULT_FS = "Internal Error"; } // End XmlaConstants.java mondrian-3.4.1/src/main/mondrian/xmla/XmlaUtil.java0000644000175000017500000004245711735330606022127 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.xmla; import mondrian.olap.MondrianException; import mondrian.olap.Util; import mondrian.xmla.impl.DefaultXmlaResponse; import org.olap4j.OlapConnection; import org.olap4j.OlapException; import org.w3c.dom.*; import org.xml.sax.InputSource; import java.io.*; import java.nio.charset.Charset; import java.sql.SQLException; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import static org.olap4j.metadata.XmlaConstants.Format; import static org.olap4j.metadata.XmlaConstants.Method; /** * Utility methods for XML/A implementation. * * @author Gang Chen */ public class XmlaUtil implements XmlaConstants { /** * Invalid characters for XML element name. * *

    XML element name: * * Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] * | [#xE000-#xFFFD] | [#x10000-#x10FFFF] * S ::= (#x20 | #x9 | #xD | #xA)+ * NameChar ::= Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar * | Extender * Name ::= (Letter | '_' | ':') (NameChar)* * Names ::= Name (#x20 Name)* * Nmtoken ::= (NameChar)+ * Nmtokens ::= Nmtoken (#x20 Nmtoken)* * */ private static final String[] CHAR_TABLE = new String[256]; private static final Pattern LOWERCASE_PATTERN = Pattern.compile(".*[a-z].*"); static { initCharTable(" \t\r\n(){}[]+/*%!,?"); } private static void initCharTable(String charStr) { char[] chars = charStr.toCharArray(); for (char c : chars) { CHAR_TABLE[c] = encodeChar(c); } } private static String encodeChar(char c) { StringBuilder buf = new StringBuilder(); buf.append("_x"); String str = Integer.toHexString(c); for (int i = 4 - str.length(); i > 0; i--) { buf.append("0"); } return buf.append(str).append("_").toString(); } /** * Encodes an XML element name. * *

    This function is mainly for encode element names in result of Drill * Through execute, because its element names come from database, we cannot * make sure they are valid XML contents. * *

    Quoth the XML/A specification, version * 1.1: *

    * XML does not allow certain characters as element and attribute names. * XML for Analysis supports encoding as defined by SQL Server 2000 to * address this XML constraint. For column names that contain invalid XML * name characters (according to the XML 1.0 specification), the nonvalid * Unicode characters are encoded using the corresponding hexadecimal * values. These are escaped as _xHHHH_ where HHHH stands for * the four-digit hexadecimal UCS-2 code for the character in * most-significant bit first order. For example, the name "Order Details" * is encoded as Order_x0020_Details, where the space character is * replaced by the corresponding hexadecimal code. *
    * * @param name Name of XML element * @return encoded name */ private static String encodeElementName(String name) { StringBuilder buf = new StringBuilder(); char[] nameChars = name.toCharArray(); for (char ch : nameChars) { String encodedStr = (ch >= CHAR_TABLE.length ? null : CHAR_TABLE[ch]); if (encodedStr == null) { buf.append(ch); } else { buf.append(encodedStr); } } return buf.toString(); } public static void element2Text(Element elem, final StringWriter writer) throws XmlaException { try { TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); transformer.transform( new DOMSource(elem), new StreamResult(writer)); } catch (Exception e) { throw new XmlaException( CLIENT_FAULT_FC, USM_DOM_PARSE_CODE, USM_DOM_PARSE_FAULT_FS, e); } } public static Element text2Element(String text) throws XmlaException { return _2Element(new InputSource(new StringReader(text))); } public static Element stream2Element(InputStream stream) throws XmlaException { return _2Element(new InputSource(stream)); } private static Element _2Element(InputSource source) throws XmlaException { try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setIgnoringElementContentWhitespace(true); factory.setIgnoringComments(true); factory.setNamespaceAware(true); DocumentBuilder builder = factory.newDocumentBuilder(); Document doc = builder.parse(source); return doc.getDocumentElement(); } catch (Exception e) { throw new XmlaException( CLIENT_FAULT_FC, USM_DOM_PARSE_CODE, USM_DOM_PARSE_FAULT_FS, e); } } public static Element[] filterChildElements( Element parent, String ns, String lname) { /* way too noisy if (LOGGER.isDebugEnabled()) { StringBuilder buf = new StringBuilder(100); buf.append("XmlaUtil.filterChildElements: "); buf.append(" ns=\""); buf.append(ns); buf.append("\", lname=\""); buf.append(lname); buf.append("\""); LOGGER.debug(buf.toString()); } */ List elems = new ArrayList(); NodeList nlst = parent.getChildNodes(); for (int i = 0, nlen = nlst.getLength(); i < nlen; i++) { Node n = nlst.item(i); if (n instanceof Element) { Element e = (Element) n; if ((ns == null || ns.equals(e.getNamespaceURI())) && (lname == null || lname.equals(e.getLocalName()))) { elems.add(e); } } } return elems.toArray(new Element[elems.size()]); } public static String textInElement(Element elem) { StringBuilder buf = new StringBuilder(100); elem.normalize(); NodeList nlst = elem.getChildNodes(); for (int i = 0, nlen = nlst.getLength(); i < nlen ; i++) { Node n = nlst.item(i); if (n instanceof Text) { final String data = ((Text) n).getData(); buf.append(data); } } return buf.toString(); } /** * Finds root MondrianException in exception chain if exists, * otherwise the input throwable. * * @param throwable Exception * @return Root exception */ public static Throwable rootThrowable(Throwable throwable) { Throwable rootThrowable = throwable.getCause(); if (rootThrowable != null && rootThrowable instanceof MondrianException) { return rootThrowable(rootThrowable); } return throwable; } /** * Corrects for the differences between numeric strings arising because * JDBC drivers use different representations for numbers * ({@link Double} vs. {@link java.math.BigDecimal}) and * these have different toString() behavior. * *

    If it contains a decimal point, then * strip off trailing '0's. After stripping off * the '0's, if there is nothing right of the * decimal point, then strip off decimal point. * * @param numericStr Numeric string * @return Normalized string */ public static String normalizeNumericString(String numericStr) { int index = numericStr.indexOf('.'); if (index > 0) { // If it uses exponential notation, 1.0E4, then it could // have a trailing '0' that should not be stripped of, // e.g., 1.0E10. This would be rather bad. if (numericStr.indexOf('e') != -1) { return numericStr; } else if (numericStr.indexOf('E') != -1) { return numericStr; } boolean found = false; int p = numericStr.length(); char c = numericStr.charAt(p - 1); while (c == '0') { found = true; p--; c = numericStr.charAt(p - 1); } if (c == '.') { p--; } if (found) { return numericStr.substring(0, p); } } return numericStr; } /** * Returns a set of column headings and rows for a given metadata request. * *

    Leverages mondrian's implementation of the XML/A specification, and * is exposed here for use by mondrian's olap4j driver. * * @param connection Connection * @param methodName Metadata method name per XMLA (e.g. "MDSCHEMA_CUBES") * @param restrictionMap Restrictions * @return Set of rows and column headings */ public static MetadataRowset getMetadataRowset( final OlapConnection connection, String methodName, final Map restrictionMap) throws OlapException { RowsetDefinition rowsetDefinition = RowsetDefinition.valueOf(methodName); final XmlaHandler.ConnectionFactory connectionFactory = new XmlaHandler.ConnectionFactory() { public OlapConnection getConnection( String catalog, String schema, String roleName, Properties props) throws SQLException { return connection; } public Map getPreConfiguredDiscoverDatasourcesResponse() { // This method should not be used by the olap4j xmla // servlet. For the mondrian xmla servlet we don't provide // the "pre configured discover datasources" feature. return null; } }; final XmlaRequest request = new XmlaRequest() { public Method getMethod() { return Method.DISCOVER; } public Map getProperties() { return Collections.emptyMap(); } public Map getRestrictions() { return restrictionMap; } public String getStatement() { return null; } public String getRoleName() { return null; } public String getRequestType() { throw new UnsupportedOperationException(); } public boolean isDrillThrough() { throw new UnsupportedOperationException(); } public Format getFormat() { throw new UnsupportedOperationException(); } public String getUsername() { return null; } public String getPassword() { return null; } public String getSessionId() { return null; } }; final Rowset rowset = rowsetDefinition.getRowset( request, new XmlaHandler( connectionFactory, "xmla") { @Override public OlapConnection getConnection( XmlaRequest request, Map propMap) { return connection; } } ); List rowList = new ArrayList(); rowset.populate( new DefaultXmlaResponse( new ByteArrayOutputStream(), Charset.defaultCharset().name(), Enumeration.ResponseMimeType.SOAP), connection, rowList); MetadataRowset result = new MetadataRowset(); final List colDefs = new ArrayList(); for (RowsetDefinition.Column columnDefinition : rowsetDefinition.columnDefinitions) { if (columnDefinition.type == RowsetDefinition.Type.Rowset) { // olap4j does not support the extended columns, e.g. // Cube.Dimensions continue; } colDefs.add(columnDefinition); } for (Rowset.Row row : rowList) { Object[] values = new Object[colDefs.size()]; int k = -1; for (RowsetDefinition.Column colDef : colDefs) { Object o = row.get(colDef.name); if (o instanceof List) { o = toString((List) o); } else if (o instanceof String[]) { o = toString(Arrays.asList((String []) o)); } values[++k] = o; } result.rowList.add(Arrays.asList(values)); } for (RowsetDefinition.Column colDef : colDefs) { String columnName = colDef.name; if (LOWERCASE_PATTERN.matcher(columnName).matches()) { columnName = Util.camelToUpper(columnName); } // VALUE is a SQL reserved word if (columnName.equals("VALUE")) { columnName = "PROPERTY_VALUE"; } result.headerList.add(columnName); } return result; } private static String toString(List list) { StringBuilder buf = new StringBuilder(); int k = -1; for (T t : list) { if (++k > 0) { buf.append(", "); } buf.append(t); } return buf.toString(); } /** * Chooses the appropriate response mime type given an HTTP "Accept" header. * *

    The header can contain a list of mime types and optional qualities, * for example "text/html,application/xhtml+xml,application/xml;q=0.9" * * @param accept Accept header * @return Mime type, or null if none is acceptable */ public static Enumeration.ResponseMimeType chooseResponseMimeType( String accept) { for (String s : accept.split(",")) { s = s.trim(); final int semicolon = s.indexOf(";"); if (semicolon >= 0) { s = s.substring(0, semicolon); } Enumeration.ResponseMimeType mimeType = Enumeration.ResponseMimeType.MAP.get(s); if (mimeType != null) { return mimeType; } } return null; } /** * Returns whether an XMLA request should return invisible members. * *

    According to the XMLA spec, it should not. But we allow the client to * specify different behavior. In particular, the olap4j driver for XMLA * may need to access invisible members. * *

    Returns true if the EmitInvisibleMembers property is specified and * equal to "true". * * @param request XMLA request * @return Whether to return invisible members */ public static boolean shouldEmitInvisibleMembers(XmlaRequest request) { final String value = request.getProperties().get( PropertyDefinition.EmitInvisibleMembers.name()); return Boolean.parseBoolean(value); } /** * Result of a metadata query. */ public static class MetadataRowset { public final List headerList = new ArrayList(); public final List> rowList = new ArrayList>(); } /** * Wrapper which indicates that a restriction is to be treated as a * SQL-style wildcard match. */ public static class Wildcard { public final String pattern; public Wildcard(String pattern) { this.pattern = pattern; } } public static class ElementNameEncoder { private final Map map = new ConcurrentHashMap(); public static final ElementNameEncoder INSTANCE = new ElementNameEncoder(); public String encode(String name) { String encoded = map.get(name); if (encoded == null) { encoded = encodeElementName(name); map.put(name, encoded); } return encoded; } } } // End XmlaUtil.java mondrian-3.4.1/src/main/mondrian/xmla/XmlaRequest.java0000644000175000017500000000400011735330606022620 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.xmla; import org.olap4j.metadata.XmlaConstants; import java.util.Map; /** * XML/A request interface. * * @author Gang Chen */ public interface XmlaRequest { /** * Indicate DISCOVER or EXECUTE method. */ XmlaConstants.Method getMethod(); /** * Properties of XML/A request. */ Map getProperties(); /** * Restrictions of DISCOVER method. * *

    If the value is a list of strings, the restriction passes if the * column has one of the values. */ Map getRestrictions(); /** * Statement of EXECUTE method. */ String getStatement(); /** * Role name binds with this XML/A request. Maybe null. */ String getRoleName(); /** * Request type of DISCOVER method. */ String getRequestType(); /** * Indicate whether statement is a drill through statement of * EXECUTE method. */ boolean isDrillThrough(); /** * The username to use to open the underlying olap4j connection. * Can be null. */ String getUsername(); /** * The password to use to open the underlying olap4j connection. * Can be null. */ String getPassword(); /** * Returns the id of the session this request belongs to. * *

    Not necessarily the same as the HTTP session: the SOAP request * contains its own session information.

    * *

    The session id is used to retrieve existing olap connections. And * username / password only need to be passed on the first request in a * session.

    * * @return Id of the session */ String getSessionId(); } // End XmlaRequest.java mondrian-3.4.1/src/main/mondrian/xmla/Rowset.java0000644000175000017500000005027511735330606021650 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho // All Rights Reserved. */ package mondrian.xmla; import mondrian.olap.Util; import org.apache.log4j.Logger; import org.olap4j.OlapConnection; import org.olap4j.impl.LcidLocale; import org.olap4j.metadata.Catalog; import java.sql.SQLException; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Base class for an XML for Analysis schema rowset. A concrete derived class * should implement {@link #populateImpl}, calling {@link #addRow} for each row. * * @author jhyde * @see mondrian.xmla.RowsetDefinition * @since May 2, 2003 */ abstract class Rowset implements XmlaConstants { protected static final Logger LOGGER = Logger.getLogger(Rowset.class); protected final RowsetDefinition rowsetDefinition; protected final Map restrictions; protected final Map properties; protected final Map extraProperties = new HashMap(); protected final XmlaRequest request; protected final XmlaHandler handler; private final RowsetDefinition.Column[] restrictedColumns; protected final boolean deep; /** * Creates a Rowset. * *

    The exceptions thrown in this constructor are not produced during the * execution of an XMLA request and so can be ordinary exceptions and not * XmlaException (which are specifically for generating SOAP Fault xml). */ Rowset( RowsetDefinition definition, XmlaRequest request, XmlaHandler handler) { this.rowsetDefinition = definition; this.restrictions = request.getRestrictions(); this.properties = request.getProperties(); this.request = request; this.handler = handler; ArrayList list = new ArrayList(); for (Map.Entry restrictionEntry : restrictions.entrySet()) { String restrictedColumn = restrictionEntry.getKey(); LOGGER.debug( "Rowset: restrictedColumn=\"" + restrictedColumn + "\""); final RowsetDefinition.Column column = definition.lookupColumn( restrictedColumn); if (column == null) { throw Util.newError( "Rowset '" + definition.name() + "' does not contain column '" + restrictedColumn + "'"); } if (!column.restriction) { throw Util.newError( "Rowset '" + definition.name() + "' column '" + restrictedColumn + "' does not allow restrictions"); } // Check that the value is of the right type. final Object restriction = restrictionEntry.getValue(); if (restriction instanceof List && ((List) restriction).size() > 1) { final RowsetDefinition.Type type = column.type; switch (type) { case StringArray: case EnumerationArray: case StringSometimesArray: break; // OK default: throw Util.newError( "Rowset '" + definition.name() + "' column '" + restrictedColumn + "' can only be restricted on one value at a time"); } } list.add(column); } list = pruneRestrictions(list); this.restrictedColumns = list.toArray( new RowsetDefinition.Column[list.size()]); boolean deep = false; for (Map.Entry propertyEntry : properties.entrySet()) { String propertyName = propertyEntry.getKey(); final PropertyDefinition propertyDef = Util.lookup(PropertyDefinition.class, propertyName); if (propertyDef == null) { throw Util.newError( "Rowset '" + definition.name() + "' does not support property '" + propertyName + "'"); } final String propertyValue = propertyEntry.getValue(); setProperty(propertyDef, propertyValue); if (propertyDef == PropertyDefinition.Deep) { deep = Boolean.valueOf(propertyValue); } } this.deep = deep; } protected ArrayList pruneRestrictions( ArrayList list) { return list; } /** * Sets a property for this rowset. Called by the constructor for each * supplied property.

    * * A derived class should override this method and intercept each * property it supports. Any property it does not support, it should forward * to the base class method, which will probably throw an error.

    */ protected void setProperty(PropertyDefinition propertyDef, String value) { switch (propertyDef) { case Format: break; case DataSourceInfo: break; case Catalog: break; case LocaleIdentifier: if (value != null) { try { // First check for a numeric locale id (LCID) as used by // Windows. final short lcid = Short.valueOf(value); final Locale locale = LcidLocale.lcidToLocale(lcid); if (locale != null) { extraProperties.put( XmlaHandler.JDBC_LOCALE, locale.toString()); return; } } catch (NumberFormatException nfe) { // Since value is not a valid LCID, now see whether it is a // locale name, e.g. "en_US". This behavior is an // extension to the XMLA spec. try { Locale locale = Util.parseLocale(value); extraProperties.put( XmlaHandler.JDBC_LOCALE, locale.toString()); return; } catch (RuntimeException re) { // probably a bad locale string; fall through } } return; } // fall through default: LOGGER.warn( "Warning: Rowset '" + rowsetDefinition.name() + "' does not support property '" + propertyDef.name() + "' (value is '" + value + "')"); } } /** * Writes the contents of this rowset as a series of SAX events. */ public final void unparse(XmlaResponse response) throws XmlaException, SQLException { final List rows = new ArrayList(); populate(response, null, rows); final Comparator comparator = rowsetDefinition.getComparator(); if (comparator != null) { Collections.sort(rows, comparator); } final SaxWriter writer = response.getWriter(); writer.startSequence(null, "row"); for (Row row : rows) { emit(row, response); } writer.endSequence(); } /** * Gathers the set of rows which match a given set of the criteria. */ public final void populate( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException { boolean ourConnection = false; try { if (needConnection() && connection == null) { connection = handler.getConnection(request, extraProperties); ourConnection = true; } populateImpl(response, connection, rows); } catch (SQLException e) { // TODO: e.printStackTrace(); } finally { if (connection != null && ourConnection) { try { connection.close(); } catch (SQLException e) { // ignore } } } } protected boolean needConnection() { return true; } /** * Gathers the set of rows which match a given set of the criteria. */ protected abstract void populateImpl( XmlaResponse response, OlapConnection connection, List rows) throws XmlaException, SQLException; /** * Adds a {@link Row} to a result, provided that it meets the necessary * criteria. Returns whether the row was added. * * @param row Row * @param rows List of result rows */ protected final boolean addRow( Row row, List rows) throws XmlaException { return rows.add(row); } /** * Emits a row for this rowset, reading fields from a * {@link mondrian.xmla.Rowset.Row} object. * * @param row Row * @param response XMLA response writer */ protected void emit(Row row, XmlaResponse response) throws XmlaException, SQLException { SaxWriter writer = response.getWriter(); writer.startElement("row"); for (RowsetDefinition.Column column : rowsetDefinition.columnDefinitions) { Object value = row.get(column.name); if (value == null) { if (!column.nullable) { throw new XmlaException( CLIENT_FAULT_FC, HSB_BAD_NON_NULLABLE_COLUMN_CODE, HSB_BAD_NON_NULLABLE_COLUMN_FAULT_FS, Util.newInternal( "Value required for column " + column.name + " of rowset " + rowsetDefinition.name())); } } else if (value instanceof XmlElement[]) { XmlElement[] elements = (XmlElement[]) value; for (XmlElement element : elements) { emitXmlElement(writer, element); } } else if (value instanceof Object[]) { Object[] values = (Object[]) value; for (Object value1 : values) { writer.startElement(column.name); writer.characters(value1.toString()); writer.endElement(); } } else if (value instanceof List) { List values = (List) value; for (Object value1 : values) { if (value1 instanceof XmlElement) { XmlElement xmlElement = (XmlElement) value1; emitXmlElement(writer, xmlElement); } else { writer.startElement(column.name); writer.characters(value1.toString()); writer.endElement(); } } } else if (value instanceof Rowset) { Rowset rowset = (Rowset) value; final List rows = new ArrayList(); rowset.populate(response, null, rows); writer.startSequence(column.name, "row"); for (Row row1 : rows) { rowset.emit(row1, response); } writer.endSequence(); } else { writer.textElement(column.name, value); } } writer.endElement(); } private void emitXmlElement(SaxWriter writer, XmlElement element) { if (element.attributes == null) { writer.startElement(element.tag); } else { writer.startElement(element.tag, element.attributes); } if (element.text == null) { for (XmlElement aChildren : element.children) { emitXmlElement(writer, aChildren); } } else { writer.characters(element.text); } writer.endElement(); } /** * Populates all of the values in an enumeration into a list of rows. */ protected void populate( Class clazz, List rows, final Comparator comparator) throws XmlaException { final E[] enumsSortedByName = clazz.getEnumConstants().clone(); Arrays.sort(enumsSortedByName, comparator); for (E anEnum : enumsSortedByName) { Row row = new Row(); for (RowsetDefinition.Column column : rowsetDefinition.columnDefinitions) { row.names.add(column.name); row.values.add(column.get(anEnum)); } rows.add(row); } } /** * Creates a condition functor based on the restrictions on a given metadata * column specified in an XMLA request. * *

    A condition is a {@link mondrian.olap.Util.Functor1} whose return * type is boolean. * * Restrictions are used in each Rowset's discovery request. If there is no * restriction then the passes method always returns true. * *

    It is known at the beginning of a * {@link Rowset#populate(XmlaResponse, org.olap4j.OlapConnection, java.util.List)} * method whether the restriction is not specified (null), a single value * (String) or an array of values (String[]). So, creating the conditions * just once at the beginning is faster than having to determine the * restriction status each time it is needed. * * @param column Metadata column * @param Element type, e.g. {@link org.olap4j.metadata.Catalog} or * {@link org.olap4j.metadata.Level} * @return Condition functor */ Util.Functor1 makeCondition( RowsetDefinition.Column column) { return makeCondition( Util.identityFunctor(), column); } /** * Creates a condition functor using an accessor. * *

    The accessor gets a particular property of the element in question * for the column restrictions to act upon. * * @param getter Attribute accessor * @param column Metadata column * @param Element type, e.g. {@link org.olap4j.metadata.Catalog} or * {@link org.olap4j.metadata.Level} * @param Value type; often {@link String}, since many restrictions * work on the name or unique name of elements * @return Condition functor */ Util.Functor1 makeCondition( final Util.Functor1 getter, RowsetDefinition.Column column) { final Object restriction = restrictions.get(column.name); if (restriction == null) { return Util.trueFunctor(); } else if (restriction instanceof XmlaUtil.Wildcard) { XmlaUtil.Wildcard wildcard = (XmlaUtil.Wildcard) restriction; String regexp = Util.wildcardToRegexp( Collections.singletonList(wildcard.pattern)); final Matcher matcher = Pattern.compile(regexp).matcher(""); return new Util.Functor1() { public Boolean apply(E element) { V value = getter.apply(element); return matcher.reset(String.valueOf(value)).matches(); } }; } else if (restriction instanceof List) { final List requiredValues = (List) restriction; return new Util.Functor1() { public Boolean apply(E element) { if (element == null) { return requiredValues.contains(""); } V value = getter.apply(element); return requiredValues.contains(value); } }; } else { throw Util.newInternal( "unexpected restriction type: " + restriction.getClass()); } } /** * Returns the restriction if it is a String, or null otherwise. Does not * attempt two determine if the restriction is an array of Strings * if all members of the array have the same value (in which case * one could return, again, simply a single String). */ String getRestrictionValueAsString(RowsetDefinition.Column column) { final Object restriction = restrictions.get(column.name); if (restriction instanceof List) { List rval = (List) restriction; if (rval.size() == 1) { return rval.get(0); } } return null; } /** * Returns a column's restriction as an int if it * exists, -1 otherwise. */ int getRestrictionValueAsInt(RowsetDefinition.Column column) { final Object restriction = restrictions.get(column.name); if (restriction instanceof List) { List rval = (List) restriction; if (rval.size() == 1) { try { return Integer.parseInt(rval.get(0)); } catch (NumberFormatException ex) { LOGGER.info( "Rowset.getRestrictionValue: " + "bad integer restriction \"" + rval + "\""); return -1; } } } return -1; } /** * Returns true if there is a restriction for the given column * definition. * */ protected boolean isRestricted(RowsetDefinition.Column column) { return (restrictions.get(column.name) != null); } protected Util.Functor1 catNameCond() { Map properties = request.getProperties(); final String catalogName = properties.get(PropertyDefinition.Catalog.name()); if (catalogName != null) { return new Util.Functor1() { public Boolean apply(Catalog catalog) { return catalog.getName().equals(catalogName); } }; } else { return Util.trueFunctor(); } } /** * A set of name/value pairs, which can be output using * {@link Rowset#addRow}. This uses less memory than simply * using a HashMap and for very big data sets memory is * a concern. */ protected static class Row { private final ArrayList names; private final ArrayList values; Row() { this.names = new ArrayList(); this.values = new ArrayList(); } void set(String name, Object value) { this.names.add(name); this.values.add(value); } /** * Retrieves the value of a field with a given name, or null if the * field's value is not defined. */ public Object get(String name) { int i = this.names.indexOf(name); return (i < 0) ? null : this.values.get(i); } } /** * Holder for non-scalar column values of a * {@link mondrian.xmla.Rowset.Row}. */ protected static class XmlElement { private final String tag; private final Object[] attributes; private final String text; private final XmlElement[] children; XmlElement(String tag, Object[] attributes, String text) { this(tag, attributes, text, null); } XmlElement(String tag, Object[] attributes, XmlElement[] children) { this(tag, attributes, null, children); } private XmlElement( String tag, Object[] attributes, String text, XmlElement[] children) { assert attributes == null || attributes.length % 2 == 0; this.tag = tag; this.attributes = attributes; this.text = text; this.children = children; } } } // End Rowset.java mondrian-3.4.1/src/main/mondrian/xmla/XmlaHandler.java0000644000175000017500000035306011735330606022562 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2012 Pentaho // All Rights Reserved. */ package mondrian.xmla; import mondrian.olap.MondrianProperties; import mondrian.olap.Util; import mondrian.util.CompositeList; import mondrian.xmla.impl.DefaultSaxWriter; import org.apache.log4j.Logger; import org.olap4j.*; import org.olap4j.impl.Olap4jUtil; import org.olap4j.metadata.*; import org.olap4j.metadata.Property.StandardCellProperty; import org.olap4j.metadata.Property.StandardMemberProperty; import org.xml.sax.SAXException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.UndeclaredThrowableException; import java.math.BigDecimal; import java.math.BigInteger; import java.sql.*; import java.util.*; import java.util.Date; import static mondrian.xmla.XmlaConstants.*; import static org.olap4j.metadata.XmlaConstants.*; /** * An XmlaHandler responds to XML for Analysis (XML/A) requests. * * @author jhyde, Gang Chen * @since 27 April, 2003 */ public class XmlaHandler { private static final Logger LOGGER = Logger.getLogger(XmlaHandler.class); /** * Name of property used by JDBC to hold user name. */ private static final String JDBC_USER = "user"; /** * Name of property used by JDBC to hold password. */ private static final String JDBC_PASSWORD = "password"; /** * Name of property used by JDBC to hold locale. It is not hard-wired into * DriverManager like "user" and "password", but we do expect any olap4j * driver that supports i18n to use this property name. */ public static final String JDBC_LOCALE = "locale"; final ConnectionFactory connectionFactory; private final String prefix; public static XmlaExtra getExtra(OlapConnection connection) { try { final XmlaExtra extra = connection.unwrap(XmlaExtra.class); if (extra != null) { return extra; } } catch (SQLException e) { // Connection cannot provide an XmlaExtra. Fall back and give a // default implementation. } catch (UndeclaredThrowableException ute) { // // Note: this is necessary because we use a dynamic proxy for the // connection. // I could not catch and un-wrap the Undeclared Throwable within // the proxy. // The exception comes out here and I couldn't find any better // ways to deal with it. // // The undeclared throwable contains an Invocation Target Exception // which in turns contains the real exception thrown by the "unwrap" // method, for example OlapException. // Throwable cause = ute.getCause(); if (cause instanceof InvocationTargetException) { cause = cause.getCause(); } // this maintains the original behaviour: don't catch exceptions // that are not subclasses of SQLException if (! (cause instanceof SQLException)) { throw ute; } } return new XmlaExtraImpl(); } /** * Returns a new OlapConnection opened with the credentials specified in the * XMLA request or an existing connection if one can be found associated * with the request session id. * * @param request Request * @param propMap Extra properties */ public OlapConnection getConnection( XmlaRequest request, Map propMap) { String sessionId = request.getSessionId(); if (sessionId == null) { // With a Simba O2X Client session ID is only null when // serving "discover datasources". // // Let's have a magic ID for the non-authenticated session. // // REVIEW: Security hole? sessionId = ""; } LOGGER.debug( "Creating new connection for user [" + request.getUsername() + "] and session [" + sessionId + "]"); Properties props = new Properties(); for (Map.Entry entry : propMap.entrySet()) { props.put(entry.getKey(), entry.getValue()); } if (request.getUsername() != null) { props.put(JDBC_USER, request.getUsername()); } if (request.getPassword() != null) { props.put(JDBC_PASSWORD, request.getPassword()); } final String databaseName = request .getProperties() .get(PropertyDefinition.DataSourceInfo.name()); final String catalogName = request .getProperties() .get(PropertyDefinition.Catalog.name()); return getConnection( databaseName, catalogName, request.getRoleName(), props); } private enum SetType { ROW_SET, MD_DATA_SET } private static final String EMPTY_ROW_SET_XML_SCHEMA = computeEmptyXsd(SetType.ROW_SET); private static final String MD_DATA_SET_XML_SCHEMA = computeXsd(SetType.MD_DATA_SET); private static final String EMPTY_MD_DATA_SET_XML_SCHEMA = computeEmptyXsd(SetType.MD_DATA_SET); private static final String NS_XML_SQL = "urn:schemas-microsoft-com:xml-sql"; // // Some xml schema data types. // public static final String XSD_BOOLEAN = "xsd:boolean"; public static final String XSD_STRING = "xsd:string"; public static final String XSD_UNSIGNED_INT = "xsd:unsignedInt"; public static final String XSD_BYTE = "xsd:byte"; public static final byte XSD_BYTE_MAX_INCLUSIVE = 127; public static final byte XSD_BYTE_MIN_INCLUSIVE = -128; public static final String XSD_SHORT = "xsd:short"; public static final short XSD_SHORT_MAX_INCLUSIVE = 32767; public static final short XSD_SHORT_MIN_INCLUSIVE = -32768; public static final String XSD_INT = "xsd:int"; public static final int XSD_INT_MAX_INCLUSIVE = 2147483647; public static final int XSD_INT_MIN_INCLUSIVE = -2147483648; public static final String XSD_LONG = "xsd:long"; public static final long XSD_LONG_MAX_INCLUSIVE = 9223372036854775807L; public static final long XSD_LONG_MIN_INCLUSIVE = -9223372036854775808L; // xsd:double: IEEE 64-bit floating-point public static final String XSD_DOUBLE = "xsd:double"; // xsd:decimal: Decimal numbers (BigDecimal) public static final String XSD_DECIMAL = "xsd:decimal"; // xsd:integer: Signed integers of arbitrary length (BigInteger) public static final String XSD_INTEGER = "xsd:integer"; public static boolean isValidXsdInt(long l) { return (l <= XSD_INT_MAX_INCLUSIVE) && (l >= XSD_INT_MIN_INCLUSIVE); } /** * Takes a DataType String (null, Integer, Numeric or non-null) * and Value Object (Integer, Double, String, other) and * canonicalizes them to XSD data type and corresponding object. *

    * If the input DataType is Integer, then it attempts to return * an XSD_INT with value java.lang.Integer (and failing that an * XSD_LONG (java.lang.Long) or XSD_INTEGER (java.math.BigInteger)). * Worst case is the value loses precision with any integral * representation and must be returned as a decimal type (Double * or java.math.BigDecimal). *

    * If the input DataType is Decimal, then it attempts to return * an XSD_DOUBLE with value java.lang.Double (and failing that an * XSD_DECIMAL (java.math.BigDecimal)). */ static class ValueInfo { /** * Returns XSD_INT, XSD_DOUBLE, XSD_STRING or null. * * @param dataType null, Integer, Numeric or non-null. * @return Returns the suggested XSD type for a given datatype */ static String getValueTypeHint(final String dataType) { if (dataType != null) { return (dataType.equals("Integer")) ? XSD_INT : ((dataType.equals("Numeric")) ? XSD_DOUBLE : XSD_STRING); } else { return null; } } String valueType; Object value; boolean isDecimal; ValueInfo(final String dataType, final Object inputValue) { final String valueTypeHint = getValueTypeHint(dataType); // This is a hint: should it be a string, integer or decimal type. // In the following, if the hint is integer, then there is // an attempt that the value types // be XSD_INT, XST_LONG, or XSD_INTEGER (but they could turn // out to be XSD_DOUBLE or XSD_DECIMAL if precision is loss // with the integral formats). It the hint is a decimal type // (double, float, decimal), then a XSD_DOUBLE or XSD_DECIMAL // is returned. if (valueTypeHint != null) { // The value type is a hint. If the value can be // converted to the data type without precision loss, ok; // otherwise value data type must be adjusted. if (valueTypeHint.equals(XSD_STRING)) { // For String types, nothing to do. this.valueType = valueTypeHint; this.value = inputValue; this.isDecimal = false; } else if (valueTypeHint.equals(XSD_INT)) { // If valueTypeHint is XSD_INT, then see if value can be // converted to (first choice) integer, (second choice), // long and (last choice) BigInteger - otherwise must // use double/decimal. // Most of the time value ought to be an Integer so // try it first if (inputValue instanceof Integer) { // For integer, its already the right type this.valueType = valueTypeHint; this.value = inputValue; this.isDecimal = false; } else if (inputValue instanceof Byte) { this.valueType = valueTypeHint; this.value = inputValue; this.isDecimal = false; } else if (inputValue instanceof Short) { this.valueType = valueTypeHint; this.value = inputValue; this.isDecimal = false; } else if (inputValue instanceof Long) { // See if it can be an integer or long long lval = (Long) inputValue; setValueAndType(lval); } else if (inputValue instanceof BigInteger) { BigInteger bi = (BigInteger) inputValue; // See if it can be an integer or long long lval = bi.longValue(); if (bi.equals(BigInteger.valueOf(lval))) { // It can be converted from BigInteger to long // without loss of precision. setValueAndType(lval); } else { // It can not be converted to a long. this.valueType = XSD_INTEGER; this.value = inputValue; this.isDecimal = false; } } else if (inputValue instanceof Float) { Float f = (Float) inputValue; // See if it can be an integer or long long lval = f.longValue(); if (f.equals(new Float(lval))) { // It can be converted from double to long // without loss of precision. setValueAndType(lval); } else { // It can not be converted to a long. this.valueType = XSD_DOUBLE; this.value = inputValue; this.isDecimal = true; } } else if (inputValue instanceof Double) { Double d = (Double) inputValue; // See if it can be an integer or long long lval = d.longValue(); if (d.equals(new Double(lval))) { // It can be converted from double to long // without loss of precision. setValueAndType(lval); } else { // It can not be converted to a long. this.valueType = XSD_DOUBLE; this.value = inputValue; this.isDecimal = true; } } else if (inputValue instanceof BigDecimal) { // See if it can be an integer or long BigDecimal bd = (BigDecimal) inputValue; try { // Can it be converted to a long // Throws ArithmeticException on conversion failure. // The following line is only available in // Java5 and above: //long lval = bd.longValueExact(); long lval = bd.longValue(); setValueAndType(lval); } catch (ArithmeticException ex) { // No, it can not be converted to long try { // Can it be an integer BigInteger bi = bd.toBigIntegerExact(); this.valueType = XSD_INTEGER; this.value = bi; this.isDecimal = false; } catch (ArithmeticException ex1) { // OK, its a decimal this.valueType = XSD_DECIMAL; this.value = inputValue; this.isDecimal = true; } } } else if (inputValue instanceof Number) { // Don't know what Number type we have here. // Note: this could result in precision loss. this.value = ((Number) inputValue).longValue(); this.valueType = valueTypeHint; this.isDecimal = false; } else { // Who knows what we are dealing with, // hope for the best?!? this.valueType = valueTypeHint; this.value = inputValue; this.isDecimal = false; } } else if (valueTypeHint.equals(XSD_DOUBLE)) { // The desired type is double. // Most of the time value ought to be an Double so // try it first if (inputValue instanceof Double) { // For Double, its already the right type this.valueType = valueTypeHint; this.value = inputValue; this.isDecimal = true; } else if (inputValue instanceof Byte || inputValue instanceof Short || inputValue instanceof Integer || inputValue instanceof Long) { // Convert from byte/short/integer/long to double this.value = ((Number) inputValue).doubleValue(); this.valueType = valueTypeHint; this.isDecimal = true; } else if (inputValue instanceof Float) { this.value = inputValue; this.valueType = valueTypeHint; this.isDecimal = true; } else if (inputValue instanceof BigDecimal) { BigDecimal bd = (BigDecimal) inputValue; double dval = bd.doubleValue(); // make with same scale as Double try { BigDecimal bd2 = Util.makeBigDecimalFromDouble(dval); // Can it be a double // Must use compareTo - see BigDecimal.equals if (bd.compareTo(bd2) == 0) { this.valueType = XSD_DOUBLE; this.value = dval; } else { this.valueType = XSD_DECIMAL; this.value = inputValue; } } catch (NumberFormatException ex) { this.valueType = XSD_DECIMAL; this.value = inputValue; } this.isDecimal = true; } else if (inputValue instanceof BigInteger) { // What should be done here? Convert ot BigDecimal // and see if it can be a double or not? // See if there is loss of precision in the convertion? // Don't know. For now, just keep it a integral // value. BigInteger bi = (BigInteger) inputValue; // See if it can be an integer or long long lval = bi.longValue(); if (bi.equals(BigInteger.valueOf(lval))) { // It can be converted from BigInteger to long // without loss of precision. setValueAndType(lval); } else { // It can not be converted to a long. this.valueType = XSD_INTEGER; this.value = inputValue; this.isDecimal = true; } } else if (inputValue instanceof Number) { // Don't know what Number type we have here. // Note: this could result in precision loss. this.value = ((Number) inputValue).doubleValue(); this.valueType = valueTypeHint; this.isDecimal = true; } else { // Who knows what we are dealing with, // hope for the best?!? this.valueType = valueTypeHint; this.value = inputValue; this.isDecimal = true; } } } else { // There is no valueType "hint", so just get it from the value. if (inputValue instanceof String) { this.valueType = XSD_STRING; this.value = inputValue; this.isDecimal = false; } else if (inputValue instanceof Integer) { this.valueType = XSD_INT; this.value = inputValue; this.isDecimal = false; } else if (inputValue instanceof Byte) { Byte b = (Byte) inputValue; this.valueType = XSD_INT; this.value = b.intValue(); this.isDecimal = false; } else if (inputValue instanceof Short) { Short s = (Short) inputValue; this.valueType = XSD_INT; this.value = s.intValue(); this.isDecimal = false; } else if (inputValue instanceof Long) { // See if it can be an integer or long setValueAndType((Long) inputValue); } else if (inputValue instanceof BigInteger) { BigInteger bi = (BigInteger) inputValue; // See if it can be an integer or long long lval = bi.longValue(); if (bi.equals(BigInteger.valueOf(lval))) { // It can be converted from BigInteger to long // without loss of precision. setValueAndType(lval); } else { // It can not be converted to a long. this.valueType = XSD_INTEGER; this.value = inputValue; this.isDecimal = false; } } else if (inputValue instanceof Float) { this.valueType = XSD_DOUBLE; this.value = inputValue; this.isDecimal = true; } else if (inputValue instanceof Double) { this.valueType = XSD_DOUBLE; this.value = inputValue; this.isDecimal = true; } else if (inputValue instanceof BigDecimal) { // See if it can be a double BigDecimal bd = (BigDecimal) inputValue; double dval = bd.doubleValue(); // make with same scale as Double try { BigDecimal bd2 = Util.makeBigDecimalFromDouble(dval); // Can it be a double // Must use compareTo - see BigDecimal.equals if (bd.compareTo(bd2) == 0) { this.valueType = XSD_DOUBLE; this.value = dval; } else { this.valueType = XSD_DECIMAL; this.value = inputValue; } } catch (NumberFormatException ex) { this.valueType = XSD_DECIMAL; this.value = inputValue; } this.isDecimal = true; } else if (inputValue instanceof Number) { // Don't know what Number type we have here. // Note: this could result in precision loss. this.value = ((Number) inputValue).longValue(); this.valueType = XSD_LONG; this.isDecimal = false; } else { // Who knows what we are dealing with, // hope for the best?!? this.valueType = XSD_STRING; this.value = inputValue; this.isDecimal = false; } } } private void setValueAndType(long lval) { if (! isValidXsdInt(lval)) { // No, it can not be a integer, must be a long this.valueType = XSD_LONG; this.value = lval; } else { // Its an integer. this.valueType = XSD_INT; this.value = (int) lval; } this.isDecimal = false; } } private static String computeXsd(SetType setType) { final StringWriter sw = new StringWriter(); SaxWriter writer = new DefaultSaxWriter(new PrintWriter(sw), 3); writeDatasetXmlSchema(writer, setType); writer.flush(); return sw.toString(); } private static String computeEmptyXsd(SetType setType) { final StringWriter sw = new StringWriter(); SaxWriter writer = new DefaultSaxWriter(new PrintWriter(sw), 3); writeEmptyDatasetXmlSchema(writer, setType); writer.flush(); return sw.toString(); } private static interface QueryResult { void unparse(SaxWriter res) throws SAXException, OlapException; void close() throws SQLException; void metadata(SaxWriter writer); } /** * Creates an XmlaHandler. * *

    The connection factory may be null, as long as you override * {@link #getConnection(String, String, String, Properties)}. * * @param connectionFactory Connection factory * @param prefix XML Namespace. Typical value is "xmla", but a value of * "cxmla" works around an Internet Explorer 7 bug */ public XmlaHandler(ConnectionFactory connectionFactory, String prefix) { assert prefix != null; this.connectionFactory = connectionFactory; this.prefix = prefix; } /** * Processes a request. * * @param request XML request, for example, "". * @param response Destination for response * @throws XmlaException on error */ public void process(XmlaRequest request, XmlaResponse response) throws XmlaException { Method method = request.getMethod(); long start = System.currentTimeMillis(); switch (method) { case DISCOVER: discover(request, response); break; case EXECUTE: execute(request, response); break; default: throw new XmlaException( CLIENT_FAULT_FC, HSB_BAD_METHOD_CODE, HSB_BAD_METHOD_FAULT_FS, new IllegalArgumentException( "Unsupported XML/A method: " + method)); } if (LOGGER.isDebugEnabled()) { long end = System.currentTimeMillis(); LOGGER.debug("XmlaHandler.process: time = " + (end - start)); LOGGER.debug("XmlaHandler.process: " + Util.printMemory()); } } private void checkFormat(XmlaRequest request) throws XmlaException { // Check response's rowset format in request final Map properties = request.getProperties(); if (request.isDrillThrough()) { Format format = getFormat(request, null); if (format != Format.Tabular) { throw new XmlaException( CLIENT_FAULT_FC, HSB_DRILL_THROUGH_FORMAT_CODE, HSB_DRILL_THROUGH_FORMAT_FAULT_FS, new UnsupportedOperationException( ": only 'Tabular' allowed when drilling " + "through")); } } else { final String formatName = properties.get(PropertyDefinition.Format.name()); if (formatName != null) { Format format = getFormat(request, null); if (format != Format.Multidimensional && format != Format.Tabular) { throw new UnsupportedOperationException( ": only 'Multidimensional', 'Tabular' " + "currently supported"); } } final String axisFormatName = properties.get(PropertyDefinition.AxisFormat.name()); if (axisFormatName != null) { AxisFormat axisFormat = Util.lookup( AxisFormat.class, axisFormatName, null); if (axisFormat != AxisFormat.TupleFormat) { throw new UnsupportedOperationException( ": only 'TupleFormat' currently supported"); } } } } private void execute( XmlaRequest request, XmlaResponse response) throws XmlaException { final Map properties = request.getProperties(); // Default responseMimeType is SOAP. Enumeration.ResponseMimeType responseMimeType = getResponseMimeType(request); // Default value is SchemaData, or Data for JSON responses. final String contentName = properties.get(PropertyDefinition.Content.name()); Content content = Util.lookup( Content.class, contentName, responseMimeType == Enumeration.ResponseMimeType.JSON ? Content.Data : Content.DEFAULT); // Handle execute QueryResult result = null; try { if (request.isDrillThrough()) { result = executeDrillThroughQuery(request); } else { result = executeQuery(request); } SaxWriter writer = response.getWriter(); writer.startDocument(); writer.startElement( prefix + ":ExecuteResponse", "xmlns:" + prefix, NS_XMLA); writer.startElement(prefix + ":return"); boolean rowset = request.isDrillThrough() || Format.Tabular.name().equals( request.getProperties().get( PropertyDefinition.Format.name())); writer.startElement( "root", "xmlns", result == null ? NS_XMLA_EMPTY : rowset ? NS_XMLA_ROWSET : NS_XMLA_MDDATASET, "xmlns:xsi", NS_XSI, "xmlns:xsd", NS_XSD, "xmlns:EX", NS_XMLA_EX); switch (content) { case Schema: case SchemaData: if (result != null) { result.metadata(writer); } else { if (rowset) { writer.verbatim(EMPTY_ROW_SET_XML_SCHEMA); } else { writer.verbatim(EMPTY_MD_DATA_SET_XML_SCHEMA); } } break; } try { switch (content) { case Data: case SchemaData: case DataOmitDefaultSlicer: case DataIncludeDefaultSlicer: if (result != null) { result.unparse(writer); } break; } } catch (XmlaException xex) { throw xex; } catch (Throwable t) { throw new XmlaException( SERVER_FAULT_FC, HSB_EXECUTE_UNPARSE_CODE, HSB_EXECUTE_UNPARSE_FAULT_FS, t); } finally { writer.endElement(); // root writer.endElement(); // return writer.endElement(); // ExecuteResponse } writer.endDocument(); } finally { if (result != null) { try { result.close(); } catch (SQLException e) { // ignore } } } } /** * Computes the XML Schema for a dataset. * * @param writer SAX writer * @param settype rowset or dataset? * @see RowsetDefinition#writeRowsetXmlSchema(SaxWriter) */ static void writeDatasetXmlSchema(SaxWriter writer, SetType settype) { String setNsXmla = (settype == SetType.ROW_SET) ? NS_XMLA_ROWSET : NS_XMLA_MDDATASET; writer.startElement( "xsd:schema", "xmlns:xsd", NS_XSD, "targetNamespace", setNsXmla, "xmlns", setNsXmla, "xmlns:xsi", NS_XSI, "xmlns:sql", NS_XML_SQL, "elementFormDefault", "qualified"); // MemberType writer.startElement( "xsd:complexType", "name", "MemberType"); writer.startElement("xsd:sequence"); writer.element( "xsd:element", "name", "UName", "type", XSD_STRING); writer.element( "xsd:element", "name", "Caption", "type", XSD_STRING); writer.element( "xsd:element", "name", "LName", "type", XSD_STRING); writer.element( "xsd:element", "name", "LNum", "type", XSD_UNSIGNED_INT); writer.element( "xsd:element", "name", "DisplayInfo", "type", XSD_UNSIGNED_INT); writer.startElement( "xsd:sequence", "maxOccurs", "unbounded", "minOccurs", 0); writer.element( "xsd:any", "processContents", "lax", "maxOccurs", "unbounded"); writer.endElement(); // xsd:sequence writer.endElement(); // xsd:sequence writer.element( "xsd:attribute", "name", "Hierarchy", "type", XSD_STRING); writer.endElement(); // xsd:complexType name="MemberType" // PropType writer.startElement( "xsd:complexType", "name", "PropType"); writer.element( "xsd:attribute", "name", "name", "type", XSD_STRING); writer.endElement(); // xsd:complexType name="PropType" // TupleType writer.startElement( "xsd:complexType", "name", "TupleType"); writer.startElement( "xsd:sequence", "maxOccurs", "unbounded"); writer.element( "xsd:element", "name", "Member", "type", "MemberType"); writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType name="TupleType" // MembersType writer.startElement( "xsd:complexType", "name", "MembersType"); writer.startElement( "xsd:sequence", "maxOccurs", "unbounded"); writer.element( "xsd:element", "name", "Member", "type", "MemberType"); writer.endElement(); // xsd:sequence writer.element( "xsd:attribute", "name", "Hierarchy", "type", XSD_STRING); writer.endElement(); // xsd:complexType // TuplesType writer.startElement( "xsd:complexType", "name", "TuplesType"); writer.startElement( "xsd:sequence", "maxOccurs", "unbounded"); writer.element( "xsd:element", "name", "Tuple", "type", "TupleType"); writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType // CrossProductType writer.startElement( "xsd:complexType", "name", "CrossProductType"); writer.startElement("xsd:sequence"); writer.startElement( "xsd:choice", "minOccurs", 0, "maxOccurs", "unbounded"); writer.element( "xsd:element", "name", "Members", "type", "MembersType"); writer.element( "xsd:element", "name", "Tuples", "type", "TuplesType"); writer.endElement(); // xsd:choice writer.endElement(); // xsd:sequence writer.element( "xsd:attribute", "name", "Size", "type", XSD_UNSIGNED_INT); writer.endElement(); // xsd:complexType // OlapInfo writer.startElement( "xsd:complexType", "name", "OlapInfo"); writer.startElement("xsd:sequence"); { // writer.startElement( "xsd:element", "name", "CubeInfo"); writer.startElement("xsd:complexType"); writer.startElement("xsd:sequence"); { // writer.startElement( "xsd:element", "name", "Cube", "maxOccurs", "unbounded"); writer.startElement("xsd:complexType"); writer.startElement("xsd:sequence"); writer.element( "xsd:element", "name", "CubeName", "type", XSD_STRING); writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType writer.endElement(); // xsd:element name=Cube } writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType writer.endElement(); // xsd:element name=CubeInfo } { // writer.startElement( "xsd:element", "name", "AxesInfo"); writer.startElement("xsd:complexType"); writer.startElement("xsd:sequence"); { // writer.startElement( "xsd:element", "name", "AxisInfo", "maxOccurs", "unbounded"); writer.startElement("xsd:complexType"); writer.startElement("xsd:sequence"); { // writer.startElement( "xsd:element", "name", "HierarchyInfo", "minOccurs", 0, "maxOccurs", "unbounded"); writer.startElement("xsd:complexType"); writer.startElement("xsd:sequence"); writer.startElement( "xsd:sequence", "maxOccurs", "unbounded"); writer.element( "xsd:element", "name", "UName", "type", "PropType"); writer.element( "xsd:element", "name", "Caption", "type", "PropType"); writer.element( "xsd:element", "name", "LName", "type", "PropType"); writer.element( "xsd:element", "name", "LNum", "type", "PropType"); writer.element( "xsd:element", "name", "DisplayInfo", "type", "PropType", "minOccurs", 0, "maxOccurs", "unbounded"); if (false) writer.element( "xsd:element", "name", "PARENT_MEMBER_NAME", "type", "PropType", "minOccurs", 0, "maxOccurs", "unbounded"); writer.endElement(); // xsd:sequence // This is the Depth element for JPivot?? writer.startElement("xsd:sequence"); writer.element( "xsd:any", "processContents", "lax", "minOccurs", 0, "maxOccurs", "unbounded"); writer.endElement(); // xsd:sequence writer.endElement(); // xsd:sequence writer.element( "xsd:attribute", "name", "name", "type", XSD_STRING, "use", "required"); writer.endElement(); // xsd:complexType writer.endElement(); // xsd:element name=HierarchyInfo } writer.endElement(); // xsd:sequence writer.element( "xsd:attribute", "name", "name", "type", XSD_STRING); writer.endElement(); // xsd:complexType writer.endElement(); // xsd:element name=AxisInfo } writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType writer.endElement(); // xsd:element name=AxesInfo } // CellInfo { // writer.startElement( "xsd:element", "name", "CellInfo"); writer.startElement("xsd:complexType"); writer.startElement("xsd:sequence"); writer.startElement( "xsd:sequence", "minOccurs", 0, "maxOccurs", "unbounded"); writer.startElement("xsd:choice"); writer.element( "xsd:element", "name", "Value", "type", "PropType"); writer.element( "xsd:element", "name", "FmtValue", "type", "PropType"); writer.element( "xsd:element", "name", "BackColor", "type", "PropType"); writer.element( "xsd:element", "name", "ForeColor", "type", "PropType"); writer.element( "xsd:element", "name", "FontName", "type", "PropType"); writer.element( "xsd:element", "name", "FontSize", "type", "PropType"); writer.element( "xsd:element", "name", "FontFlags", "type", "PropType"); writer.element( "xsd:element", "name", "FormatString", "type", "PropType"); writer.element( "xsd:element", "name", "NonEmptyBehavior", "type", "PropType"); writer.element( "xsd:element", "name", "SolveOrder", "type", "PropType"); writer.element( "xsd:element", "name", "Updateable", "type", "PropType"); writer.element( "xsd:element", "name", "Visible", "type", "PropType"); writer.element( "xsd:element", "name", "Expression", "type", "PropType"); writer.endElement(); // xsd:choice writer.endElement(); // xsd:sequence writer.startElement( "xsd:sequence", "maxOccurs", "unbounded", "minOccurs", 0); writer.element( "xsd:any", "processContents", "lax", "maxOccurs", "unbounded"); writer.endElement(); // xsd:sequence writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType writer.endElement(); // xsd:element name=CellInfo } writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType // Axes writer.startElement( "xsd:complexType", "name", "Axes"); writer.startElement( "xsd:sequence", "maxOccurs", "unbounded"); { // writer.startElement( "xsd:element", "name", "Axis"); writer.startElement("xsd:complexType"); writer.startElement( "xsd:choice", "minOccurs", 0, "maxOccurs", "unbounded"); writer.element( "xsd:element", "name", "CrossProduct", "type", "CrossProductType"); writer.element( "xsd:element", "name", "Tuples", "type", "TuplesType"); writer.element( "xsd:element", "name", "Members", "type", "MembersType"); writer.endElement(); // xsd:choice writer.element( "xsd:attribute", "name", "name", "type", XSD_STRING); writer.endElement(); // xsd:complexType } writer.endElement(); // xsd:element writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType // CellData writer.startElement( "xsd:complexType", "name", "CellData"); writer.startElement("xsd:sequence"); { // writer.startElement( "xsd:element", "name", "Cell", "minOccurs", 0, "maxOccurs", "unbounded"); writer.startElement("xsd:complexType"); writer.startElement( "xsd:sequence", "maxOccurs", "unbounded"); writer.startElement("xsd:choice"); writer.element( "xsd:element", "name", "Value"); writer.element( "xsd:element", "name", "FmtValue", "type", XSD_STRING); writer.element( "xsd:element", "name", "BackColor", "type", XSD_UNSIGNED_INT); writer.element( "xsd:element", "name", "ForeColor", "type", XSD_UNSIGNED_INT); writer.element( "xsd:element", "name", "FontName", "type", XSD_STRING); writer.element( "xsd:element", "name", "FontSize", "type", "xsd:unsignedShort"); writer.element( "xsd:element", "name", "FontFlags", "type", XSD_UNSIGNED_INT); writer.element( "xsd:element", "name", "FormatString", "type", XSD_STRING); writer.element( "xsd:element", "name", "NonEmptyBehavior", "type", "xsd:unsignedShort"); writer.element( "xsd:element", "name", "SolveOrder", "type", XSD_UNSIGNED_INT); writer.element( "xsd:element", "name", "Updateable", "type", XSD_UNSIGNED_INT); writer.element( "xsd:element", "name", "Visible", "type", XSD_UNSIGNED_INT); writer.element( "xsd:element", "name", "Expression", "type", XSD_STRING); writer.endElement(); // xsd:choice writer.endElement(); // xsd:sequence writer.element( "xsd:attribute", "name", "CellOrdinal", "type", XSD_UNSIGNED_INT, "use", "required"); writer.endElement(); // xsd:complexType writer.endElement(); // xsd:element name=Cell } writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType { // writer.startElement( "xsd:element", "name", "root"); writer.startElement("xsd:complexType"); writer.startElement( "xsd:sequence", "maxOccurs", "unbounded"); writer.element( "xsd:element", "name", "OlapInfo", "type", "OlapInfo"); writer.element( "xsd:element", "name", "Axes", "type", "Axes"); writer.element( "xsd:element", "name", "CellData", "type", "CellData"); writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType writer.endElement(); // xsd:element name=root } writer.endElement(); // xsd:schema } static void writeEmptyDatasetXmlSchema(SaxWriter writer, SetType setType) { String setNsXmla = NS_XMLA_ROWSET; writer.startElement( "xsd:schema", "xmlns:xsd", NS_XSD, "targetNamespace", setNsXmla, "xmlns", setNsXmla, "xmlns:xsi", NS_XSI, "xmlns:sql", NS_XML_SQL, "elementFormDefault", "qualified"); writer.element( "xsd:element", "name", "root"); writer.endElement(); // xsd:schema } private QueryResult executeDrillThroughQuery(XmlaRequest request) throws XmlaException { checkFormat(request); final Map properties = request.getProperties(); String tabFields = properties.get(PropertyDefinition.TableFields.name()); if (tabFields != null && tabFields.length() == 0) { tabFields = null; } final String advancedFlag = properties.get(PropertyDefinition.AdvancedFlag.name()); final boolean advanced = Boolean.parseBoolean(advancedFlag); final boolean enableRowCount = MondrianProperties.instance().EnableTotalCount.booleanValue(); final int[] rowCountSlot = enableRowCount ? new int[]{0} : null; OlapConnection connection = null; OlapStatement statement = null; ResultSet resultSet = null; try { connection = getConnection(request, Collections.emptyMap()); statement = connection.createStatement(); resultSet = getExtra(connection).executeDrillthrough( statement, request.getStatement(), advanced, tabFields, rowCountSlot); int rowCount = enableRowCount ? rowCountSlot[0] : -1; return new TabularRowSet(resultSet, rowCount); } catch (XmlaException xex) { throw xex; } catch (SQLException sqle) { throw new XmlaException( SERVER_FAULT_FC, HSB_DRILL_THROUGH_SQL_CODE, HSB_DRILL_THROUGH_SQL_FAULT_FS, Util.newError(sqle, "Error in drill through")); } catch (RuntimeException e) { // NOTE: One important error is "cannot drill through on the cell" throw new XmlaException( SERVER_FAULT_FC, HSB_DRILL_THROUGH_SQL_CODE, HSB_DRILL_THROUGH_SQL_FAULT_FS, e); } finally { if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { // ignore } } if (statement != null) { try { statement.close(); } catch (SQLException e) { // ignore } } if (connection != null) { try { connection.close(); } catch (SQLException e) { // ignore } } } } static class Column { private final String name; private final String encodedName; private final String xsdType; Column(String name, int type, int scale) { this.name = name; // replace invalid XML element name, like " ", with "_x0020_" in // column headers, otherwise will generate a badly-formatted xml // doc. this.encodedName = XmlaUtil.ElementNameEncoder.INSTANCE.encode(name); this.xsdType = sqlToXsdType(type, scale); } } static class TabularRowSet implements QueryResult { private final List columns = new ArrayList(); private final List rows; private int totalCount; /** * Creates a TabularRowSet based upon a SQL statement result. * *

    Does not close the ResultSet, on success or failure. Client * must do it. * * @param rs Result set * @param totalCount Total number of rows. If >= 0, writes the * "totalCount" attribute into the XMLA response. * * @throws SQLException on error */ public TabularRowSet( ResultSet rs, int totalCount) throws SQLException { this.totalCount = totalCount; ResultSetMetaData md = rs.getMetaData(); int columnCount = md.getColumnCount(); // populate column defs for (int i = 0; i < columnCount; i++) { columns.add( new Column( md.getColumnLabel(i + 1), md.getColumnType(i + 1), md.getScale(i + 1))); } // Populate data; assume that SqlStatement is already positioned // on first row (or isDone() is true), and assume that the // number of rows returned is limited. rows = new ArrayList(); while (rs.next()) { Object[] row = new Object[columnCount]; for (int i = 0; i < columnCount; i++) { row[i] = rs.getObject(i + 1); } rows.add(row); } } /** * Alternate constructor for advanced drill-through. * * @param tableFieldMap Map from table name to a list of the names of * the fields in the table * @param tableList List of table names */ public TabularRowSet( Map> tableFieldMap, List tableList) { for (String tableName : tableList) { List fieldNames = tableFieldMap.get(tableName); for (String fieldName : fieldNames) { columns.add( new Column( tableName + "." + fieldName, Types.VARCHAR, // don't know the real type 0)); } } rows = new ArrayList(); Object[] row = new Object[columns.size()]; for (int k = 0; k < row.length; k++) { row[k] = k; } rows.add(row); } public void close() { // no resources to close } public void unparse(SaxWriter writer) throws SAXException { // write total count row if enabled if (totalCount >= 0) { String countStr = Integer.toString(totalCount); writer.startElement("row"); for (Column column : columns) { writer.startElement(column.encodedName); writer.characters(countStr); writer.endElement(); } writer.endElement(); // row } for (Object[] row : rows) { writer.startElement("row"); for (int i = 0; i < row.length; i++) { writer.startElement( columns.get(i).encodedName, new Object[] { "xsi:type", columns.get(i).xsdType}); Object value = row[i]; if (value == null) { writer.characters("null"); } else { String valueString = value.toString(); if (value instanceof Number) { valueString = XmlaUtil.normalizeNumericString(valueString); } writer.characters(valueString); } writer.endElement(); } writer.endElement(); // row } } /** * Writes the tabular drillthrough schema * * @param writer Writer */ public void metadata(SaxWriter writer) { writer.startElement( "xsd:schema", "xmlns:xsd", NS_XSD, "targetNamespace", NS_XMLA_ROWSET, "xmlns", NS_XMLA_ROWSET, "xmlns:xsi", NS_XSI, "xmlns:sql", NS_XML_SQL, "elementFormDefault", "qualified"); { // writer.startElement( "xsd:element", "name", "root"); writer.startElement("xsd:complexType"); writer.startElement("xsd:sequence"); writer.element( "xsd:element", "maxOccurs", "unbounded", "minOccurs", 0, "name", "row", "type", "row"); writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType writer.endElement(); // xsd:element name=root } { // xsd:simpleType name="uuid" writer.startElement( "xsd:simpleType", "name", "uuid"); writer.startElement( "xsd:restriction", "base", XSD_STRING); writer.element( "xsd:pattern", "value", RowsetDefinition.UUID_PATTERN); writer.endElement(); // xsd:restriction writer.endElement(); // xsd:simpleType } { // xsd:complexType name="row" writer.startElement( "xsd:complexType", "name", "row"); writer.startElement("xsd:sequence"); for (Column column : columns) { writer.element( "xsd:element", "minOccurs", 0, "name", column.encodedName, "sql:field", column.name, "type", column.xsdType); } writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType } writer.endElement(); // xsd:schema } } /** * Converts a SQL type to XSD type. * * @param sqlType SQL type * @return XSD type */ private static String sqlToXsdType(final int sqlType, final int scale) { switch (sqlType) { // Integer case Types.INTEGER: case Types.SMALLINT: case Types.TINYINT: return XSD_INT; case Types.NUMERIC: case Types.DECIMAL: /* * Oracle reports all numbers as NUMERIC. We check * the scale of the column and pick the right XSD type. */ if (scale == 0) { return XSD_INT; } else { return XSD_DECIMAL; } case Types.BIGINT: return XSD_INTEGER; // Real case Types.DOUBLE: case Types.FLOAT: return XSD_DOUBLE; // Date and time case Types.TIME: case Types.TIMESTAMP: case Types.DATE: return XSD_STRING; // Other default: return XSD_STRING; } } private QueryResult executeQuery(XmlaRequest request) throws XmlaException { final String mdx = request.getStatement(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("mdx: \"" + mdx + "\""); } if ((mdx == null) || (mdx.length() == 0)) { return null; } checkFormat(request); OlapConnection connection = null; PreparedOlapStatement statement = null; CellSet cellSet = null; boolean success = false; try { connection = getConnection(request, Collections.emptyMap()); getExtra(connection).setPreferList(connection); try { statement = connection.prepareOlapStatement(mdx); } catch (XmlaException ex) { throw ex; } catch (Exception ex) { throw new XmlaException( CLIENT_FAULT_FC, HSB_PARSE_QUERY_CODE, HSB_PARSE_QUERY_FAULT_FS, ex); } try { cellSet = statement.executeQuery(); final Format format = getFormat(request, null); final Content content = getContent(request); final Enumeration.ResponseMimeType responseMimeType = getResponseMimeType(request); final MDDataSet dataSet; if (format == Format.Multidimensional) { dataSet = new MDDataSet_Multidimensional( cellSet, content != Content.DataIncludeDefaultSlicer, responseMimeType == Enumeration.ResponseMimeType.JSON); } else { dataSet = new MDDataSet_Tabular(cellSet); } success = true; return dataSet; } catch (XmlaException ex) { throw ex; } catch (Exception ex) { throw new XmlaException( SERVER_FAULT_FC, HSB_EXECUTE_QUERY_CODE, HSB_EXECUTE_QUERY_FAULT_FS, ex); } } finally { if (!success) { if (cellSet != null) { try { cellSet.close(); } catch (SQLException e) { // ignore } } if (statement != null) { try { statement.close(); } catch (SQLException e) { // ignore } } if (connection != null) { try { connection.close(); } catch (SQLException e) { // ignore } } } } } private static Format getFormat( XmlaRequest request, Format defaultValue) { final String formatName = request.getProperties().get( PropertyDefinition.Format.name()); return Util.lookup( Format.class, formatName, defaultValue); } private static Content getContent(XmlaRequest request) { final String contentName = request.getProperties().get( PropertyDefinition.Content.name()); return Util.lookup( Content.class, contentName, Content.DEFAULT); } private static Enumeration.ResponseMimeType getResponseMimeType( XmlaRequest request) { Enumeration.ResponseMimeType mimeType = Enumeration.ResponseMimeType.MAP.get( request.getProperties().get( PropertyDefinition.ResponseMimeType.name())); if (mimeType == null) { mimeType = Enumeration.ResponseMimeType.SOAP; } return mimeType; } static abstract class MDDataSet implements QueryResult { protected final CellSet cellSet; protected static final List cellProps = Arrays.asList( rename(StandardCellProperty.VALUE, "Value"), rename(StandardCellProperty.FORMATTED_VALUE, "FmtValue"), rename(StandardCellProperty.FORMAT_STRING, "FormatString")); protected static final List cellPropLongs = Arrays.asList( StandardCellProperty.VALUE, StandardCellProperty.FORMATTED_VALUE, StandardCellProperty.FORMAT_STRING); protected static final List defaultProps = Arrays.asList( rename(StandardMemberProperty.MEMBER_UNIQUE_NAME, "UName"), rename(StandardMemberProperty.MEMBER_CAPTION, "Caption"), rename(StandardMemberProperty.LEVEL_UNIQUE_NAME, "LName"), rename(StandardMemberProperty.LEVEL_NUMBER, "LNum"), rename(StandardMemberProperty.DISPLAY_INFO, "DisplayInfo")); protected static final Map longProps = new HashMap(); static { longProps.put("UName", StandardMemberProperty.MEMBER_UNIQUE_NAME); longProps.put("Caption", StandardMemberProperty.MEMBER_CAPTION); longProps.put("LName", StandardMemberProperty.LEVEL_UNIQUE_NAME); longProps.put("LNum", StandardMemberProperty.LEVEL_NUMBER); longProps.put("DisplayInfo", StandardMemberProperty.DISPLAY_INFO); } protected MDDataSet(CellSet cellSet) { this.cellSet = cellSet; } public void close() throws SQLException { cellSet.getStatement().getConnection().close(); } private static Property rename( final Property property, final String name) { return new Property() { public Datatype getDatatype() { return property.getDatatype(); } public Set getType() { return property.getType(); } public ContentType getContentType() { return property.getContentType(); } public String getName() { return name; } public String getUniqueName() { return property.getUniqueName(); } public String getCaption() { return property.getCaption(); } public String getDescription() { return property.getDescription(); } public boolean isVisible() { return property.isVisible(); } }; } } static class MDDataSet_Multidimensional extends MDDataSet { private List slicerAxisHierarchies; private final boolean omitDefaultSlicerInfo; private final boolean json; private final Hierarchy measuresHierarchy; private XmlaUtil.ElementNameEncoder encoder = XmlaUtil.ElementNameEncoder.INSTANCE; private XmlaExtra extra; protected MDDataSet_Multidimensional( CellSet cellSet, boolean omitDefaultSlicerInfo, boolean json) throws SQLException { super(cellSet); this.omitDefaultSlicerInfo = omitDefaultSlicerInfo; this.json = json; this.measuresHierarchy = cellSet.getMetaData().getCube().getHierarchies().get(0); this.extra = getExtra(cellSet.getStatement().getConnection()); } public void unparse(SaxWriter writer) throws SAXException, OlapException { olapInfo(writer); axes(writer); cellData(writer); } public void metadata(SaxWriter writer) { writer.verbatim(MD_DATA_SET_XML_SCHEMA); } private void olapInfo(SaxWriter writer) throws OlapException { // What are all of the cube's hierarchies Cube cube = cellSet.getMetaData().getCube(); writer.startElement("OlapInfo"); writer.startElement("CubeInfo"); writer.startElement("Cube"); writer.textElement("CubeName", cube.getName()); writer.endElement(); writer.endElement(); // CubeInfo // create AxesInfo for axes // ----------- writer.startSequence("AxesInfo", "AxisInfo"); final List axes = cellSet.getAxes(); List axisHierarchyList = new ArrayList(); for (int i = 0; i < axes.size(); i++) { List hierarchyList = axisInfo(writer, axes.get(i), "Axis" + i); axisHierarchyList.addAll(hierarchyList); } /////////////////////////////////////////////// // create AxesInfo for slicer axes // List hierarchies; CellSetAxis slicerAxis = cellSet.getFilterAxis(); if (omitDefaultSlicerInfo) { hierarchies = slicerAxis.getAxisMetaData().getHierarchies(); if (hierarchies.isEmpty()) { // Slicer axis has one tuple. This occurs when the query // has no WHERE clause. Don't emit SlicerAxis. } else { hierarchies = axisInfo( writer, slicerAxis, "SlicerAxis"); } } else { // The slicer axes contains the default hierarchy // of each dimension not seen on another axis. List unseenDimensionList = new ArrayList(cube.getDimensions()); for (Hierarchy hier1 : axisHierarchyList) { unseenDimensionList.remove(hier1.getDimension()); } hierarchies = new ArrayList(); for (Dimension dimension : unseenDimensionList) { for (Hierarchy hierarchy : dimension.getHierarchies()) { hierarchies.add(hierarchy); } } writer.startElement( "AxisInfo", "name", "SlicerAxis"); writeHierarchyInfo( writer, hierarchies, getProps(slicerAxis.getAxisMetaData())); writer.endElement(); // AxisInfo } slicerAxisHierarchies = hierarchies; // /////////////////////////////////////////////// writer.endSequence(); // AxesInfo // ----------- writer.startElement("CellInfo"); cellProperty(writer, StandardCellProperty.VALUE, true, "Value"); cellProperty( writer, StandardCellProperty.FORMATTED_VALUE, true, "FmtValue"); cellProperty( writer, StandardCellProperty.FORMAT_STRING, true, "FormatString"); cellProperty( writer, StandardCellProperty.LANGUAGE, false, "Language"); cellProperty( writer, StandardCellProperty.BACK_COLOR, false, "BackColor"); cellProperty( writer, StandardCellProperty.FORE_COLOR, false, "ForeColor"); cellProperty( writer, StandardCellProperty.FONT_FLAGS, false, "FontFlags"); writer.endElement(); // CellInfo // ----------- writer.endElement(); // OlapInfo } private void cellProperty( SaxWriter writer, StandardCellProperty cellProperty, boolean evenEmpty, String elementName) { if (extra.shouldReturnCellProperty( cellSet, cellProperty, evenEmpty)) { writer.element( elementName, "name", cellProperty.getName()); } } private List axisInfo( SaxWriter writer, CellSetAxis axis, String axisName) { writer.startElement( "AxisInfo", "name", axisName); List hierarchies; Iterator it = axis.getPositions().iterator(); if (it.hasNext()) { final org.olap4j.Position position = it.next(); hierarchies = new ArrayList(); for (Member member : position.getMembers()) { hierarchies.add(member.getHierarchy()); } if (hierarchies.isEmpty() && axis.getAxisOrdinal().isFilter()) { // Excel 2007 abhors empty slicer. hierarchies.add(measuresHierarchy); } } else { hierarchies = axis.getAxisMetaData().getHierarchies(); } List props = getProps(axis.getAxisMetaData()); writeHierarchyInfo(writer, hierarchies, props); writer.endElement(); // AxisInfo return hierarchies; } private void writeHierarchyInfo( SaxWriter writer, List hierarchies, List props) { writer.startSequence(null, "HierarchyInfo"); for (Hierarchy hierarchy : hierarchies) { writer.startElement( "HierarchyInfo", "name", hierarchy.getName()); for (final Property prop : props) { final String encodedProp = encoder.encode(prop.getName()); final Object[] attributes = getAttributes(prop, hierarchy); writer.element(encodedProp, attributes); } writer.endElement(); // HierarchyInfo } writer.endSequence(); // "HierarchyInfo" } private Object[] getAttributes(Property prop, Hierarchy hierarchy) { Property longProp = longProps.get(prop.getName()); if (longProp == null) { longProp = prop; } List values = new ArrayList(); values.add("name"); values.add( hierarchy.getUniqueName() + "." + Util.quoteMdxIdentifier(longProp.getName())); if (longProp == prop) { //Adding type attribute to the optional properties values.add("type"); values.add(getXsdType(longProp)); } return values.toArray(); } private String getXsdType(Property property) { Datatype datatype = property.getDatatype(); switch (datatype) { case UNSIGNED_INTEGER: return RowsetDefinition.Type.UnsignedInteger.columnType; case BOOLEAN: return RowsetDefinition.Type.Boolean.columnType; default: return RowsetDefinition.Type.String.columnType; } } private void axes(SaxWriter writer) throws OlapException { writer.startSequence("Axes", "Axis"); final List axes = cellSet.getAxes(); for (int i = 0; i < axes.size(); i++) { final CellSetAxis axis = axes.get(i); final List props = getProps(axis.getAxisMetaData()); axis(writer, axis, props, "Axis" + i); } //////////////////////////////////////////// // now generate SlicerAxis information // if (omitDefaultSlicerInfo) { CellSetAxis slicerAxis = cellSet.getFilterAxis(); // There are two kinds of 'empty' slicer axis, and we // need to be able to distinguish between them. // // 1. Slicer axis has zero positions (which happens when the // WHERE clause evaluates to an empty set). We write an Axis // element, with 0 tuples. // // 2. One position containing a tuple of zero // members (which happens when there is no WHERE clause). We // omit the Axis element altogether. if (slicerAxis.getAxisMetaData().getHierarchies().isEmpty()) { assert slicerAxis.getPositions().size() == 1; } else { axis( writer, slicerAxis, getProps(slicerAxis.getAxisMetaData()), "SlicerAxis"); } } else { List hierarchies = slicerAxisHierarchies; writer.startElement( "Axis", "name", "SlicerAxis"); writer.startSequence("Tuples", "Tuple"); writer.startSequence("Tuple", "Member"); Map memberMap = new HashMap(); Member positionMember; CellSetAxis slicerAxis = cellSet.getFilterAxis(); final List slicerPositions = slicerAxis.getPositions(); if (slicerPositions.size() > 0) { final Position pos0 = slicerPositions.get(0); int i = 0; for (Member member : pos0.getMembers()) { memberMap.put(member.getHierarchy().getName(), i++); } } final List slicerMembers = slicerPositions.isEmpty() ? Collections.emptyList() : slicerPositions.get(0).getMembers(); for (Hierarchy hierarchy : hierarchies) { // Find which member is on the slicer. // If it's not explicitly there, use the default member. Member member = hierarchy.getDefaultMember(); final Integer indexPosition = memberMap.get(hierarchy.getName()); if (indexPosition != null) { positionMember = slicerMembers.get(indexPosition); } else { positionMember = null; } for (Member slicerMember : slicerMembers) { if (slicerMember.getHierarchy().equals(hierarchy)) { member = slicerMember; break; } } if (member != null) { if (positionMember != null) { writeMember( writer, positionMember, null, slicerPositions.get(0), indexPosition, getProps(slicerAxis.getAxisMetaData())); } else { slicerAxis( writer, member, getProps(slicerAxis.getAxisMetaData())); } } else { LOGGER.warn( "Can not create SlicerAxis: " + "null default member for Hierarchy " + hierarchy.getUniqueName()); } } writer.endSequence(); // Tuple writer.endSequence(); // Tuples writer.endElement(); // Axis } // //////////////////////////////////////////// writer.endSequence(); // Axes } private List getProps(CellSetAxisMetaData queryAxis) { if (queryAxis == null) { return defaultProps; } return CompositeList.of( defaultProps, queryAxis.getProperties()); } private void axis( SaxWriter writer, CellSetAxis axis, List props, String axisName) throws OlapException { writer.startElement( "Axis", "name", axisName); writer.startSequence("Tuples", "Tuple"); List positions = axis.getPositions(); Iterator pit = positions.iterator(); Position prevPosition = null; Position position = pit.hasNext() ? pit.next() : null; Position nextPosition = pit.hasNext() ? pit.next() : null; while (position != null) { writer.startSequence("Tuple", "Member"); int k = 0; List memberList = position.getMembers(); if (axis.getAxisOrdinal().isFilter() && memberList.size() == 0) { memberList = Collections.singletonList( measuresHierarchy.getDefaultMember()); } for (Member member : memberList) { writeMember( writer, member, prevPosition, nextPosition, k++, props); } writer.endSequence(); // Tuple prevPosition = position; position = nextPosition; nextPosition = pit.hasNext() ? pit.next() : null; } writer.endSequence(); // Tuples writer.endElement(); // Axis } private void writeMember( SaxWriter writer, Member member, Position prevPosition, Position nextPosition, int k, List props) throws OlapException { writer.startElement( "Member", "Hierarchy", member.getHierarchy().getName()); for (Property prop : props) { Object value; Property longProp = longProps.get(prop.getName()); if (longProp == null) { longProp = prop; } if (longProp == StandardMemberProperty.DISPLAY_INFO) { Integer childrenCard = (Integer) member.getPropertyValue( StandardMemberProperty.CHILDREN_CARDINALITY); value = calculateDisplayInfo( prevPosition, nextPosition, member, k, childrenCard); } else if (longProp == StandardMemberProperty.DEPTH) { value = member.getDepth(); } else { value = member.getPropertyValue(longProp); } if (value != null) { writer.textElement( encoder.encode(prop.getName()), value); } } writer.endElement(); // Member } private void slicerAxis( SaxWriter writer, Member member, List props) throws OlapException { writer.startElement( "Member", "Hierarchy", member.getHierarchy().getName()); for (Property prop : props) { Object value; Property longProp = longProps.get(prop.getName()); if (longProp == null) { longProp = prop; } if (longProp == StandardMemberProperty.DISPLAY_INFO) { Integer childrenCard = (Integer) member.getPropertyValue( StandardMemberProperty.CHILDREN_CARDINALITY); // NOTE: don't know if this is correct for // SlicerAxis int displayInfo = 0xffff & childrenCard; /* int displayInfo = calculateDisplayInfo((j == 0 ? null : positions[j - 1]), (j + 1 == positions.length ? null : positions[j + 1]), member, k, childrenCard.intValue()); */ value = displayInfo; } else if (longProp == StandardMemberProperty.DEPTH) { value = member.getDepth(); } else { value = member.getPropertyValue(longProp); } if (value != null) { writer.textElement( encoder.encode(prop.getName()), value); } } writer.endElement(); // Member } private int calculateDisplayInfo( Position prevPosition, Position nextPosition, Member currentMember, int memberOrdinal, int childrenCount) { int displayInfo = 0xffff & childrenCount; if (nextPosition != null) { String currentUName = currentMember.getUniqueName(); Member nextMember = nextPosition.getMembers().get(memberOrdinal); String nextParentUName = parentUniqueName(nextMember); if (currentUName.equals(nextParentUName)) { displayInfo |= 0x10000; } } if (prevPosition != null) { String currentParentUName = parentUniqueName(currentMember); Member prevMember = prevPosition.getMembers().get(memberOrdinal); String prevParentUName = parentUniqueName(prevMember); if (currentParentUName != null && currentParentUName.equals(prevParentUName)) { displayInfo |= 0x20000; } } return displayInfo; } private String parentUniqueName(Member member) { final Member parent = member.getParentMember(); if (parent == null) { return null; } return parent.getUniqueName(); } private void cellData(SaxWriter writer) { writer.startSequence("CellData", "Cell"); final int axisCount = cellSet.getAxes().size(); List pos = new ArrayList(); for (int i = 0; i < axisCount; i++) { pos.add(-1); } int[] cellOrdinal = new int[] {0}; int axisOrdinal = axisCount - 1; recurse(writer, pos, axisOrdinal, cellOrdinal); writer.endSequence(); // CellData } private void recurse( SaxWriter writer, List pos, int axisOrdinal, int[] cellOrdinal) { if (axisOrdinal < 0) { emitCell(writer, pos, cellOrdinal[0]++); } else { CellSetAxis axis = cellSet.getAxes().get(axisOrdinal); List positions = axis.getPositions(); for (int i = 0, n = positions.size(); i < n; i++) { pos.set(axisOrdinal, i); recurse(writer, pos, axisOrdinal - 1, cellOrdinal); } } } private void emitCell( SaxWriter writer, List pos, int ordinal) { Cell cell = cellSet.getCell(pos); if (cell.isNull() && ordinal != 0) { // Ignore null cell like MS AS, except for Oth ordinal return; } writer.startElement( "Cell", "CellOrdinal", ordinal); for (int i = 0; i < cellProps.size(); i++) { Property cellPropLong = cellPropLongs.get(i); Object value = cell.getPropertyValue(cellPropLong); if (value == null) { continue; } if (!extra.shouldReturnCellProperty( cellSet, cellPropLong, true)) { continue; } if (!json && cellPropLong == StandardCellProperty.VALUE) { if (cell.isNull()) { // Return cell without value as in case of AS2005 continue; } final String dataType = (String) cell.getPropertyValue( StandardCellProperty.DATATYPE); final ValueInfo vi = new ValueInfo(dataType, value); final String valueType = vi.valueType; final String valueString; if (vi.isDecimal) { valueString = XmlaUtil.normalizeNumericString( vi.value.toString()); } else { valueString = vi.value.toString(); } writer.startElement( cellProps.get(i).getName(), "xsi:type", valueType); writer.characters(valueString); writer.endElement(); } else { writer.textElement(cellProps.get(i).getName(), value); } } writer.endElement(); // Cell } } static abstract class ColumnHandler { protected final String name; protected final String encodedName; protected ColumnHandler(String name) { this.name = name; this.encodedName = XmlaUtil.ElementNameEncoder.INSTANCE.encode(name); } abstract void write(SaxWriter writer, Cell cell, Member[] members) throws OlapException; abstract void metadata(SaxWriter writer); } /** * Callback to handle one column, representing the combination of a * level and a property (e.g. [Store].[Store State].[MEMBER_UNIQUE_NAME]) * in a flattened dataset. */ static class CellColumnHandler extends ColumnHandler { CellColumnHandler(String name) { super(name); } public void metadata(SaxWriter writer) { writer.element( "xsd:element", "minOccurs", 0, "name", encodedName, "sql:field", name); } public void write( SaxWriter writer, Cell cell, Member[] members) { if (cell.isNull()) { return; } Object value = cell.getValue(); final String dataType = (String) cell.getPropertyValue(StandardCellProperty.DATATYPE); final ValueInfo vi = new ValueInfo(dataType, value); final String valueType = vi.valueType; value = vi.value; boolean isDecimal = vi.isDecimal; String valueString = value.toString(); writer.startElement( encodedName, "xsi:type", valueType); if (isDecimal) { valueString = XmlaUtil.normalizeNumericString(valueString); } writer.characters(valueString); writer.endElement(); } } /** * Callback to handle one column, representing the combination of a * level and a property (e.g. [Store].[Store State].[MEMBER_UNIQUE_NAME]) * in a flattened dataset. */ static class MemberColumnHandler extends ColumnHandler { private final Property property; private final Level level; private final int memberOrdinal; public MemberColumnHandler( Property property, Level level, int memberOrdinal) { super( level.getUniqueName() + "." + Util.quoteMdxIdentifier(property.getName())); this.property = property; this.level = level; this.memberOrdinal = memberOrdinal; } public void metadata(SaxWriter writer) { writer.element( "xsd:element", "minOccurs", 0, "name", encodedName, "sql:field", name, "type", XSD_STRING); } public void write( SaxWriter writer, Cell cell, Member[] members) throws OlapException { Member member = members[memberOrdinal]; final int depth = level.getDepth(); if (member.getDepth() < depth) { // This column deals with a level below the current member. // There is no value to write. return; } while (member.getDepth() > depth) { member = member.getParentMember(); } final Object propertyValue = member.getPropertyValue(property); if (propertyValue == null) { return; } writer.startElement(encodedName); writer.characters(propertyValue.toString()); writer.endElement(); } } static class MDDataSet_Tabular extends MDDataSet { private final boolean empty; private final int[] pos; private final List posList; private final int axisCount; private int cellOrdinal; private static final List MemberCaptionIdArray = Collections.singletonList( StandardMemberProperty.MEMBER_CAPTION); private final Member[] members; private final ColumnHandler[] columnHandlers; public MDDataSet_Tabular(CellSet cellSet) { super(cellSet); final List axes = cellSet.getAxes(); axisCount = axes.size(); pos = new int[axisCount]; posList = new IntList(pos); // Count dimensions, and deduce list of levels which appear on // non-COLUMNS axes. boolean empty = false; int dimensionCount = 0; for (int i = axes.size() - 1; i > 0; i--) { CellSetAxis axis = axes.get(i); if (axis.getPositions().size() == 0) { // If any axis is empty, the whole data set is empty. empty = true; continue; } dimensionCount += axis.getPositions().get(0).getMembers().size(); } this.empty = empty; // Build a list of the lowest level used on each non-COLUMNS axis. Level[] levels = new Level[dimensionCount]; List columnHandlerList = new ArrayList(); int memberOrdinal = 0; if (!empty) { for (int i = axes.size() - 1; i > 0; i--) { final CellSetAxis axis = axes.get(i); final int z0 = memberOrdinal; // save ordinal so can rewind final List positions = axis.getPositions(); int jj = 0; for (Position position : positions) { memberOrdinal = z0; // rewind to start for (Member member : position.getMembers()) { if (jj == 0 || member.getLevel().getDepth() > levels[memberOrdinal].getDepth()) { levels[memberOrdinal] = member.getLevel(); } memberOrdinal++; } jj++; } // Now we know the lowest levels on this axis, add // properties. List dimProps = axis.getAxisMetaData().getProperties(); if (dimProps.size() == 0) { dimProps = MemberCaptionIdArray; } for (int j = z0; j < memberOrdinal; j++) { Level level = levels[j]; for (int k = 0; k <= level.getDepth(); k++) { final Level level2 = level.getHierarchy().getLevels().get(k); if (level2.getLevelType() == Level.Type.ALL) { continue; } for (Property dimProp : dimProps) { columnHandlerList.add( new MemberColumnHandler( dimProp, level2, j)); } } } } } this.members = new Member[memberOrdinal + 1]; // Deduce the list of column headings. if (axes.size() > 0) { CellSetAxis columnsAxis = axes.get(0); for (Position position : columnsAxis.getPositions()) { String name = null; int j = 0; for (Member member : position.getMembers()) { if (j == 0) { name = member.getUniqueName(); } else { name = name + "." + member.getUniqueName(); } j++; } columnHandlerList.add( new CellColumnHandler(name)); } } this.columnHandlers = columnHandlerList.toArray( new ColumnHandler[columnHandlerList.size()]); } public void metadata(SaxWriter writer) { writer.startElement( "xsd:schema", "xmlns:xsd", NS_XSD, "targetNamespace", NS_XMLA_ROWSET, "xmlns", NS_XMLA_ROWSET, "xmlns:xsi", NS_XSI, "xmlns:sql", NS_XML_SQL, "elementFormDefault", "qualified"); { // writer.startElement( "xsd:element", "name", "root"); writer.startElement("xsd:complexType"); writer.startElement("xsd:sequence"); writer.element( "xsd:element", "maxOccurs", "unbounded", "minOccurs", 0, "name", "row", "type", "row"); writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType writer.endElement(); // xsd:element name=root } { // xsd:simpleType name="uuid" writer.startElement( "xsd:simpleType", "name", "uuid"); writer.startElement( "xsd:restriction", "base", XSD_STRING); writer.element( "xsd:pattern", "value", RowsetDefinition.UUID_PATTERN); writer.endElement(); // xsd:restriction writer.endElement(); // xsd:simpleType } { // xsd:complexType name="row" writer.startElement( "xsd:complexType", "name", "row"); writer.startElement("xsd:sequence"); for (ColumnHandler columnHandler : columnHandlers) { columnHandler.metadata(writer); } writer.endElement(); // xsd:sequence writer.endElement(); // xsd:complexType } writer.endElement(); // xsd:schema } public void unparse(SaxWriter writer) throws SAXException, OlapException { if (empty) { return; } cellData(writer); } private void cellData(SaxWriter writer) throws SAXException, OlapException { cellOrdinal = 0; iterate(writer); } /** * Iterates over the resust writing tabular rows. * * @param writer Writer * @throws org.xml.sax.SAXException on error */ private void iterate(SaxWriter writer) throws SAXException, OlapException { switch (axisCount) { case 0: // For MDX like: SELECT FROM Sales emitCell(writer, cellSet.getCell(posList)); return; default: // throw new SAXException("Too many axes: " + axisCount); iterate(writer, axisCount - 1, 0); break; } } private void iterate(SaxWriter writer, int axis, final int xxx) throws OlapException { final List positions = cellSet.getAxes().get(axis).getPositions(); int axisLength = axis == 0 ? 1 : positions.size(); for (int i = 0; i < axisLength; i++) { final Position position = positions.get(i); int ho = xxx; final List members = position.getMembers(); for (int j = 0; j < members.size() && ho < this.members.length; j++, ho++) { this.members[ho] = position.getMembers().get(j); } ++cellOrdinal; Util.discard(cellOrdinal); if (axis >= 2) { iterate(writer, axis - 1, ho); } else { writer.startElement("row");//abrimos la fila pos[axis] = i; //coordenadas: fila i pos[0] = 0; //coordenadas (0,i): columna 0 for (ColumnHandler columnHandler : columnHandlers) { if (columnHandler instanceof MemberColumnHandler) { columnHandler.write(writer, null, this.members); } else if (columnHandler instanceof CellColumnHandler) { columnHandler.write( writer, cellSet.getCell(posList), null); pos[0]++;// next col. } } writer.endElement();//cerramos la fila } } } private void emitCell(SaxWriter writer, Cell cell) throws OlapException { ++cellOrdinal; Util.discard(cellOrdinal); // Ignore empty cells. final Object cellValue = cell.getValue(); if (cellValue == null) { return; } writer.startElement("row"); for (ColumnHandler columnHandler : columnHandlers) { columnHandler.write(writer, cell, members); } writer.endElement(); } } private void discover(XmlaRequest request, XmlaResponse response) throws XmlaException { final RowsetDefinition rowsetDefinition = RowsetDefinition.valueOf(request.getRequestType()); Rowset rowset = rowsetDefinition.getRowset(request, this); Format format = getFormat(request, Format.Tabular); if (format != Format.Tabular) { throw new XmlaException( CLIENT_FAULT_FC, HSB_DISCOVER_FORMAT_CODE, HSB_DISCOVER_FORMAT_FAULT_FS, new UnsupportedOperationException( ": only 'Tabular' allowed in Discover method " + "type")); } final Content content = getContent(request); SaxWriter writer = response.getWriter(); writer.startDocument(); writer.startElement( prefix + ":DiscoverResponse", "xmlns:" + prefix, NS_XMLA); writer.startElement(prefix + ":return"); writer.startElement( "root", "xmlns", NS_XMLA_ROWSET, "xmlns:xsi", NS_XSI, "xmlns:xsd", NS_XSD, "xmlns:EX", NS_XMLA_EX); switch (content) { case Schema: case SchemaData: rowset.rowsetDefinition.writeRowsetXmlSchema(writer); break; } try { switch (content) { case Data: case SchemaData: rowset.unparse(response); break; } } catch (XmlaException xex) { throw xex; } catch (Throwable t) { throw new XmlaException( SERVER_FAULT_FC, HSB_DISCOVER_UNPARSE_CODE, HSB_DISCOVER_UNPARSE_FAULT_FS, t); } finally { // keep the tags balanced, even if there's an error try { writer.endElement(); writer.endElement(); writer.endElement(); } catch (Throwable e) { // Ignore any errors balancing the tags. The original exception // is more important. } } writer.endDocument(); } /** * Gets a Connection given a catalog (and implicitly the catalog's data * source) and the name of a user role. * *

    If you want to pass in a role object, and you are making the call * within the same JVM (i.e. not RPC), register the role using * {@link mondrian.olap.MondrianServer#getLockBox()} and pass in the moniker * for the generated lock box entry. The server will retrieve the role from * the moniker. * * @param catalog Catalog name * @param schema Schema name * @param role User role name * @return Connection * @throws XmlaException If error occurs */ protected OlapConnection getConnection( String catalog, String schema, final String role) throws XmlaException { return this.getConnection( catalog, schema, role, new Properties()); } /** * Gets a Connection given a catalog (and implicitly the catalog's data * source) and the name of a user role. * *

    If you want to pass in a role object, and you are making the call * within the same JVM (i.e. not RPC), register the role using * {@link mondrian.olap.MondrianServer#getLockBox()} and pass in the moniker * for the generated lock box entry. The server will retrieve the role from * the moniker. * * @param catalog Catalog name * @param schema Schema name * @param role User role name * @param props Properties to pass down to the native driver. * @return Connection * @throws XmlaException If error occurs */ protected OlapConnection getConnection( String catalog, String schema, final String role, Properties props) throws XmlaException { try { return connectionFactory.getConnection( catalog, schema, role, props); } catch (SecurityException e) { throw new XmlaException( CLIENT_FAULT_FC, HSB_ACCESS_DENIED_CODE, HSB_ACCESS_DENIED_FAULT_FS, e); } catch (SQLException e) { throw new XmlaException( CLIENT_FAULT_FC, HSB_CONNECTION_DATA_SOURCE_CODE, HSB_CONNECTION_DATA_SOURCE_FAULT_FS, e); } } private static class IntList extends AbstractList { private final int[] ints; IntList(int[] ints) { this.ints = ints; } public Integer get(int index) { return ints[index]; } public int size() { return ints.length; } } /** * Extra support for XMLA server. If a connection provides this interface, * the XMLA server will call methods in this interface instead of relying * on the core olap4j interface. * *

    The {@link mondrian.xmla.XmlaHandler.XmlaExtraImpl} class provides * a default implementation that uses the olap4j interface exclusively. */ public interface XmlaExtra { ResultSet executeDrillthrough( OlapStatement olapStatement, String mdx, boolean advanced, String tabFields, int[] rowCountSlot) throws SQLException; void setPreferList(OlapConnection connection); Date getSchemaLoadDate(Schema schema); int getLevelCardinality(Level level) throws OlapException; void getSchemaFunctionList( List funDefs, Schema schema, Util.Functor1 functionFilter); int getHierarchyCardinality(Hierarchy hierarchy) throws OlapException; int getHierarchyStructure(Hierarchy hierarchy); boolean isHierarchyParentChild(Hierarchy hierarchy); int getMeasureAggregator(Member member); void checkMemberOrdinal(Member member) throws OlapException; /** * Returns whether we should return a cell property in the XMLA result. * * @param cellSet Cell set * @param cellProperty Cell property definition * @param evenEmpty Whether to return even if cell has no properties * @return Whether to return cell property in XMLA result */ boolean shouldReturnCellProperty( CellSet cellSet, Property cellProperty, boolean evenEmpty); /** * Returns a list of names of roles in the given schema to which the * current user belongs. * * @param schema Schema * @return List of roles */ List getSchemaRoleNames(Schema schema); String getCubeType(Cube cube); boolean isLevelUnique(Level level); /** * Returns the defined properties of a level. (Not including system * properties that every level has.) * * @param level Level * @return Defined properties */ List getLevelProperties(Level level); boolean isPropertyInternal(Property property); /** * Returns a list of the data sources in this server. One element * per data source, each element a map whose keys are the XMLA fields * describing a data source: "DataSourceName", "DataSourceDescription", * "URL", etc. Unrecognized fields are ignored. * * @param connection Connection * @return List of data source definitions * @throws OlapException on error */ List> getDataSources(OlapConnection connection) throws OlapException; /** * Returns a map containing annotations on this element. * * @param element Element * @return Annotation map, never null */ Map getAnnotationMap(MetadataElement element) throws SQLException; class FunctionDefinition { public final String functionName; public final String description; public final String parameterList; public final int returnType; public final int origin; public final String interfaceName; public final String caption; public FunctionDefinition( String functionName, String description, String parameterList, int returnType, int origin, String interfaceName, String caption) { this.functionName = functionName; this.description = description; this.parameterList = parameterList; this.returnType = returnType; this.origin = origin; this.interfaceName = interfaceName; this.caption = caption; } } } /** * Default implementation of {@link mondrian.xmla.XmlaHandler.XmlaExtra}. * Connections based on mondrian's olap4j driver can do better. */ private static class XmlaExtraImpl implements XmlaExtra { public ResultSet executeDrillthrough( OlapStatement olapStatement, String mdx, boolean advanced, String tabFields, int[] rowCountSlot) throws SQLException { return olapStatement.executeQuery(mdx); } public void setPreferList(OlapConnection connection) { // ignore } public Date getSchemaLoadDate(Schema schema) { return new Date(); } public int getLevelCardinality(Level level) { return level.getCardinality(); } public void getSchemaFunctionList( List funDefs, Schema schema, Util.Functor1 functionFilter) { // no function definitions } public int getHierarchyCardinality(Hierarchy hierarchy) { int cardinality = 0; for (Level level : hierarchy.getLevels()) { cardinality += level.getCardinality(); } return cardinality; } public int getHierarchyStructure(Hierarchy hierarchy) { return 0; } public boolean isHierarchyParentChild(Hierarchy hierarchy) { return false; } public int getMeasureAggregator(Member member) { return RowsetDefinition.MdschemaMeasuresRowset .MDMEASURE_AGGR_UNKNOWN; } public void checkMemberOrdinal(Member member) { // nothing to do } public boolean shouldReturnCellProperty( CellSet cellSet, Property cellProperty, boolean evenEmpty) { return true; } public List getSchemaRoleNames(Schema schema) { return Collections.emptyList(); } public String getCubeType(Cube cube) { return RowsetDefinition.MdschemaCubesRowset.MD_CUBTYPE_CUBE; } public boolean isLevelUnique(Level level) { return false; } public List getLevelProperties(Level level) { return level.getProperties(); } public boolean isPropertyInternal(Property property) { return property instanceof Property.StandardMemberProperty && ((Property.StandardMemberProperty) property).isInternal() || property instanceof Property.StandardCellProperty && ((Property.StandardCellProperty) property).isInternal(); } public List> getDataSources( OlapConnection connection) throws OlapException { Database olapDb = connection.getOlapDatabase(); final String modes = createCsv(olapDb.getAuthenticationModes()); final String providerTypes = createCsv(olapDb.getProviderTypes()); return Collections.singletonList( Olap4jUtil.mapOf( "DataSourceName", (Object) olapDb.getName(), "DataSourceDescription", olapDb.getDescription(), "URL", olapDb.getURL(), "DataSourceInfo", olapDb.getDataSourceInfo(), "ProviderName", olapDb.getProviderName(), "ProviderType", providerTypes, "AuthenticationMode", modes)); } public Map getAnnotationMap(MetadataElement element) { return Collections.emptyMap(); } } private static String createCsv(Iterable iterable) { StringBuilder sb = new StringBuilder(); boolean first = true; for (Object o : iterable) { if (!first) { sb.append(','); } sb.append(o); first = false; } return sb.toString(); } /** * Creates an olap4j connection for responding to XMLA requests. * *

    A typical implementation will probably just use a * {@link javax.sql.DataSource} or a connect string, but it is important * that the connection is assigned to the correct catalog, schema and role * consistent with the client's XMLA context. */ public interface ConnectionFactory { /** * Creates a connection. * *

    The implementation passes the properties to the underlying driver. * * @param catalog The name of the catalog to use. * @param schema The name of the schema to use. * @param roleName The name of the role to use, or NULL. * @param props Properties to be passed to the underlying native driver. * @return An OlapConnection object. * @throws SQLException on error */ OlapConnection getConnection( String catalog, String schema, String roleName, Properties props) throws SQLException; /** * Returns a map of property name-value pairs with which to populate * the response to the DISCOVER_DATASOURCES request. * *

    Properties correspond to the columns of that request: * ""DataSourceName", et cetera.

    * *

    Returns null if there is no pre-configured response; in * which case, the driver will have to connect to get a response.

    * * @return Column names and values for the DISCOVER_DATASOURCES * response */ Map getPreConfiguredDiscoverDatasourcesResponse(); } } // End XmlaHandler.java mondrian-3.4.1/src/main/mondrian/xmla/XmlaException.java0000644000175000017500000000445011735330606023137 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2009 Pentaho and others // All Rights Reserved. */ package mondrian.xmla; import mondrian.olap.MondrianException; /** * An exception thrown while processing an XMLA request. The faultcode * corresponds to the SOAP Fault faultcode and the faultstring * to the SOAP Fault faultstring. * * @author Richard M. Emberson */ public class XmlaException extends MondrianException { public static String formatFaultCode(XmlaException xex) { return formatFaultCode(xex.getFaultCode(), xex.getCode()); } public static String formatFaultCode(String faultCode, String code) { return formatFaultCode( XmlaConstants.SOAP_PREFIX, faultCode, code); } public static String formatFaultCode( String nsPrefix, String faultCode, String code) { return nsPrefix + ':' + faultCode + '.' + code; } public static String formatDetail(String msg) { return XmlaConstants.FAULT_FS_PREFIX + msg; } public static Throwable getRootCause(Throwable throwable) { Throwable t = throwable; while (t.getCause() != null) { t = t.getCause(); } return t; } private final String faultCode; private final String code; private final String faultString; public XmlaException( String faultCode, String code, String faultString, Throwable cause) { super(faultString, cause); this.faultCode = faultCode; this.code = code; this.faultString = faultString; } public String getFaultCode() { return faultCode; } public String getCode() { return code; } public String getFaultString() { return faultString; } public String getDetail() { Throwable t = getCause(); t = getRootCause(t); String detail = t.getMessage(); return (detail != null) ? detail : t.getClass().getName(); } } // End XmlaException.java mondrian-3.4.1/src/main/mondrian/xmla/SaxWriter.java0000644000175000017500000000450411735330606022307 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho // All Rights Reserved. */ package mondrian.xmla; /** * SaxWriter is similar to a SAX {@link org.xml.sax.ContentHandler} * which, perversely, converts its events into an output document. * * @author jhyde * @author Gang Chen * @since 27 April, 2003 */ public interface SaxWriter { public void startDocument(); public void endDocument(); public void startElement(String name); public void startElement(String name, Object... attrs); public void endElement(); public void element(String name, Object... attrs); public void characters(String data); /** * Informs the writer that a sequence of elements of the same name is * starting. * *

    For XML, is equivalent to {@code startElement(name)}. * *

    For JSON, initiates the array construct: * *

    "name" : [
    *   { ... },
    *   { ... }
    * ]
    * * @param name Element name * @param subName Child element name */ public void startSequence(String name, String subName); /** * Informs the writer that a sequence of elements of the same name has * ended. */ public void endSequence(); /** * Generates a text-only element, {@code <name>data</name>}. * *

    For XML, this is equivalent to * *

    startElement(name);
    * characters(data);
    * endElement();
    * * but for JSON, generates {@code "name": "data"}. * * @param name Name of element * @param data Text content of element */ public void textElement(String name, Object data); public void completeBeforeElement(String tagName); /** * Sends a piece of text verbatim through the writer. It must be a piece * of well-formed XML. */ public void verbatim(String text); /** * Flushes any unwritten output. */ public void flush(); } // End SaxWriter.java mondrian-3.4.1/src/main/mondrian/xmla/Enumeration.java0000644000175000017500000000714211735330606022646 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2003-2005 Julian Hyde // Copyright (C) 2005-2010 Pentaho // All Rights Reserved. // // jhyde, May 2, 2003 */ package mondrian.xmla; import org.olap4j.impl.UnmodifiableArrayMap; import org.olap4j.metadata.XmlaConstant; import org.olap4j.metadata.XmlaConstants; import java.util.List; import java.util.Map; /** * Contains inner classes which define enumerations used in XML for Analysis. * * @author jhyde * @since May 2, 2003 */ public class Enumeration { public final String name; public final String description; public final RowsetDefinition.Type type; private final XmlaConstant.Dictionary dictionary; public static final Enumeration TREE_OP = new Enumeration( "TREE_OP", "Bitmap which controls which relatives of a member are " + "returned", RowsetDefinition.Type.Integer, org.olap4j.metadata.Member.TreeOp.getDictionary()); public static final Enumeration VISUAL_MODE = new Enumeration( "VisualMode", "This property determines the default behavior for visual " + "totals.", RowsetDefinition.Type.Integer, org.olap4j.metadata.XmlaConstants.VisualMode.getDictionary()); public static final Enumeration METHODS = new Enumeration( "Methods", "Set of methods for which a property is applicable", RowsetDefinition.Type.Enumeration, XmlaConstants.Method.getDictionary()); public static final Enumeration ACCESS = new Enumeration( "Access", "The read/write behavior of a property", RowsetDefinition.Type.Enumeration, XmlaConstants.Access.getDictionary()); public static final Enumeration AUTHENTICATION_MODE = new Enumeration( "AuthenticationMode", "Specification of what type of security mode the data source " + "uses.", RowsetDefinition.Type.EnumString, XmlaConstants.AuthenticationMode.getDictionary()); public static final Enumeration PROVIDER_TYPE = new Enumeration( "ProviderType", "The types of data supported by the provider.", RowsetDefinition.Type.Array, XmlaConstants.ProviderType.getDictionary()); public Enumeration( String name, String description, RowsetDefinition.Type type, XmlaConstant.Dictionary dictionary) { this.name = name; this.description = description; this.type = type; this.dictionary = dictionary; } public String getName() { return name; } public List getValues() { return dictionary.getValues(); } public enum ResponseMimeType { SOAP("text/xml"), JSON("application/json"); public static final Map MAP = UnmodifiableArrayMap.of( "application/soap+xml", SOAP, "application/xml", SOAP, "text/xml", SOAP, "application/json", JSON, "*/*", SOAP); private final String mimeType; ResponseMimeType(String mimeType) { this.mimeType = mimeType; } public String getMimeType() { return mimeType; } } } // End Enumeration.java mondrian-3.4.1/src/main/mondrian/xmla/impl/0000755000175000017500000000000011735330606020452 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/xmla/impl/DefaultXmlaServlet.java0000644000175000017500000007633111735330606025102 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.xmla.impl; import mondrian.xmla.*; import org.olap4j.impl.Olap4jUtil; import org.w3c.dom.*; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import java.io.*; import java.nio.ByteBuffer; import java.nio.channels.*; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.parsers.*; /** * Default implementation of XML/A servlet. * * @author Gang Chen */ public abstract class DefaultXmlaServlet extends XmlaServlet { protected static final String nl = System.getProperty("line.separator"); /** * Servlet config parameter that determines whether the xmla servlet * requires authenticated sessions. */ private static final String REQUIRE_AUTHENTICATED_SESSIONS = "requireAuthenticatedSessions"; private DocumentBuilderFactory domFactory = null; private boolean requireAuthenticatedSessions = false; /** * Session properties, keyed by session ID. Currently just username and * password. */ private final Map sessionInfos = new HashMap(); public void init(ServletConfig servletConfig) throws ServletException { super.init(servletConfig); this.domFactory = getDocumentBuilderFactory(); this.requireAuthenticatedSessions = Boolean.parseBoolean( servletConfig.getInitParameter(REQUIRE_AUTHENTICATED_SESSIONS)); } protected static DocumentBuilderFactory getDocumentBuilderFactory() { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setIgnoringComments(true); factory.setIgnoringElementContentWhitespace(true); factory.setNamespaceAware(true); return factory; } protected void unmarshallSoapMessage( HttpServletRequest request, Element[] requestSoapParts) throws XmlaException { try { InputStream inputStream; try { inputStream = request.getInputStream(); } catch (IllegalStateException ex) { throw new XmlaException( SERVER_FAULT_FC, USM_REQUEST_STATE_CODE, USM_REQUEST_STATE_FAULT_FS, ex); } catch (IOException ex) { // This is either Client or Server throw new XmlaException( SERVER_FAULT_FC, USM_REQUEST_INPUT_CODE, USM_REQUEST_INPUT_FAULT_FS, ex); } DocumentBuilder domBuilder; try { domBuilder = domFactory.newDocumentBuilder(); } catch (ParserConfigurationException ex) { throw new XmlaException( SERVER_FAULT_FC, USM_DOM_FACTORY_CODE, USM_DOM_FACTORY_FAULT_FS, ex); } Document soapDoc; try { soapDoc = domBuilder.parse(new InputSource(inputStream)); } catch (IOException ex) { // This is either Client or Server throw new XmlaException( SERVER_FAULT_FC, USM_DOM_PARSE_IO_CODE, USM_DOM_PARSE_IO_FAULT_FS, ex); } catch (SAXException ex) { // Assume client passed bad xml throw new XmlaException( CLIENT_FAULT_FC, USM_DOM_PARSE_CODE, USM_DOM_PARSE_FAULT_FS, ex); } /* Check SOAP message */ Element envElem = soapDoc.getDocumentElement(); if (LOGGER.isDebugEnabled()) { logXmlaRequest(envElem); } if ("Envelope".equals(envElem.getLocalName())) { if (!(NS_SOAP_ENV_1_1.equals(envElem.getNamespaceURI()))) { throw new XmlaException( CLIENT_FAULT_FC, USM_DOM_PARSE_CODE, USM_DOM_PARSE_FAULT_FS, new SAXException( "Invalid SOAP message: " + "Envelope element not in SOAP namespace")); } } else { throw new XmlaException( CLIENT_FAULT_FC, USM_DOM_PARSE_CODE, USM_DOM_PARSE_FAULT_FS, new SAXException( "Invalid SOAP message: " + "Top element not Envelope")); } Element[] childs = XmlaUtil.filterChildElements( envElem, NS_SOAP_ENV_1_1, "Header"); if (childs.length > 1) { throw new XmlaException( CLIENT_FAULT_FC, USM_DOM_PARSE_CODE, USM_DOM_PARSE_FAULT_FS, new SAXException( "Invalid SOAP message: " + "More than one Header elements")); } requestSoapParts[0] = childs.length == 1 ? childs[0] : null; childs = XmlaUtil.filterChildElements(envElem, NS_SOAP_ENV_1_1, "Body"); if (childs.length != 1) { throw new XmlaException( CLIENT_FAULT_FC, USM_DOM_PARSE_CODE, USM_DOM_PARSE_FAULT_FS, new SAXException( "Invalid SOAP message: " + "Does not have one Body element")); } requestSoapParts[1] = childs[0]; } catch (XmlaException xex) { throw xex; } catch (Exception ex) { throw new XmlaException( SERVER_FAULT_FC, USM_UNKNOWN_CODE, USM_UNKNOWN_FAULT_FS, ex); } } protected void logXmlaRequest(Element envElem) { final StringWriter writer = new StringWriter(); writer.write("XML/A request content"); writer.write(nl); XmlaUtil.element2Text(envElem, writer); LOGGER.debug(writer.toString()); } /** * {@inheritDoc} * *

    See if there is a "mustUnderstand" header element. * If there is a BeginSession element, then generate a session id and * add to context Map.

    * *

    Excel 2000 and Excel XP generate both a BeginSession, Session and * EndSession mustUnderstand=1 * in the "urn:schemas-microsoft-com:xml-analysis" namespace * Header elements and a NamespaceCompatibility mustUnderstand=0 * in the "http://schemas.microsoft.com/analysisservices/2003/xmla" * namespace. Here we handle only the session Header elements. * *

    We also handle the Security element.

    */ protected void handleSoapHeader( HttpServletResponse response, Element[] requestSoapParts, byte[][] responseSoapParts, Map context) throws XmlaException { try { Element hdrElem = requestSoapParts[0]; if ((hdrElem == null) || (! hdrElem.hasChildNodes())) { return; } String encoding = response.getCharacterEncoding(); byte[] bytes = null; NodeList nlst = hdrElem.getChildNodes(); int nlen = nlst.getLength(); boolean authenticatedSession = false; boolean beginSession = false; for (int i = 0; i < nlen; i++) { Node n = nlst.item(i); if (!(n instanceof Element)) { continue; } Element e = (Element) n; String localName = e.getLocalName(); if (localName.equals(XMLA_SECURITY) && NS_SOAP_SECEXT.equals(e.getNamespaceURI())) { // Example: // // // // MICHELE // ROSSI // // // NodeList childNodes = e.getChildNodes(); Element userNameToken = (Element) childNodes.item(1); NodeList userNamePassword = userNameToken.getChildNodes(); Element username = (Element) userNamePassword.item(1); Element password = (Element) userNamePassword.item(3); String userNameStr = username.getChildNodes().item(0).getNodeValue(); context.put(CONTEXT_XMLA_USERNAME, userNameStr); String passwordStr = ""; if (password.getChildNodes().item(0) != null) { passwordStr = password.getChildNodes().item(0).getNodeValue(); } context.put(CONTEXT_XMLA_PASSWORD, passwordStr); if ("".equals(passwordStr) || null == passwordStr) { LOGGER.warn( "Security header for user [" + userNameStr + "] provided without password"); } authenticatedSession = true; continue; } // Make sure Element has mustUnderstand=1 attribute. Attr attr = e.getAttributeNode(SOAP_MUST_UNDERSTAND_ATTR); boolean mustUnderstandValue = attr != null && attr.getValue() != null && attr.getValue().equals("1"); if (!mustUnderstandValue) { continue; } // Is it an XMLA element if (!NS_XMLA.equals(e.getNamespaceURI())) { continue; } // So, an XMLA mustUnderstand-er // Do we know what to do with it // We understand: // BeginSession // Session // EndSession String sessionIdStr; if (localName.equals(XMLA_BEGIN_SESSION)) { sessionIdStr = generateSessionId(context); context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr); context.put( CONTEXT_XMLA_SESSION_STATE, CONTEXT_XMLA_SESSION_STATE_BEGIN); } else if (localName.equals(XMLA_SESSION)) { sessionIdStr = getSessionIdFromRequest(e, context); SessionInfo sessionInfo = getSessionInfo(sessionIdStr); if (sessionInfo != null) { context.put(CONTEXT_XMLA_USERNAME, sessionInfo.user); context.put( CONTEXT_XMLA_PASSWORD, sessionInfo.password); } context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr); context.put( CONTEXT_XMLA_SESSION_STATE, CONTEXT_XMLA_SESSION_STATE_WITHIN); } else if (localName.equals(XMLA_END_SESSION)) { sessionIdStr = getSessionIdFromRequest(e, context); context.put( CONTEXT_XMLA_SESSION_STATE, CONTEXT_XMLA_SESSION_STATE_END); } else { // error String msg = "Invalid XML/A message: Unknown " + "\"mustUnderstand\" XMLA Header element \"" + localName + "\""; throw new XmlaException( MUST_UNDERSTAND_FAULT_FC, HSH_MUST_UNDERSTAND_CODE, HSH_MUST_UNDERSTAND_FAULT_FS, new RuntimeException(msg)); } StringBuilder buf = new StringBuilder(100); buf.append(""); bytes = buf.toString().getBytes(encoding); if (authenticatedSession) { String username = (String) context.get(CONTEXT_XMLA_USERNAME); String password = (String) context.get(CONTEXT_XMLA_PASSWORD); String sessionId = (String) context.get(CONTEXT_XMLA_SESSION_ID); LOGGER.debug( "New authenticated session; storing credentials [" + username + "/********] for session id [" + sessionId + "]"); saveSessionInfo( username, password, sessionId); } else { if (beginSession && requireAuthenticatedSessions) { throw new XmlaException( XmlaConstants.CLIENT_FAULT_FC, XmlaConstants.CHH_AUTHORIZATION_CODE, XmlaConstants.CHH_AUTHORIZATION_FAULT_FS, new Exception("Session Credentials NOT PROVIDED")); } } } responseSoapParts[0] = bytes; } catch (XmlaException xex) { throw xex; } catch (Exception ex) { throw new XmlaException( SERVER_FAULT_FC, HSH_UNKNOWN_CODE, HSH_UNKNOWN_FAULT_FS, ex); } } protected String generateSessionId(Map context) { for (XmlaRequestCallback callback : getCallbacks()) { final String sessionId = callback.generateSessionId(context); if (sessionId != null) { return sessionId; } } // Generate a pseudo-random new session ID. return Long.toString(17L * System.nanoTime() + 3L * System.currentTimeMillis(), 35); } private static String getSessionIdFromRequest(Element e, Map context) throws Exception { // extract the SessionId attrs value and put into context Attr attr = e.getAttributeNode(XMLA_SESSION_ID); if (attr == null) { throw new SAXException( "Invalid XML/A message: " + XMLA_SESSION + " Header element with no " + XMLA_SESSION_ID + " attribute"); } String sessionId = attr.getValue(); if (sessionId == null) { throw new SAXException( "Invalid XML/A message: " + XMLA_SESSION + " Header element with " + XMLA_SESSION_ID + " attribute but no attribute value"); } return sessionId; } protected void handleSoapBody( HttpServletResponse response, Element[] requestSoapParts, byte[][] responseSoapParts, Map context) throws XmlaException { try { String encoding = response.getCharacterEncoding(); Element hdrElem = requestSoapParts[0]; // not used Element bodyElem = requestSoapParts[1]; Element[] dreqs = XmlaUtil.filterChildElements(bodyElem, NS_XMLA, "Discover"); Element[] ereqs = XmlaUtil.filterChildElements(bodyElem, NS_XMLA, "Execute"); if (dreqs.length + ereqs.length != 1) { throw new XmlaException( CLIENT_FAULT_FC, HSB_BAD_SOAP_BODY_CODE, HSB_BAD_SOAP_BODY_FAULT_FS, new RuntimeException( "Invalid XML/A message: Body has " + dreqs.length + " Discover Requests and " + ereqs.length + " Execute Requests")); } Element xmlaReqElem = (dreqs.length == 0 ? ereqs[0] : dreqs[0]); ByteArrayOutputStream osBuf = new ByteArrayOutputStream(); // use context variable 'role_name' as this request's XML/A role String roleName = (String) context.get(CONTEXT_ROLE_NAME); String username = (String) context.get(CONTEXT_XMLA_USERNAME); String password = (String) context.get(CONTEXT_XMLA_PASSWORD); String sessionId = (String) context.get(CONTEXT_XMLA_SESSION_ID); XmlaRequest xmlaReq = new DefaultXmlaRequest( xmlaReqElem, roleName, username, password, sessionId); // "ResponseMimeType" may be in the context if the "Accept" HTTP // header was specified. But override if the SOAP request has the // "ResponseMimeType" property. Enumeration.ResponseMimeType responseMimeType = Enumeration.ResponseMimeType.SOAP; final String responseMimeTypeName = xmlaReq.getProperties().get("ResponseMimeType"); if (responseMimeTypeName != null) { responseMimeType = Enumeration.ResponseMimeType.MAP.get( responseMimeTypeName); if (responseMimeType != null) { context.put(CONTEXT_MIME_TYPE, responseMimeType); } } XmlaResponse xmlaRes = new DefaultXmlaResponse(osBuf, encoding, responseMimeType); try { getXmlaHandler().process(xmlaReq, xmlaRes); } catch (XmlaException ex) { throw ex; } catch (Exception ex) { throw new XmlaException( SERVER_FAULT_FC, HSB_PROCESS_CODE, HSB_PROCESS_FAULT_FS, ex); } responseSoapParts[1] = osBuf.toByteArray(); } catch (XmlaException xex) { throw xex; } catch (Exception ex) { throw new XmlaException( SERVER_FAULT_FC, HSB_UNKNOWN_CODE, HSB_UNKNOWN_FAULT_FS, ex); } } protected void marshallSoapMessage( HttpServletResponse response, byte[][] responseSoapParts, Enumeration.ResponseMimeType responseMimeType) throws XmlaException { try { // If CharacterEncoding was set in web.xml, use this value String encoding = (charEncoding != null) ? charEncoding : response.getCharacterEncoding(); /* * Since we just reset response, encoding and content-type were * reset too */ if (charEncoding != null) { response.setCharacterEncoding(charEncoding); } switch (responseMimeType) { case JSON: response.setContentType("application/json"); break; case SOAP: default: response.setContentType("text/xml"); break; } /* * The setCharacterEncoding, setContentType, or setLocale method * must be called BEFORE getWriter or getOutputStream and before * committing the response for the character encoding to be used. * * @see javax.servlet.ServletResponse */ OutputStream outputStream = response.getOutputStream(); byte[] soapHeader = responseSoapParts[0]; byte[] soapBody = responseSoapParts[1]; Object[] byteChunks = null; try { switch (responseMimeType) { case JSON: byteChunks = new Object[] { soapBody, }; break; case SOAP: default: String s0 = "\n<" + SOAP_PREFIX + ":Envelope xmlns:" + SOAP_PREFIX + "=\"" + NS_SOAP_ENV_1_1 + "\" " + SOAP_PREFIX + ":encodingStyle=\"" + NS_SOAP_ENC_1_1 + "\" >" + "\n<" + SOAP_PREFIX + ":Header>\n"; String s2 = "\n<" + SOAP_PREFIX + ":Body>\n"; String s4 = "\n\n\n"; byteChunks = new Object[] { s0.getBytes(encoding), soapHeader, s2.getBytes(encoding), soapBody, s4.getBytes(encoding), }; break; } } catch (UnsupportedEncodingException uee) { LOGGER.warn( "This should be handled at begin of processing request", uee); } if (LOGGER.isDebugEnabled()) { StringBuilder buf = new StringBuilder(100); buf.append("XML/A response content").append(nl); try { for (Object byteChunk : byteChunks) { byte[] chunk = (byte[]) byteChunk; if (chunk != null && chunk.length > 0) { buf.append(new String(chunk, encoding)); } } } catch (UnsupportedEncodingException uee) { LOGGER.warn( "This should be handled at begin of processing request", uee); } LOGGER.debug(buf.toString()); } if (LOGGER.isDebugEnabled()) { StringBuilder buf = new StringBuilder(); buf.append("XML/A response content").append(nl); } try { int bufferSize = 4096; ByteBuffer buffer = ByteBuffer.allocate(bufferSize); WritableByteChannel wch = Channels.newChannel(outputStream); ReadableByteChannel rch; for (Object byteChunk : byteChunks) { if (byteChunk == null || ((byte[]) byteChunk).length == 0) { continue; } rch = Channels.newChannel( new ByteArrayInputStream((byte[]) byteChunk)); int readSize; do { buffer.clear(); readSize = rch.read(buffer); buffer.flip(); int writeSize = 0; while ((writeSize += wch.write(buffer)) < readSize) { } } while (readSize == bufferSize); rch.close(); } outputStream.flush(); } catch (IOException ioe) { LOGGER.error( "Damn exception when transferring bytes over sockets", ioe); } } catch (XmlaException xex) { throw xex; } catch (Exception ex) { throw new XmlaException( SERVER_FAULT_FC, MSM_UNKNOWN_CODE, MSM_UNKNOWN_FAULT_FS, ex); } } /** * This produces a SOAP 1.1 version Fault element - not a 1.2 version. * */ protected void handleFault( HttpServletResponse response, byte[][] responseSoapParts, Phase phase, Throwable t) { // Regardless of whats been put into the response so far, clear // it out. response.reset(); // NOTE: if you can think of better/other status codes to use // for the various phases, please make changes. // I think that XMLA faults always returns OK. switch (phase) { case VALIDATE_HTTP_HEAD: response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); break; case INITIAL_PARSE: case CALLBACK_PRE_ACTION: case PROCESS_HEADER: case PROCESS_BODY: case CALLBACK_POST_ACTION: case SEND_RESPONSE: response.setStatus(HttpServletResponse.SC_OK); break; } String code; String faultCode; String faultString; String detail; if (t instanceof XmlaException) { XmlaException xex = (XmlaException) t; code = xex.getCode(); faultString = xex.getFaultString(); faultCode = XmlaException.formatFaultCode(xex); detail = XmlaException.formatDetail(xex.getDetail()); } else { // some unexpected Throwable t = XmlaException.getRootCause(t); code = UNKNOWN_ERROR_CODE; faultString = UNKNOWN_ERROR_FAULT_FS; faultCode = XmlaException.formatFaultCode( SERVER_FAULT_FC, code); detail = XmlaException.formatDetail(t.getMessage()); } String encoding = response.getCharacterEncoding(); ByteArrayOutputStream osBuf = new ByteArrayOutputStream(); try { SaxWriter writer = new DefaultSaxWriter(osBuf, encoding); writer.startDocument(); writer.startElement(SOAP_PREFIX + ":Fault"); // The faultcode element is intended for use by software to provide // an algorithmic mechanism for identifying the fault. The faultcode // MUST be present in a SOAP Fault element and the faultcode value // MUST be a qualified name writer.startElement("faultcode"); writer.characters(faultCode); writer.endElement(); // The faultstring element is intended to provide a human readable // explanation of the fault and is not intended for algorithmic // processing. writer.startElement("faultstring"); writer.characters(faultString); writer.endElement(); // The faultactor element is intended to provide information about // who caused the fault to happen within the message path writer.startElement("faultactor"); writer.characters(FAULT_ACTOR); writer.endElement(); // The detail element is intended for carrying application specific // error information related to the Body element. It MUST be present // if the contents of the Body element could not be successfully // processed. It MUST NOT be used to carry information about error // information belonging to header entries. Detailed error // information belonging to header entries MUST be carried within // header entries. if (phase != Phase.PROCESS_HEADER) { writer.startElement("detail"); writer.startElement( FAULT_NS_PREFIX + ":error", "xmlns:" + FAULT_NS_PREFIX, MONDRIAN_NAMESPACE); writer.startElement("code"); writer.characters(code); writer.endElement(); // code writer.startElement("desc"); writer.characters(detail); writer.endElement(); // desc writer.endElement(); // error writer.endElement(); // detail } writer.endElement(); // writer.endDocument(); } catch (UnsupportedEncodingException uee) { LOGGER.warn( "This should be handled at begin of processing request", uee); } catch (Exception e) { LOGGER.error( "Unexcepted runimt exception when handing SOAP fault :("); } responseSoapParts[1] = osBuf.toByteArray(); } private SessionInfo getSessionInfo(String sessionId) { if (sessionId == null) { return null; } SessionInfo sessionInfo = null; synchronized (sessionInfos) { sessionInfo = sessionInfos.get(sessionId); } if (sessionInfo == null) { LOGGER.error( "No login credentials for found for session ["+ sessionId + "]"); } else { LOGGER.debug( "Found credentials for session id [" + sessionId + "], username=[" + sessionInfo.user + "] in servlet cache"); } return sessionInfo; } private SessionInfo saveSessionInfo( String username, String password, String sessionId) { synchronized (sessionInfos) { SessionInfo sessionInfo = sessionInfos.get(sessionId); if (sessionInfo != null && Olap4jUtil.equal(sessionInfo.user, username)) { // Overwrite the password, but only if it is non-empty. // (Sometimes Simba sends the credentials object again // but without a password.) if (password != null && password.length() > 0) { sessionInfo = new SessionInfo(username, password); sessionInfos.put(sessionId, sessionInfo); } } else { // A credentials object was stored against the provided session // ID but the username didn't match, so create a new holder. sessionInfo = new SessionInfo(username, password); sessionInfos.put(sessionId, sessionInfo); } return sessionInfo; } } /** * Holds authentication credentials of a XMLA session. */ private static class SessionInfo { final String user; final String password; public SessionInfo(String user, String password) { this.user = user; this.password = password; } } } // End DefaultXmlaServlet.java mondrian-3.4.1/src/main/mondrian/xmla/impl/JsonSaxWriter.java0000644000175000017500000001410311735330606024076 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2010 Pentaho // All Rights Reserved. */ package mondrian.xmla.impl; import mondrian.olap.Util; import mondrian.util.ArrayStack; import mondrian.xmla.SaxWriter; import java.io.IOException; import java.io.OutputStream; import java.util.Arrays; /** * Implementation of SaxWriter which, perversely, generates a * JSON (JavaScript Object Notation) document. * * @author jhyde */ class JsonSaxWriter implements SaxWriter { private final StringBuilder buf = new StringBuilder(); private int indent; private String[] indentStrings = INITIAL_INDENT_STRINGS; private String indentString = indentStrings[0]; private final ArrayStack stack = new ArrayStack(); private OutputStream outputStream; private static final String[] INITIAL_INDENT_STRINGS = { "", " ", " ", " ", " ", " ", " ", " ", " ", " ", }; /** * Creates a JsonSaxWriter. * * @param outputStream Output stream */ public JsonSaxWriter(OutputStream outputStream) { this.outputStream = outputStream; } public void startDocument() { stack.push(new Frame(null)); } public void endDocument() { stack.pop(); flush(); } public void startSequence(String name, String subName) { comma(); buf.append(indentString); if (name == null) { name = subName; } if (stack.peek().name != null) { assert name.equals(stack.peek().name) : "In sequence [" + stack.peek() + "], element name [" + name + "]"; buf.append("["); } else { Util.quoteForMdx(buf, name); buf.append(": ["); } assert subName != null; stack.push(new Frame(subName)); indent(); } public void endSequence() { assert stack.peek() != null : "not in sequence"; stack.pop(); outdent(); buf.append("\n"); buf.append(indentString); buf.append("]"); } public void startElement(String name) { comma(); buf.append(indentString); if (stack.peek().name != null) { assert name.equals(stack.peek().name) : "In sequence [" + stack.peek() + "], element name [" + name + "]"; buf.append("{"); } else { Util.quoteForMdx(buf, name); buf.append(": {"); } stack.push(new Frame(null)); indent(); } public void startElement(String name, Object... attrs) { startElement(name); for (int i = 0; i < attrs.length;) { if (i > 0) { buf.append(",\n"); } else { buf.append("\n"); } String attr = (String) attrs[i++]; buf.append(indentString); Util.quoteForMdx(buf, attr); buf.append(": "); Object value = attrs[i++]; value(value); } stack.peek().ordinal = attrs.length / 2; } public void endElement() { Frame prev = stack.pop(); assert prev.name == null : "Ended an element, but in sequence " + prev.name; buf.append("\n"); outdent(); buf.append(indentString); buf.append("}"); } public void element(String name, Object... attrs) { startElement(name, attrs); endElement(); } public void characters(String data) { throw new UnsupportedOperationException(); } public void textElement(String name, Object data) { comma(); buf.append(indentString); Util.quoteForMdx(buf, name); buf.append(": "); value(data); } public void completeBeforeElement(String tagName) { throw new UnsupportedOperationException(); } public void verbatim(String text) { throw new UnsupportedOperationException(); } public void flush() { try { outputStream.write(buf.toString().substring(1).getBytes()); } catch (IOException e) { throw Util.newError(e, "While encoding JSON response"); } } // helper methods private void indent() { ++indent; if (indent >= indentStrings.length) { final int newLength = indentStrings.length * 2 + 1; final int INDENT = 2; assert indentStrings[1].length() == INDENT; char[] chars = new char[newLength * INDENT]; Arrays.fill(chars, ' '); String s = new String(chars); indentStrings = new String[newLength]; for (int i = 0; i < newLength; ++i) { indentStrings[i] = s.substring(0, i * INDENT); } } indentString = indentStrings[indent]; } private void outdent() { indentString = indentStrings[--indent]; } /** * Writes a value with appropriate quoting for a JavaScript constant * of that type. * *

    Examples: {@code "a \"quoted\" string"} (string), * {@code 12} (int), {@code 12.345} (float), {@code null} (null value). * * @param value Value */ private void value(Object value) { if (value instanceof String) { String s = (String) value; Util.quoteForMdx(buf, s); } else { buf.append(value); } } private void comma() { if (stack.peek().ordinal++ > 0) { buf.append(",\n"); } else { buf.append("\n"); } } private static class Frame { final String name; int ordinal; Frame(String name) { this.name = name; } } } // End JsonSaxWriter.java mondrian-3.4.1/src/main/mondrian/xmla/impl/DefaultXmlaRequest.java0000644000175000017500000003204511735330606025100 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.xmla.impl; import mondrian.olap.Util; import mondrian.xmla.*; import org.apache.log4j.Logger; import org.w3c.dom.*; import java.util.*; import static org.olap4j.metadata.XmlaConstants.Method; /** * Default implementation of {@link mondrian.xmla.XmlaRequest} by DOM API. * * @author Gang Chen */ public class DefaultXmlaRequest implements XmlaRequest, XmlaConstants { private static final Logger LOGGER = Logger.getLogger(DefaultXmlaRequest.class); private static final String MSG_INVALID_XMLA = "Invalid XML/A message"; /* common content */ private Method method; private Map properties; private final String roleName; /* EXECUTE content */ private String statement; private boolean drillthrough; /* DISCOVER contnet */ private String requestType; private Map restrictions; private final String username; private final String password; private final String sessionId; public DefaultXmlaRequest( final Element xmlaRoot, final String roleName, final String username, final String password, final String sessionId) throws XmlaException { init(xmlaRoot); this.roleName = roleName; this.username = username; this.password = password; this.sessionId = sessionId; } public String getSessionId() { return sessionId; } public String getUsername() { return username; } public String getPassword() { return password; } public Method getMethod() { return method; } public Map getProperties() { return properties; } public Map getRestrictions() { if (method != Method.DISCOVER) { throw new IllegalStateException( "Only METHOD_DISCOVER has restrictions"); } return restrictions; } public String getStatement() { if (method != Method.EXECUTE) { throw new IllegalStateException( "Only METHOD_EXECUTE has statement"); } return statement; } public String getRoleName() { return roleName; } public String getRequestType() { if (method != Method.DISCOVER) { throw new IllegalStateException( "Only METHOD_DISCOVER has requestType"); } return requestType; } public boolean isDrillThrough() { if (method != Method.EXECUTE) { throw new IllegalStateException( "Only METHOD_EXECUTE determines drillthrough"); } return drillthrough; } protected final void init(Element xmlaRoot) throws XmlaException { if (NS_XMLA.equals(xmlaRoot.getNamespaceURI())) { String lname = xmlaRoot.getLocalName(); if ("Discover".equals(lname)) { method = Method.DISCOVER; initDiscover(xmlaRoot); } else if ("Execute".equals(lname)) { method = Method.EXECUTE; initExecute(xmlaRoot); } else { // Note that is code will never be reached because // the error will be caught in // DefaultXmlaServlet.handleSoapBody first StringBuilder buf = new StringBuilder(100); buf.append(MSG_INVALID_XMLA); buf.append(": Bad method name \""); buf.append(lname); buf.append("\""); throw new XmlaException( CLIENT_FAULT_FC, HSB_BAD_METHOD_CODE, HSB_BAD_METHOD_FAULT_FS, Util.newError(buf.toString())); } } else { // Note that is code will never be reached because // the error will be caught in // DefaultXmlaServlet.handleSoapBody first StringBuilder buf = new StringBuilder(100); buf.append(MSG_INVALID_XMLA); buf.append(": Bad namespace url \""); buf.append(xmlaRoot.getNamespaceURI()); buf.append("\""); throw new XmlaException( CLIENT_FAULT_FC, HSB_BAD_METHOD_NS_CODE, HSB_BAD_METHOD_NS_FAULT_FS, Util.newError(buf.toString())); } } private void initDiscover(Element discoverRoot) throws XmlaException { Element[] childElems = XmlaUtil.filterChildElements( discoverRoot, NS_XMLA, "RequestType"); if (childElems.length != 1) { StringBuilder buf = new StringBuilder(100); buf.append(MSG_INVALID_XMLA); buf.append(": Wrong number of RequestType elements: "); buf.append(childElems.length); throw new XmlaException( CLIENT_FAULT_FC, HSB_BAD_REQUEST_TYPE_CODE, HSB_BAD_REQUEST_TYPE_FAULT_FS, Util.newError(buf.toString())); } requestType = XmlaUtil.textInElement(childElems[0]); // childElems = XmlaUtil.filterChildElements( discoverRoot, NS_XMLA, "Restrictions"); if (childElems.length != 1) { StringBuilder buf = new StringBuilder(100); buf.append(MSG_INVALID_XMLA); buf.append(": Wrong number of Restrictions elements: "); buf.append(childElems.length); throw new XmlaException( CLIENT_FAULT_FC, HSB_BAD_RESTRICTIONS_CODE, HSB_BAD_RESTRICTIONS_FAULT_FS, Util.newError(buf.toString())); } initRestrictions(childElems[0]); // childElems = XmlaUtil.filterChildElements( discoverRoot, NS_XMLA, "Properties"); if (childElems.length != 1) { StringBuilder buf = new StringBuilder(100); buf.append(MSG_INVALID_XMLA); buf.append(": Wrong number of Properties elements: "); buf.append(childElems.length); throw new XmlaException( CLIENT_FAULT_FC, HSB_BAD_PROPERTIES_CODE, HSB_BAD_PROPERTIES_FAULT_FS, Util.newError(buf.toString())); } initProperties(childElems[0]); // } private void initExecute(Element executeRoot) throws XmlaException { Element[] childElems = XmlaUtil.filterChildElements( executeRoot, NS_XMLA, "Command"); if (childElems.length != 1) { StringBuilder buf = new StringBuilder(100); buf.append(MSG_INVALID_XMLA); buf.append(": Wrong number of Command elements: "); buf.append(childElems.length); throw new XmlaException( CLIENT_FAULT_FC, HSB_BAD_COMMAND_CODE, HSB_BAD_COMMAND_FAULT_FS, Util.newError(buf.toString())); } initCommand(childElems[0]); // childElems = XmlaUtil.filterChildElements( executeRoot, NS_XMLA, "Properties"); if (childElems.length != 1) { StringBuilder buf = new StringBuilder(100); buf.append(MSG_INVALID_XMLA); buf.append(": Wrong number of Properties elements: "); buf.append(childElems.length); throw new XmlaException( CLIENT_FAULT_FC, HSB_BAD_PROPERTIES_CODE, HSB_BAD_PROPERTIES_FAULT_FS, Util.newError(buf.toString())); } initProperties(childElems[0]); // } private void initRestrictions(Element restrictionsRoot) throws XmlaException { Map> restrictions = new HashMap>(); Element[] childElems = XmlaUtil.filterChildElements( restrictionsRoot, NS_XMLA, "RestrictionList"); if (childElems.length == 1) { NodeList nlst = childElems[0].getChildNodes(); for (int i = 0, nlen = nlst.getLength(); i < nlen; i++) { Node n = nlst.item(i); if (n instanceof Element) { Element e = (Element) n; if (NS_XMLA.equals(e.getNamespaceURI())) { String key = e.getLocalName(); String value = XmlaUtil.textInElement(e); List values; if (restrictions.containsKey(key)) { values = restrictions.get(key); } else { values = new ArrayList(); restrictions.put(key, values); } if (LOGGER.isDebugEnabled()) { LOGGER.debug( "DefaultXmlaRequest.initRestrictions: " + " key=\"" + key + "\", value=\"" + value + "\""); } values.add(value); } } } } else if (childElems.length > 1) { StringBuilder buf = new StringBuilder(100); buf.append(MSG_INVALID_XMLA); buf.append(": Wrong number of RestrictionList elements: "); buf.append(childElems.length); throw new XmlaException( CLIENT_FAULT_FC, HSB_BAD_RESTRICTION_LIST_CODE, HSB_BAD_RESTRICTION_LIST_FAULT_FS, Util.newError(buf.toString())); } this.restrictions = (Map) Collections.unmodifiableMap(restrictions); } private void initProperties(Element propertiesRoot) throws XmlaException { Map properties = new HashMap(); Element[] childElems = XmlaUtil.filterChildElements( propertiesRoot, NS_XMLA, "PropertyList"); if (childElems.length == 1) { NodeList nlst = childElems[0].getChildNodes(); for (int i = 0, nlen = nlst.getLength(); i < nlen; i++) { Node n = nlst.item(i); if (n instanceof Element) { Element e = (Element) n; if (NS_XMLA.equals(e.getNamespaceURI())) { String key = e.getLocalName(); String value = XmlaUtil.textInElement(e); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "DefaultXmlaRequest.initProperties: " + " key=\"" + key + "\", value=\"" + value + "\""); } properties.put(key, value); } } } } else if (childElems.length > 1) { StringBuilder buf = new StringBuilder(100); buf.append(MSG_INVALID_XMLA); buf.append(": Wrong number of PropertyList elements: "); buf.append(childElems.length); throw new XmlaException( CLIENT_FAULT_FC, HSB_BAD_PROPERTIES_LIST_CODE, HSB_BAD_PROPERTIES_LIST_FAULT_FS, Util.newError(buf.toString())); } else { } this.properties = Collections.unmodifiableMap(properties); } private void initCommand(Element commandRoot) throws XmlaException { Element[] childElems = XmlaUtil.filterChildElements( commandRoot, NS_XMLA, "Statement"); if (childElems.length != 1) { StringBuilder buf = new StringBuilder(100); buf.append(MSG_INVALID_XMLA); buf.append(": Wrong number of Statement elements: "); buf.append(childElems.length); throw new XmlaException( CLIENT_FAULT_FC, HSB_BAD_STATEMENT_CODE, HSB_BAD_STATEMENT_FAULT_FS, Util.newError(buf.toString())); } statement = XmlaUtil.textInElement(childElems[0]).replaceAll("\\r", ""); drillthrough = statement.toUpperCase().indexOf("DRILLTHROUGH") != -1; } } // End DefaultXmlaRequest.java mondrian-3.4.1/src/main/mondrian/xmla/impl/MondrianXmlaServlet.java0000644000175000017500000001404511735330606025257 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho // All Rights Reserved. */ package mondrian.xmla.impl; import mondrian.olap.MondrianServer; import mondrian.olap.Util; import mondrian.server.RepositoryContentFinder; import mondrian.server.UrlRepositoryContentFinder; import mondrian.spi.CatalogLocator; import mondrian.spi.impl.ServletContextCatalogLocator; import mondrian.xmla.XmlaHandler; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import javax.servlet.*; /** * Extension to {@link mondrian.xmla.XmlaServlet} that instantiates a * Mondrian engine. * * @author jhyde */ public class MondrianXmlaServlet extends DefaultXmlaServlet { public static final String DEFAULT_DATASOURCE_FILE = "datasources.xml"; protected MondrianServer server; @Override protected XmlaHandler.ConnectionFactory createConnectionFactory( ServletConfig servletConfig) throws ServletException { if (server == null) { // A derived class can alter how the calalog locator object is // created. CatalogLocator catalogLocator = makeCatalogLocator(servletConfig); String dataSources = makeDataSourcesUrl(servletConfig); RepositoryContentFinder contentFinder = makeContentFinder(dataSources); server = MondrianServer.createWithRepository( contentFinder, catalogLocator); } return (XmlaHandler.ConnectionFactory) server; } @Override public void destroy() { super.destroy(); if (server != null) { server.shutdown(); server = null; } } /** * Creates a callback for reading the repository. Derived classes may * override. * * @param dataSources Data sources * @return Callback for reading repository */ protected RepositoryContentFinder makeContentFinder(String dataSources) { return new UrlRepositoryContentFinder(dataSources); } /** * Make catalog locator. Derived classes can roll their own. * * @param servletConfig Servlet configuration info * @return Catalog locator */ protected CatalogLocator makeCatalogLocator(ServletConfig servletConfig) { ServletContext servletContext = servletConfig.getServletContext(); return new ServletContextCatalogLocator(servletContext); } /** * Creates the URL where the data sources file is to be found. * *

    Derived classes can roll their own. * *

    If there is an initParameter called "DataSourcesConfig" * get its value, replace any "${key}" content with "value" where * "key/value" are System properties, and try to create a URL * instance out of it. If that fails, then assume its a * real filepath and if the file exists then create a URL from it * (but only if the file exists). * If there is no initParameter with that name, then attempt to * find the file called "datasources.xml" under "WEB-INF/" * and if it exists, use it. * * @param servletConfig Servlet config * @return URL where data sources are to be found */ protected String makeDataSourcesUrl(ServletConfig servletConfig) { String paramValue = servletConfig.getInitParameter(PARAM_DATASOURCES_CONFIG); // if false, then do not throw exception if the file/url // can not be found boolean optional = getBooleanInitParameter( servletConfig, PARAM_OPTIONAL_DATASOURCE_CONFIG); URL dataSourcesConfigUrl = null; try { if (paramValue == null) { // fallback to default String defaultDS = "WEB-INF/" + DEFAULT_DATASOURCE_FILE; ServletContext servletContext = servletConfig.getServletContext(); File realPath = new File(servletContext.getRealPath(defaultDS)); if (realPath.exists()) { // only if it exists dataSourcesConfigUrl = realPath.toURL(); return dataSourcesConfigUrl.toString(); } else { return null; } } else if (paramValue.startsWith("inline:")) { return paramValue; } else { paramValue = Util.replaceProperties( paramValue, Util.toMap(System.getProperties())); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "XmlaServlet.makeDataSources: paramValue=" + paramValue); } // is the parameter a valid URL MalformedURLException mue = null; try { dataSourcesConfigUrl = new URL(paramValue); } catch (MalformedURLException e) { // not a valid url mue = e; } if (dataSourcesConfigUrl == null) { // see if its a full valid file path File f = new File(paramValue); if (f.exists()) { // yes, a real file path dataSourcesConfigUrl = f.toURL(); } else if (mue != null) { // neither url or file, // is it not optional if (! optional) { throw mue; } } return null; } return dataSourcesConfigUrl.toString(); } } catch (MalformedURLException mue) { throw Util.newError(mue, "invalid URL path '" + paramValue + "'"); } } } // End MondrianXmlaServlet.java mondrian-3.4.1/src/main/mondrian/xmla/impl/DynamicDatasourceXmlaServlet.java0000644000175000017500000000157211735330606027110 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2006-2011 Pentaho // All Rights Reserved. */ package mondrian.xmla.impl; import mondrian.server.DynamicContentFinder; import mondrian.server.RepositoryContentFinder; /** * Extends DefaultXmlaServlet to add dynamic datasource loading capability. * * @author Thiyagu Ajit * @author Luc Boudreau */ public class DynamicDatasourceXmlaServlet extends MondrianXmlaServlet { protected RepositoryContentFinder makeContentFinder(String dataSources) { return new DynamicContentFinder(dataSources); } @Override public void destroy() { super.destroy(); } } // End DynamicDatasourceXmlaServlet.java mondrian-3.4.1/src/main/mondrian/xmla/impl/DefaultSaxWriter.java0000644000175000017500000002323511735330606024557 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2011 Pentaho // All Rights Reserved. */ package mondrian.xmla.impl; import mondrian.olap.Util; import mondrian.util.ArrayStack; import mondrian.xmla.SaxWriter; import org.eigenbase.xom.XMLUtil; import org.eigenbase.xom.XOMUtil; import org.xml.sax.Attributes; import java.io.*; /** * Default implementation of {@link SaxWriter}. * * @author jhyde * @author Gang Chen * @since 27 April, 2003 */ public class DefaultSaxWriter implements SaxWriter { /** Inside the tag of an element. */ private static final int STATE_IN_TAG = 0; /** After the tag at the end of an element. */ private static final int STATE_END_ELEMENT = 1; /** After the tag at the start of an element. */ private static final int STATE_AFTER_TAG = 2; /** After a burst of character data. */ private static final int STATE_CHARACTERS = 3; private final PrintWriter writer; private int indent; private String indentStr = " "; private final ArrayStack stack = new ArrayStack(); private int state = STATE_END_ELEMENT; /** * Creates a DefaultSaxWriter writing to an {@link java.io.OutputStream}. */ public DefaultSaxWriter(OutputStream stream) { this(new OutputStreamWriter(stream)); } public DefaultSaxWriter(OutputStream stream, String xmlEncoding) throws UnsupportedEncodingException { this(new OutputStreamWriter(stream, xmlEncoding)); } /** * Creates a SAXWriter writing to a {@link java.io.Writer}. * *

    If writer is a {@link java.io.PrintWriter}, * {@link #DefaultSaxWriter(java.io.OutputStream)} is preferred. */ public DefaultSaxWriter(Writer writer) { this(new PrintWriter(writer), 0); } /** * Creates a DefaultSaxWriter writing to a {@link java.io.PrintWriter}. * * @param writer * @param initialIndent */ public DefaultSaxWriter(PrintWriter writer, int initialIndent) { this.writer = writer; this.indent = initialIndent; } private void _startElement( String namespaceURI, String localName, String qName, Attributes atts) { _checkTag(); if (indent > 0) { writer.println(); } for (int i = 0; i < indent; i++) { writer.write(indentStr); } indent++; writer.write('<'); writer.write(qName); for (int i = 0; i < atts.getLength(); i++) { XMLUtil.printAtt(writer, atts.getQName(i), atts.getValue(i)); } state = STATE_IN_TAG; } private void _checkTag() { if (state == STATE_IN_TAG) { state = STATE_AFTER_TAG; writer.print(">"); } } private void _endElement( String namespaceURI, String localName, String qName) { indent--; if (state == STATE_IN_TAG) { writer.write("/>"); } else { if (state != STATE_CHARACTERS) { writer.println(); for (int i = 0; i < indent; i++) { writer.write(indentStr); } } writer.write("'); } state = STATE_END_ELEMENT; } private void _characters(char ch[], int start, int length) { _checkTag(); // Display the string, quoting in if necessary, // or using XML escapes as a last result. String s = new String(ch, start, length); if (XOMUtil.stringHasXMLSpecials(s)) { XMLUtil.stringEncodeXML(s, writer); /* if (s.indexOf("]]>") < 0) { writer.print(""); } else { XMLUtil.stringEncodeXML(s, writer); } */ } else { writer.print(s); } state = STATE_CHARACTERS; } // // Simplifying methods public void characters(String s) { if (s != null && s.length() > 0) { _characters(s.toCharArray(), 0, s.length()); } } public void startSequence(String name, String subName) { if (name != null) { startElement(name); } else { stack.push(null); } } public void endSequence() { if (stack.peek() == null) { stack.pop(); } else { endElement(); } } public final void textElement(String name, Object data) { startElement(name); String s = data.toString(); // Replace line endings with spaces. IBM's DOM implementation keeps // line endings, whereas Sun's does not. For consistency, always strip // them. // // REVIEW: It would be better to enclose in CDATA, but some clients // might not be expecting this. s = Util.replace(s, Util.nl, " "); characters(s); endElement(); } public void element(String tagName, Object... attributes) { startElement(tagName, attributes); endElement(); } public void startElement(String tagName) { _startElement(null, null, tagName, EmptyAttributes); stack.add(tagName); } public void startElement(String tagName, Object... attributes) { _startElement(null, null, tagName, new StringAttributes(attributes)); assert tagName != null; stack.add(tagName); } public void endElement() { String tagName = stack.pop(); _endElement(null, null, tagName); } public void startDocument() { if (stack.size() != 0) { throw new IllegalStateException("Document already started"); } } public void endDocument() { if (stack.size() != 0) { throw new IllegalStateException( "Document may have unbalanced elements"); } writer.flush(); } public void completeBeforeElement(String tagName) { if (stack.indexOf(tagName) == -1) { return; } String currentTagName = stack.peek(); while (!tagName.equals(currentTagName)) { _endElement(null, null, currentTagName); stack.pop(); currentTagName = stack.peek(); } } public void verbatim(String text) { _checkTag(); writer.print(text); } public void flush() { writer.flush(); } private static final Attributes EmptyAttributes = new Attributes() { public int getLength() { return 0; } public String getURI(int index) { return null; } public String getLocalName(int index) { return null; } public String getQName(int index) { return null; } public String getType(int index) { return null; } public String getValue(int index) { return null; } public int getIndex(String uri, String localName) { return 0; } public int getIndex(String qName) { return 0; } public String getType(String uri, String localName) { return null; } public String getType(String qName) { return null; } public String getValue(String uri, String localName) { return null; } public String getValue(String qName) { return null; } }; /** * List of SAX attributes based upon a string array. */ public static class StringAttributes implements Attributes { private final Object[] strings; public StringAttributes(Object[] strings) { this.strings = strings; } public int getLength() { return strings.length / 2; } public String getURI(int index) { return null; } public String getLocalName(int index) { return null; } public String getQName(int index) { return (String) strings[index * 2]; } public String getType(int index) { return null; } public String getValue(int index) { return stringValue(strings[index * 2 + 1]); } public int getIndex(String uri, String localName) { return -1; } public int getIndex(String qName) { final int count = strings.length / 2; for (int i = 0; i < count; i++) { String string = (String) strings[i * 2]; if (string.equals(qName)) { return i; } } return -1; } public String getType(String uri, String localName) { return null; } public String getType(String qName) { return null; } public String getValue(String uri, String localName) { return null; } public String getValue(String qName) { final int index = getIndex(qName); if (index < 0) { return null; } else { return stringValue(strings[index * 2 + 1]); } } private static String stringValue(Object s) { return s == null ? null : s.toString(); } } } // End DefaultSaxWriter.java mondrian-3.4.1/src/main/mondrian/xmla/impl/AuthenticatingXmlaRequestCallback.java0000644000175000017500000001055511735330606030102 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2011 Pentaho and others // All Rights Reserved. */ package mondrian.xmla.impl; import mondrian.xmla.*; import org.w3c.dom.Element; import java.util.Map; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * This is an abstract implementation of {@link XmlaRequestCallback} * specialized in authenticating the requests coming in. Subclasses are * only required to implement {@link #authenticate(String, String, String)}. * *

    Once implemented, you only need to register your class using the XMLA * servlet config, within your web.xml descriptor. * * @author LBoudreau */ public abstract class AuthenticatingXmlaRequestCallback implements XmlaRequestCallback { public String generateSessionId(Map context) { // We don't want to override the session ID generation algorithm. return null; } public void init(ServletConfig servletConfig) throws ServletException { // Nothing to initialize here. Subclasses can override // this if they wish. } public void postAction( HttpServletRequest request, HttpServletResponse response, byte[][] responseSoapParts, Map context) throws Exception { return; } public void preAction( HttpServletRequest request, Element[] requestSoapParts, Map context) throws Exception { /* * This is where the magic happens. At this stage, we have * the username/password known. We will delegate the authentication * process down to the subclass. */ final String roleNames = authenticate( (String) context.get(XmlaConstants.CONTEXT_XMLA_USERNAME), (String) context.get(XmlaConstants.CONTEXT_XMLA_PASSWORD), (String) context.get(XmlaConstants.CONTEXT_XMLA_SESSION_ID)); context.put( XmlaConstants.CONTEXT_ROLE_NAME, roleNames); } /** * This function is expected to do two things. *

      *
    • Validate the credentials.
    • *
    • Return a comma separated list of role names associated to * these credentials.
    • *
    *

    Should there be any problems with the credentials, subclasses * can invoke {@link #throwAuthenticationException(String)} to throw * an authentication exception back to the client. * @param username Username used for authentication, as specified * in the SOAP security header. Might be null. * @param password Password used for authentication, as specified * in the SOAP security header. Might be null. * @param sessionID A unique identifier for this client session. * Session IDs should remain the same between different queries from * a same client, although some clients do not implement the XMLA * Session header properly, resulting in a new session ID for each * request. * @return A comma separated list of roles associated to this user, * or null for root access. */ public abstract String authenticate( String username, String password, String sessionID); /** * Helper method to create and throw an authentication exception. * @param reason A textual explanation of why the credentials are * rejected. */ protected void throwAuthenticationException(String reason) { throw new XmlaException( XmlaConstants.CLIENT_FAULT_FC, XmlaConstants.CHH_AUTHORIZATION_CODE, XmlaConstants.CHH_AUTHORIZATION_FAULT_FS, new Exception( "There was a problem with the credentials: " + reason)); } public boolean processHttpHeader( HttpServletRequest request, HttpServletResponse response, Map context) throws Exception { // We do not perform any special header treatment. return true; } } // End AuthenticatingXmlaRequestCallback.java mondrian-3.4.1/src/main/mondrian/xmla/impl/DefaultXmlaResponse.java0000644000175000017500000000377611735330606025257 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2010 Pentaho // All Rights Reserved. */ package mondrian.xmla.impl; import mondrian.olap.Util; import mondrian.xmla.*; import java.io.OutputStream; import java.io.UnsupportedEncodingException; /** * Default implementation of {@link mondrian.xmla.XmlaResponse}. * * @author Gang Chen */ public class DefaultXmlaResponse implements XmlaResponse { // TODO: add a msg to MondrianResource for this. private static final String MSG_ENCODING_ERROR = "Encoding unsupported: "; private final SaxWriter writer; public DefaultXmlaResponse( OutputStream outputStream, String encoding, Enumeration.ResponseMimeType responseMimeType) { try { switch (responseMimeType) { case JSON: writer = new JsonSaxWriter(outputStream); break; case SOAP: default: writer = new DefaultSaxWriter(outputStream, encoding); break; } } catch (UnsupportedEncodingException uee) { throw Util.newError(uee, MSG_ENCODING_ERROR + encoding); } } public SaxWriter getWriter() { return writer; } public void error(Throwable t) { writer.completeBeforeElement("root"); @SuppressWarnings({"ThrowableResultOfMethodCallIgnored"}) Throwable throwable = XmlaUtil.rootThrowable(t); writer.startElement("Messages"); writer.startElement( "Error", "ErrorCode", throwable.getClass().getName(), "Description", throwable.getMessage(), "Source", "Mondrian", "Help", ""); writer.endElement(); // writer.endElement(); // } } // End DefaultXmlaResponse.java mondrian-3.4.1/src/main/mondrian/xmla/impl/Olap4jXmlaServlet.java0000644000175000017500000004045511735330606024645 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2011-2012 Pentaho // All Rights Reserved. */ package mondrian.xmla.impl; import mondrian.olap.Util; import mondrian.xmla.XmlaHandler; import org.apache.commons.dbcp.BasicDataSource; import org.apache.commons.dbcp.DelegatingConnection; import org.apache.log4j.Logger; import org.olap4j.OlapConnection; import org.olap4j.OlapWrapper; import java.lang.reflect.*; import java.sql.Connection; import java.sql.SQLException; import java.util.*; import javax.servlet.ServletConfig; import javax.servlet.ServletException; /** * XMLA servlet that gets its connections from an olap4j data source. * * @author Julian Hyde * @author Michele Rossi */ public class Olap4jXmlaServlet extends DefaultXmlaServlet { private static final Logger LOGGER = Logger.getLogger(Olap4jXmlaServlet.class); private static final String OLAP_DRIVER_CLASS_NAME_PARAM = "OlapDriverClassName"; private static final String OLAP_DRIVER_CONNECTION_STRING_PARAM = "OlapDriverConnectionString"; private static final String OLAP_DRIVER_CONNECTION_PROPERTIES_PREFIX = "OlapDriverConnectionProperty."; private static final String OLAP_DRIVER_PRECONFIGURED_DISCOVER_DATASOURCES_RESPONSE = "OlapDriverUsePreConfiguredDiscoverDatasourcesResponse"; private static final String OLAP_DRIVER_IDLE_CONNECTIONS_TIMEOUT_MINUTES = "OlapDriverIdleConnectionsTimeoutMinutes"; private static final String OLAP_DRIVER_PRECONFIGURED_DISCOVER_DATASOURCES_PREFIX = "OlapDriverDiscoverDatasources."; /** * Name of property used by JDBC to hold user name. */ private static final String JDBC_USER = "user"; /** * Name of property used by JDBC to hold password. */ private static final String JDBC_PASSWORD = "password"; /** idle connections are cleaned out after 5 minutes by default */ private static final int DEFAULT_IDLE_CONNECTIONS_TIMEOUT_MS = 5 * 60 * 1000; private static final String OLAP_DRIVER_MAX_NUM_CONNECTIONS_PER_USER = "OlapDriverMaxNumConnectionsPerUser"; /** * Unwraps a given interface from a given connection. * * @param connection Connection object * @param clazz Interface to unwrap * @param Type of interface * @return Unwrapped object; never null * @throws java.sql.SQLException if cannot convert */ private static T unwrap(Connection connection, Class clazz) throws SQLException { // Invoke Wrapper.unwrap(). Works for JDK 1.6 and later, but we use // reflection so that it compiles on JDK 1.5. try { final Class wrapperClass = Class.forName("java.sql.Wrapper"); if (wrapperClass.isInstance(connection)) { Method unwrapMethod = wrapperClass.getMethod("unwrap"); return clazz.cast(unwrapMethod.invoke(connection, clazz)); } } catch (ClassNotFoundException e) { // ignore } catch (NoSuchMethodException e) { // ignore } catch (InvocationTargetException e) { // ignore } catch (IllegalAccessException e) { // ignore } if (connection instanceof OlapWrapper) { OlapWrapper olapWrapper = (OlapWrapper) connection; return olapWrapper.unwrap(clazz); } throw new SQLException("not an instance"); } @Override protected XmlaHandler.ConnectionFactory createConnectionFactory( ServletConfig servletConfig) throws ServletException { final String olap4jDriverClassName = servletConfig.getInitParameter(OLAP_DRIVER_CLASS_NAME_PARAM); final String olap4jDriverConnectionString = servletConfig.getInitParameter(OLAP_DRIVER_CONNECTION_STRING_PARAM); final String olap4jUsePreConfiguredDiscoverDatasourcesRes = servletConfig.getInitParameter( OLAP_DRIVER_PRECONFIGURED_DISCOVER_DATASOURCES_RESPONSE); boolean hardcodedDiscoverDatasources = olap4jUsePreConfiguredDiscoverDatasourcesRes != null && Boolean.parseBoolean( olap4jUsePreConfiguredDiscoverDatasourcesRes); final String idleConnTimeoutStr = servletConfig.getInitParameter( OLAP_DRIVER_IDLE_CONNECTIONS_TIMEOUT_MINUTES); final int idleConnectionsCleanupTimeoutMs = idleConnTimeoutStr != null ? Integer.parseInt(idleConnTimeoutStr) * 60 * 1000 : DEFAULT_IDLE_CONNECTIONS_TIMEOUT_MS; final String maxNumConnPerUserStr = servletConfig.getInitParameter( OLAP_DRIVER_MAX_NUM_CONNECTIONS_PER_USER); int maxNumConnectionsPerUser = maxNumConnPerUserStr != null ? Integer.parseInt(maxNumConnPerUserStr) : 1; try { Map connectionProperties = getOlap4jConnectionProperties( servletConfig, OLAP_DRIVER_CONNECTION_PROPERTIES_PREFIX); final Map ddhcRes; if (hardcodedDiscoverDatasources) { ddhcRes = getDiscoverDatasourcesPreConfiguredResponse(servletConfig); } else { ddhcRes = null; } return new Olap4jPoolingConnectionFactory( olap4jDriverClassName, olap4jDriverConnectionString, connectionProperties, idleConnectionsCleanupTimeoutMs, maxNumConnectionsPerUser, ddhcRes); } catch (Exception ex) { String msg = "Exception [" + ex + "] while trying to create " + "olap4j connection to [" + olap4jDriverConnectionString + "] using driver " + "[" + olap4jDriverClassName + "]"; LOGGER.error(msg, ex); throw new ServletException(msg, ex); } } private static Map getDiscoverDatasourcesPreConfiguredResponse( ServletConfig servletConfig) { final Map map = new LinkedHashMap(); foo(map, "DataSourceName", servletConfig, "dataSourceName"); foo( map, "DataSourceDescription", servletConfig, "dataSourceDescription"); foo(map, "URL", servletConfig, "url"); foo(map, "DataSourceInfo", servletConfig, "dataSourceInfo"); foo(map, "ProviderName", servletConfig, "providerName"); foo(map, "ProviderType", servletConfig, "providerType"); foo(map, "AuthenticationMode", servletConfig, "authenticationMode"); return map; } private static void foo( Map map, String targetProp, ServletConfig servletConfig, String sourceProp) { final String value = servletConfig.getInitParameter( OLAP_DRIVER_PRECONFIGURED_DISCOVER_DATASOURCES_PREFIX + sourceProp); map.put(targetProp, value); } private static class Olap4jPoolingConnectionFactory implements XmlaHandler.ConnectionFactory { private final String olap4jDriverConnectionString; private final Properties connProperties; private final Map discoverDatasourcesResponse; private final String olap4jDriverClassName; private final Map datasourcesPool = new HashMap(); private final int idleConnectionsCleanupTimeoutMs; private final int maxPerUserConnectionCount; /** * Creates an Olap4jPoolingConnectionFactory. * * @param olap4jDriverClassName Driver class name * @param olap4jDriverConnectionString Connect string * @param connectionProperties Connection properties * @param maxPerUserConnectionCount max number of connections to create * for every different username * @param idleConnectionsCleanupTimeoutMs pooled connections inactive * for longer than this period of time can be cleaned up * @param discoverDatasourcesResponse Pre-configured response to * DISCOVER_DATASOURCES request, or null * @throws ClassNotFoundException if driver class is not found */ public Olap4jPoolingConnectionFactory( final String olap4jDriverClassName, final String olap4jDriverConnectionString, final Map connectionProperties, final int idleConnectionsCleanupTimeoutMs, final int maxPerUserConnectionCount, final Map discoverDatasourcesResponse) throws ClassNotFoundException { Class.forName(olap4jDriverClassName); this.maxPerUserConnectionCount = maxPerUserConnectionCount; this.idleConnectionsCleanupTimeoutMs = idleConnectionsCleanupTimeoutMs; this.olap4jDriverClassName = olap4jDriverClassName; this.olap4jDriverConnectionString = olap4jDriverConnectionString; this.connProperties = new Properties(); this.connProperties.putAll(connectionProperties); this.discoverDatasourcesResponse = discoverDatasourcesResponse; } public OlapConnection getConnection( String catalog, String schema, String roleName, Properties props) throws SQLException { final String user = props.getProperty(JDBC_USER); final String pwd = props.getProperty(JDBC_PASSWORD); // note: this works also for un-authenticated connections; they will // simply all be created by the same BasicDataSource object final String dataSourceKey = user + "_" + pwd; BasicDataSource bds; synchronized (datasourcesPool) { bds = datasourcesPool.get(dataSourceKey); if (bds == null) { bds = new BasicDataSource() { { connectionProperties.putAll(connProperties); } }; bds.setDefaultReadOnly(true); bds.setDriverClassName(olap4jDriverClassName); bds.setPassword(pwd); bds.setUsername(user); bds.setUrl(olap4jDriverConnectionString); bds.setPoolPreparedStatements(false); bds.setMaxIdle(maxPerUserConnectionCount); bds.setMaxActive(maxPerUserConnectionCount); bds.setMinEvictableIdleTimeMillis( idleConnectionsCleanupTimeoutMs); bds.setAccessToUnderlyingConnectionAllowed(true); bds.setInitialSize(1); bds.setTimeBetweenEvictionRunsMillis(60000); if (catalog != null) { bds.setDefaultCatalog(catalog); } datasourcesPool.put(dataSourceKey, bds); } } Connection connection = bds.getConnection(); DelegatingConnection dc = (DelegatingConnection) connection; Connection underlyingOlapConnection = dc.getInnermostDelegate(); OlapConnection olapConnection = unwrap(underlyingOlapConnection, OlapConnection.class); if (LOGGER.isDebugEnabled()) { LOGGER.debug( "Obtained connection object [" + olapConnection + "] (ext pool wrapper " + connection + ") for key " + dataSourceKey); } if (catalog != null) { olapConnection.setCatalog(catalog); } if (schema != null) { olapConnection.setSchema(schema); } if (roleName != null) { olapConnection.setRoleName(roleName); } return createDelegatingOlapConnection(connection, olapConnection); } public Map getPreConfiguredDiscoverDatasourcesResponse() { return discoverDatasourcesResponse; } } /** * Obtains connection properties from the * ServletConfig init parameters and from System properties. * *

    The properties found in the System properties override the ones in * the ServletConfig. * *

    copies the values of init parameters / properties which * start with the given prefix to a target Map object stripping out the * configured prefix from the property name. * *

    The following example uses prefix "olapConn.": * *

         *  <init-param>
         *      <param-name>olapConn.User</param-name>
         *      <param-value>mrossi</param-value>
         *  </init-param>
         *  <init-param>
         *      <param-name>olapConn.Password</param-name>
         *      <param-value>manhattan</param-value>
         *  </init-param>
         *
         * 
    * *

    This will result in a connection properties object with entries * {("User", "mrossi"), ("Password", "manhattan")}. * * @param prefix Prefix to property name * @param servletConfig Servlet config * @return Map containing property names and values */ private static Map getOlap4jConnectionProperties( final ServletConfig servletConfig, final String prefix) { Map options = new LinkedHashMap(); // Get properties from servlet config. @SuppressWarnings({"unchecked"}) java.util.Enumeration en = servletConfig.getInitParameterNames(); while (en.hasMoreElements()) { String paramName = en.nextElement(); if (paramName.startsWith(prefix)) { String paramValue = servletConfig.getInitParameter(paramName); String prefixRemovedParamName = paramName.substring(prefix.length()); options.put(prefixRemovedParamName, paramValue); } } // Get system properties. final Map systemProps = Util.toMap(System.getProperties()); for (Map.Entry entry : systemProps.entrySet()) { String sk = entry.getKey(); if (sk.startsWith(prefix)) { String value = entry.getValue(); String prefixRemovedKey = sk.substring(prefix.length()); options.put(prefixRemovedKey, value); } } return options; } /** * Returns something that implements {@link OlapConnection} but still * behaves as the wrapper returned by the connection pool. * *

    In other words we want the "close" method to play nice and do all the * pooling actions while we want all the olap methods to execute directly on * the un-wrapped OlapConnection object. */ private static OlapConnection createDelegatingOlapConnection( final Connection connection, final OlapConnection olapConnection) { return (OlapConnection) Proxy.newProxyInstance( olapConnection.getClass().getClassLoader(), new Class[] {OlapConnection.class}, new InvocationHandler() { public Object invoke( Object proxy, Method method, Object[] args) throws Throwable { if ("unwrap".equals(method.getName()) || OlapConnection.class .isAssignableFrom(method.getDeclaringClass())) { return method.invoke(olapConnection, args); } else { return method.invoke(connection, args); } } } ); } } // End Olap4jXmlaServlet.java mondrian-3.4.1/src/main/mondrian/xmla/DataSourcesConfig.xml0000644000175000017500000001300511735330606023575 0ustar drazzibdrazzib This is the XML model for XMLA DataSources Configuration.

    The list of data sources supported by XMLA service.

    The list of data sources supported by XMLA service. Definition of a data source. Name. Description. URL of Web Services invocation. ConnectString of Mondrian (minus the catalog entry). Required, but catalog may override. Customized Service Provider Name. Ignored. Only return "MDP" for DISCOVER_DATASOURCES. Ignored. Only return "Unauthenticated" for DISCOVER_DATASOURCES. One or more Catalogs.

    The list of catalogs associated with a data source.

    The list of catalogs.
    Name of the catalog. ConnectString of Mondrian (minus the catalog entry). This entry is optional; if present, it overrides the DataSourceInfo within the DataSource. URI of the schema definition file, for example "/WEB-INF/schema/Marketing.xml". mondrian-3.4.1/src/main/mondrian/xmla/XmlaRequestCallback.java0000644000175000017500000001057211735330606024250 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2007 Pentaho // All Rights Reserved. */ package mondrian.xmla; import org.w3c.dom.Element; import java.util.Map; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * Extract data from HTTP request, SOAP header for following XML/A request.

    * * Fill context binding with whatever data you want, then use them in * {@link XmlaServlet#handleSoapHeader} and {@link XmlaServlet#handleSoapBody}. * * @author Gang Chen */ public interface XmlaRequestCallback { String AUTHORIZATION = "Authorization"; String EXPECT = "Expect"; String EXPECT_100_CONTINUE = "100-continue"; public class Helper { public static XmlaException authorizationException(Exception ex) { return new XmlaException( XmlaConstants.CLIENT_FAULT_FC, XmlaConstants.CHH_AUTHORIZATION_CODE, XmlaConstants.CHH_AUTHORIZATION_FAULT_FS, ex); } /* HTTP/1.1 100 Continue Server: Microsoft-IIS/5.0 Date: Tue, 21 Feb 2006 21:07:57 GMT X-Powered-By: ASP.NET */ public static void generatedExpectResponse( HttpServletRequest request, HttpServletResponse response, Map context) throws Exception { response.reset(); response.setStatus(HttpServletResponse.SC_CONTINUE); } } void init(ServletConfig servletConfig) throws ServletException; /** * Process the request header items. Specifically if present the * Authorization and Expect headers. If the Authorization header is * present, then the callback can validate the user/password. If * authentication fails, the callback should throw an XmlaException * with the correct XmlaConstants values. The XmlaRequestCallback.Helper * class contains the authorizationException method that can be used * by a callback to generate the XmlaException with the correct values. * If the Expect header is set with "100-continue", then it is * upto the callback to create the appropriate response and return false. * In this case, the XmlaServlet stops processing and returns the * response to the client application. To facilitate the generation of * the response, the XmlaRequestCallback.Helper has the method * generatedExpectResponse that can be called by the callback. *

    * Note that it is upto the XMLA client to determine whether or not * there is an Expect header entry (ADOMD.NET seems to like to do this). * * @return true if XmlaServlet handling is to continue and false if * there was an Expect header "100-continue". */ boolean processHttpHeader( HttpServletRequest request, HttpServletResponse response, Map context) throws Exception; /** * This is called after the headers have been process but before the * body (DISCOVER/EXECUTE) has been processed. * */ void preAction( HttpServletRequest request, Element[] requestSoapParts, Map context) throws Exception; /** * The Callback is requested to generate a sequence id string. This * sequence id was requested by the XMLA client and will be used * for all subsequent communications in the Soap Header block. * * Implementation can return null if they do not want * to generate a custom session ID, in which case, the default algorithm * to generate session IDs will be used. * @param context The context of this query. * @return An arbitrary session id to use, or null. */ String generateSessionId(Map context); /** * This is called after all Mondrian processing (DISCOVER/EXECUTE) has * occurred. * */ void postAction( HttpServletRequest request, HttpServletResponse response, byte[][] responseSoapParts, Map context) throws Exception; } // End XmlaRequestCallback.java mondrian-3.4.1/src/main/mondrian/recorder/0000755000175000017500000000000011735330606020355 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/recorder/AbstractRecorder.java0000644000175000017500000001351111735330606024452 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2011 Pentaho and others // All Rights Reserved. */ package mondrian.recorder; import mondrian.resource.MondrianResource; import java.util.ArrayList; import java.util.List; /** * Abstract implemention of the {@link MessageRecorder} interface. * * @author Richard M. Emberson */ public abstract class AbstractRecorder implements MessageRecorder { /** * Helper method to format a message and write to logger. */ public static void logMessage( final String context, final String msg, final MsgType msgType, final org.apache.log4j.Logger logger) { StringBuilder buf = new StringBuilder(64); buf.append(context); buf.append(": "); buf.append(msg); switch (msgType) { case INFO: logger.info(buf.toString()); break; case WARN: logger.warn(buf.toString()); break; case ERROR: logger.error(buf.toString()); break; default: logger.warn( "Unknown message type enum \"" + msgType + "\" for message: " + buf.toString()); } } enum MsgType { INFO, WARN, ERROR } public static final int DEFAULT_MSG_LIMIT = 10; private final int errorMsgLimit; private final List contexts; private int errorMsgCount; private int warningMsgCount; private int infoMsgCount; private String contextMsgCache; private long startTime; protected AbstractRecorder() { this(DEFAULT_MSG_LIMIT); } protected AbstractRecorder(final int errorMsgLimit) { this.errorMsgLimit = errorMsgLimit; this.contexts = new ArrayList(); this.startTime = System.currentTimeMillis(); } /** * Resets this MessageRecorder. */ public void clear() { errorMsgCount = 0; warningMsgCount = 0; infoMsgCount = 0; contextMsgCache = null; contexts.clear(); this.startTime = System.currentTimeMillis(); } public long getStartTimeMillis() { return this.startTime; } public long getRunTimeMillis() { return (System.currentTimeMillis() - this.startTime); } public boolean hasInformation() { return (infoMsgCount > 0); } public boolean hasWarnings() { return (warningMsgCount > 0); } public boolean hasErrors() { return (errorMsgCount > 0); } public int getInfoCount() { return infoMsgCount; } public int getWarningCount() { return warningMsgCount; } public int getErrorCount() { return errorMsgCount; } public String getContext() { // heavy weight if (contextMsgCache == null) { final StringBuilder buf = new StringBuilder(); int k = 0; for (String name : contexts) { if (k++ > 0) { buf.append(':'); } buf.append(name); } contextMsgCache = buf.toString(); } return contextMsgCache; } public void pushContextName(final String name) { // light weight contexts.add(name); contextMsgCache = null; } public void popContextName() { // light weight contexts.remove(contexts.size() - 1); contextMsgCache = null; } public void throwRTException() throws RecorderException { if (hasErrors()) { final String errorMsg = MondrianResource.instance().ForceMessageRecorderError.str( getContext(), errorMsgCount); throw new RecorderException(errorMsg); } } public void reportError(final Exception ex) throws RecorderException { reportError(ex, null); } public void reportError(final Exception ex, final Object info) throws RecorderException { reportError(ex.toString(), info); } public void reportError(final String msg) throws RecorderException { reportError(msg, null); } public void reportError(final String msg, final Object info) throws RecorderException { errorMsgCount++; recordMessage(msg, info, MsgType.ERROR); if (errorMsgCount >= errorMsgLimit) { final String errorMsg = MondrianResource.instance().TooManyMessageRecorderErrors.str( getContext(), errorMsgCount); throw new RecorderException(errorMsg); } } public void reportWarning(final String msg) { reportWarning(msg, null); } public void reportWarning(final String msg, final Object info) { warningMsgCount++; recordMessage(msg, info, MsgType.WARN); } public void reportInfo(final String msg) { reportInfo(msg, null); } public void reportInfo(final String msg, final Object info) { infoMsgCount++; recordMessage(msg, info, MsgType.INFO); } /** * Handles a message. * Classes implementing this abstract class must provide an implemention * of this method; it receives all warning/error messages. * * @param msg the error or warning message. * @param info the information Object which might be null. * @param msgType one of the message type enum values */ protected abstract void recordMessage( String msg, Object info, MsgType msgType); } // End AbstractRecorder.java mondrian-3.4.1/src/main/mondrian/recorder/package.html0000644000175000017500000000013311735330606022633 0ustar drazzibdrazzib Provides a set a classes for logging the process of a task. mondrian-3.4.1/src/main/mondrian/recorder/ListRecorder.java0000644000175000017500000000742611735330606023632 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.recorder; import org.apache.log4j.Logger; import java.util.*; /** * Implementation of {@link MessageRecorder} that records each message * in a {@link List}. The calling code can then access the list and take * actions as needed. */ public class ListRecorder extends AbstractRecorder { private final List errorList; private final List warnList; private final List infoList; public ListRecorder() { errorList = new ArrayList(); warnList = new ArrayList(); infoList = new ArrayList(); } public void clear() { super.clear(); errorList.clear(); warnList.clear(); infoList.clear(); } public Iterator getErrorEntries() { return errorList.iterator(); } public Iterator getWarnEntries() { return warnList.iterator(); } public Iterator getInfoEntries() { return infoList.iterator(); } protected void recordMessage( final String msg, final Object info, final MsgType msgType) { String context = getContext(); Entry e = new Entry(context, msg, msgType, info); switch (msgType) { case INFO: infoList.add(e); break; case WARN: warnList.add(e); break; case ERROR: errorList.add(e); break; default: e = new Entry( context, "Unknown message type enum \"" + msgType + "\" for message: " + msg, MsgType.WARN, info); warnList.add(e); } } public void logInfoMessage(final Logger logger) { if (hasInformation()) { logMessage(getInfoEntries(), logger); } } public void logWarningMessage(final Logger logger) { if (hasWarnings()) { logMessage(getWarnEntries(), logger); } } public void logErrorMessage(final Logger logger) { if (hasErrors()) { logMessage(getErrorEntries(), logger); } } static void logMessage(Iterator it, Logger logger) { while (it.hasNext()) { Entry e = it.next(); logMessage(e, logger); } } static void logMessage( final Entry e, final Logger logger) { logMessage(e.getContext(), e.getMessage(), e.getMsgType(), logger); } /** * Entry is a Info, Warning or Error message. This is the object stored * in the Lists MessageRecorder's info, warning and error message lists. */ public static class Entry { private final String context; private final String msg; private final MsgType msgType; private final Object info; private Entry( final String context, final String msg, final MsgType msgType, final Object info) { this.context = context; this.msg = msg; this.msgType = msgType; this.info = info; } public String getContext() { return context; } public String getMessage() { return msg; } public MsgType getMsgType() { return msgType; } public Object getInfo() { return info; } } } // End ListRecorder.java mondrian-3.4.1/src/main/mondrian/recorder/RecorderException.java0000644000175000017500000000133011735330606024641 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2006 Pentaho and others // All Rights Reserved. */ package mondrian.recorder; import mondrian.olap.MondrianException; /** * Exception thrown by {@link MessageRecorder} when too many errors * have been reported. * * @author Richard M. Emberson */ public final class RecorderException extends MondrianException { protected RecorderException(String msg) { super(msg); } } // End RecorderException.java mondrian-3.4.1/src/main/mondrian/recorder/MessageRecorder.java0000644000175000017500000001510211735330606024271 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.recorder; /** * Records warnings and errors during the processing of a task. * Contexts can be added and removed. * This allows one to collect more than one warning/error, keep processing, * and then the code that initiated the processing can determine what to do * with the warnings/errors if they exist. *

    * A typical usage might be: *

    
     *    void process(MessageRecorder msgRecorder) {
     *        msgRecorder.pushContextName(getName());
     *        try {
     *              // prcess task
     *              ....
     *              // need to generate warning message
     *              String msg = ...
     *              msgRecorder.reportWarning(msg);
     *              ....
     *        } finally {
     *              msgRecorder.popContextName();
     *        }
     *    }
     * 
    *

    * Implementations must provide the means for extracting the error/warning * messages. *

    * Code that is processing should not catch the MessageRecorder.RTException. * This Exception is thrown by the MessageRecorder when too many errors have * been seen. Throwing this Exception is the mechanism used to stop processing * and return to the initiating code. The initiating code should expect to * catch the MessageRecorder.RTException Exception. *

    
     *    void initiatingCode(MessageRecorder msgRecorder) {
     *      // get MessageRecorder implementation
     *      MessageRecorder msgRecorder = ....
     *      try {
     *          processingCode(msgRecorder);
     *      } catch (MessageRecorder.RTException mrex) {
     *          // empty
     *      }
     *      if (msgRecorder.hasErrors()) {
     *          // handle errors
     *      } else if (msgRecorder.hasWarnings()) {
     *          // handle warnings
     *      }
     *    }
     * 
    *

    * The reporting methods all have variations that take an "info" Object. * This can be used to pass something, beyond a text message, from the point * of warning/error to the initiating code. *

    * Concerning logging, it is a rule that a message, if logged by the code * creating the MessageRecorder implementation, is logged at is reporting level, * errors are logged at the error log level, warnings at the warning level and * info at the info level. This allows the client code to "know" what log level * their messages might appear at. * * @author Richard M. Emberson */ public interface MessageRecorder { /** * Clear all context, warnings and errors from the MessageRecorder. * After calling this method the MessageRecorder implemenation should * be in the same state as if it were just constructed. */ void clear(); /** * Get the time when the MessageRecorder was created or the last time that * the clear method was called. * * @return the start time */ long getStartTimeMillis(); /** * How long the MessageRecorder has been running since it was created or the * last time clear was called. */ long getRunTimeMillis(); /** * Returns true if there are one or more informational messages. * * @return true if there are one or more infos. */ boolean hasInformation(); /** * Returns true if there are one or more warning messages. * * @return true if there are one or more warnings. */ boolean hasWarnings(); /** * Returns true if there are one or more error messages. * * @return true if there are one or more errors. */ boolean hasErrors(); /** * Get the current context string. * * @return the context string. */ String getContext(); /** * Add the name parameter to the current context. * * @param name */ void pushContextName(final String name); /** * Remove the last context name added. */ void popContextName(); /** * This simply throws a RTException. A client calls this if 1) there is one * or more error messages reported and 2) the client wishes to stop * processing. Implementations of this method should only throw the * RTException if there have been errors reported - if there are no errors, * then this method does nothing. * * @throws RecorderException */ void throwRTException() throws RecorderException; /** * Add an Exception. * * @param ex the Exception added. * @throws RecorderException if too many error messages have been added. */ void reportError(final Exception ex) throws RecorderException; /** * Add an Exception and extra informaton. * * @param ex the Exception added. * @param info extra information (not meant to be part of printed message) * @throws RecorderException if too many error messages have been added. */ void reportError(final Exception ex, final Object info) throws RecorderException; /** * Add an error message. * * @param msg the text of the error message. * @throws RecorderException if too many error messages have been added. */ void reportError(final String msg) throws RecorderException; /** * Add an error message and extra information. * * @param msg the text of the error message. * @param info extra information (not meant to be part of printed message) * @throws RecorderException if too many error messages have been added. */ void reportError(final String msg, final Object info) throws RecorderException; /** * Add a warning message. * * @param msg the text of the warning message. */ void reportWarning(final String msg); /** * Add a warning message and extra information. * * @param msg the text of the warning message. * @param info extra information (not meant to be part of printed message) */ void reportWarning(final String msg, final Object info); /** * Add an informational message. * * @param msg the text of the info message. */ void reportInfo(final String msg); /** * Add an informational message and extra information. * * @param msg the text of the info message. * @param info extra information (not meant to be part of printed message) */ void reportInfo(final String msg, final Object info); } // End MessageRecorder.java mondrian-3.4.1/src/main/mondrian/recorder/PrintStreamRecorder.java0000644000175000017500000000311011735330606025151 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.recorder; import mondrian.olap.Util; import java.io.PrintStream; /** * Implementation of {@link MessageRecorder} simply writes messages to * PrintStreams. */ public class PrintStreamRecorder extends AbstractRecorder { private final PrintStream err; private final PrintStream out; public PrintStreamRecorder() { this(System.out, System.err); } public PrintStreamRecorder(final PrintStream out, final PrintStream err) { this.out = out; this.err = err; } protected void recordMessage( final String msg, final Object info, final MsgType msgType) { PrintStream ps; String prefix; switch (msgType) { case INFO: prefix = "INFO: "; ps = out; break; case WARN: prefix = "WARN: "; ps = out; break; case ERROR: prefix = "ERROR: "; ps = err; break; default: throw Util.unexpected(msgType); } String context = getContext(); ps.print(prefix); ps.print(context); ps.print(": "); ps.println(msg); } } // End PrintStreamRecorder.java mondrian-3.4.1/src/main/mondrian/recorder/LoggerRecorder.java0000644000175000017500000000172111735330606024126 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2005-2005 Julian Hyde // Copyright (C) 2005-2009 Pentaho and others // All Rights Reserved. */ package mondrian.recorder; import org.apache.log4j.Logger; /** * Implementation of {@link MessageRecorder} that writes to a * {@link Logger log4j logger}. * * @author Richard M. Emberson */ public class LoggerRecorder extends AbstractRecorder { private final Logger logger; public LoggerRecorder(final Logger logger) { this.logger = logger; } protected void recordMessage( final String msg, final Object info, final MsgType msgType) { String context = getContext(); logMessage(context, msg, msgType, logger); } } // End LoggerRecorder.java mondrian-3.4.1/src/main/mondrian/parser/0000755000175000017500000000000011744246546020055 5ustar drazzibdrazzibmondrian-3.4.1/src/main/mondrian/parser/MdxParserImplConstants.java0000644000175000017500000001317311744246546025351 0ustar drazzibdrazzib/* Generated By:JavaCC: Do not edit this line. MdxParserImplConstants.java */ package mondrian.parser; /** * Token literal values and constants. * Generated by org.javacc.parser.OtherFilesGen#start() */ public interface MdxParserImplConstants { /** End of File. */ int EOF = 0; /** RegularExpression Id. */ int AND = 1; /** RegularExpression Id. */ int AS = 2; /** RegularExpression Id. */ int AXIS = 3; /** RegularExpression Id. */ int CASE = 4; /** RegularExpression Id. */ int CAST = 5; /** RegularExpression Id. */ int CELL = 6; /** RegularExpression Id. */ int CHAPTERS = 7; /** RegularExpression Id. */ int COLUMNS = 8; /** RegularExpression Id. */ int DIMENSION = 9; /** RegularExpression Id. */ int DRILLTHROUGH = 10; /** RegularExpression Id. */ int ELSE = 11; /** RegularExpression Id. */ int EMPTY = 12; /** RegularExpression Id. */ int END = 13; /** RegularExpression Id. */ int EXPLAIN = 14; /** RegularExpression Id. */ int FIRSTROWSET = 15; /** RegularExpression Id. */ int FOR = 16; /** RegularExpression Id. */ int FROM = 17; /** RegularExpression Id. */ int IN = 18; /** RegularExpression Id. */ int IS = 19; /** RegularExpression Id. */ int MATCHES = 20; /** RegularExpression Id. */ int MAXROWS = 21; /** RegularExpression Id. */ int MEMBER = 22; /** RegularExpression Id. */ int NON = 23; /** RegularExpression Id. */ int NOT = 24; /** RegularExpression Id. */ int NULL = 25; /** RegularExpression Id. */ int ON = 26; /** RegularExpression Id. */ int OR = 27; /** RegularExpression Id. */ int PAGES = 28; /** RegularExpression Id. */ int PLAN = 29; /** RegularExpression Id. */ int PROPERTIES = 30; /** RegularExpression Id. */ int RETURN = 31; /** RegularExpression Id. */ int ROWS = 32; /** RegularExpression Id. */ int SECTIONS = 33; /** RegularExpression Id. */ int SELECT = 34; /** RegularExpression Id. */ int SET = 35; /** RegularExpression Id. */ int THEN = 36; /** RegularExpression Id. */ int WHEN = 37; /** RegularExpression Id. */ int WHERE = 38; /** RegularExpression Id. */ int XOR = 39; /** RegularExpression Id. */ int WITH = 40; /** RegularExpression Id. */ int SINGLE_LINE_COMMENT = 50; /** RegularExpression Id. */ int FORMAL_COMMENT = 51; /** RegularExpression Id. */ int MULTI_LINE_COMMENT = 52; /** RegularExpression Id. */ int ASTERISK = 54; /** RegularExpression Id. */ int BANG = 55; /** RegularExpression Id. */ int COLON = 56; /** RegularExpression Id. */ int COMMA = 57; /** RegularExpression Id. */ int CONCAT = 58; /** RegularExpression Id. */ int DOT = 59; /** RegularExpression Id. */ int EQ = 60; /** RegularExpression Id. */ int GE = 61; /** RegularExpression Id. */ int GT = 62; /** RegularExpression Id. */ int LBRACE = 63; /** RegularExpression Id. */ int LE = 64; /** RegularExpression Id. */ int LPAREN = 65; /** RegularExpression Id. */ int LT = 66; /** RegularExpression Id. */ int MINUS = 67; /** RegularExpression Id. */ int NE = 68; /** RegularExpression Id. */ int PLUS = 69; /** RegularExpression Id. */ int RBRACE = 70; /** RegularExpression Id. */ int RPAREN = 71; /** RegularExpression Id. */ int SOLIDUS = 72; /** RegularExpression Id. */ int UNSIGNED_INTEGER_LITERAL = 73; /** RegularExpression Id. */ int APPROX_NUMERIC_LITERAL = 74; /** RegularExpression Id. */ int DECIMAL_NUMERIC_LITERAL = 75; /** RegularExpression Id. */ int EXPONENT = 76; /** RegularExpression Id. */ int SINGLE_QUOTED_STRING = 77; /** RegularExpression Id. */ int DOUBLE_QUOTED_STRING = 78; /** RegularExpression Id. */ int WHITESPACE = 79; /** RegularExpression Id. */ int ID = 80; /** RegularExpression Id. */ int QUOTED_ID = 81; /** RegularExpression Id. */ int AMP_QUOTED_ID = 82; /** RegularExpression Id. */ int LETTER = 83; /** RegularExpression Id. */ int DIGIT = 84; /** Lexical state. */ int DEFAULT = 0; /** Lexical state. */ int IN_SINGLE_LINE_COMMENT = 1; /** Lexical state. */ int IN_FORMAL_COMMENT = 2; /** Lexical state. */ int IN_MULTI_LINE_COMMENT = 3; /** Literal token values. */ String[] tokenImage = { "", "\"AND\"", "\"AS\"", "\"AXIS\"", "\"CASE\"", "\"CAST\"", "\"CELL\"", "\"CHAPTERS\"", "\"COLUMNS\"", "\"DIMENSION\"", "\"DRILLTHROUGH\"", "\"ELSE\"", "\"EMPTY\"", "\"END\"", "\"EXPLAIN\"", "\"FIRSTROWSET\"", "\"FOR\"", "\"FROM\"", "\"IN\"", "\"IS\"", "\"MATCHES\"", "\"MAXROWS\"", "\"MEMBER\"", "\"NON\"", "\"NOT\"", "\"NULL\"", "\"ON\"", "\"OR\"", "\"PAGES\"", "\"PLAN\"", "\"PROPERTIES\"", "\"RETURN\"", "\"ROWS\"", "\"SECTIONS\"", "\"SELECT\"", "\"SET\"", "\"THEN\"", "\"WHEN\"", "\"WHERE\"", "\"XOR\"", "\"WITH\"", "\" \"", "\"\\t\"", "\"\\n\"", "\"\\r\"", "\"\\f\"", "", "\"//\"", "\"--\"", "\"/*\"", "", "\"*/\"", "\"*/\"", "", "\"*\"", "\"!\"", "\":\"", "\",\"", "\"||\"", "\".\"", "\"=\"", "\">=\"", "\">\"", "\"{\"", "\"<=\"", "\"(\"", "\"<\"", "\"-\"", "\"<>\"", "\"+\"", "\"}\"", "\")\"", "\"/\"", "", "", "", "", "", "", "", "", "", "", "", "", }; } mondrian-3.4.1/src/main/mondrian/parser/MdxParser.jj0000644000175000017500000006503011735330606022302 0ustar drazzibdrazzib/* // This software is subject to the terms of the Eclipse Public License v1.0 // Agreement, available at the following URL: // http://www.eclipse.org/legal/epl-v10.html. // You must accept the terms of that agreement to use this software. // // Copyright (C) 2010-2011 Pentaho // All Rights Reserved. */ options { STATIC = false; IGNORE_CASE = true; UNICODE_INPUT = true; } PARSER_BEGIN(MdxParserImpl) package mondrian.parser; import java.util.*; import java.io.StringReader; import java.math.BigDecimal; import mondrian.olap.*; import mondrian.mdx.*; import mondrian.parser.*; import mondrian.resource.MondrianResource; import mondrian.server.Statement; /** * MDX parser, generated from MdxParser.jj. * *

    The public wrapper for this parser is {@link JavaccParserValidatorImpl}. * * @author jhyde * @since Dec 14, 2010 */ @SuppressWarnings({ "ConstantIfStatement", "UnnecessarySemicolon", "UnnecessaryLabelOnBreakStatement", "RedundantIfStatement" }) public class MdxParserImpl { private MdxParserValidator.QueryPartFactory factory; private Statement statement; private FunTable funTable; private boolean strictValidation; public MdxParserImpl( MdxParserValidator.QueryPartFactory factory, Statement statement, String queryString, boolean debug, FunTable funTable, boolean strictValidation) { this(new StringReader(term(queryString))); this.factory = factory; this.statement = statement; this.funTable = funTable; this.strictValidation = strictValidation; } private static String term(String s) { return s.endsWith("\n") ? s : (s + "\n"); } public void setTabSize(int tabSize) { jj_input_stream.setTabSize(tabSize); } Exp recursivelyParseExp(String s) throws ParseException { MdxParserImpl parser = new MdxParserImpl( factory, statement, s, false, funTable, strictValidation); return parser.expression(); } static Id[] toIdArray(List idList) { if (idList == null || idList.size() == 0) { return EmptyIdArray; } else { return idList.toArray(new Id[idList.size()]); } } static Exp[] toExpArray(List expList) { if (expList == null || expList.size() == 0) { return EmptyExpArray; } else { return expList.toArray(new Exp[expList.size()]); } } static Formula[] toFormulaArray(List formulaList) { if (formulaList == null || formulaList.size() == 0) { return EmptyFormulaArray; } else { return formulaList.toArray(new Formula[formulaList.size()]); } } static MemberProperty[] toMemberPropertyArray(List mpList) { if (mpList == null || mpList.size() == 0) { return EmptyMemberPropertyArray; } else { return mpList.toArray(new MemberProperty[mpList.size()]); } } static QueryPart[] toQueryPartArray(List qpList) { if (qpList == null || qpList.size() == 0) { return EmptyQueryPartArray; } else { return qpList.toArray(new QueryPart[qpList.size()]); } } static QueryAxis[] toQueryAxisArray(List qpList) { if (qpList == null || qpList.size() == 0) { return EmptyQueryAxisArray; } else { return qpList.toArray(new QueryAxis[qpList.size()]); } } private static final MemberProperty[] EmptyMemberPropertyArray = new MemberProperty[0]; private static final Exp[] EmptyExpArray = new Exp[0]; private static final Formula[] EmptyFormulaArray = new Formula[0]; private static final Id[] EmptyIdArray = new Id[0]; private static final QueryPart[] EmptyQueryPartArray = new QueryPart[0]; private static final QueryAxis[] EmptyQueryAxisArray = new QueryAxis[0]; private static final String DQ = '"' + ""; private static final String DQDQ = DQ + DQ; private static String stripQuotes( String s, String prefix, String suffix, String quoted) { assert s.startsWith(prefix) && s.endsWith(suffix); s = s.substring(prefix.length(), s.length() - suffix.length()); s = Util.replace(s, quoted, suffix); return s; } private Exp createCall( Exp left, Id.Segment segment, List argList) { if (argList != null) { if (left != null) { // Method syntax: "x.foo(arg1, arg2)" or "x.foo()" argList.add(0, left); return new UnresolvedFunCall( segment.name, Syntax.Method, toExpArray(argList)); } else { // Function syntax: "foo(arg1, arg2)" or "foo()" return new UnresolvedFunCall( segment.name, Syntax.Function, toExpArray(argList)); } } else { // Member syntax: "foo.bar" // or property syntax: "foo.RESERVED_WORD" Syntax syntax; boolean call = false; switch (segment.quoting) { case UNQUOTED: syntax = Syntax.Property; call = funTable.isProperty(segment.name); break; case QUOTED: syntax = Syntax.QuotedProperty; break; default: syntax = Syntax.AmpersandQuotedProperty; break; } if (left instanceof Id && !call) { return ((Id) left).append(segment); } else if (left == null) { return new Id(segment); } else { return new UnresolvedFunCall( segment.name, syntax, new Exp[] {left}); } } } } PARSER_END(MdxParserImpl) // ---------------------------------------------------------------------------- // Keywords and reserved words. TOKEN : { | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | } // White space SKIP : { " " | "\t" | "\n" | "\r" | "\f" } // Comments MORE : { <"/**" ~["/"]> : IN_FORMAL_COMMENT } MORE : { "//" : IN_SINGLE_LINE_COMMENT | "--" : IN_SINGLE_LINE_COMMENT | "/*" : IN_MULTI_LINE_COMMENT } SPECIAL_TOKEN : { : DEFAULT } SPECIAL_TOKEN : { : DEFAULT } SPECIAL_TOKEN : { : DEFAULT } MORE : { < ~[] > } // Operators and other symbols TOKEN : { < ASTERISK: "*" > | < BANG: "!" > | < COLON : ":" > | < COMMA : "," > | < CONCAT : "||" > | < DOT : "." > | < EQ : "=" > | < GE : ">=" > | < GT : ">" > | < LBRACE : "{" > | < LE : "<=" > | < LPAREN : "(" > | < LT : "<" > | < MINUS : "-" > | < NE : "<>" > | < PLUS : "+" > | < RBRACE : "}" > | < RPAREN : ")" > | < SOLIDUS : "/" > } // Literals TOKEN : { < UNSIGNED_INTEGER_LITERAL: (["0"-"9"])+ > | < APPROX_NUMERIC_LITERAL: ( | ) > | < DECIMAL_NUMERIC_LITERAL: (["0"-"9"])+(".")?(["0"-"9"])* | "."(["0"-"9"])+ > | < #EXPONENT: ["e","E"] (["+","-"])? (["0"-"9"])+ > | < SINGLE_QUOTED_STRING: "'" ( (~["'"]) | ("''"))* "'" > | < DOUBLE_QUOTED_STRING: "\"" ( (~["\""]) | ("\"\""))* "\"" > | < #WHITESPACE: [ " ","\t","\n","\r","\f" ] > } // Identifiers TOKEN : { < ID: ( | )* > | < QUOTED_ID: "[" ( (~["]","\n","\r"]) | ("]]") )* "]" > | < AMP_QUOTED_ID: "&" > | < #LETTER: [ "\u0024", "\u0041"-"\u005a", "\u005f", "\u0061"-"\u007a", "\u00c0"-"\u00d6", "\u00d8"-"\u00f6", "\u00f8"-"\u00ff", "\u0100"-"\u1fff", "\u3040"-"\u318f", "\u3300"-"\u337f", "\u3400"-"\u3d2d", "\u4e00"-"\u9fff", "\uf900"-"\ufaff" ] > | < #DIGIT: [ "\u0030"-"\u0039", "\u0660"-"\u0669", "\u06f0"-"\u06f9", "\u0966"-"\u096f", "\u09e6"-"\u09ef", "\u0a66"-"\u0a6f", "\u0ae6"-"\u0aef", "\u0b66"-"\u0b6f", "\u0be7"-"\u0bef", "\u0c66"-"\u0c6f", "\u0ce6"-"\u0cef", "\u0d66"-"\u0d6f", "\u0e50"-"\u0e59", "\u0ed0"-"\u0ed9", "\u1040"-"\u1049" ] > } // ---------------------------------------------------------------------------- // Entry points QueryPart statementEof() : { QueryPart qp; } { qp = statement() { return qp; } } Exp expressionEof() : { Exp e; } { e = expression() { return e; } } // ---------------------------------------------------------------------------- // Elements // // // ::= | Id.Segment identifier() : { String id; } { id = keyword() { // Allow a non-reserved keyword to be converted back into an identifier // if it is not in a context where it is meaningful. return new Id.Segment(id, Id.Quoting.UNQUOTED); } | { return new Id.Segment(token.image, Id.Quoting.UNQUOTED); } | { return new Id.Segment( stripQuotes(token.image, "[", "]", "]]"), Id.Quoting.QUOTED); } | { return new Id.Segment( stripQuotes(token.image, "&[", "]", "]]"), Id.Quoting.KEY); } } // a keyword (unlike a reserved word) can be converted back into an // identifier in some contexts String keyword() : { } { { return "Dimension"; } | { return "Properties"; } } Id compoundId() : { Id.Segment i; List list = new ArrayList(); } { i = identifier() { list.add(i); } ( LOOKAHEAD() i = identifier() { list.add(i); } )* { return new Id(list); } } // ---------------------------------------------------------------------------- // Expressions Exp unaliasedExpression() : { Exp x, y; } { x = term5() ( y = term5() { x = new UnresolvedFunCall("OR", Syntax.Infix, new Exp[] {x, y}); } | y = term5() { x = new UnresolvedFunCall("XOR", Syntax.Infix, new Exp[] {x, y}); } | // range 'm1 : m2' yields set of members y = term5() { x = new UnresolvedFunCall(":", Syntax.Infix, new Exp[] {x, y}); } )* { return x; } } Exp term5() : { Exp x, y; } { x = term4() ( y = term4() { x = new UnresolvedFunCall("AND", Syntax.Infix, new Exp[] {x, y}); } )* { return x; } } Exp term4() : { Exp x; } { x = term3() { return x; } | x = term4() { return new UnresolvedFunCall("NOT", Syntax.Prefix, new Exp[] {x}); } } Exp term3() : { Exp x, y; Token op; } { x = term2() ( // e.g. "1 < 5" ( { op = token; } | { op = token; } | { op = token; } | { op = token; } | { op = token; } | { op = token; } ) y = term2() { x = new UnresolvedFunCall(op.image, Syntax.Infix, new Exp[] {x, y}); } | // We expect a shift-reduce conflict here, because NULL is a literal and // so is a valid argument to the IS operator. We want to shift. LOOKAHEAD(2) { x = new UnresolvedFunCall("IS NULL", Syntax.Postfix, new Exp[] {x}); } | // e.g. "x IS y"; but "x IS NULL" is handled elsewhere LOOKAHEAD(2) y = term2() { x = new UnresolvedFunCall("IS", Syntax.Infix, new Exp[] {x, y}); } | { x = new UnresolvedFunCall( "IS EMPTY", Syntax.Postfix, new Exp[] {x}); } | y = term2() { x = new UnresolvedFunCall("MATCHES", Syntax.Infix, new Exp[] {x, y}); } | LOOKAHEAD(2) y = term2() { x = new UnresolvedFunCall( "NOT", Syntax.Prefix, new Exp[] { new UnresolvedFunCall( "MATCHES", Syntax.Infix, new Exp[] {x, y})}); } | y = term2() { x = new UnresolvedFunCall("IN", Syntax.Infix, new Exp[] {x, y}); } | y = term2() { x = new UnresolvedFunCall( "NOT", Syntax.Prefix, new Exp[] { new UnresolvedFunCall( "IN", Syntax.Infix, new Exp[] {x, y})}); } )* { return x; } } Exp term2() : { Exp x, y; } { x = term() ( y = term() { x = new UnresolvedFunCall("+", Syntax.Infix, new Exp[] {x, y}); } | y = term() { x = new UnresolvedFunCall("-", Syntax.Infix, new Exp[] {x, y}); } | y = term() { x = new UnresolvedFunCall("||", Syntax.Infix, new Exp[] {x, y}); } )* { return x; } } Exp term() : { Exp x, y; } { x = factor() ( y = factor() { x = new UnresolvedFunCall("*", Syntax.Infix, new Exp[] {x, y}); } | y = factor() { x = new UnresolvedFunCall("/", Syntax.Infix, new Exp[] {x, y}); } )* { return x; } } Exp factor() : { Exp p; } { p = primary() { return p; } | p = primary() { return p; } | p = primary() { return new UnresolvedFunCall("-", Syntax.Prefix, new Exp[] {p}); } } Exp primary() : { Exp e; } { e = atom() ( e = segmentOrFuncall(e) )* { return e; } } Exp segmentOrFuncall(Exp left) : { Id.Segment segment; List argList = null; } { segment = identifier() ( ( LOOKAHEAD() { argList = Collections.emptyList(); } | argList = expOrEmptyList() ) )? { return createCall(left, segment, argList); } } Literal numericLiteral() : { } { { return Literal.create(new BigDecimal(token.image)); } | { return Literal.create(new BigDecimal(token.image)); } | { return Literal.create(new BigDecimal(token.image)); } } Exp atom() : { Exp e; Id.Segment segment; List lis; } { { return Literal.createString(stripQuotes(token.image, "'", "'", "''")); } | { return Literal.createString(stripQuotes(token.image, DQ, DQ, DQDQ)); } | e = numericLiteral() { return e; } | { return Literal.nullValue; } | e = unaliasedExpression() segment = identifier() { return new UnresolvedFunCall( "CAST", Syntax.Cast, new Exp[] { e, Literal.createSymbol(segment.name)}); } | lis = expList() { // Whereas ([Sales],[Time]) and () are tuples, ([Sales]) and (5) // are just expressions. return new UnresolvedFunCall( "()", Syntax.Parentheses, toExpArray(lis)); } | // set built from sets/tuples ( LOOKAHEAD() { lis = Collections.emptyList(); } | lis = expList() ) { return new UnresolvedFunCall( "{}", Syntax.Braces, toExpArray(lis)); } | e = caseExpression() { return e; } | // Function call "foo(a, b)" or "whiz!bang!foo(a, b)". // Properties "x.PROP" and methods "exp.meth(a)" are in primary(). segment = identifier() ( segment = identifier() { // We support the syntax for qualifying function names with package // names separated by bang ('!'), e.g. 'whiz!bang!foo(a, b)' // but currently we ignore the qualifiers. The previous example is // equivalent to 'foo(a, b)'. } )* ( ( LOOKAHEAD() { lis = Collections.emptyList(); } | lis = expOrEmptyList() ) | /* empty */ { lis = null; } ) { return createCall(null, segment, lis); } } Exp caseExpression() : { Exp e, e2; List list = new ArrayList(); boolean match = false; } { ( e = expression() { match = true; list.add(e); } )? ( e = expression() e2 = expression() { list.add(e); list.add(e2); } )* ( e = expression() { list.add(e); } )? { if (match) { return new UnresolvedFunCall( "_CaseMatch", Syntax.Case, toExpArray(list)); } else { return new UnresolvedFunCall( "_CaseTest", Syntax.Case, toExpArray(list)); } } } // ---------------------------------------------------------------------------- // Member Value Expression Exp expression() : { Exp e; Id.Segment i; } { e = unaliasedExpression() ( i = identifier() { Id id = new Id(i); e = new UnresolvedFunCall("AS", Syntax.Infix, new Exp[] {e, id}); } )* { return e; } } Exp expressionOrEmpty() : { Exp e; } { e = expression() { return e; } | /* empty */ { return new UnresolvedFunCall("", Syntax.Empty, new Exp[] {}); } } // Comma-separated list of expressions, some of which may be empty. Used // for functions. List expOrEmptyList() : { Exp e; List list = new LinkedList(); } { e = expressionOrEmpty() { list.add(e); } ( e = expressionOrEmpty() { list.add(e); } )* { return list; } } // List of expressions, none of which may be empty. List expList() : { Exp e; List list = new LinkedList(); } { e = expression() { list.add(e); } ( e = expression() { list.add(e); } )* { return list; } } // ---------------------------------------------------------------------------- // MDX Statement // // ::= // | // | // // ::= [WITH ] // SELECT [ // [, ...]] // FROM [] // [WHERE ] // [] // // ::= // DRILLTHROUGH // [ MAXROWS ] // [ FIRSTROWSET ] // // [ RETURN [, ...] ] // // ::= // EXPLAIN PLAN FOR ( | ) // QueryPart statement() : { QueryPart qp; } { ( qp = selectStatement() | qp = drillthroughStatement() | qp = explainStatement() ) { return qp; } } QueryPart selectOrDrillthroughStatement() : { QueryPart qp; } { qp = selectStatement() { return qp; } | qp = drillthroughStatement() { return qp; } } Query selectStatement() : { Formula e; List f = new ArrayList(); Exp w = null; QueryAxis i; List a = new ArrayList(); Id c, p; List cellPropList = new ArrayList(); } { ( ( e = memberSpecification() { f.add(e); } | e = setSpecification() { f.add(e); } )+ )?